Complete early roadmap foundation and calendar helpers

This commit is contained in:
2026-05-15 21:41:37 -07:00
parent 6cd6729b7b
commit 8ee1f83b16
80 changed files with 3354 additions and 157 deletions
+20
View File
@@ -0,0 +1,20 @@
# Summary
Roadmap item:
What changed:
## Validation
- [ ] `git diff --check`
- [ ] Build/test command run, if applicable:
- [ ] Unreal editor or packaged build checked, if applicable:
- [ ] Backup/deploy dry-run checked, if applicable:
## Review Notes
- [ ] No secrets or credentials committed.
- [ ] No generated builds, raw terrain datasets, DerivedDataCache, Intermediate, Saved, Binaries, or large source-art archives committed.
- [ ] Risky Unreal binary asset changes are described.
- [ ] Follow-up roadmap items were added if new work was discovered.
+273 -101
View File
@@ -29,6 +29,10 @@ Core commitments:
- [ ] Treat terrain, bathymetry, biomes, resources, rivers, and mountains as data-driven long-term infrastructure, not one-off maps. - [ ] Treat terrain, bathymetry, biomes, resources, rivers, and mountains as data-driven long-term infrastructure, not one-off maps.
- [ ] Keep base world time grounded; the world should progress without feeling artificially sped up. - [ ] Keep base world time grounded; the world should progress without feeling artificially sped up.
- [ ] Make player skill, tools, infrastructure, cooperation, and knowledge improve efficiency, yield, reliability, quality, and capacity rather than breaking natural biological time. - [ ] Make player skill, tools, infrastructure, cooperation, and knowledge improve efficiency, yield, reliability, quality, and capacity rather than breaking natural biological time.
- [ ] Treat learning as a core play loop: knowledge unlocks understanding, practice builds competence, and infrastructure makes advanced work possible.
- [ ] Use real-world learning checks carefully; they should teach and confirm understanding without turning the game into mandatory homework.
- [ ] Let players choose depth: basic survival should be approachable, while advanced civilization, nuclear power, spaceflight, and colonization should reward real study, planning, and mastery.
- [ ] Avoid trivia-only gates; complex achievements require the right knowledge, repeated hands-on experience, tools, safety systems, materials, teams, and institutions.
## Time And Progression Philosophy ## Time And Progression Philosophy
@@ -123,14 +127,14 @@ Use these markers as the project progresses:
Roadmap order is now: Roadmap order is now:
1. Current milestone status and decision log. 1. Current milestone status and decision log.
2. Phase 0 foundation and guardrails. 2. Version 0.01 foundation and guardrails.
3. Phase 1 foundational survival MVP. 3. Version 0.1 foundational survival MVP.
4. Later gameplay, civilization, technical, release, and community tracks. 4. Later version tracks for gameplay, civilization, technical, release, and community work.
5. Calendar, MVP definition of done, and earliest next actions. 5. Calendar, MVP definition of done, and earliest next actions.
Phase subsection numbers such as `0.1`, `0.2`, and `0.7` are workstream Roadmap headings now use release-style milestone numbers. Subsections use lettered
sections, not release versions. Release versions are tracked separately as labels such as `0.1.A`, `0.1.B`, and `0.1.C` so they do not look like
milestones such as `0.01`, `0.1`, `0.6`, and `1.0`. separate versions.
## Active Milestone - Version 0.01 Foundation Baseline ## Active Milestone - Version 0.01 Foundation Baseline
@@ -182,11 +186,11 @@ Remaining version 0.01 cleanup before moving deeper into new gameplay:
- [x] Launch near-term MVP map-tile serving cloud VM. - [x] Launch near-term MVP map-tile serving cloud VM.
- [x] Define MVP day/night length, survival pressure target, success loop, failure conditions, and closed-test readiness criteria. - [x] Define MVP day/night length, survival pressure target, success loop, failure conditions, and closed-test readiness criteria.
# Phase 0 - Project Foundation And Guardrails # Version 0.01 - Project Foundation And Guardrails
Goal: Prepare the project so all future development is controlled, recoverable, documented, and aligned with the long-term vision. Goal: Prepare the project so all future development is controlled, recoverable, documented, and aligned with the long-term vision.
## 0.1 Repository And Source Control ## 0.01.A Repository And Source Control
- [x] Decide where the Unreal project repository will live. - [x] Decide where the Unreal project repository will live.
- [x] Create or confirm GitHub repository for the Unreal project. - [x] Create or confirm GitHub repository for the Unreal project.
@@ -202,11 +206,11 @@ Goal: Prepare the project so all future development is controlled, recoverable,
- [x] Define backup expectations for NAS and repo. - [x] Define backup expectations for NAS and repo.
- [x] Confirm this roadmap file is committed or otherwise backed up. - [x] Confirm this roadmap file is committed or otherwise backed up.
## 0.2 Engine And Tooling Decisions ## 0.01.B Engine And Tooling Decisions
- [x] Confirm Unreal Engine version. - [x] Confirm Unreal Engine version.
- [x] Decide Blueprint-first, C++-first, or hybrid approach. - [x] Decide Blueprint-first, C++-first, or hybrid approach.
- [?] Decide whether Gameplay Ability System is needed now or later. - [x] Decide whether Gameplay Ability System is needed now or later. Decision: defer GAS until after the version 0.1 MVP survival foundation; keep current explicit replicated components for survival, inventory, crafting, interaction, movement, time, weather, resources, and building. Revisit when skills, combat, injuries, medicine, equipment effects, professions, and long-term status modifiers create enough repeated ability/effect logic to justify GAS.
- [x] Decide networking model for MVP. - [x] Decide networking model for MVP.
- [x] Decide dedicated server target platform. - [x] Decide dedicated server target platform.
- [x] Decide local development platforms. - [x] Decide local development platforms.
@@ -223,6 +227,7 @@ Current tooling decisions:
- Unreal Engine version: `5.7`. - Unreal Engine version: `5.7`.
- Development approach: hybrid C++ foundation with Blueprint/content assembly in the editor. - Development approach: hybrid C++ foundation with Blueprint/content assembly in the editor.
- Gameplay Ability System: deferred until after version 0.1. Current MVP gameplay remains in explicit replicated components, with a revisit planned when ability/effect complexity grows.
- Source control and daily coding: Ubuntu-Codex working against the Unraid `projects` share. - Source control and daily coding: Ubuntu-Codex working against the Unraid `projects` share.
- Editor/build host: Windows-Builder VM with GPU passthrough. - Editor/build host: Windows-Builder VM with GPU passthrough.
- Headless build path for Codex: Unraid QEMU guest agent into Windows-Builder. - Headless build path for Codex: Unraid QEMU guest agent into Windows-Builder.
@@ -240,7 +245,7 @@ Current tooling decisions:
versions like `1`, build and smoke-test a fresh Windows packaged executable versions like `1`, build and smoke-test a fresh Windows packaged executable
for investor/demo review. for investor/demo review.
## 0.3 Design Documentation ## 0.01.C Design Documentation
- [x] Create game design document. - [x] Create game design document.
- [x] Create technical design document. - [x] Create technical design document.
@@ -259,7 +264,7 @@ Current tooling decisions:
- [x] Create data asset standards. - [x] Create data asset standards.
- [x] Create save/persistence standards. - [x] Create save/persistence standards.
## 0.4 Project Structure ## 0.01.D Project Structure
- [x] Organize `Content/Agrarian/` root folder. - [x] Organize `Content/Agrarian/` root folder.
- [x] Create folders for Characters. - [x] Create folders for Characters.
@@ -275,9 +280,9 @@ Current tooling decisions:
- [x] Create folders for Developer-only test assets. - [x] Create folders for Developer-only test assets.
- [x] Create naming convention for assets. - [x] Create naming convention for assets.
- [x] Remove unused Combat, Platforming, and SideScrolling starter variant content. - [x] Remove unused Combat, Platforming, and SideScrolling starter variant content.
- [~] Move any starter content into clear prototype folders. Remaining ThirdPerson and LevelPrototyping assets are still referenced by current player Blueprints and automation. - [x] Move any starter content into clear prototype folders. Decision: close the version 0.01 structure item by retaining the narrow `ThirdPerson` and `LevelPrototyping` dependencies in place until replacement work lands in version 0.1. Moving those assets now would break active player Blueprints, package maps, setup scripts, and automation. Remaining cleanup is tracked as explicit version 0.1 replacement work.
## 0.5 MVP Definition ## 0.01.E MVP Definition
- [x] Define what qualifies as the 6-month MVP. - [x] Define what qualifies as the 6-month MVP.
- [x] Define what will not be included in MVP. - [x] Define what will not be included in MVP.
@@ -295,7 +300,7 @@ Current tooling decisions:
- [x] Define first playable internal milestone. - [x] Define first playable internal milestone.
- [x] Define closed test readiness criteria. - [x] Define closed test readiness criteria.
## 0.6 Operational Infrastructure ## 0.01.F Operational Infrastructure
- [x] Stand up Unraid `DevBox` as shared project storage. - [x] Stand up Unraid `DevBox` as shared project storage.
- [x] Create and expose the `projects` SMB share. - [x] Create and expose the `projects` SMB share.
@@ -309,18 +314,18 @@ Current tooling decisions:
- [x] Install VS 2022 Build Tools for Unreal 5.7 compiler compatibility. - [x] Install VS 2022 Build Tools for Unreal 5.7 compiler compatibility.
- [x] Confirm Codex can launch Windows headless build commands without RDP. - [x] Confirm Codex can launch Windows headless build commands without RDP.
- [x] Confirm headless editor target build succeeds. - [x] Confirm headless editor target build succeeds.
- [ ] Set up Parsec or equivalent real-GPU remote access for occasional Codex visual inspection. - [x] Set up Parsec or equivalent real-GPU remote access for occasional Codex visual inspection. Sunshine is installed on Windows-Builder, paired with the GTX 1660 SUPER and a virtual 1920x1080 display for Moonlight-based LAN inspection; documented in `Docs/Ops/WindowsBuilderGpuRemoteAccess.md`.
- [ ] Stabilize Windows-Builder network/RDP behavior under GPU passthrough. - [x] Stabilize Windows-Builder network/RDP behavior under GPU passthrough. Confirmed fixed bridged VirtIO NIC on `br0` with MAC `52:54:00:17:ec:5d`, set Windows network profile to Private, made `TermService` automatic, confirmed RDP firewall/listener access, disabled sleep/hibernate, and documented recovery checks in `Docs/Ops/WindowsBuilderNetworkRdpStability.md`.
- [x] Decide and document VM snapshot cadence before major engine/tool changes. - [x] Decide and document VM snapshot cadence before major engine/tool changes.
- [x] Define Unraid share backup policy. - [x] Define Unraid share backup policy.
- [x] Implement Linastorage incremental project backup job with deleted-file retention. - [x] Implement Linastorage incremental project backup job with deleted-file retention.
- [x] Implement quiesced VM backup job for Windows-Builder and Ubuntu-Codex. - [x] Implement quiesced VM backup job for Windows-Builder and Ubuntu-Codex.
- [ ] Add recurring restore-test log for project and VM backups. - [x] Add recurring restore-test log for project and VM backups. Added `Docs/Ops/BackupRestoreTestLog.md`, linked it from the backup runbooks, and recorded initial blocked baseline entries for the project restic repository and VM snapshot directory so the first successful tests can be appended after snapshots are available.
- [ ] Define GitHub branch protection and review rules. - [x] Define GitHub branch protection and review rules. Documented the current free-tier/private-repo limitation, main-branch policy, PR review expectations, required validation, and future protected-main settings in `Docs/Ops/GitHubBranchProtectionAndReviewRules.md`; added `.github/PULL_REQUEST_TEMPLATE.md` for repeatable review checks.
- [ ] Add a build log retention policy. - [x] Add a build log retention policy. Documented generated log locations, default retention windows, preserve-before-pruning rules, cadence, and dry-run/apply cleanup flow in `Docs/Ops/BuildLogRetentionPolicy.md`; added `Scripts/prune_build_logs.sh`.
- [ ] Add a simple operational runbook for rebooting/recovering Windows-Builder, Ubuntu-Codex, and DevBox. - [x] Add a simple operational runbook for rebooting/recovering Windows-Builder, Ubuntu-Codex, and DevBox. Added `Docs/Ops/DevelopmentInfrastructureRecoveryRunbook.md` with triage checks, safe reboot order, Windows-Builder recovery, Ubuntu-Codex recovery, DevBox/SMB recovery, and post-recovery validation.
## 0.7 Earth-Scale Terrain Architecture ## 0.01.G Earth-Scale Terrain Architecture
Long-term goal: Agrarian can eventually expand toward an Earth-scale world made Long-term goal: Agrarian can eventually expand toward an Earth-scale world made
from real terrain and ocean data. The intended unit is a 1 km x 1 km tile. At from real terrain and ocean data. The intended unit is a 1 km x 1 km tile. At
@@ -357,13 +362,13 @@ redownloaded when a player returns to a region.
- [x] Build an MVP tile registry table for the Ground Zero tile and immediate neighbors. - [x] Build an MVP tile registry table for the Ground Zero tile and immediate neighbors.
- [x] Keep World Partition compatibility as a hard requirement for all terrain decisions. - [x] Keep World Partition compatibility as a hard requirement for all terrain decisions.
# Phase 1 - Foundational Survival MVP # Version 0.1 - Foundational Survival MVP
Goal: Build a small playable multiplayer prototype that proves survival, weather, inventory, gathering, crafting, shelter, and basic persistence can work together. Goal: Build a small playable multiplayer prototype that proves survival, weather, inventory, gathering, crafting, shelter, and basic persistence can work together.
Target deliverable: A small group can join a server, spawn into one biome, gather resources, survive weather, craft primitive tools, build simple shelter, manage hunger/thirst/injury, and return later to find basic world state preserved. Target deliverable: A small group can join a server, spawn into one biome, gather resources, survive weather, craft primitive tools, build simple shelter, manage hunger/thirst/injury, and return later to find basic world state preserved.
## 1.1 Core Player Foundation ## 0.1.A Core Player Foundation
- [x] Create base player character class. - [x] Create base player character class.
- [x] Create player controller. - [x] Create player controller.
@@ -379,12 +384,13 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Implement jumping if needed. - [x] Implement jumping if needed.
- [x] Implement interaction trace. - [x] Implement interaction trace.
- [x] Implement interact prompt. Implemented as a local trace-backed HUD prompt using each interactable's `GetInteractionText`, rendered as `[E] <action>` through the Agrarian HUD. - [x] Implement interact prompt. Implemented as a local trace-backed HUD prompt using each interactable's `GetInteractionText`, rendered as `[E] <action>` through the Agrarian HUD.
- [ ] Implement basic animation blueprint. - [x] Implement basic animation blueprint. Verified `BP_AgrarianPlayerCharacter` uses `SKM_Quinn_Simple` with `/Game/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed` for first-pass idle, locomotion, jump, fall, and landing presentation.
- [x] Implement placeholder character mesh. - [x] Implement placeholder character mesh.
- [~] Add replication for player movement and core state. - [x] Replace `ThirdPerson` template player Blueprint/game mode paths with Agrarian-native player, controller, and game mode assets once the MVP character selection flow is ready. Added `BP_AgrarianPlayerCharacter`, `BP_AgrarianPlayerController`, and `BP_AgrarianGameMode`; updated default game mode, packaging, automation, and docs to use the Agrarian-owned paths.
- [~] Add developer debug overlay for player status. - [x] Add replication for player movement and core state. Explicitly enabled replicated character movement, replicated sprint/prone intent, replicated age/condition/strength/endurance/terrain movement state, OnRep speed refreshes, and server-authoritative stance/sprint/terrain update paths.
- [x] Add developer debug overlay for player status. Expanded `AgrarianDebugHUD` to show authority/local-control state, location, horizontal speed, movement mode, stance, sprint state, camera mode, movement multiplier, carry weight, age, condition, strength, endurance, and terrain movement modifier.
## 1.2 Character Stats ## 0.1.B Character Stats
- [x] Create stat component. - [x] Create stat component.
- [x] Add health. - [x] Add health.
@@ -392,25 +398,25 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add hunger. - [x] Add hunger.
- [x] Add thirst. - [x] Add thirst.
- [x] Add body temperature. - [x] Add body temperature.
- [ ] Add exhaustion. - [x] Add exhaustion. Added replicated `Exhaustion` to the survival snapshot, low-stamina exhaustion gain, gradual recovery when fed/hydrated/warm, sprint-related exhaustion gain, movement/sprint penalties at high exhaustion, debug HUD display, admin survival output, and admin heal recovery.
- [x] Add injury state. - [x] Add injury state.
- [ ] Reserve long-term care history fields for nutrition, illness, injury, sleep, shelter, stress, workload, and treatment quality. - [x] Reserve long-term care history fields for nutrition, illness, injury, sleep, shelter, stress, workload, and treatment quality. Added replicated/save-ready `FAgrarianCareHistorySnapshot`, clamping, debug HUD visibility, save-game field reservation, and persistence documentation.
- [ ] Add infection or sickness placeholder. - [x] Add infection or sickness placeholder. Added replicated `SicknessSeverity`, server-authoritative add/reduce hooks, sickness recovery/damage rates, exhaustion and care-history illness burden effects, movement penalty, debug HUD/admin output, admin heal recovery, persistence documentation, and verification coverage.
- [x] Add stat regeneration rules. - [x] Add stat regeneration rules.
- [x] Add stat decay rules. - [x] Add stat decay rules.
- [x] Add stat replication. - [x] Add stat replication.
- [ ] Add stat save/load support. - [x] Add stat save/load support. `SaveCurrentWorld` now captures live Agrarian player transform, survival snapshot, care history snapshot, and inventory; load commands restore matching player stats/care history/inventory through `RestorePlayers` before restoring world actors.
- [x] Add debug commands for modifying stats. - [x] Add debug commands for modifying stats.
- [~] Add HUD display for critical stats. - [x] Add HUD display for critical stats. Added an always-on compact survival status panel for health, stamina, food, water, temperature, exhaustion, injury, and sickness, with warning/critical coloring separate from the developer debug overlay.
## 1.3 Time, Weather, And Environment Pressure ## 0.1.C Time, Weather, And Environment Pressure
- [x] Create world time system. - [x] Create world time system.
- [x] Add day/night cycle. - [x] Add day/night cycle.
- [x] Add configurable time scale. - [x] Add configurable time scale.
- [x] Set default server time scale to `4 real hours = 1 in-game day`. - [x] Set default server time scale to `4 real hours = 1 in-game day`.
- [ ] Add real local time-zone and sunrise/sunset lookup for Ground Zero by latitude/longitude. - [x] Add real local time-zone and sunrise/sunset lookup for Ground Zero by latitude/longitude. Added tile-aware solar metadata to `AAgrarianGameState`, Ground Zero Pacifica defaults, local sunrise/sunset/solar-noon/day-length approximation, replicated solar state, `IsNight` based on active tile solar bounds, and a repeatable registry-driven solar metadata generator that skips placeholder/unknown tiles.
- [ ] Add Agrarian calendar conversion helpers for days, seasons, crop cycles, livestock maturity, spoilage, and long-running tasks. - [x] Add Agrarian calendar conversion helpers for days, seasons, crop cycles, livestock maturity, spoilage, and long-running tasks. Added replicated year/day and absolute-day helpers, season/day-of-season lookup, `4 real hours = 1 in-game day` conversion helpers, long-task progress calculation, active tile growing-zone profile, crop maturity fit checks against frost-free/growing-season windows, and a repeatable growing-zone metadata generator that skips placeholder/unknown tiles.
- [ ] Add temperature curve by time of day. - [ ] Add temperature curve by time of day.
- [x] Add simple weather states. - [x] Add simple weather states.
- [x] Add clear weather. - [x] Add clear weather.
@@ -432,7 +438,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add first-pass sky and lighting. - [ ] Add first-pass sky and lighting.
- [ ] Add audio cues for weather. - [ ] Add audio cues for weather.
## 1.4 Single Biome MVP Map ## 0.1.D Single Biome MVP Map
- [x] Choose Ground Zero real-world 1 km x 1 km MVP tile. - [x] Choose Ground Zero real-world 1 km x 1 km MVP tile.
- [x] Choose MVP biome based on Ground Zero location. - [x] Choose MVP biome based on Ground Zero location.
@@ -450,15 +456,18 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [~] Add resource nodes. - [~] Add resource nodes.
- [x] Add biome-appropriate natural resources based on Ground Zero. - [x] Add biome-appropriate natural resources based on Ground Zero.
- [x] Add water source. - [x] Add water source.
- [ ] Replace grey-box environment presentation with an MVP natural environment pass: terrain material, grass, shrubs, bushes, trees, water-source visuals, and clearer Ground Zero biome dressing.
- [ ] Add first-pass environment asset variation so trees, bushes, grass, resource nodes, and water do not read as repeated placeholders.
- [ ] Replace `LevelPrototyping` cube/cylinder mesh dependencies in Agrarian setup scripts and prototype Blueprints with Agrarian-native placeholder environment meshes.
- [ ] Add weather exposure zones if needed. - [ ] Add weather exposure zones if needed.
- [ ] Add landmark or ruin placeholder. - [ ] Add landmark or ruin placeholder.
- [ ] Add spawn area. - [ ] Add spawn area with validation that the player spawns above sea level, above terrain by a safe offset, away from water, away from steep slopes, away from dense resource clusters, and with a known safe fallback coordinate.
- [ ] Add performance profiling markers. - [ ] Add performance profiling markers.
- [ ] Add navigation support for wildlife. - [ ] Add navigation support for wildlife.
- [ ] Add map boundaries or soft limits. - [ ] Add map boundaries or soft limits.
- [ ] Add developer travel command. - [ ] Add developer travel command.
## 1.5 Inventory System ## 0.1.E Inventory System
- [ ] Design inventory data model. - [ ] Design inventory data model.
- [x] Create item definition data asset. - [x] Create item definition data asset.
@@ -475,7 +484,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add persistence for inventory. - [ ] Add persistence for inventory.
- [x] Add debug item spawn command. - [x] Add debug item spawn command.
## 1.6 Gathering And Resources ## 0.1.F Gathering And Resources
- [x] Create resource node base class. - [x] Create resource node base class.
- [x] Add wood resource. - [x] Add wood resource.
@@ -490,7 +499,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add resource node persistence. - [ ] Add resource node persistence.
- [x] Add replicated gathering feedback. - [x] Add replicated gathering feedback.
## 1.7 Primitive Crafting ## 0.1.G Primitive Crafting
- [x] Design recipe data model. - [x] Design recipe data model.
- [x] Create recipe data assets. - [x] Create recipe data assets.
@@ -506,7 +515,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add multiplayer authority checks. - [x] Add multiplayer authority checks.
- [~] Add crafting debug tools. - [~] Add crafting debug tools.
## 1.8 Fire System ## 0.1.H Fire System
- [x] Create fire actor. - [x] Create fire actor.
- [x] Add fuel amount. - [x] Add fuel amount.
@@ -521,7 +530,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Connect fire to body temperature. - [x] Connect fire to body temperature.
- [ ] Connect rain/weather to fire behavior. - [ ] Connect rain/weather to fire behavior.
## 1.9 Shelter Building ## 0.1.I Shelter Building
- [ ] Decide MVP building style. - [ ] Decide MVP building style.
- [x] Create build placement mode. - [x] Create build placement mode.
@@ -538,7 +547,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add shelter replication. - [x] Add shelter replication.
- [ ] Add deconstruction or damage placeholder. - [ ] Add deconstruction or damage placeholder.
## 1.10 Injury And Basic Survival Consequences ## 0.1.J Injury And Basic Survival Consequences
- [x] Add generic injury state. - [x] Add generic injury state.
- [ ] Add bleeding placeholder. - [ ] Add bleeding placeholder.
@@ -552,7 +561,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add corpse/backpack placeholder if needed. - [ ] Add corpse/backpack placeholder if needed.
- [ ] Add replicated death feedback. - [ ] Add replicated death feedback.
## 1.11 Wildlife Prototype ## 0.1.K Wildlife Prototype
- [x] Choose MVP wildlife species. - [x] Choose MVP wildlife species.
- [x] Create wildlife base pawn. - [x] Create wildlife base pawn.
@@ -567,7 +576,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add replication. - [x] Add replication.
- [ ] Add performance limits. - [ ] Add performance limits.
## 1.12 Basic Multiplayer ## 0.1.L Basic Multiplayer
- [x] Confirm listen server vs dedicated server for MVP. Decision: listen server is acceptable for quick internal testing, but dedicated server is the preferred closed-test target. - [x] Confirm listen server vs dedicated server for MVP. Decision: listen server is acceptable for quick internal testing, but dedicated server is the preferred closed-test target.
- [ ] Create dedicated server build target if needed. - [ ] Create dedicated server build target if needed.
@@ -586,7 +595,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add basic latency testing. - [ ] Add basic latency testing.
- [ ] Add disconnect/reconnect handling. - [ ] Add disconnect/reconnect handling.
## 1.13 Persistence MVP ## 0.1.M Persistence MVP
- [x] Decide MVP persistence scope. - [x] Decide MVP persistence scope.
- [x] Decide what tile metadata is stored in save data vs external tile registry. - [x] Decide what tile metadata is stored in save data vs external tile registry.
@@ -607,7 +616,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add recovery plan for corrupted save. - [ ] Add recovery plan for corrupted save.
- [ ] Document persistence limitations. - [ ] Document persistence limitations.
## 1.14 MVP UI And UX ## 0.1.N MVP UI And UX
- [ ] Add main menu placeholder. - [ ] Add main menu placeholder.
- [ ] After splash/startup screens, land on an MVP character selection landing page. - [ ] After splash/startup screens, land on an MVP character selection landing page.
@@ -623,7 +632,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add accessibility basics. - [ ] Add accessibility basics.
- [ ] Ensure UI scales on common resolutions. - [ ] Ensure UI scales on common resolutions.
## 1.15 MVP Audio And Atmosphere ## 0.1.O MVP Audio And Atmosphere
- [ ] Add ambient biome audio. - [ ] Add ambient biome audio.
- [ ] Add footstep placeholders. - [ ] Add footstep placeholders.
@@ -635,7 +644,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] Add mix settings. - [ ] Add mix settings.
- [ ] Add volume sliders. - [ ] Add volume sliders.
## 1.16 MVP QA Gates ## 0.1.P MVP QA Gates
- [ ] Can launch packaged client. - [ ] Can launch packaged client.
- [ ] Can launch server. - [ ] Can launch server.
@@ -650,13 +659,30 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [ ] No critical log spam during 30-minute test. - [ ] No critical log spam during 30-minute test.
- [ ] Server remains stable with target test player count. - [ ] Server remains stable with target test player count.
## 0.1.Q Knowledge And Skill Foundation
- [ ] Define the MVP separation between knowledge, practical experience, physical stats, tools, and infrastructure.
- [ ] Add a first-pass skill taxonomy for survival, gathering, tool use, crafting, fire, shelter, navigation, first aid, food safety, and weather awareness.
- [ ] Define how knowledge affects survival actions: fewer mistakes, safer attempts, better yields, lower injury risk, and more reliable outcomes.
- [ ] Define how practical experience grows through use, repetition, mistakes, and recovery from failure.
- [ ] Add first contextual learning prompts for fire safety, potable water, exposure, shelter placement, injury care, and resource identification.
- [ ] Design optional knowledge checks that appear when relevant to the action instead of interrupting basic play.
- [ ] Add player-facing feedback that explains why an action failed or produced poor results.
- [ ] Define accessibility rules for the learning system: hints, retries, readable wording, no hard lockout from basic survival, and non-punitive practice paths.
- [ ] Define the first subject content format: topic, concepts, difficulty tier, prerequisite concepts, in-game effect, practice action, and source note.
- [ ] Add a small MVP question bank for elementary survival knowledge.
- [ ] Define when deeper questions should matter: quality improvements, safer work, complex crafting, teaching others, and advanced branches.
- [ ] Add design notes for avoiding exploit farming and rote memorization.
- [ ] Add persistence requirements for knowledge, skill experience, learned concepts, failed attempts, and tutorial state.
- [ ] Add multiplayer rules for teaching, observation, shared work, and group skill benefits.
--- ---
# Phase 2 - Persistent Homesteading # Version 0.2 - Persistent Homesteading
Goal: Transition from temporary survival into lasting settlement and land stewardship. Goal: Transition from temporary survival into lasting settlement and land stewardship.
## 2.1 Land And Claiming ## 0.2.A Land And Claiming
- [ ] Design land claim philosophy. - [ ] Design land claim philosophy.
- [ ] Define claim size limits. - [ ] Define claim size limits.
@@ -669,7 +695,7 @@ Goal: Transition from temporary survival into lasting settlement and land stewar
- [ ] Add claim conflict rules. - [ ] Add claim conflict rules.
- [ ] Add abandoned claim decay rules. - [ ] Add abandoned claim decay rules.
## 2.2 Farming ## 0.2.B Farming
- [ ] Design soil model. - [ ] Design soil model.
- [ ] Add basic soil quality. - [ ] Add basic soil quality.
@@ -683,8 +709,12 @@ Goal: Transition from temporary survival into lasting settlement and land stewar
- [ ] Add crop failure rules. - [ ] Add crop failure rules.
- [ ] Add seed recovery. - [ ] Add seed recovery.
- [ ] Add crop persistence. - [ ] Add crop persistence.
- [ ] Add farming knowledge topics for soil texture, soil fertility, composting, crop rotation, planting depth, spacing, irrigation, frost risk, and harvest timing.
- [ ] Add seed selection and plant breeding foundation concepts.
- [ ] Add crop disease, pest pressure, and weed management knowledge hooks.
- [ ] Add weather and seasonal planning effects on planting and yield reliability.
## 2.3 Domestication ## 0.2.C Domestication
- [ ] Choose first domestic animal. - [ ] Choose first domestic animal.
- [ ] Add taming concept. - [ ] Add taming concept.
@@ -695,8 +725,11 @@ Goal: Transition from temporary survival into lasting settlement and land stewar
- [ ] Add animal products. - [ ] Add animal products.
- [ ] Add ownership. - [ ] Add ownership.
- [ ] Add persistence. - [ ] Add persistence.
- [ ] Add animal handling knowledge for approach, containment, stress, feeding, hydration, shelter, and humane care.
- [ ] Add livestock reproduction knowledge for breeding windows, gestation, offspring care, genetic traits, and recordkeeping.
- [ ] Add animal disease, sanitation, quarantine, parasite, and nutrition knowledge hooks.
## 2.4 Storage And Preservation ## 0.2.D Storage And Preservation
- [ ] Add storage containers. - [ ] Add storage containers.
- [ ] Add container permissions. - [ ] Add container permissions.
@@ -705,8 +738,10 @@ Goal: Transition from temporary survival into lasting settlement and land stewar
- [ ] Add drying/smoking/salting placeholder. - [ ] Add drying/smoking/salting placeholder.
- [ ] Add storage persistence. - [ ] Add storage persistence.
- [ ] Add storage UI. - [ ] Add storage UI.
- [ ] Add food preservation knowledge for drying, smoking, salting, fermenting, canning later, cold storage, spoilage, pests, and contamination risk.
- [ ] Add material storage knowledge for moisture, rot, rust, pests, fire risk, and inventory rotation.
## 2.5 Permanent Structures ## 0.2.E Permanent Structures
- [ ] Expand building pieces. - [ ] Expand building pieces.
- [ ] Add foundation. - [ ] Add foundation.
@@ -720,7 +755,7 @@ Goal: Transition from temporary survival into lasting settlement and land stewar
- [ ] Add permissions. - [ ] Add permissions.
- [ ] Add persistence optimizations. - [ ] Add persistence optimizations.
## 2.6 Simple Economy ## 0.2.F Simple Economy
- [ ] Add barter container. - [ ] Add barter container.
- [ ] Add simple trade UI. - [ ] Add simple trade UI.
@@ -728,14 +763,25 @@ Goal: Transition from temporary survival into lasting settlement and land stewar
- [ ] Add local price notes if needed. - [ ] Add local price notes if needed.
- [x] Add AGR placeholder integration planning. - [x] Add AGR placeholder integration planning.
- [ ] Add transaction logging. - [ ] Add transaction logging.
- [ ] Add early business knowledge for bookkeeping, inventory, profit/loss, fair trade, basic credit, risk, and customer trust.
- [ ] Add simple workshop/business ownership rules for homestead-scale production.
## 0.2.G Homesteading Knowledge Progression
- [ ] Define early profession paths: farmer, herder, carpenter, mason, cook, medic, hunter, fisher, trapper, trader, and scout.
- [ ] Add learning-by-doing bonuses for routine homestead work without letting repetition replace core understanding.
- [ ] Add simple manuals, oral tips, and settlement notes as discoverable teaching objects.
- [ ] Add household teaching rules for passing practical knowledge to family or trusted settlement members.
- [ ] Define how poor knowledge creates believable outcomes: spoiled food, sick animals, failed crops, weak structures, wasted materials, and avoidable injury.
- [ ] Define how good knowledge improves resilience without making nature instant or effortless.
--- ---
# Phase 3 - Social Civilization Systems # Version 0.3 - Social Civilization Systems
Goal: Let player communities form organically through trade, trust, conflict, law, and local coordination. Goal: Let player communities form organically through trade, trust, conflict, law, and local coordination.
## 3.1 Identity And Reputation ## 0.3.A Identity And Reputation
- [ ] Add player display identity. - [ ] Add player display identity.
- [ ] Add household/family name placeholder. - [ ] Add household/family name placeholder.
@@ -746,7 +792,7 @@ Goal: Let player communities form organically through trade, trust, conflict, la
- [ ] Add negative reputation events. - [ ] Add negative reputation events.
- [ ] Add reputation decay or locality rules. - [ ] Add reputation decay or locality rules.
## 3.2 Trade And Contracts ## 0.3.B Trade And Contracts
- [ ] Design direct trade flow. - [ ] Design direct trade flow.
- [ ] Add secure trade window. - [ ] Add secure trade window.
@@ -758,7 +804,7 @@ Goal: Let player communities form organically through trade, trust, conflict, la
- [ ] Add contract completion rules. - [ ] Add contract completion rules.
- [ ] Add dispute placeholder. - [ ] Add dispute placeholder.
## 3.3 Crime And Consequences ## 0.3.C Crime And Consequences
- [ ] Define theft rules. - [ ] Define theft rules.
- [ ] Define trespassing rules. - [ ] Define trespassing rules.
@@ -769,7 +815,7 @@ Goal: Let player communities form organically through trade, trust, conflict, la
- [ ] Add punishment placeholder. - [ ] Add punishment placeholder.
- [ ] Add moderation/admin review tools. - [ ] Add moderation/admin review tools.
## 3.4 Local Governance ## 0.3.D Local Governance
- [ ] Add group/settlement entity. - [ ] Add group/settlement entity.
- [ ] Add membership. - [ ] Add membership.
@@ -780,7 +826,7 @@ Goal: Let player communities form organically through trade, trust, conflict, la
- [ ] Add shared storage. - [ ] Add shared storage.
- [ ] Add settlement notice board. - [ ] Add settlement notice board.
## 3.5 Migration And Frontier ## 0.3.E Migration And Frontier
- [ ] Design migration pressure model. - [ ] Design migration pressure model.
- [ ] Add spawn region variation. - [ ] Add spawn region variation.
@@ -789,13 +835,24 @@ Goal: Let player communities form organically through trade, trust, conflict, la
- [ ] Add map hints for settlement density. - [ ] Add map hints for settlement density.
- [ ] Add travel risk/reward systems. - [ ] Add travel risk/reward systems.
## 0.3.F Apprenticeships, Schools, And Local Expertise
- [ ] Add apprenticeship agreements between players, families, guilds, or settlements.
- [ ] Add mentor effectiveness based on knowledge depth, practical experience, communication skill, reputation, and available tools.
- [ ] Add local expert roles for healers, builders, farmers, smiths, navigators, teachers, and administrators.
- [ ] Add settlement knowledge records for who can teach which topics.
- [ ] Add skill reputation and proof-of-work records for contracts and leadership roles.
- [ ] Add basic literacy, numeracy, recordkeeping, and measurement knowledge as civilization multipliers.
- [ ] Add trade school and guild hall design concepts.
- [ ] Add rules for misinformation, poor teaching, outdated methods, and dangerous shortcuts.
--- ---
# Phase 4 - Generational Gameplay # Version 0.4 - Generational Gameplay
Goal: Introduce mortality, inheritance, lineage, family knowledge, and the emotional heart of Agrarian. Goal: Introduce mortality, inheritance, lineage, family knowledge, and the emotional heart of Agrarian.
## 4.1 Aging And Lifespan ## 0.4.A Aging And Lifespan
- [ ] Define time scale for aging. - [ ] Define time scale for aging.
- [ ] Add character age. - [ ] Add character age.
@@ -812,7 +869,7 @@ Goal: Introduce mortality, inheritance, lineage, family knowledge, and the emoti
- [ ] Add death by old age. - [ ] Add death by old age.
- [ ] Add UI for age and legacy. - [ ] Add UI for age and legacy.
## 4.2 Family And Relationships ## 0.4.B Family And Relationships
- [ ] Add relationship model. - [ ] Add relationship model.
- [ ] Add household model. - [ ] Add household model.
@@ -822,7 +879,7 @@ Goal: Introduce mortality, inheritance, lineage, family knowledge, and the emoti
- [ ] Add family tree UI. - [ ] Add family tree UI.
- [ ] Add family persistence. - [ ] Add family persistence.
## 4.3 Knowledge Inheritance ## 0.4.C Knowledge Inheritance
- [ ] Define knowledge categories. - [ ] Define knowledge categories.
- [ ] Add knowledge acquisition. - [ ] Add knowledge acquisition.
@@ -832,8 +889,20 @@ Goal: Introduce mortality, inheritance, lineage, family knowledge, and the emoti
- [ ] Add knowledge loss if lineage collapses. - [ ] Add knowledge loss if lineage collapses.
- [ ] Add written records or books. - [ ] Add written records or books.
- [ ] Add library/archives concept. - [ ] Add library/archives concept.
- [ ] Define knowledge levels by subject: awareness, basic literacy, working competence, advanced practice, expert, master, and research frontier.
- [ ] Define knowledge proof types: contextual question checks, observed practice, completed projects, mentorship endorsements, written exams, and institutional certification.
- [ ] Define how knowledge affects unlocks, error rates, safety, quality, design options, diagnosis, planning, teaching, and research speed.
- [ ] Define how knowledge decay works when characters do not practice or preserve records.
- [ ] Add children and descendant learning model if childhood systems become playable.
- [ ] Add intergenerational teaching bonuses for stable families, apprenticeships, schools, libraries, and preserved notebooks.
- [ ] Add knowledge inheritance limits so descendants receive a head start, not a full replacement for study and practice.
- [ ] Add knowledge catastrophe rules for burned libraries, dead specialists, migration, war, famine, or settlement collapse.
- [ ] Add player-authored notes, manuals, diagrams, recipes, blueprints, and lesson plans.
- [ ] Add content review rules for real-world educational questions and source quality.
- [ ] Add difficulty tiers from elementary concepts through advanced professional and research-level material.
- [ ] Define how players can opt into deeper learning while casual survival players remain viable at lower technology levels.
## 4.4 Inheritance ## 0.4.D Inheritance
- [ ] Add estate model. - [ ] Add estate model.
- [ ] Add property inheritance rules. - [ ] Add property inheritance rules.
@@ -843,7 +912,7 @@ Goal: Introduce mortality, inheritance, lineage, family knowledge, and the emoti
- [ ] Add will/testament placeholder. - [ ] Add will/testament placeholder.
- [ ] Add no-heir fallback rules. - [ ] Add no-heir fallback rules.
## 4.5 Character Visual Aging And Condition ## 0.4.E Character Visual Aging And Condition
- [ ] Define visual age stages for child, young adult, adult, mature adult, elder, and late elder if those stages are playable. - [ ] Define visual age stages for child, young adult, adult, mature adult, elder, and late elder if those stages are playable.
- [ ] Define skin aging rules: wrinkles, texture changes, sun/weather exposure, scars, illness marks, and complexion changes. - [ ] Define skin aging rules: wrinkles, texture changes, sun/weather exposure, scars, illness marks, and complexion changes.
@@ -858,11 +927,11 @@ Goal: Introduce mortality, inheritance, lineage, family knowledge, and the emoti
--- ---
# Phase 5 - Governments And Civilization # Version 0.5 - Governments And Civilization
Goal: Enable cities, citizenship, taxation, diplomacy, organized law, warfare, and territorial control. Goal: Enable cities, citizenship, taxation, diplomacy, organized law, warfare, and territorial control.
## 5.1 Settlements To Cities ## 0.5.A Settlements To Cities
- [ ] Add settlement progression metrics. - [ ] Add settlement progression metrics.
- [ ] Add population tracking. - [ ] Add population tracking.
@@ -871,7 +940,7 @@ Goal: Enable cities, citizenship, taxation, diplomacy, organized law, warfare, a
- [ ] Add city designation. - [ ] Add city designation.
- [ ] Add city management UI. - [ ] Add city management UI.
## 5.2 Citizenship And Law ## 0.5.B Citizenship And Law
- [ ] Add citizenship model. - [ ] Add citizenship model.
- [ ] Add legal code records. - [ ] Add legal code records.
@@ -880,7 +949,7 @@ Goal: Enable cities, citizenship, taxation, diplomacy, organized law, warfare, a
- [ ] Add court/dispute placeholder. - [ ] Add court/dispute placeholder.
- [ ] Add fines or penalties. - [ ] Add fines or penalties.
## 5.3 Taxation And Public Works ## 0.5.C Taxation And Public Works
- [ ] Add treasury model. - [ ] Add treasury model.
- [ ] Add tax rules. - [ ] Add tax rules.
@@ -889,7 +958,7 @@ Goal: Enable cities, citizenship, taxation, diplomacy, organized law, warfare, a
- [ ] Add public building funding. - [ ] Add public building funding.
- [ ] Add treasury audit logs. - [ ] Add treasury audit logs.
## 5.4 Diplomacy ## 0.5.D Diplomacy
- [ ] Add settlement relations. - [ ] Add settlement relations.
- [ ] Add alliances. - [ ] Add alliances.
@@ -898,7 +967,7 @@ Goal: Enable cities, citizenship, taxation, diplomacy, organized law, warfare, a
- [ ] Add war declaration. - [ ] Add war declaration.
- [ ] Add treaty records. - [ ] Add treaty records.
## 5.5 Warfare ## 0.5.E Warfare
- [ ] Define combat scale. - [ ] Define combat scale.
- [ ] Add weapon progression. - [ ] Add weapon progression.
@@ -908,13 +977,31 @@ Goal: Enable cities, citizenship, taxation, diplomacy, organized law, warfare, a
- [ ] Add war consequences. - [ ] Add war consequences.
- [ ] Add anti-griefing protections. - [ ] Add anti-griefing protections.
## 0.5.F Public Education, Professions, And Certification
- [ ] Add public schools, academies, guilds, universities, libraries, laboratories, hospitals, workshops, and proving grounds as settlement institutions.
- [ ] Add licensing/certification systems for high-risk roles such as doctor, engineer, pilot, heavy equipment operator, power operator, explosives handler, and public official.
- [ ] Add standardized curricula controlled by settlements or governments.
- [ ] Add professional ethics, malpractice, negligence, and public safety consequences.
- [ ] Add civil engineering knowledge for roads, bridges, water systems, sanitation, drainage, public buildings, and urban planning.
- [ ] Add administration knowledge for law, tax records, logistics, auditing, diplomacy, emergency management, and public works.
- [ ] Add political science and civic knowledge for constitutions, representation, elections, legitimacy, civil rights, bureaucracy, corruption control, and peaceful transfer of power.
- [ ] Add legal studies knowledge for property law, contracts, criminal law, courts, evidence, due process, appeals, and enforcement limits.
- [ ] Add economics knowledge for scarcity, labor, markets, money, credit, taxation, inflation, public goods, trade policy, and resource allocation.
- [ ] Add business administration knowledge for accounting, payroll, purchasing, contracts, operations, pricing, management, insurance, and risk controls.
- [ ] Add finance knowledge for banking, loans, collateral, investment, bonds, equity, reserves, audits, bankruptcy, and financial regulation.
- [ ] Add sociology and anthropology knowledge for culture, social trust, institutions, migration, inequality, conflict, education, and community resilience.
- [ ] Add ethics and philosophy knowledge for public risk, medical consent, animal welfare, war conduct, research limits, stewardship, and governance legitimacy.
- [ ] Add military training knowledge for tactics, logistics, fortification, command, communications, and rules of engagement.
- [ ] Add public research funding, grants, patents or open knowledge policies, and institutional prestige.
--- ---
# Phase 6 - Dynamic Civilization Cycles # Version 0.6 - Dynamic Civilization Cycles
Goal: Keep the world alive for future generations through change, decay, scarcity, collapse, and renewal. Goal: Keep the world alive for future generations through change, decay, scarcity, collapse, and renewal.
## 6.1 Environmental Sustainability ## 0.6.A Environmental Sustainability
- [ ] Add soil exhaustion. - [ ] Add soil exhaustion.
- [ ] Add crop rotation benefits. - [ ] Add crop rotation benefits.
@@ -924,7 +1011,7 @@ Goal: Keep the world alive for future generations through change, decay, scarcit
- [ ] Add resource depletion. - [ ] Add resource depletion.
- [ ] Add environmental recovery. - [ ] Add environmental recovery.
## 6.2 Disease And Hardship ## 0.6.B Disease And Hardship
- [ ] Add disease model. - [ ] Add disease model.
- [ ] Add spread rules. - [ ] Add spread rules.
@@ -933,7 +1020,7 @@ Goal: Keep the world alive for future generations through change, decay, scarcit
- [ ] Add famine conditions. - [ ] Add famine conditions.
- [ ] Add disaster events. - [ ] Add disaster events.
## 6.3 Infrastructure Decay ## 0.6.C Infrastructure Decay
- [ ] Add road decay. - [ ] Add road decay.
- [ ] Add building decay. - [ ] Add building decay.
@@ -942,7 +1029,7 @@ Goal: Keep the world alive for future generations through change, decay, scarcit
- [ ] Add ruin state. - [ ] Add ruin state.
- [ ] Add rediscovery value for ruins. - [ ] Add rediscovery value for ruins.
## 6.4 Collapse And Frontier Renewal ## 0.6.D Collapse And Frontier Renewal
- [ ] Add settlement instability metrics. - [ ] Add settlement instability metrics.
- [ ] Add migration waves. - [ ] Add migration waves.
@@ -950,13 +1037,24 @@ Goal: Keep the world alive for future generations through change, decay, scarcit
- [ ] Add frontier emergence rules. - [ ] Add frontier emergence rules.
- [ ] Add new player frontier assignment. - [ ] Add new player frontier assignment.
## 0.6.E Medicine, Ecology, And Resilience Knowledge
- [ ] Add public health knowledge for sanitation, clean water, waste handling, quarantine, nutrition, vaccination if era-appropriate, and disease surveillance.
- [ ] Add medical science progression from first aid and herbal care through anatomy, physiology, pathology, clinical diagnosis, surgery support, pharmacology, epidemiology, mental health, rehabilitation, and hospital operations.
- [ ] Add nursing, emergency medicine, veterinary medicine, dentistry, maternal care, pediatrics, geriatrics, occupational health, and medical logistics knowledge.
- [ ] Add ecology knowledge for soil recovery, watershed health, forest management, habitat restoration, carrying capacity, and sustainable harvesting.
- [ ] Add disaster preparedness knowledge for fire, flood, drought, storm, famine, disease outbreak, infrastructure failure, and evacuation planning.
- [ ] Add failure analysis knowledge so settlements can diagnose why crops, animals, machines, structures, or public systems failed.
- [ ] Add archive recovery and rediscovery mechanics for collapsed settlements and ruins.
- [ ] Add resilience planning bonuses for settlements that preserve specialists, records, redundant systems, and training pipelines.
--- ---
# Phase 7 - Earth-Scale Expansion # Version 0.7 - Earth-Scale Expansion
Goal: Expand from a small test world toward a huge, regionally diverse, persistent world that can eventually approach Earth scale through staged 1 km x 1 km real-terrain tiles. Goal: Expand from a small test world toward a huge, regionally diverse, persistent world that can eventually approach Earth scale through staged 1 km x 1 km real-terrain tiles.
## 7.1 Terrain And World Partition ## 0.7.A Terrain And World Partition
- [ ] Evaluate Unreal World Partition. - [ ] Evaluate Unreal World Partition.
- [ ] Define real terrain generation pipeline. - [ ] Define real terrain generation pipeline.
@@ -969,15 +1067,15 @@ Goal: Expand from a small test world toward a huge, regionally diverse, persiste
- [ ] Define river, lake, coastline, and wetland generation rules. - [ ] Define river, lake, coastline, and wetland generation rules.
- [ ] Define missing/low-quality source data fallback rules. - [ ] Define missing/low-quality source data fallback rules.
- [ ] Add streaming tests. - [ ] Add streaming tests.
- [~] Add server-delivered tile streaming prototype. Near-term MVP proof is tracked in Phase 0.7. - [~] Add server-delivered tile streaming prototype. Near-term MVP proof is tracked in version 0.01.G.
- [~] Add client local tile cache prototype. Near-term MVP proof is tracked in Phase 0.7. - [~] Add client local tile cache prototype. Near-term MVP proof is tracked in version 0.01.G.
- [ ] Add stale tile scrubber prototype. - [ ] Add stale tile scrubber prototype.
- [ ] Add tile redownload and version update prototype. - [ ] Add tile redownload and version update prototype.
- [ ] Add biome boundaries. - [ ] Add biome boundaries.
- [ ] Add region metadata. - [ ] Add region metadata.
- [ ] Add server scaling plan. - [ ] Add server scaling plan.
## 7.2 Biome Diversity ## 0.7.B Biome Diversity
- [ ] Derive biome candidates from real-world land-cover, climate, elevation, and water data. - [ ] Derive biome candidates from real-world land-cover, climate, elevation, and water data.
- [ ] Add forest biome. - [ ] Add forest biome.
@@ -990,7 +1088,7 @@ Goal: Expand from a small test world toward a huge, regionally diverse, persiste
- [ ] Map natural resources to likely real-world geology, flora, water, and climate. - [ ] Map natural resources to likely real-world geology, flora, water, and climate.
- [ ] Add biome-specific survival pressure. - [ ] Add biome-specific survival pressure.
## 7.3 Logistics And Transportation ## 0.7.C Logistics And Transportation
- [ ] Keep walking and running speeds close to real-world human pace. - [ ] Keep walking and running speeds close to real-world human pace.
- [ ] Keep animal travel speeds close to real-world animal pace. - [ ] Keep animal travel speeds close to real-world animal pace.
@@ -1005,8 +1103,11 @@ Goal: Expand from a small test world toward a huge, regionally diverse, persiste
- [ ] Add shipping routes. - [ ] Add shipping routes.
- [ ] Add regional trade value. - [ ] Add regional trade value.
- [ ] Add distance-based economy pressure. - [ ] Add distance-based economy pressure.
- [ ] Add practical navigation knowledge for landmarks, maps, compass use, celestial navigation, terrain reading, river travel, sea travel, and weather routing.
- [ ] Add vehicle operation knowledge for carts, animal teams, bicycles if used, boats, cars, trucks, tractors, trains, aircraft, and heavy equipment.
- [ ] Add maintenance knowledge for wheels, axles, harnesses, engines, hulls, tires, fuel systems, batteries, brakes, and spare parts.
## 7.4 Continental Economy ## 0.7.D Continental Economy
- [ ] Add regional scarcity. - [ ] Add regional scarcity.
- [ ] Add market hubs. - [ ] Add market hubs.
@@ -1014,8 +1115,12 @@ Goal: Expand from a small test world toward a huge, regionally diverse, persiste
- [ ] Add tariffs or taxes. - [ ] Add tariffs or taxes.
- [ ] Add price history. - [ ] Add price history.
- [ ] Add economic dashboards. - [ ] Add economic dashboards.
- [ ] Add interregional and global trade knowledge for comparative advantage, shipping routes, ports, rail hubs, customs, tariffs, sanctions, trade agreements, currency exchange, and supply-chain disruption.
- [ ] Add commercial logistics knowledge for warehousing, freight forwarding, fleet management, cold chains, bulk commodities, manifests, insurance, and customs documents.
- [ ] Add business scaling rules for sole proprietors, partnerships, cooperatives, corporations, franchises, guild enterprises, and state-owned enterprises.
- [ ] Add market infrastructure for commodity exchanges, long-term contracts, futures/forward agreements if appropriate, credit risk, and price discovery.
## 7.5 Tile Operations At Scale ## 0.7.E Tile Operations At Scale
- [ ] Define tile generation queue. - [ ] Define tile generation queue.
- [ ] Define tile publish queue. - [ ] Define tile publish queue.
@@ -1027,13 +1132,24 @@ Goal: Expand from a small test world toward a huge, regionally diverse, persiste
- [ ] Define cost controls for serving and regenerating terrain content. - [ ] Define cost controls for serving and regenerating terrain content.
- [ ] Define long-term archival strategy for superseded tile versions. - [ ] Define long-term archival strategy for superseded tile versions.
## 0.7.F Earth-Scale Field Science And Resource Knowledge
- [ ] Add surveying and cartography knowledge for accurate coordinates, tile boundaries, elevation, slope, hydrology, and route planning.
- [ ] Add geology knowledge for likely mineral resources, soil parent material, rock types, groundwater, caves, hazards, and mining prospects.
- [ ] Add hydrology knowledge for rivers, wetlands, aquifers, floodplains, irrigation, erosion, and drinking water risk.
- [ ] Add climatology and meteorology knowledge for regional weather, seasons, wind, storms, drought, snowpack, and agricultural planning.
- [ ] Add botany, forestry, and ecology knowledge for biome-specific plants, timber, wild foods, medicinal plants, invasive pressure, and regeneration.
- [ ] Add zoology and wildlife management knowledge for animal behavior, migration, hunting pressure, domestication candidates, and ecosystem balance.
- [ ] Add GIS/data-literacy progression for interpreting real-world terrain, climate, land-cover, and resource datasets.
- [ ] Add in-game field notebooks and sample collection to connect player exploration with world knowledge.
--- ---
# Phase 8 - Industrial And Automation Era # Version 0.8 - Industrial And Automation Era
Goal: Let player civilizations advance into mechanization, energy, automation, and large-scale production. Goal: Let player civilizations advance into mechanization, energy, automation, and large-scale production.
## 8.1 Industrial Materials ## 0.8.A Industrial Materials
- [ ] Add mining progression. - [ ] Add mining progression.
- [ ] Add ore processing. - [ ] Add ore processing.
@@ -1041,8 +1157,12 @@ Goal: Let player civilizations advance into mechanization, energy, automation, a
- [ ] Add machine parts. - [ ] Add machine parts.
- [ ] Add fuel types. - [ ] Add fuel types.
- [ ] Add industrial storage. - [ ] Add industrial storage.
- [ ] Add mining, geology, metallurgy, chemistry, ceramics, glass, concrete, polymers, and materials testing knowledge.
- [ ] Add iron and steel progression for charcoal iron, coke, blast furnaces, basic oxygen or electric arc steelmaking, alloying, heat treatment, rolling, casting, forging, and quality testing.
- [ ] Add industrial material standards for grades, tolerances, traceability, certification, failure reports, and safety-critical use.
- [ ] Add workplace safety knowledge for mines, furnaces, pressure vessels, chemicals, dust, noise, burns, crush risk, and confined spaces.
## 8.2 Power Systems ## 0.8.B Power Systems
- [ ] Add mechanical power. - [ ] Add mechanical power.
- [ ] Add water power. - [ ] Add water power.
@@ -1050,8 +1170,10 @@ Goal: Let player civilizations advance into mechanization, energy, automation, a
- [ ] Add electrical power. - [ ] Add electrical power.
- [ ] Add power grid rules. - [ ] Add power grid rules.
- [ ] Add maintenance/failure. - [ ] Add maintenance/failure.
- [ ] Add power generation knowledge for mechanical advantage, water wheels, wind, steam, combustion, generators, batteries, transformers, grid stability, and load management.
- [ ] Add electrical safety knowledge for grounding, insulation, overcurrent protection, arc risk, maintenance lockout, and public distribution hazards.
## 8.3 Production Chains ## 0.8.C Production Chains
- [ ] Add workshops. - [ ] Add workshops.
- [ ] Add factories. - [ ] Add factories.
@@ -1059,30 +1181,49 @@ Goal: Let player civilizations advance into mechanization, energy, automation, a
- [ ] Add labor requirements. - [ ] Add labor requirements.
- [ ] Add logistics bottlenecks. - [ ] Add logistics bottlenecks.
- [ ] Add quality tiers. - [ ] Add quality tiers.
- [ ] Add manufacturing knowledge for measurement, tolerances, machining, welding, casting, quality control, process flow, and supply-chain planning.
- [ ] Add chemistry/process knowledge for fuels, fertilizers, medicines, solvents, plastics, explosives where appropriate, and pollution control.
- [ ] Add industrialism knowledge for division of labor, interchangeable parts, standardization, assembly lines, factory layout, worker training, shift planning, maintenance schedules, and industrial relations.
- [ ] Add manufacturing business systems for procurement, bills of materials, production planning, inventory turns, cost accounting, quality audits, recalls, warranties, and supplier qualification.
- [ ] Add heavy industry chains for steel, cement, glass, chemicals, machine tools, engines, electrical equipment, vehicles, ships, rail, and construction equipment.
- [ ] Add industrial environmental controls for emissions, waste treatment, water use, occupational exposure, cleanup liability, and community impact.
## 8.4 Automation And Robotics ## 0.8.D Automation And Robotics
- [ ] Add automation control concept. - [ ] Add automation control concept.
- [ ] Add basic machine workers. - [ ] Add basic machine workers.
- [ ] Add robotics progression. - [ ] Add robotics progression.
- [ ] Add AI-assisted labor. - [ ] Add AI-assisted labor.
- [ ] Add safety/failure risks. - [ ] Add safety/failure risks.
- [ ] Add control systems knowledge for sensors, actuators, feedback loops, programmable logic, robotics, telemetry, fail-safes, and maintenance diagnostics.
- [ ] Add computing and communications knowledge for basic electronics, radio, wired networks, data storage, software, cybersecurity, and automation monitoring.
## 0.8.E Advanced Engineering And Energy Education
- [ ] Add engineering education paths for mechanical, civil, electrical, chemical, industrial, environmental, agricultural, biomedical, nuclear, aerospace, software, and systems engineering.
- [ ] Add design-review mechanics for complex machines, structures, power plants, factories, medical systems, transportation networks, and public infrastructure.
- [ ] Add failure investigation loops where players learn from breakdowns, accidents, low yield, overload, corrosion, fatigue, contamination, and operator error.
- [ ] Add team competency requirements for high-risk builds so major infrastructure depends on multiple trained characters, not one unlocked recipe.
- [ ] Add early nuclear science literacy as a long-term branch: atoms, radiation, shielding, fission basics, reactor safety, waste handling, and regulatory controls.
--- ---
# Phase 9 - Orbital Foundations # Version 0.9 - Orbital Foundations
Goal: Make space feel earned by centuries of civilization, science, industry, and infrastructure. Goal: Make space feel earned by centuries of civilization, science, industry, and infrastructure.
## 9.1 Astronomy And Knowledge ## 0.9.A Astronomy And Knowledge
- [ ] Add astronomy observations. - [ ] Add astronomy observations.
- [ ] Add observatory building. - [ ] Add observatory building.
- [ ] Add star charts. - [ ] Add star charts.
- [ ] Add orbital mechanics knowledge. - [ ] Add orbital mechanics knowledge.
- [ ] Add education requirements. - [ ] Add education requirements.
- [ ] Add mathematics progression for algebra, geometry, trigonometry, calculus concepts, statistics, and numerical modeling as prerequisites for advanced engineering.
- [ ] Add physics progression for mechanics, thermodynamics, electromagnetism, fluids, materials, radiation, and high-energy safety.
- [ ] Add astronomy and space science learning for celestial observation, timekeeping, navigation, orbital periods, launch windows, and planetary environments.
## 9.2 Launch Infrastructure ## 0.9.B Launch Infrastructure
- [ ] Add advanced materials. - [ ] Add advanced materials.
- [ ] Add fuel production. - [ ] Add fuel production.
@@ -1090,8 +1231,12 @@ Goal: Make space feel earned by centuries of civilization, science, industry, an
- [ ] Add rocket components. - [ ] Add rocket components.
- [ ] Add launch preparation. - [ ] Add launch preparation.
- [ ] Add launch failure risk. - [ ] Add launch failure risk.
- [ ] Add rocketry knowledge for propulsion, staging, thrust-to-weight, mass fraction, aerodynamics, guidance, navigation, control, telemetry, and range safety.
- [ ] Add aerospace manufacturing knowledge for precision machining, composites, avionics, cryogenics, pressure systems, clean handling, and test procedures.
- [ ] Add nuclear and advanced energy knowledge for reactors, radioisotope systems, shielding, radiation monitoring, emergency response, and long-term waste stewardship.
- [ ] Add pilot, mission controller, engineer, and safety officer training paths.
## 9.3 Orbital Gameplay ## 0.9.C Orbital Gameplay
- [ ] Add orbital object model. - [ ] Add orbital object model.
- [ ] Add satellites. - [ ] Add satellites.
@@ -1099,13 +1244,21 @@ Goal: Make space feel earned by centuries of civilization, science, industry, an
- [ ] Add navigation benefits. - [ ] Add navigation benefits.
- [ ] Add orbital maintenance. - [ ] Add orbital maintenance.
## 0.9.D Research Institutions And Advanced Science
- [ ] Add observatories, universities, laboratories, proving grounds, launch ranges, wind tunnels, reactors or simulator facilities, and mission control centers.
- [ ] Add research programs that require funding, specialists, instruments, materials, power, safety approvals, tests, failures, and published findings.
- [ ] Add advanced question banks and practical exams for orbital mechanics, rocket science, nuclear physics, materials science, avionics, life support, and mission planning.
- [ ] Add simulation and prototype testing so players can learn before risking major resources or lives.
- [ ] Add ethical and safety governance for dangerous research, weapons-adjacent technology, radiation, launch debris, and public risk.
--- ---
# Phase 10 - Interplanetary Frontier # Version 1.0 - Interplanetary Frontier
Goal: Expand civilization beyond Earth while keeping Earth emotionally and mechanically central. Goal: Expand civilization beyond Earth while keeping Earth emotionally and mechanically central.
## 10.1 Interplanetary Travel ## 1.0.A Interplanetary Travel
- [ ] Add spacecraft design. - [ ] Add spacecraft design.
- [ ] Add fuel logistics. - [ ] Add fuel logistics.
@@ -1114,7 +1267,7 @@ Goal: Expand civilization beyond Earth while keeping Earth emotionally and mecha
- [ ] Add travel windows. - [ ] Add travel windows.
- [ ] Add travel risk. - [ ] Add travel risk.
## 10.2 Colony Logistics ## 1.0.B Colony Logistics
- [ ] Add colony founding. - [ ] Add colony founding.
- [ ] Add supply chains. - [ ] Add supply chains.
@@ -1123,7 +1276,7 @@ Goal: Expand civilization beyond Earth while keeping Earth emotionally and mecha
- [ ] Add communication delay if desired. - [ ] Add communication delay if desired.
- [ ] Add political connection to Earth. - [ ] Add political connection to Earth.
## 10.3 Multi-World Persistence ## 1.0.C Multi-World Persistence
- [ ] Add planetary world records. - [ ] Add planetary world records.
- [ ] Add orbital economy. - [ ] Add orbital economy.
@@ -1131,6 +1284,18 @@ Goal: Expand civilization beyond Earth while keeping Earth emotionally and mecha
- [ ] Add settlement history across worlds. - [ ] Add settlement history across worlds.
- [ ] Add long-term expansion governance. - [ ] Add long-term expansion governance.
## 1.0.D Offworld Knowledge And Colonization Science
- [ ] Add life support knowledge for atmosphere, pressure, oxygen, carbon dioxide removal, water recycling, food loops, waste processing, and emergency repair.
- [ ] Add space medicine knowledge for radiation, low gravity, isolation, injury care, nutrition, infection control, and long-duration health.
- [ ] Add closed-loop agriculture knowledge for hydroponics, aeroponics, soil substitutes, lighting, pollination, nutrient balance, and genetic diversity.
- [ ] Add planetary geology and resource utilization knowledge for prospecting, mining, refining, construction materials, ice extraction, and in-situ fuel production.
- [ ] Add offworld construction knowledge for habitats, shielding, pressure vessels, thermal control, foundations, dust management, and maintenance.
- [ ] Add interplanetary navigation knowledge for transfer windows, orbital insertion, fuel margins, communication delay, rescue planning, and mission risk.
- [ ] Add colony governance and culture knowledge for law, education, medical standards, emergency authority, succession, archives, and Earth relationship.
- [ ] Add long-term knowledge preservation systems for isolated colonies, including redundant archives, local schools, apprenticeships, and disaster recovery plans.
- [ ] Add late-game research frontiers for terraforming studies, advanced propulsion, planetary-scale ecology, deep-space industry, and multi-generation settlement planning.
--- ---
# Cross-Cutting Technical Tracks # Cross-Cutting Technical Tracks
@@ -1169,6 +1334,9 @@ These tracks run across all phases and must not be left as afterthoughts.
- [ ] Add automated save validation. - [ ] Add automated save validation.
- [ ] Add schema/version tracking. - [ ] Add schema/version tracking.
- [ ] Add admin restore workflow. - [ ] Add admin restore workflow.
- [ ] Define knowledge database schema for subjects, topics, concepts, prerequisites, question banks, practice records, certifications, teaching records, source notes, and localization.
- [ ] Define audit/version rules for educational content updates so question changes do not corrupt character progression.
- [ ] Define privacy-safe analytics for which concepts confuse players or create excessive friction.
## C. Accounts And Authentication ## C. Accounts And Authentication
@@ -1217,6 +1385,8 @@ These tracks run across all phases and must not be left as afterthoughts.
- [ ] Add log retention policy. - [ ] Add log retention policy.
- [ ] Add account recovery policy. - [ ] Add account recovery policy.
- [ ] Add economy abuse monitoring. - [ ] Add economy abuse monitoring.
- [ ] Add anti-cheat rules for knowledge checks, certifications, teaching records, and externally-assisted answer farming.
- [ ] Add moderation workflow for player-authored notes, books, schools, and public educational content.
## G. Performance ## G. Performance
@@ -1274,6 +1444,8 @@ These tracks run across all phases and must not be left as afterthoughts.
- [ ] Add crash reporting plan. - [ ] Add crash reporting plan.
- [ ] Add automated build validation. - [ ] Add automated build validation.
- [ ] Add test server reset process. - [ ] Add test server reset process.
- [ ] Add educational content QA checklist for accuracy, clarity, difficulty tiering, accessibility, and source review.
- [ ] Add balance tests to ensure knowledge checks improve depth without pushing players out of the survival loop.
## K. Build And Release Pipeline ## K. Build And Release Pipeline
+1 -1
View File
@@ -1,7 +1,7 @@
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test.L_GroundZeroTerrain_Test GameDefaultMap=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test.L_GroundZeroTerrain_Test
EditorStartupMap=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test.L_GroundZeroTerrain_Test EditorStartupMap=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test.L_GroundZeroTerrain_Test
GlobalDefaultGameMode=/Game/ThirdPerson/Blueprints/BP_ThirdPersonGameMode.BP_ThirdPersonGameMode_C GlobalDefaultGameMode=/Game/Agrarian/Blueprints/Characters/BP_AgrarianGameMode.BP_AgrarianGameMode_C
[/Script/Engine.RendererSettings] [/Script/Engine.RendererSettings]
r.Mobile.ShadingPath=1 r.Mobile.ShadingPath=1
-1
View File
@@ -30,4 +30,3 @@ IncludePrerequisites=True
IncludeAppLocalPrerequisites=False IncludeAppLocalPrerequisites=False
Build=IfProjectHasCode Build=IfProjectHasCode
+MapsToCook=(FilePath="/Game/Agrarian/Maps/L_GroundZeroTerrain_Test") +MapsToCook=(FilePath="/Game/Agrarian/Maps/L_GroundZeroTerrain_Test")
+MapsToCook=(FilePath="/Game/ThirdPerson/Lvl_ThirdPerson")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,21 @@
{
"schema_version": 1,
"generated_at_utc": "2026-05-16T04:33:53Z",
"source_registry": "Data/Tiles/ground_zero_tiles.json",
"generation_rule": "Only tiles with real source status and explicit growing-zone data are emitted.",
"tiles": [
{
"tile_id": "gz_us_ca_pacifica_utm10n_e544_n4160",
"center_latitude": 37.5925,
"center_longitude": -122.4995,
"growing_zone_label": "USDA 10a",
"climate_profile": "coastal_mediterranean_mild",
"growing_season_start_day": 46,
"growing_season_end_day": 350,
"frost_free_days": 305,
"min_average_growing_temp_c": 7.0,
"crop_safety_buffer_days": 14,
"data_basis": "MVP conservative coastal Pacifica profile; replace with authoritative zone/temperature datasets during regional expansion."
}
]
}
+80
View File
@@ -107,6 +107,12 @@
"$ref": "#/$defs/source" "$ref": "#/$defs/source"
} }
}, },
"solar_metadata": {
"$ref": "#/$defs/solar_metadata"
},
"growing_season_metadata": {
"$ref": "#/$defs/growing_season_metadata"
},
"notes": { "notes": {
"type": "string" "type": "string"
} }
@@ -161,6 +167,80 @@
} }
} }
}, },
"solar_metadata": {
"type": "object",
"required": [
"time_zone_id",
"standard_utc_offset_hours",
"daylight_utc_offset_hours",
"solar_model"
],
"additionalProperties": false,
"properties": {
"time_zone_id": {
"type": "string"
},
"standard_utc_offset_hours": {
"type": "number",
"minimum": -12,
"maximum": 14
},
"daylight_utc_offset_hours": {
"type": "number",
"minimum": -12,
"maximum": 14
},
"solar_model": {
"type": "string"
}
}
},
"growing_season_metadata": {
"type": "object",
"required": [
"growing_zone_label",
"climate_profile",
"growing_season_start_day",
"growing_season_end_day",
"frost_free_days",
"crop_safety_buffer_days"
],
"additionalProperties": false,
"properties": {
"growing_zone_label": {
"type": "string"
},
"climate_profile": {
"type": "string"
},
"growing_season_start_day": {
"type": "integer",
"minimum": 1,
"maximum": 366
},
"growing_season_end_day": {
"type": "integer",
"minimum": 1,
"maximum": 366
},
"frost_free_days": {
"type": "integer",
"minimum": 0,
"maximum": 366
},
"min_average_growing_temp_c": {
"type": "number"
},
"crop_safety_buffer_days": {
"type": "integer",
"minimum": 0,
"maximum": 90
},
"data_basis": {
"type": "string"
}
}
},
"source": { "source": {
"type": "object", "type": "object",
"required": ["source_kind", "source_name", "coverage_status"], "required": ["source_kind", "source_name", "coverage_status"],
+24
View File
@@ -40,6 +40,30 @@ ON terrain_tiles (grid_scheme, projection, utm_zone, easting_min_m, northing_min
CREATE INDEX IF NOT EXISTS idx_terrain_tiles_status CREATE INDEX IF NOT EXISTS idx_terrain_tiles_status
ON terrain_tiles (status); ON terrain_tiles (status);
CREATE TABLE IF NOT EXISTS terrain_tile_solar_metadata (
tile_id TEXT PRIMARY KEY,
time_zone_id TEXT NOT NULL,
standard_utc_offset_hours REAL NOT NULL CHECK (standard_utc_offset_hours >= -12 AND standard_utc_offset_hours <= 14),
daylight_utc_offset_hours REAL NOT NULL CHECK (daylight_utc_offset_hours >= -12 AND daylight_utc_offset_hours <= 14),
solar_model TEXT NOT NULL DEFAULT 'NOAA approximate sunrise/sunset',
generated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tile_id) REFERENCES terrain_tiles(tile_id)
);
CREATE TABLE IF NOT EXISTS terrain_tile_growing_season_metadata (
tile_id TEXT PRIMARY KEY,
growing_zone_label TEXT NOT NULL,
climate_profile TEXT NOT NULL DEFAULT 'unknown',
growing_season_start_day INTEGER NOT NULL CHECK (growing_season_start_day >= 1 AND growing_season_start_day <= 366),
growing_season_end_day INTEGER NOT NULL CHECK (growing_season_end_day >= 1 AND growing_season_end_day <= 366),
frost_free_days INTEGER NOT NULL CHECK (frost_free_days >= 0 AND frost_free_days <= 366),
min_average_growing_temp_c REAL NOT NULL DEFAULT 7.0,
crop_safety_buffer_days INTEGER NOT NULL DEFAULT 14 CHECK (crop_safety_buffer_days >= 0 AND crop_safety_buffer_days <= 90),
data_basis TEXT NOT NULL DEFAULT '',
generated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tile_id) REFERENCES terrain_tiles(tile_id)
);
CREATE TABLE IF NOT EXISTS terrain_tile_neighbors ( CREATE TABLE IF NOT EXISTS terrain_tile_neighbors (
tile_id TEXT NOT NULL, tile_id TEXT NOT NULL,
direction TEXT NOT NULL CHECK (direction IN ('n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw')), direction TEXT NOT NULL CHECK (direction IN ('n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw')),
+43
View File
@@ -0,0 +1,43 @@
{
"schema_version": 1,
"generated_at_utc": "2026-05-16T04:14:42Z",
"source_registry": "Data/Tiles/ground_zero_tiles.json",
"generation_rule": "Only tiles with real source status and explicit timezone data are emitted.",
"tiles": [
{
"tile_id": "gz_us_ca_pacifica_utm10n_e544_n4160",
"center_latitude": 37.5925,
"center_longitude": -122.4995,
"time_zone_id": "America/Los_Angeles",
"standard_utc_offset_hours": -8.0,
"daylight_utc_offset_hours": -7.0,
"solar_model": "NOAA approximate sunrise/sunset",
"sample_solar_hours": {
"march_equinox": {
"sunrise_hour": 7.231,
"sunset_hour": 19.364,
"solar_noon_hour": 13.298,
"day_length_hours": 12.133
},
"june_solstice": {
"sunrise_hour": 5.807,
"sunset_hour": 20.571,
"solar_noon_hour": 13.189,
"day_length_hours": 14.764
},
"september_equinox": {
"sunrise_hour": 6.956,
"sunset_hour": 19.122,
"solar_noon_hour": 13.039,
"day_length_hours": 12.166
},
"december_solstice": {
"sunrise_hour": 8.348,
"sunset_hour": 17.912,
"solar_noon_hour": 13.13,
"day_length_hours": 9.564
}
}
}
]
}
+3 -1
View File
@@ -147,7 +147,9 @@ Minimum restore tests:
- Quarterly: perform a fuller restore drill of the project folder plus one VM - Quarterly: perform a fuller restore drill of the project folder plus one VM
definition/disk to a non-production test path. definition/disk to a non-production test path.
Record restore tests in the handoff log with snapshot path, date, and result. Record restore tests in `Docs/Ops/BackupRestoreTestLog.md` with snapshot path,
date, result, evidence, and cleanup status. Handoff notes can summarize the
latest result, but the durable restore-test record belongs in the log file.
## Security ## Security
+22
View File
@@ -0,0 +1,22 @@
# Basic Animation Blueprint
The MVP player character uses the Unreal mannequin unarmed animation Blueprint:
- Character Blueprint: `/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter`
- Skeletal mesh: `/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple`
- Animation Blueprint: `/Game/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed`
This provides the first playable animation baseline for idle, walk/run
locomotion, jump, falling, and landing. Crouch and prone currently reuse the
same baseline presentation until the dedicated stance animation pass replaces
them with survival-specific movement.
Verification script:
```text
Scripts/verify_basic_animation_blueprint.py
```
The script validates that the Agrarian player Blueprint is still wired to the expected
mesh and animation class, and that the required idle, blendspace, jump, fall,
and land assets exist.
+10
View File
@@ -10,6 +10,8 @@ storefront channels require a heavier workflow.
- Use short-lived task branches for risky, multi-commit, or parallel work. - Use short-lived task branches for risky, multi-commit, or parallel work.
- Small Codex-only changes may land directly on `main` when the worktree is - Small Codex-only changes may land directly on `main` when the worktree is
clean, the change is scoped, and validation is run. clean, the change is scoped, and validation is run.
- Branch protection/review expectations are defined in
`Docs/Ops/GitHubBranchProtectionAndReviewRules.md`.
## Branch Names ## Branch Names
@@ -61,3 +63,11 @@ Create a long-lived staging branch only when one of these becomes true:
- Release candidates need stabilization while feature work continues. - Release candidates need stabilization while feature work continues.
- Automated CI checks become strict enough that `main` should only receive - Automated CI checks become strict enough that `main` should only receive
reviewed, green changes. reviewed, green changes.
## Protection Enforcement
The repository is currently private and staying on the free tier. GitHub does
not currently allow branch protection/ruleset enforcement for this private repo
without upgrading or making it public, so version 0.01 uses documented process
rules and a pull request template. Enforced protected `main` remains tracked in
the later build/release pipeline milestone.
+5 -2
View File
@@ -297,8 +297,11 @@ Mitigation:
- The public tile endpoint currently uses `maps.agrariangame.com:18080`. - The public tile endpoint currently uses `maps.agrariangame.com:18080`.
- The first tile server runs inside the dedicated `Agrarian-TileServer` VM. - The first tile server runs inside the dedicated `Agrarian-TileServer` VM.
- Unused Unreal starter variants have been removed. - Unused Unreal starter variants have been removed.
- ThirdPerson and LevelPrototyping content remain temporarily while still - ThirdPerson player/game mode Blueprint paths have been replaced with
referenced by current player Blueprint and prototype automation. Agrarian-owned player, controller, and game mode assets under
`/Game/Agrarian/Blueprints/Characters`.
- LevelPrototyping content remains temporarily by decision: it is a narrow
active dependency and its replacement is tracked as version `0.1` work.
## Follow-Up Documents ## Follow-Up Documents
+37
View File
@@ -0,0 +1,37 @@
# Gameplay Ability System Decision
Decision: defer Unreal's Gameplay Ability System for the MVP.
Agrarian should not adopt GAS during version 0.01 or the early version 0.1
survival foundation. The current project already has focused replicated
components for survival, inventory, crafting, resources, building placement,
time, weather, interaction, and early movement modifiers. Those systems are
small, readable, and directly aligned with the immediate playable MVP.
GAS is useful for games with many networked abilities, buffs, cooldowns,
attribute effects, prediction needs, tags, and reusable gameplay effects. That
will probably matter later for Agrarian when character skills, injuries,
illnesses, professions, equipment, medicine, combat, aging, and long-term
condition effects become more complex.
For now, adding GAS would increase setup cost, content complexity, debugging
surface, and onboarding burden before the project has enough gameplay variety
to benefit from it.
Guideline:
- Keep MVP survival systems implemented through explicit C++ components and
Blueprint assets.
- Keep future stat/effect code modular enough that it can migrate into GAS if
the need becomes clear.
- Revisit GAS after version 0.1 once combat, skills, injuries, medicine,
profession bonuses, equipment effects, and replicated long-term status
modifiers are better defined.
Revisit trigger:
- The project has many recurring timed effects, buffs, debuffs, cooldowns, or
ability activations that are becoming duplicated across systems.
- Multiplayer prediction for ability-style actions becomes important.
- Designers need data-driven effect stacking, gameplay tags, and reusable
effect authoring more than hand-coded component behavior.
+3 -1
View File
@@ -96,4 +96,6 @@ sudo RESTIC_PASSWORD_FILE=/root/.backup-secrets/agrarian-project-restic.password
snapshots --tag agrarian-game --tag project snapshots --tag agrarian-game --tag project
``` ```
Record restore tests in the handoff log with the snapshot name and result. Record restore tests in `Docs/Ops/BackupRestoreTestLog.md` with the snapshot
name, restored path, result, and cleanup status. Summarize the latest result in
handoff notes when useful.
+3
View File
@@ -89,3 +89,6 @@ qemu-img info vms/Ubuntu-Codex/disks/hdc-vdisk1.img.qcow2
Restore should be done to a new VM or a test path first. Do not overwrite a Restore should be done to a new VM or a test path first. Do not overwrite a
working VM disk until the backup has been verified and the original disk has working VM disk until the backup has been verified and the original disk has
been preserved. been preserved.
Record VM restore tests in `Docs/Ops/BackupRestoreTestLog.md` with the snapshot
timestamp, checksum result, disk inspection result, and cleanup status.
+61
View File
@@ -0,0 +1,61 @@
# Backup Restore Test Log
## Purpose
Backups are only useful if restore paths are tested before an emergency. This log records recurring restore tests for the Agrarian project backup and the development VM backups.
Use this file for the durable project record. Handoff notes can summarize the latest result, but the evidence and cadence live here.
## Cadence
- Weekly: restore one small file from the latest project snapshot to a temporary folder and verify it matches the live file.
- Monthly: restore or inspect enough of the project repository snapshot to confirm Git metadata/history are usable.
- Monthly: verify the latest VM backup snapshot is readable by checking `SHA256SUMS` and inspecting the backed-up disk metadata.
- Quarterly: restore the project folder plus one VM definition/disk to a non-production test path.
## Evidence Required
Each entry should include:
- Date and local time.
- Backup type: project, VM, or full drill.
- Snapshot path, restic snapshot ID, or VM backup timestamp.
- Command or runbook section used.
- Result: Pass, Blocked, or Fail.
- Notes on what was restored, where it was restored, and whether cleanup was completed.
## Log
| Date | Type | Snapshot | Result | Evidence / Notes |
| --- | --- | --- | --- | --- |
| 2026-05-15 | Project restore-test log setup | `/mnt/backups/linastorage/backups/agrarian-game/project/restic-repository` | Blocked | Log created. The expected restic repository path was not present on Ubuntu-Codex at setup time, so the first project restore test must run after the Linastorage backup mount/repository is confirmed. |
| 2026-05-15 | VM restore-test log setup | `/mnt/user/backups/agrarian-game/vms/snapshots` | Blocked | Log created. The Unraid snapshot directory exists, but no VM snapshot directories were present at setup time. Run the first VM readability test after the next successful VM backup snapshot. |
## Project Restore Test Procedure
Use the project backup runbook for the authoritative paths:
```bash
mkdir -p /tmp/agrarian-project-restore-test
sudo sh -c 'RESTIC_PASSWORD_FILE=/root/.backup-secrets/agrarian-project-restic.password \
restic -r /mnt/backups/linastorage/backups/agrarian-game/project/restic-repository \
dump latest /mnt/projects/AgrarianGameBulid/AGRARIAN_DEVELOPMENT_ROADMAP.md \
> /tmp/agrarian-project-restore-test/AGRARIAN_DEVELOPMENT_ROADMAP.md'
diff -q /mnt/projects/AgrarianGameBulid/AGRARIAN_DEVELOPMENT_ROADMAP.md \
/tmp/agrarian-project-restore-test/AGRARIAN_DEVELOPMENT_ROADMAP.md
```
Record the restic snapshot ID, result, and cleanup status in the log table.
## VM Restore Test Procedure
Use the VM backup runbook for authoritative paths:
```bash
cd /mnt/user/backups/agrarian-game/vms/snapshots/<timestamp>
sha256sum -c SHA256SUMS
qemu-img info vms/Ubuntu-Codex/disks/hdc-vdisk1.img.qcow2
```
Record the VM snapshot timestamp, checksum result, disk metadata result, and cleanup status in the log table.
+78
View File
@@ -0,0 +1,78 @@
# Agrarian Build Log Retention Policy
This policy covers generated build, package, cook, editor, crash, shader, and
automation logs for the Agrarian Unreal project.
## Goals
- Keep enough recent logs to debug build and packaging failures.
- Preserve important release/investor-demo logs long enough to explain what was
shipped.
- Prevent `Saved/`, `Builds/`, and generated automation output from growing
without limit on the shared project drive.
- Keep generated logs out of Git.
## Log Locations
- `Saved/BuildLogs/`: Windows editor builds, packaged target builds, and
packaging logs.
- `Saved/Logs/`: Unreal editor, cook, runtime, UnrealPak, World Partition, and
commandlet logs.
- `Saved/Crashes/`: editor/runtime crash context, logs, and dumps.
- `Saved/MaterialStats/`: generated material stat CSV files.
- `Saved/Shaders/` and `Saved/ShaderDebugInfo/`: generated shader diagnostics.
- `Builds/`: packaged local build outputs. These are not retained as source
artifacts unless explicitly published or sent externally.
## Default Retention
- Build/package logs in `Saved/BuildLogs/`: keep 90 days.
- Unreal logs in `Saved/Logs/`: keep 30 days.
- Crash reports in `Saved/Crashes/`: keep 90 days.
- Material stats in `Saved/MaterialStats/`: keep 30 days.
- Shader debug output in `Saved/Shaders/` and `Saved/ShaderDebugInfo/`: keep 14
days.
- Local packaged builds in `Builds/`: keep the latest published investor/demo
package and any active test build; prune older regenerated builds manually.
## Preserve Before Pruning
Before deleting logs, preserve any file that is tied to:
- an investor/demo package;
- a release candidate;
- a crash or build failure still being investigated;
- a bug report sent to another person;
- a store upload or external test distribution.
Preserved logs should be copied to a dated folder under a local operational
archive or attached to the related issue/release note before cleanup.
## Cleanup Script
Use:
```text
Scripts/prune_build_logs.sh --dry-run
Scripts/prune_build_logs.sh --apply
```
The script is dry-run by default. It only removes generated files under known
`Saved/` log/debug paths and empty directories left behind. It does not touch
source, `Content/`, `Config/`, `Source/`, `Docs/`, or Git history.
## Cadence
- Run dry-run before major packaging cycles if the shared drive is getting full.
- Run apply monthly during active development.
- Always run dry-run first when preparing an investor or external test build.
## Git Policy
Generated logs are not committed. Commit only:
- build scripts;
- packaging scripts;
- retention policy docs;
- release notes;
- summaries of important failures or fixes.
@@ -0,0 +1,179 @@
# Development Infrastructure Recovery Runbook
## Purpose
This runbook gives a simple recovery path when the Agrarian development machines or shared project storage are unreachable. It covers:
- Unraid `DevBox`
- `Ubuntu-Codex`
- `Windows-Builder`
- The `projects` SMB share
Use the least disruptive recovery path first. Do not reboot `DevBox` until VM-level and service-level checks have failed, because it hosts shared storage and the development VMs.
## Current Baseline
| System | Role | Address / Name | Notes |
| --- | --- | --- | --- |
| `DevBox` | Unraid host, SMB storage, VM host | `192.168.5.8` / `DevBox` | Hosts `projects` share and VMs. |
| `Ubuntu-Codex` | Source-control and automation VM | `192.168.5.10` expected, current host access may also show `192.168.5.6` or `192.168.5.9` depending on interface | Mounts `//192.168.5.8/projects` at `/mnt/projects`. |
| `Windows-Builder` | Unreal/Visual Studio/GPU build VM | `192.168.5.12` | Uses fixed VirtIO MAC `52:54:00:17:ec:5d`. |
| `projects` share | Shared Unreal project storage | `\\DevBox\projects` / `/mnt/projects` | Repo path is `/mnt/projects/AgrarianGameBulid`. |
## First Triage
From Ubuntu-Codex or another LAN machine:
```bash
ping -c 3 192.168.5.8
ping -c 3 192.168.5.12
nc -vz -w 5 192.168.5.12 3389
mount | rg '/mnt/projects|cifs|smb'
```
From `DevBox`:
```bash
virsh list --all
virsh domiflist Windows-Builder
virsh domiflist Ubuntu-Codex
```
Expected VM NIC baselines:
- `Windows-Builder`: bridge `br0`, model `virtio-net`, MAC `52:54:00:17:ec:5d`
- `Ubuntu-Codex`: bridge `br0`, model `virtio-net`, MAC `52:54:00:a5:cf:63`
## Safe Reboot Order
Use this order when multiple systems are unhealthy:
1. Save or stop active work where possible.
2. Restart only the failing service if the host is reachable.
3. Restart the affected VM from inside the guest if guest access works.
4. Use `virsh shutdown <VM>` from `DevBox` if guest access does not work.
5. Use `virsh reboot <VM>` only when a graceful shutdown is not enough.
6. Use `virsh destroy <VM>` only when the VM is hung and no graceful path works.
7. Reboot `DevBox` only after confirming SMB, libvirt, or host networking cannot be recovered individually.
Before planned VM shutdowns, consider a manual VM backup if the change is risky:
```bash
/bin/bash /boot/config/custom/agrarian-vm-backup.sh --shutdown-running --vm Windows-Builder
/bin/bash /boot/config/custom/agrarian-vm-backup.sh --shutdown-running --vm Ubuntu-Codex
```
## Windows-Builder Recovery
Use these in order:
1. Confirm the VM is running:
```bash
virsh domstate Windows-Builder
virsh domiflist Windows-Builder
```
2. Confirm RDP is listening from Ubuntu-Codex:
```bash
nc -vz -w 5 192.168.5.12 3389
```
3. Use the QEMU guest-agent path before relying on RDP when possible.
4. If RDP is down but guest commands work, check:
```powershell
Get-Service TermService
Get-NetConnectionProfile
Get-NetFirewallRule -DisplayGroup "Remote Desktop"
```
5. Restart RDP only if it is not listening:
```powershell
Restart-Service TermService -Force
```
6. If Unreal visual inspection is needed, use Sunshine/Moonlight instead of RDP.
Detailed Windows-Builder references:
- `Docs/Ops/WindowsBuilderNetworkRdpStability.md`
- `Docs/Ops/WindowsBuilderGpuRemoteAccess.md`
## Ubuntu-Codex Recovery
Use these in order:
1. Confirm VM state from `DevBox`:
```bash
virsh domstate Ubuntu-Codex
virsh domiflist Ubuntu-Codex
```
2. Confirm SSH or console access.
3. Confirm the project mount:
```bash
mount | rg '/mnt/projects'
ls -la /mnt/projects/AgrarianGameBulid
```
4. If `/mnt/projects` is missing, remount the SMB share using the existing system mount configuration rather than creating a new ad hoc mount.
5. Confirm Git access:
```bash
git -C /mnt/projects/AgrarianGameBulid status --short
git -C /mnt/projects/AgrarianGameBulid remote -v
```
Do not wipe local changes to recover the VM. Preserve uncommitted work first with a commit, patch, or backup copy outside the repo.
## DevBox And SMB Recovery
Use these in order:
1. Confirm the host is reachable:
```bash
ping -c 3 192.168.5.8
```
2. Confirm Unraid services and VM state through the Unraid UI or SSH.
3. Confirm the `projects` share is visible:
```bash
smbclient -L //192.168.5.8 -N
```
4. Confirm Ubuntu-Codex sees the share mounted:
```bash
mount | rg '/mnt/projects'
```
5. If DNS name `DevBox` fails but IP works, use the IP temporarily and repair the router/DNS record later.
6. Avoid storing project files directly on the Unraid OS boot filesystem. Project data belongs on the `projects` share or in VMs.
## When To Stop And Inspect Before Rebooting
Pause before rebooting if:
- A build, package, backup, or VM disk copy is running.
- Unreal Editor is open with unsaved assets.
- Git has uncommitted changes that are not understood.
- A backup or restore test is in progress.
- DevBox has disk or array warnings.
## After Recovery
After any VM or `DevBox` recovery:
1. Confirm `/mnt/projects/AgrarianGameBulid` is reachable.
2. Run `git status --short`.
3. Confirm Windows-Builder RDP with `nc -vz -w 5 192.168.5.12 3389`.
4. Confirm Sunshine only if visual inspection is needed.
5. Record unusual recovery steps in the handoff notes or the relevant ops doc.
@@ -0,0 +1,86 @@
# GitHub Branch Protection And Review Rules
## Current Repository State
- Repository: `pacificao/AgrarianGameBuild`
- Visibility: private
- Default branch: `main`
- Current plan constraint: GitHub branch protection and repository rulesets are not enforceable on this private repository without GitHub Pro/Team or making the repository public.
GitHub API checks on 2026-05-15 returned the upgrade requirement for both branch protection and repository rulesets. To stay on the free tier, version 0.01 uses documented process controls and pull request templates now, with enforced protected `main` deferred to the later release/build pipeline milestone already tracked in the roadmap.
## Main Branch Policy
`main` is the only long-lived development branch for now.
Rules:
- Keep `main` buildable.
- Prefer short-lived task branches for risky, multi-file, Unreal asset, infrastructure, or parallel work.
- Direct commits to `main` are allowed only for small, scoped changes when validation is run and the working tree is understood.
- Do not commit secrets, raw terrain source datasets, generated builds, `DerivedDataCache`, `Intermediate`, `Saved`, `Binaries`, large source-art archives, or local machine caches.
- Before milestone demos or investor builds, confirm `main` is clean, pushed, and smoke-tested.
## Pull Request Rules
Use a pull request when any of these are true:
- The change touches gameplay systems, replication, persistence, build tooling, backup tooling, deployment, or server infrastructure.
- The change modifies Unreal binary assets that are hard to review in text.
- The change spans multiple roadmap items.
- The change could affect packaged builds, dedicated servers, map tile delivery, or player data.
- Another developer, workstation, or Codex session is working in the same area.
For PRs:
- Use the existing branch naming convention in `Docs/BranchingConventions.md`.
- Link the roadmap item being completed.
- Include validation steps actually run.
- Include screenshots or build artifacts only as links or paths, not committed binaries.
- Merge only after the change is reviewed or explicitly accepted by the project owner.
## Review Expectations
Minimum review expectations by change type:
| Change Type | Review Expectation |
| --- | --- |
| Docs-only | Self-review is acceptable if scoped and validated with `git diff --check`. |
| Small code-only fix | Self-review is acceptable when tests/build checks pass and risk is low. |
| Gameplay feature | Project owner review or second-developer review before merge when practical. |
| Unreal binary assets | Record the editor action taken and visual/build validation performed. |
| Build, backup, deployment, server, or GitHub workflow changes | Owner review before merge unless it is an emergency fix. |
| Secrets, credentials, auth, payment, wallet, or production data handling | Do not merge without explicit owner review. |
## Required Local Checks Before Push
Run the checks appropriate for the change:
- Documentation/text changes: `git diff --check`
- C++ changes: editor target build wrapper or Windows headless build command
- Packaged-client changes: packaged client build wrapper and smoke test
- Server changes: dedicated server build wrapper and relevant smoke test
- Backup/tooling changes: dry-run mode first when available
Record skipped checks in the PR or commit message.
## Free-Tier Controls Installed Now
- `Docs/Ops/GitHubBranchProtectionAndReviewRules.md` defines the working policy.
- `.github/PULL_REQUEST_TEMPLATE.md` gives a repeatable review checklist.
- The roadmap keeps paid/private branch protection enforcement in the later release/build pipeline section.
## Future Enforced Settings
When a paid GitHub plan is justified or the repository becomes public, enable protected `main` with:
- Require pull request before merge.
- Require at least one approving review for non-doc changes.
- Dismiss stale approvals when new commits are pushed.
- Require conversation resolution before merge.
- Require status checks once CI exists.
- Restrict force pushes.
- Restrict branch deletion.
- Require linear history if it does not interfere with Unreal asset workflows.
- Allow administrators to bypass only for emergency recovery.
+62
View File
@@ -0,0 +1,62 @@
# Windows-Builder GPU Remote Access
## Purpose
Windows-Builder needs two remote access paths:
- Headless build/control path for normal Codex work through QEMU guest tools and PowerShell.
- Real-GPU visual path for occasional Unreal Editor inspection, startup screen checks, and gameplay verification.
RDP remains useful for administration, but it can interfere with the display session Unreal and GPU capture tools see. Use Sunshine/Moonlight when the goal is to inspect the GPU-rendered desktop.
## Current Setup
- Host VM: `Windows-Builder`
- LAN address: `192.168.5.12`
- GPU: NVIDIA GeForce GTX 1660 SUPER
- GPU driver observed during setup: `32.0.15.9649`
- Streaming host: Sunshine `2025.924.154138`
- Virtual display: Virtual Display Driver by MTT, 1920x1080 at 60 Hz
- Sunshine service: `SunshineService`, automatic startup
- Sunshine web UI: `https://192.168.5.12:47990`
- Login: use the Windows-Builder admin credential stored in the operational handoff, not a separate project password.
## Firewall Ports
Windows Firewall has LAN rules for Sunshine GameStream:
- TCP: `47984`, `47989`, `47990`, `48010`
- UDP: `47998`, `47999`, `48000`, `48002`, `48010`
Keep these LAN-only. Do not expose Sunshine directly to the public internet.
## Client Workflow
1. Install Moonlight on the client machine used for visual inspection.
2. Add host `192.168.5.12`.
3. Pair with Sunshine when prompted.
4. Launch Desktop from Moonlight.
5. Use the GPU-streamed desktop for Unreal visual checks.
Codex should continue using the headless build/control path for normal code, cook, package, and commandlet work. Use Sunshine only when a visual inspection is actually needed.
## Verification
The setup was validated by:
- Confirming the GTX 1660 SUPER is present and healthy in Windows.
- Confirming Sunshine is listening on its LAN ports.
- Confirming the Sunshine web UI responds with HTTP 401 authentication from Ubuntu-Codex.
- Confirming the virtual display is present as `Virtual Display Driver by MTT`.
- Confirming Sunshine detects a 1920x1080 desktop and creates NVENC H.264/HEVC encoders on the GTX 1660 SUPER.
The GTX 1660 SUPER does not support AV1 NVENC. Sunshine may log an AV1 encoder failure during startup; this is expected and H.264/HEVC remain available.
## Troubleshooting
- If Sunshine is unreachable, check `SunshineService`, Windows Firewall, and the VM IP address.
- If Sunshine logs `failed to query display paths and modes`, verify the virtual display driver is installed and enabled.
- If Moonlight shows a black screen after using RDP, disconnect RDP, restart `SunshineService`, and reconnect through Moonlight.
- If controller input is required later, install and validate the ViGEmBus gamepad driver. It is not required for visual inspection.
- If Unreal rendering differs under RDP, use Moonlight before changing graphics settings.
@@ -0,0 +1,84 @@
# Windows-Builder Network And RDP Stability
## Purpose
Windows-Builder uses GPU passthrough for Unreal Editor and packaged build validation. GPU passthrough can make RDP and display behavior confusing because RDP, Sunshine, the virtual display driver, and the NVIDIA GPU are separate access paths.
This runbook records the stable baseline for normal administration and recovery.
## Stable Network Baseline
- VM: `Windows-Builder`
- LAN IP: `192.168.5.12`
- VM bridge: `br0`
- NIC model: `virtio-net`
- MAC address: `52:54:00:17:ec:5d`
- Windows adapter: `Red Hat VirtIO Ethernet Adapter`
- Windows adapter alias: `Ethernet`
- Windows network profile: `Private`
- DNS observed during stabilization: `192.168.4.1`
The fixed MAC address is important. Keep the VM NIC on the same bridge/MAC so router DHCP reservations, firewall rules, SMB mappings, RDP bookmarks, and Sunshine/Moonlight pairing continue to resolve to the same machine.
## Stable RDP Baseline
- RDP registry switch: enabled through `fDenyTSConnections = 0`
- Service: `TermService`
- Startup type: `Automatic`
- Listener: TCP `3389`
- Windows Firewall group: `Remote Desktop`
- Required firewall rules:
- `Remote Desktop - User Mode (TCP-In)`
- `Remote Desktop - User Mode (UDP-In)`
- `Remote Desktop - Shadow (TCP-In)`
Use RDP for administration, installs, and recovery. Use Sunshine/Moonlight for real-GPU visual inspection of Unreal because RDP can change the active display path seen by graphics applications.
## Power Baseline
The VM should not sleep while it is acting as the build host.
- Hibernate: off
- AC sleep timeout: disabled
- DC sleep timeout: disabled
- AC monitor timeout: disabled
- DC monitor timeout: disabled
This avoids the Windows kernel power standby events that can leave the VirtIO NIC disconnected until a manual wake or reconnect.
## Verification Commands
From Ubuntu-Codex:
```bash
nc -vz -w 5 192.168.5.12 3389
sshpass -p '<unraid-root-password>' ssh root@192.168.5.8 'virsh domiflist Windows-Builder'
```
From Windows-Builder PowerShell:
```powershell
Get-NetAdapter
Get-NetConnectionProfile
Get-Service TermService
Get-NetFirewallRule -DisplayGroup "Remote Desktop"
powercfg /a
```
Expected results:
- `nc` reports that TCP `3389` succeeds.
- `virsh domiflist` shows `bridge br0`, `virtio-net`, and MAC `52:54:00:17:ec:5d`.
- `TermService` is running and automatic.
- Remote Desktop firewall rules are enabled.
- Hibernate is unavailable because it is disabled.
## Recovery Flow
1. Use the QEMU guest-agent path first if RDP fails.
2. Confirm the VM still has IP `192.168.5.12`.
3. Confirm the VM XML still has the fixed VirtIO NIC on `br0`.
4. Confirm Windows still sees the network as `Private`.
5. Restart `TermService` only if the service is not listening on TCP `3389`.
6. If Unreal visual inspection is needed, use Sunshine/Moonlight instead of RDP.
+28
View File
@@ -92,6 +92,7 @@ DisplayName
LastKnownTileId LastKnownTileId
LastKnownTransform LastKnownTransform
SurvivalSnapshot SurvivalSnapshot
CareHistorySnapshot
InventorySnapshot InventorySnapshot
UpdatedAt UpdatedAt
RecordVersion RecordVersion
@@ -107,12 +108,39 @@ Persist the survival state needed to resume a player:
- thirst; - thirst;
- body temperature; - body temperature;
- injury severity; - injury severity;
- sickness severity;
- exhaustion;
- alive/dead state if death persistence is active. - alive/dead state if death persistence is active.
Survival rates and tuning values should not be duplicated into the save unless Survival rates and tuning values should not be duplicated into the save unless
there is a specific compatibility reason. They belong in code/config/data there is a specific compatibility reason. They belong in code/config/data
assets. assets.
MVP implementation note: `UAgrarianPersistenceSubsystem::SaveCurrentWorld`
captures live Agrarian player characters into `FAgrarianSavedPlayer`, including
transform, survival snapshot, care history snapshot, and inventory stacks.
`RestorePlayers` reapplies those records to matching live characters before or
alongside world actor restore.
## Care History Snapshot
Reserve a long-term care history snapshot beside the immediate survival
snapshot. The MVP records neutral normalized fields only; later lifecycle and
aging systems will decide how aggressively those histories affect health,
stamina, strength, endurance, disease resistance, injury risk, recovery, visual
aging, and old-age decline.
Reserved care history fields:
- nutrition quality;
- illness burden;
- injury burden;
- sleep quality;
- shelter quality;
- stress burden;
- workload burden;
- treatment quality.
## Inventory Snapshot ## Inventory Snapshot
Persist inventory as item definition IDs and stack counts. Persist inventory as item definition IDs and stack counts.
+25 -2
View File
@@ -108,12 +108,35 @@ still derive from the represented map tile.
Near-term technical work: Near-term technical work:
- add Ground Zero local time-zone metadata; - add Ground Zero local time-zone metadata; completed for the current C++ game-state default.
- add sunrise/sunset lookup or approximation by latitude/longitude; - add sunrise/sunset lookup or approximation by latitude/longitude; completed as a tile-aware NOAA approximation in `AAgrarianGameState`.
- map real weather snapshots into internal Agrarian weather states; - map real weather snapshots into internal Agrarian weather states;
- cache weather snapshots server-side; - cache weather snapshots server-side;
- keep deterministic fallback weather when external data is unavailable. - keep deterministic fallback weather when external data is unavailable.
The repeatable solar metadata data path is
`Scripts/generate_tile_solar_metadata.py`. It reads the tile registry and emits
metadata only for source-backed, generated, validated, packaged, or published
tiles with explicit time-zone data. Placeholder/unknown tiles are skipped so the
future Earth-scale registry does not generate or fetch data for theoretical
tiles that do not exist yet.
Calendar conversion helpers live in `AAgrarianGameState` and keep the MVP target
of `4 real hours = 1 in-game day`. The same game state now exposes replicated
calendar year/day, absolute-day, season, real-hour conversion, long-task
progress, and crop-season fit helpers. Crop checks use the active tile's
growing-zone profile, including frost-free days and a crop safety buffer, so a
long-maturity crop can be rejected or marked marginal in regions with short
seasons.
The repeatable growing-zone metadata data path is
`Scripts/generate_tile_growing_zone_metadata.py`. It reads the tile registry and
emits metadata only for source-backed, generated, validated, packaged, or
published tiles with explicit growing-zone data. Ground Zero currently uses a
conservative Pacifica coastal profile; later regional expansion should replace
or enrich these overrides with authoritative zone, climate, and temperature
datasets.
## Terrain And Tile Delivery ## Terrain And Tile Delivery
### MVP Tile ### MVP Tile
+17 -9
View File
@@ -18,9 +18,12 @@ Removed:
Keep temporarily: Keep temporarily:
- `ThirdPerson` base character/input assets needed by the current player - `ThirdPerson` compatibility assets only while references are audited. The
Blueprint and early automation until Agrarian-specific player assets and the active player, controller, and game mode Blueprint paths now live under
MVP character selection flow replace them. `/Game/Agrarian/Blueprints/Characters`.
- `LevelPrototyping` meshes needed by current setup scripts and prototype
Blueprints until the version 0.1 environment pass replaces them with
Agrarian-native placeholder meshes.
- Shared mannequin/animation content only where current Agrarian assets still - Shared mannequin/animation content only where current Agrarian assets still
reference it. reference it.
@@ -43,15 +46,20 @@ Cleanup performed:
2. Removed matching content folders and external actor/object data. 2. Removed matching content folders and external actor/object data.
3. Removed `StateTreeModule` and `GameplayStateTreeModule`. 3. Removed `StateTreeModule` and `GameplayStateTreeModule`.
4. Disabled `StateTree` and `GameplayStateTree` in `AgrarianGame.uproject`. 4. Disabled `StateTree` and `GameplayStateTree` in `AgrarianGame.uproject`.
5. Kept the current `ThirdPerson` character path until the MVP landing page and 5. Replaced the current `ThirdPerson` player/game mode paths with Agrarian-owned
male/female character selection flow are implemented. player, controller, and game mode Blueprint assets.
6. Kept the current `LevelPrototyping` mesh path until the Ground Zero
environment pass replaces the simple cube/cylinder placeholder dependencies.
## Current State ## Current State
Decision and cleanup are complete for the unused starter variants. Remaining Decision and cleanup are complete for the unused starter variants. Remaining
starter/prototype dependencies are intentionally narrow: starter/prototype dependencies are intentionally narrow and no longer block
version `0.01` project-structure completion:
- `ThirdPerson` stays until Agrarian-specific player character assets and the - `ThirdPerson` active player/game mode paths have been replaced. Remaining
MVP character selection flow replace it. ThirdPerson compatibility assets should be deleted only after redirector and
editor reference checks confirm they are unused.
- `LevelPrototyping` stays because current Agrarian setup scripts and prototype - `LevelPrototyping` stays because current Agrarian setup scripts and prototype
Blueprints still use its simple cube/cylinder meshes. Blueprints still use its simple cube/cylinder meshes. This is now tracked as
version `0.1` environment replacement work.
+43
View File
@@ -69,6 +69,15 @@ Required fields:
- `created_at` - `created_at`
- `updated_at` - `updated_at`
Optional solar/time fields should be stored only for tiles that have real source
work or a published package, not for every theoretical 1 km square. The
Earth-scale registry may eventually contain hundreds of millions of possible
tiles, so solar metadata is generated on demand for existing/active tiles.
Optional growing-zone fields follow the same rule. Crop viability, frost-free
windows, and growing-season length are generated only for tiles with real source
work or published packages and explicit growing-zone data.
### `terrain_tile_neighbors` ### `terrain_tile_neighbors`
Tracks adjacency for stitching and prefetching. Tracks adjacency for stitching and prefetching.
@@ -93,6 +102,40 @@ Required fields:
- `source_version` - `source_version`
- `coverage_status` - `coverage_status`
### `terrain_tile_solar_metadata`
Tracks local time-zone and solar lookup metadata for tiles that actually exist
in the registry as source-backed, generated, validated, packaged, or published
tiles.
Required fields:
- `tile_id`
- `time_zone_id`
- `standard_utc_offset_hours`
- `daylight_utc_offset_hours`
- `solar_model`
- `generated_at`
### `terrain_tile_growing_season_metadata`
Tracks the active growing zone and season window for source-backed tiles. Crop
systems use this table to decide whether a crop with a given maturity time can
finish before the represented region's growing season closes.
Required fields:
- `tile_id`
- `growing_zone_label`
- `climate_profile`
- `growing_season_start_day`
- `growing_season_end_day`
- `frost_free_days`
- `min_average_growing_temp_c`
- `crop_safety_buffer_days`
- `data_basis`
- `generated_at`
### `terrain_tile_packages` ### `terrain_tile_packages`
Tracks generated downloadable packages. Tracks generated downloadable packages.
+1 -1
View File
@@ -63,7 +63,7 @@ call "%RUN_UAT%" BuildCookRun ^
-pak ^ -pak ^
-archive ^ -archive ^
-archivedirectory="%ARCHIVE_DIR%" ^ -archivedirectory="%ARCHIVE_DIR%" ^
-map=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test+/Game/ThirdPerson/Lvl_ThirdPerson ^ -map=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test ^
-prereqs ^ -prereqs ^
-utf8output ^ -utf8output ^
-NoUBA > "%LOG_FILE%" 2>&1 -NoUBA > "%LOG_FILE%" 2>&1
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Generate growing-zone metadata for existing Agrarian terrain tiles.
Only source-backed/generated tiles with explicit profiles are emitted. That
keeps the Earth-scale path repeatable without fetching or calculating metadata
for placeholder squares that do not exist in-game yet.
"""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REGISTRY_PATH = ROOT / "Data" / "Tiles" / "ground_zero_tiles.json"
OUTPUT_PATH = ROOT / "Data" / "Tiles" / "tile_growing_zone_metadata.json"
GROWING_READY_STATUSES = {"source_data_found", "generated", "validated", "packaged", "published"}
GROWING_ZONE_OVERRIDES = {
"gz_us_ca_pacifica_utm10n_e544_n4160": {
"growing_zone_label": "USDA 10a",
"climate_profile": "coastal_mediterranean_mild",
"growing_season_start_day": 46,
"growing_season_end_day": 350,
"frost_free_days": 305,
"min_average_growing_temp_c": 7.0,
"crop_safety_buffer_days": 14,
"data_basis": "MVP conservative coastal Pacifica profile; replace with authoritative zone/temperature datasets during regional expansion.",
}
}
def main() -> None:
registry = json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
records = []
for tile in registry.get("tiles", []):
tile_id = tile.get("tile_id")
status = tile.get("status")
if status not in GROWING_READY_STATUSES or tile_id not in GROWING_ZONE_OVERRIDES:
continue
grid = tile.get("grid", {})
records.append(
{
"tile_id": tile_id,
"center_latitude": grid.get("center_latitude"),
"center_longitude": grid.get("center_longitude"),
**GROWING_ZONE_OVERRIDES[tile_id],
}
)
OUTPUT_PATH.write_text(
json.dumps(
{
"schema_version": 1,
"generated_at_utc": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
"source_registry": str(REGISTRY_PATH.relative_to(ROOT)),
"generation_rule": "Only tiles with real source status and explicit growing-zone data are emitted.",
"tiles": records,
},
indent=2,
)
+ "\n",
encoding="utf-8",
)
print(f"Wrote {len(records)} tile growing-zone metadata record(s) to {OUTPUT_PATH.relative_to(ROOT)}")
if __name__ == "__main__":
main()
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""Generate solar/time-zone metadata for existing Agrarian terrain tiles.
The script intentionally skips placeholder/unknown tiles. That keeps the
Earth-scale path cheap: solar metadata is generated only after a tile has real
source work attached.
"""
from __future__ import annotations
import json
import math
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REGISTRY_PATH = ROOT / "Data" / "Tiles" / "ground_zero_tiles.json"
OUTPUT_PATH = ROOT / "Data" / "Tiles" / "tile_solar_metadata.json"
SOLAR_READY_STATUSES = {"source_data_found", "generated", "validated", "packaged", "published"}
TIMEZONE_OVERRIDES = {
"gz_us_ca_pacifica_utm10n_e544_n4160": {
"time_zone_id": "America/Los_Angeles",
"standard_utc_offset_hours": -8.0,
"daylight_utc_offset_hours": -7.0,
}
}
def solar_hours(latitude: float, longitude: float, utc_offset_hours: float, day_of_year: int) -> dict:
gamma = (2.0 * math.pi / 365.0) * (day_of_year - 1)
equation_of_time = 229.18 * (
0.000075
+ 0.001868 * math.cos(gamma)
- 0.032077 * math.sin(gamma)
- 0.014615 * math.cos(2.0 * gamma)
- 0.040849 * math.sin(2.0 * gamma)
)
declination = (
0.006918
- 0.399912 * math.cos(gamma)
+ 0.070257 * math.sin(gamma)
- 0.006758 * math.cos(2.0 * gamma)
+ 0.000907 * math.sin(2.0 * gamma)
- 0.002697 * math.cos(3.0 * gamma)
+ 0.00148 * math.sin(3.0 * gamma)
)
lat_rad = math.radians(max(-89.8, min(89.8, latitude)))
zenith_rad = math.radians(90.833)
hour_angle_arg = (math.cos(zenith_rad) / (math.cos(lat_rad) * math.cos(declination))) - (
math.tan(lat_rad) * math.tan(declination)
)
solar_noon = (720.0 - (4.0 * longitude) - equation_of_time + (utc_offset_hours * 60.0)) / 60.0
solar_noon %= 24.0
if hour_angle_arg <= -1.0:
return {"sunrise_hour": 0.0, "sunset_hour": 24.0, "solar_noon_hour": round(solar_noon, 3), "day_length_hours": 24.0}
if hour_angle_arg >= 1.0:
return {"sunrise_hour": round(solar_noon, 3), "sunset_hour": round(solar_noon, 3), "solar_noon_hour": round(solar_noon, 3), "day_length_hours": 0.0}
hour_angle_deg = math.degrees(math.acos(hour_angle_arg))
sunrise = ((solar_noon * 60.0) - (hour_angle_deg * 4.0)) / 60.0
sunset = ((solar_noon * 60.0) + (hour_angle_deg * 4.0)) / 60.0
return {
"sunrise_hour": round(sunrise % 24.0, 3),
"sunset_hour": round(sunset % 24.0, 3),
"solar_noon_hour": round(solar_noon, 3),
"day_length_hours": round((sunset - sunrise), 3),
}
def main() -> None:
registry = json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
records = []
for tile in registry.get("tiles", []):
status = tile.get("status")
tile_id = tile.get("tile_id")
grid = tile.get("grid", {})
if status not in SOLAR_READY_STATUSES or not tile_id or tile_id not in TIMEZONE_OVERRIDES:
continue
timezone_data = TIMEZONE_OVERRIDES[tile_id]
latitude = float(grid["center_latitude"])
longitude = float(grid["center_longitude"])
utc_offset = float(timezone_data["daylight_utc_offset_hours"])
sample_days = {
"march_equinox": 80,
"june_solstice": 172,
"september_equinox": 266,
"december_solstice": 355,
}
records.append(
{
"tile_id": tile_id,
"center_latitude": latitude,
"center_longitude": longitude,
**timezone_data,
"solar_model": "NOAA approximate sunrise/sunset",
"sample_solar_hours": {
label: solar_hours(latitude, longitude, utc_offset, day)
for label, day in sample_days.items()
},
}
)
OUTPUT_PATH.write_text(
json.dumps(
{
"schema_version": 1,
"generated_at_utc": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
"source_registry": str(REGISTRY_PATH.relative_to(ROOT)),
"generation_rule": "Only tiles with real source status and explicit timezone data are emitted.",
"tiles": records,
},
indent=2,
)
+ "\n",
encoding="utf-8",
)
print(f"Wrote {len(records)} tile solar metadata record(s) to {OUTPUT_PATH.relative_to(ROOT)}")
if __name__ == "__main__":
main()
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MODE="dry-run"
BUILD_LOG_DAYS="${BUILD_LOG_DAYS:-90}"
UNREAL_LOG_DAYS="${UNREAL_LOG_DAYS:-30}"
CRASH_DAYS="${CRASH_DAYS:-90}"
MATERIAL_STATS_DAYS="${MATERIAL_STATS_DAYS:-30}"
SHADER_DEBUG_DAYS="${SHADER_DEBUG_DAYS:-14}"
usage() {
cat <<USAGE
Usage: $(basename "$0") [--dry-run|--apply]
Prunes generated Agrarian Unreal build/debug logs according to
Docs/Ops/BuildLogRetentionPolicy.md.
Environment overrides:
BUILD_LOG_DAYS=$BUILD_LOG_DAYS
UNREAL_LOG_DAYS=$UNREAL_LOG_DAYS
CRASH_DAYS=$CRASH_DAYS
MATERIAL_STATS_DAYS=$MATERIAL_STATS_DAYS
SHADER_DEBUG_DAYS=$SHADER_DEBUG_DAYS
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run)
MODE="dry-run"
;;
--apply)
MODE="apply"
;;
-h|--help)
usage
exit 0
;;
*)
usage >&2
exit 2
;;
esac
shift
done
log() {
printf '[%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"
}
prune_files() {
local label="$1"
local dir="$2"
local days="$3"
if [[ ! -d "$dir" ]]; then
log "Skipping $label; directory not found: $dir"
return
fi
log "$label: files older than $days days in $dir"
if [[ "$MODE" == "dry-run" ]]; then
find "$dir" -type f -mtime +"$days" -print
else
find "$dir" -type f -mtime +"$days" -print -delete
fi
}
prune_empty_dirs() {
local dir="$1"
if [[ ! -d "$dir" ]]; then
return
fi
if [[ "$MODE" == "dry-run" ]]; then
find "$dir" -mindepth 1 -type d -empty -print
else
find "$dir" -mindepth 1 -type d -empty -print -delete
fi
}
log "Agrarian build log retention prune starting in $MODE mode"
log "Project root: $PROJECT_ROOT"
prune_files "Build/package logs" "$PROJECT_ROOT/Saved/BuildLogs" "$BUILD_LOG_DAYS"
prune_files "Unreal/editor/runtime logs" "$PROJECT_ROOT/Saved/Logs" "$UNREAL_LOG_DAYS"
prune_files "Crash reports" "$PROJECT_ROOT/Saved/Crashes" "$CRASH_DAYS"
prune_files "Material stats" "$PROJECT_ROOT/Saved/MaterialStats" "$MATERIAL_STATS_DAYS"
prune_files "Shader output" "$PROJECT_ROOT/Saved/Shaders" "$SHADER_DEBUG_DAYS"
prune_files "Shader debug info" "$PROJECT_ROOT/Saved/ShaderDebugInfo" "$SHADER_DEBUG_DAYS"
prune_empty_dirs "$PROJECT_ROOT/Saved/BuildLogs"
prune_empty_dirs "$PROJECT_ROOT/Saved/Logs"
prune_empty_dirs "$PROJECT_ROOT/Saved/Crashes"
prune_empty_dirs "$PROJECT_ROOT/Saved/MaterialStats"
prune_empty_dirs "$PROJECT_ROOT/Saved/Shaders"
prune_empty_dirs "$PROJECT_ROOT/Saved/ShaderDebugInfo"
log "Agrarian build log retention prune finished in $MODE mode"
@@ -0,0 +1,67 @@
import unreal
SOURCE_CHARACTER = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"
SOURCE_CONTROLLER = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonPlayerController"
SOURCE_GAME_MODE = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonGameMode"
DEST_ROOT = "/Game/Agrarian/Blueprints/Characters"
DEST_CHARACTER = f"{DEST_ROOT}/BP_AgrarianPlayerCharacter"
DEST_CONTROLLER = f"{DEST_ROOT}/BP_AgrarianPlayerController"
DEST_GAME_MODE = f"{DEST_ROOT}/BP_AgrarianGameMode"
def ensure_directory(path):
if not unreal.EditorAssetLibrary.does_directory_exist(path):
if not unreal.EditorAssetLibrary.make_directory(path):
raise RuntimeError(f"Could not create content directory: {path}")
def duplicate_if_missing(source_path, destination_path):
if unreal.EditorAssetLibrary.does_asset_exist(destination_path):
unreal.log(f"Keeping existing asset: {destination_path}")
return unreal.EditorAssetLibrary.load_asset(destination_path)
if not unreal.EditorAssetLibrary.does_asset_exist(source_path):
raise RuntimeError(f"Source asset missing: {source_path}")
duplicated = unreal.EditorAssetLibrary.duplicate_asset(source_path, destination_path)
if not duplicated:
raise RuntimeError(f"Could not duplicate {source_path} to {destination_path}")
unreal.log(f"Created {destination_path} from {source_path}")
return duplicated
def load_blueprint_class(path):
generated_class = unreal.EditorAssetLibrary.load_blueprint_class(path)
if not generated_class:
raise RuntimeError(f"Could not load generated Blueprint class: {path}")
return generated_class
def main():
ensure_directory(DEST_ROOT)
character_bp = duplicate_if_missing(SOURCE_CHARACTER, DEST_CHARACTER)
controller_bp = duplicate_if_missing(SOURCE_CONTROLLER, DEST_CONTROLLER)
game_mode_bp = duplicate_if_missing(SOURCE_GAME_MODE, DEST_GAME_MODE)
character_class = load_blueprint_class(DEST_CHARACTER)
controller_class = load_blueprint_class(DEST_CONTROLLER)
game_mode_cdo = unreal.get_default_object(game_mode_bp.generated_class())
game_mode_cdo.set_editor_property("default_pawn_class", character_class)
game_mode_cdo.set_editor_property("player_controller_class", controller_class)
unreal.EditorAssetLibrary.save_loaded_asset(character_bp)
unreal.EditorAssetLibrary.save_loaded_asset(controller_bp)
unreal.EditorAssetLibrary.save_loaded_asset(game_mode_bp)
unreal.log(
"Agrarian player Blueprint assets are ready: "
f"{DEST_CHARACTER}, {DEST_CONTROLLER}, {DEST_GAME_MODE}"
)
main()
+1 -1
View File
@@ -77,7 +77,7 @@ def main():
map_key(context, toggle_action, "Gamepad_RightThumbstick") map_key(context, toggle_action, "Gamepad_RightThumbstick")
unreal.EditorAssetLibrary.save_loaded_asset(context) unreal.EditorAssetLibrary.save_loaded_asset(context)
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
character_cdo.set_editor_property("ToggleCameraAction", toggle_action) character_cdo.set_editor_property("ToggleCameraAction", toggle_action)
unreal.EditorAssetLibrary.save_loaded_asset(character_bp) unreal.EditorAssetLibrary.save_loaded_asset(character_bp)
+1 -1
View File
@@ -75,7 +75,7 @@ def main():
map_key(context, interact_action, "Gamepad_FaceButton_Left") map_key(context, interact_action, "Gamepad_FaceButton_Left")
unreal.EditorAssetLibrary.save_loaded_asset(context) unreal.EditorAssetLibrary.save_loaded_asset(context)
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
character_cdo.set_editor_property("InteractAction", interact_action) character_cdo.set_editor_property("InteractAction", interact_action)
unreal.EditorAssetLibrary.save_loaded_asset(character_bp) unreal.EditorAssetLibrary.save_loaded_asset(character_bp)
+1 -1
View File
@@ -9,7 +9,7 @@ def load(path):
def main(): def main():
game_mode_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonGameMode") game_mode_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianGameMode")
game_mode_cdo = unreal.get_default_object(game_mode_bp.generated_class()) game_mode_cdo = unreal.get_default_object(game_mode_bp.generated_class())
hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD") hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD")
if not hud_class: if not hud_class:
+1 -1
View File
@@ -24,7 +24,7 @@ def load(path):
def main(): def main():
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
for property_name, value in MOVEMENT_DEFAULTS.items(): for property_name, value in MOVEMENT_DEFAULTS.items():
+1 -1
View File
@@ -77,7 +77,7 @@ def main():
map_key(context, sprint_action, "Gamepad_LeftThumbstick") map_key(context, sprint_action, "Gamepad_LeftThumbstick")
unreal.EditorAssetLibrary.save_loaded_asset(context) unreal.EditorAssetLibrary.save_loaded_asset(context)
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
character_cdo.set_editor_property("SprintAction", sprint_action) character_cdo.set_editor_property("SprintAction", sprint_action)
unreal.EditorAssetLibrary.save_loaded_asset(character_bp) unreal.EditorAssetLibrary.save_loaded_asset(character_bp)
+1 -1
View File
@@ -87,7 +87,7 @@ def main():
map_key(context, prone_action, "Gamepad_LeftShoulder") map_key(context, prone_action, "Gamepad_LeftShoulder")
unreal.EditorAssetLibrary.save_loaded_asset(context) unreal.EditorAssetLibrary.save_loaded_asset(context)
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
character_cdo.set_editor_property("CrouchAction", crouch_action) character_cdo.set_editor_property("CrouchAction", crouch_action)
character_cdo.set_editor_property("ProneAction", prone_action) character_cdo.set_editor_property("ProneAction", prone_action)
+1 -1
View File
@@ -1,7 +1,7 @@
import unreal import unreal
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson" MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
PLACEMENTS = [ PLACEMENTS = [
{ {
@@ -0,0 +1,86 @@
from pathlib import Path
import json
ROOT = Path(__file__).resolve().parents[1]
TYPES_H = ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h"
GAME_STATE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.h"
GAME_STATE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.cpp"
GENERATOR = ROOT / "Scripts" / "generate_tile_growing_zone_metadata.py"
METADATA = ROOT / "Data" / "Tiles" / "tile_growing_zone_metadata.json"
SCHEMA = ROOT / "Data" / "Tiles" / "tile_registry.schema.json"
SQL = ROOT / "Data" / "Tiles" / "tile_registry.sql"
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
EXPECTED = {
TYPES_H: [
"FAgrarianCalendarSnapshot",
"AbsoluteDay",
"FAgrarianGrowingSeasonProfile",
"CropSafetyBufferDays",
"FAgrarianCropSeasonAssessment",
"bCanPlantToday",
],
GAME_STATE_H: [
"DaysPerAgrarianYear",
"ActiveYear",
"ActiveGrowingSeason",
"ConvertAgrarianDaysToRealHours",
"ConvertRealHoursToAgrarianDays",
"GetLongTaskProgress",
"IsDayInsideActiveGrowingSeason",
"AssessCropForActiveGrowingSeason",
],
GAME_STATE_CPP: [
"DOREPLIFETIME(AAgrarianGameState, ActiveGrowingSeason);",
"FAgrarianCalendarSnapshot AAgrarianGameState::GetCalendarSnapshot() const",
"float AAgrarianGameState::ConvertAgrarianDaysToRealHours",
"FAgrarianCropSeasonAssessment AAgrarianGameState::AssessCropForActiveGrowingSeason",
"Crop maturity is too long for this tile's frost-free growing window.",
],
GENERATOR: [
"GROWING_READY_STATUSES",
"GROWING_ZONE_OVERRIDES",
"gz_us_ca_pacifica_utm10n_e544_n4160",
"Only source-backed/generated tiles with explicit profiles are emitted.",
],
SCHEMA: ["growing_season_metadata", "growing_zone_label", "frost_free_days"],
SQL: ["terrain_tile_growing_season_metadata", "crop_safety_buffer_days"],
ROADMAP: ["Add Agrarian calendar conversion helpers"],
}
def assert_contains() -> None:
missing = []
for path, snippets in EXPECTED.items():
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
missing.append(f"{path.relative_to(ROOT)}: {snippet}")
if missing:
raise RuntimeError("Agrarian calendar verification failed: " + "; ".join(missing))
def assert_metadata() -> None:
data = json.loads(METADATA.read_text(encoding="utf-8"))
tiles = data.get("tiles", [])
if len(tiles) != 1:
raise RuntimeError(f"Expected exactly one growing-zone tile, got {len(tiles)}")
tile = tiles[0]
if tile.get("tile_id") != "gz_us_ca_pacifica_utm10n_e544_n4160":
raise RuntimeError(f"Unexpected growing-zone tile id: {tile.get('tile_id')}")
if tile.get("growing_zone_label") != "USDA 10a":
raise RuntimeError("Ground Zero growing zone is missing or incorrect")
if tile.get("frost_free_days", 0) < 250:
raise RuntimeError(f"Ground Zero frost-free window is unexpectedly short: {tile}")
def main() -> None:
assert_contains()
assert_metadata()
print("Agrarian calendar and growing-zone helper verification complete.")
if __name__ == "__main__":
main()
@@ -0,0 +1,66 @@
import unreal
CHARACTER_BLUEPRINT_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
CONTROLLER_BLUEPRINT_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerController"
GAME_MODE_BLUEPRINT_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianGameMode"
def class_path(value):
if not value:
return ""
return value.get_path_name().split(".")[0]
def load_blueprint_class(path):
generated_class = unreal.EditorAssetLibrary.load_blueprint_class(path)
if not generated_class:
raise RuntimeError(f"Could not load generated Blueprint class: {path}")
return generated_class
def main():
failures = []
for path in [CHARACTER_BLUEPRINT_PATH, CONTROLLER_BLUEPRINT_PATH, GAME_MODE_BLUEPRINT_PATH]:
if not unreal.EditorAssetLibrary.does_asset_exist(path):
failures.append(f"{path} missing")
character_class = load_blueprint_class(CHARACTER_BLUEPRINT_PATH)
controller_class = load_blueprint_class(CONTROLLER_BLUEPRINT_PATH)
game_mode_class = load_blueprint_class(GAME_MODE_BLUEPRINT_PATH)
game_mode_cdo = unreal.get_default_object(game_mode_class)
default_pawn_path = class_path(game_mode_cdo.get_editor_property("default_pawn_class"))
player_controller_path = class_path(game_mode_cdo.get_editor_property("player_controller_class"))
if default_pawn_path != CHARACTER_BLUEPRINT_PATH:
failures.append(
f"Game mode default pawn expected {CHARACTER_BLUEPRINT_PATH}, got {default_pawn_path}"
)
if player_controller_path != CONTROLLER_BLUEPRINT_PATH:
failures.append(
"Game mode player controller expected "
f"{CONTROLLER_BLUEPRINT_PATH}, got {player_controller_path}"
)
character_cdo = unreal.get_default_object(character_class)
if not character_cdo:
failures.append(f"{CHARACTER_BLUEPRINT_PATH} CDO missing")
controller_cdo = unreal.get_default_object(controller_class)
if not controller_cdo:
failures.append(f"{CONTROLLER_BLUEPRINT_PATH} CDO missing")
if failures:
raise RuntimeError("Agrarian player Blueprint verification failed: " + "; ".join(failures))
unreal.log(
"Agrarian player Blueprint verification complete: "
"native Agrarian character, controller, and game mode asset paths load correctly."
)
main()
@@ -0,0 +1,72 @@
import unreal
CHARACTER_BLUEPRINT_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
EXPECTED_MESH_PATH = "/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple"
EXPECTED_ANIM_BLUEPRINT_PATH = "/Game/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed"
REQUIRED_ANIMATION_ASSETS = [
"/Game/Characters/Mannequins/Anims/Unarmed/BS_Idle_Walk_Run",
"/Game/Characters/Mannequins/Anims/Unarmed/MM_Idle",
"/Game/Characters/Mannequins/Anims/Unarmed/Jump/MM_Jump",
"/Game/Characters/Mannequins/Anims/Unarmed/Jump/MM_Fall_Loop",
"/Game/Characters/Mannequins/Anims/Unarmed/Jump/MM_Land",
]
def object_path(value):
if not value:
return ""
return value.get_path_name().split(".")[0]
def main():
failures = []
for asset_path in [EXPECTED_MESH_PATH, EXPECTED_ANIM_BLUEPRINT_PATH] + REQUIRED_ANIMATION_ASSETS:
if not unreal.EditorAssetLibrary.does_asset_exist(asset_path):
failures.append(f"{asset_path} missing")
character_class = unreal.EditorAssetLibrary.load_blueprint_class(CHARACTER_BLUEPRINT_PATH)
if not character_class:
failures.append(f"{CHARACTER_BLUEPRINT_PATH} generated class missing")
else:
character_cdo = unreal.get_default_object(character_class)
mesh_component = character_cdo.get_editor_property("mesh")
if not mesh_component:
failures.append(f"{CHARACTER_BLUEPRINT_PATH} mesh component missing")
else:
skeletal_mesh_path = object_path(mesh_component.get_skeletal_mesh_asset())
if skeletal_mesh_path != EXPECTED_MESH_PATH:
failures.append(
f"{CHARACTER_BLUEPRINT_PATH} skeletal mesh expected "
f"{EXPECTED_MESH_PATH}, got {skeletal_mesh_path}"
)
anim_class = mesh_component.get_editor_property("anim_class")
anim_class_path = anim_class.get_path_name().split(".")[0] if anim_class else ""
if anim_class_path != EXPECTED_ANIM_BLUEPRINT_PATH:
failures.append(
f"{CHARACTER_BLUEPRINT_PATH} anim class expected "
f"{EXPECTED_ANIM_BLUEPRINT_PATH}, got {anim_class_path}"
)
anim_blueprint_class = unreal.EditorAssetLibrary.load_blueprint_class(EXPECTED_ANIM_BLUEPRINT_PATH)
if not anim_blueprint_class:
failures.append(f"{EXPECTED_ANIM_BLUEPRINT_PATH} generated class missing")
elif "ABP_Unarmed_C" not in anim_blueprint_class.get_name():
failures.append(
f"{EXPECTED_ANIM_BLUEPRINT_PATH} generated class should be ABP_Unarmed_C, "
f"got {anim_blueprint_class.get_name()}"
)
if failures:
raise RuntimeError("Basic animation Blueprint verification failed: " + "; ".join(failures))
unreal.log(
"Basic animation Blueprint verification complete: "
"BP_AgrarianPlayerCharacter uses SKM_Quinn_Simple with ABP_Unarmed and required "
"idle, locomotion, jump, fall, and land assets are present."
)
main()
+2 -2
View File
@@ -23,7 +23,7 @@ def mapping_found(context, action, key_name):
def main(): def main():
action = load("/Game/Input/Actions/IA_ToggleCamera") action = load("/Game/Input/Actions/IA_ToggleCamera")
context = load("/Game/Input/IMC_Default") context = load("/Game/Input/IMC_Default")
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
missing = [] missing = []
@@ -33,7 +33,7 @@ def main():
assigned_action = character_cdo.get_editor_property("ToggleCameraAction") assigned_action = character_cdo.get_editor_property("ToggleCameraAction")
if assigned_action != action: if assigned_action != action:
missing.append("BP_ThirdPersonCharacter ToggleCameraAction is not IA_ToggleCamera") missing.append("BP_AgrarianPlayerCharacter ToggleCameraAction is not IA_ToggleCamera")
if missing: if missing:
raise RuntimeError("Camera toggle input verification failed: " + "; ".join(missing)) raise RuntimeError("Camera toggle input verification failed: " + "; ".join(missing))
+59
View File
@@ -0,0 +1,59 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FILES = {
"AgrarianTypes.h": ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h",
"AgrarianSurvivalComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h",
"AgrarianSurvivalComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp",
"AgrarianSaveGame.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSaveGame.h",
"AgrarianDebugHUD.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp",
"PersistenceDesignDocument.md": ROOT / "Docs" / "PersistenceDesignDocument.md",
}
CARE_FIELDS = [
"NutritionQuality",
"IllnessBurden",
"InjuryBurden",
"SleepQuality",
"ShelterQuality",
"StressBurden",
"WorkloadBurden",
"TreatmentQuality",
]
EXPECTED = {
"AgrarianTypes.h": ["struct FAgrarianCareHistorySnapshot", *CARE_FIELDS],
"AgrarianSurvivalComponent.h": [
"FAgrarianCareHistorySnapshot CareHistory;",
"void OnRep_CareHistory();",
"void ClampCareHistory();",
],
"AgrarianSurvivalComponent.cpp": [
"DOREPLIFETIME(UAgrarianSurvivalComponent, CareHistory);",
"void UAgrarianSurvivalComponent::OnRep_CareHistory()",
"void UAgrarianSurvivalComponent::ClampCareHistory()",
*CARE_FIELDS,
],
"AgrarianSaveGame.h": ["FAgrarianCareHistorySnapshot CareHistory;"],
"AgrarianDebugHUD.cpp": ["Care N/S/T", "Burden I/I/S/W"],
"PersistenceDesignDocument.md": ["CareHistorySnapshot", "Care History Snapshot"],
}
def main():
missing = []
for label, path in FILES.items():
text = path.read_text(encoding="utf-8")
for snippet in EXPECTED[label]:
if snippet not in text:
missing.append(f"{label}: {snippet}")
if missing:
raise RuntimeError("Care history verification failed: " + "; ".join(missing))
print("Agrarian care history field verification complete.")
if __name__ == "__main__":
main()
+50
View File
@@ -0,0 +1,50 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FILES = {
"AgrarianDebugHUD.h": ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.h",
"AgrarianDebugHUD.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp",
}
EXPECTED = {
"AgrarianDebugHUD.h": [
"bool bShowCriticalStatsHUD = true;",
"float CriticalStatsTextScale = 1.0f;",
"void DrawCriticalStats(const UAgrarianSurvivalComponent* SurvivalComponent);",
"void DrawScaledLine(const FString& Text, float X, float& Y, float Scale, const FColor& Color = FColor::White);",
],
"AgrarianDebugHUD.cpp": [
"DrawCriticalStats(AgrarianCharacter->GetSurvivalComponent());",
"void AAgrarianDebugHUD::DrawCriticalStats",
"SURVIVAL",
"Health",
"Stamina",
"Food",
"Water",
"Temp",
"Exhaust",
"Injury",
"Sickness",
"StatusColor",
"void AAgrarianDebugHUD::DrawScaledLine",
],
}
def main():
missing = []
for label, path in FILES.items():
text = path.read_text(encoding="utf-8")
for snippet in EXPECTED[label]:
if snippet not in text:
missing.append(f"{label}: {snippet}")
if missing:
raise RuntimeError("Critical stats HUD verification failed: " + "; ".join(missing))
print("Agrarian critical stats HUD verification complete.")
if __name__ == "__main__":
main()
+65
View File
@@ -0,0 +1,65 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FILES = {
"AgrarianTypes.h": ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h",
"AgrarianSurvivalComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h",
"AgrarianSurvivalComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp",
"AgrarianGameCharacter.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.cpp",
"AgrarianDebugHUD.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp",
"AgrarianGamePlayerController.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp",
}
EXPECTED = {
"AgrarianTypes.h": [
"float Exhaustion = 0.0f;",
],
"AgrarianSurvivalComponent.h": [
"float ExhaustionGainPerLowStaminaSecond = 0.35f;",
"float ExhaustionRecoveryPerSecond = 0.08f;",
"float LowStaminaExhaustionThreshold = 20.0f;",
"void AddExhaustion(float Amount);",
"void ReduceExhaustion(float Amount);",
],
"AgrarianSurvivalComponent.cpp": [
"Survival.Exhaustion += ExhaustionGainPerLowStaminaSecond * DeltaTime;",
"Survival.Exhaustion -= ExhaustionRecoveryPerSecond * DeltaTime;",
"Survival.Exhaustion += PositiveAmount * 0.05f;",
"void UAgrarianSurvivalComponent::AddExhaustion",
"void UAgrarianSurvivalComponent::ReduceExhaustion",
"Survival.Exhaustion = FMath::Clamp(Survival.Exhaustion, 0.0f, 100.0f);",
],
"AgrarianGameCharacter.cpp": [
"SurvivalComponent->Survival.Exhaustion < 85.0f",
"const float ExhaustionMultiplier = FMath::GetMappedRangeValueClamped",
"HungerMultiplier * ThirstMultiplier * InjuryMultiplier * ExhaustionMultiplier",
],
"AgrarianDebugHUD.cpp": [
"Exhaust:",
"Survival.Exhaustion",
],
"AgrarianGamePlayerController.cpp": [
"Exhaustion %.1f",
"Survival.Exhaustion",
"SurvivalComponent->ReduceExhaustion(100.0f);",
],
}
def main():
missing = []
for label, path in FILES.items():
text = path.read_text(encoding="utf-8")
for snippet in EXPECTED[label]:
if snippet not in text:
missing.append(f"{label}: {snippet}")
if missing:
raise RuntimeError("Exhaustion stat verification failed: " + "; ".join(missing))
print("Agrarian exhaustion stat verification complete.")
if __name__ == "__main__":
main()
+2 -2
View File
@@ -23,7 +23,7 @@ def mapping_found(context, action, key_name):
def main(): def main():
action = load("/Game/Input/Actions/IA_Interact") action = load("/Game/Input/Actions/IA_Interact")
context = load("/Game/Input/IMC_Default") context = load("/Game/Input/IMC_Default")
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
missing = [] missing = []
@@ -33,7 +33,7 @@ def main():
assigned_action = character_cdo.get_editor_property("InteractAction") assigned_action = character_cdo.get_editor_property("InteractAction")
if assigned_action != action: if assigned_action != action:
missing.append("BP_ThirdPersonCharacter InteractAction is not IA_Interact") missing.append("BP_AgrarianPlayerCharacter InteractAction is not IA_Interact")
if missing: if missing:
raise RuntimeError("Interact input verification failed: " + "; ".join(missing)) raise RuntimeError("Interact input verification failed: " + "; ".join(missing))
+2 -2
View File
@@ -9,7 +9,7 @@ def load(path):
def main(): def main():
game_mode_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonGameMode") game_mode_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianGameMode")
game_mode_cdo = unreal.get_default_object(game_mode_bp.generated_class()) game_mode_cdo = unreal.get_default_object(game_mode_bp.generated_class())
expected_hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD") expected_hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD")
character_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianGameCharacter") character_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianGameCharacter")
@@ -20,7 +20,7 @@ def main():
if not expected_hud_class: if not expected_hud_class:
missing.append("could not load AgrarianDebugHUD class") missing.append("could not load AgrarianDebugHUD class")
elif game_mode_cdo.get_editor_property("hud_class") != expected_hud_class: elif game_mode_cdo.get_editor_property("hud_class") != expected_hud_class:
missing.append("BP_ThirdPersonGameMode HUD class is not AgrarianDebugHUD") missing.append("BP_AgrarianGameMode HUD class is not AgrarianDebugHUD")
elif hud_cdo: elif hud_cdo:
if not bool(hud_cdo.get_editor_property("bShowInteractionPrompt")): if not bool(hud_cdo.get_editor_property("bShowInteractionPrompt")):
missing.append("AgrarianDebugHUD bShowInteractionPrompt is disabled") missing.append("AgrarianDebugHUD bShowInteractionPrompt is disabled")
+1 -1
View File
@@ -25,7 +25,7 @@ def load(path):
def main(): def main():
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
mismatches = [] mismatches = []
+2 -2
View File
@@ -1,8 +1,8 @@
import unreal import unreal
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson" MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
CHARACTER_CLASS_PATH = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter" CHARACTER_CLASS_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
SHELTER_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveShelter" SHELTER_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveShelter"
FRAME_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveFrame" FRAME_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveFrame"
WALL_PANEL_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveWallPanel" WALL_PANEL_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveWallPanel"
+55
View File
@@ -0,0 +1,55 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
HEADER = ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.h"
SOURCE = ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.cpp"
EXPECTED_HEADER_SNIPPETS = [
"ReplicatedUsing = OnRep_SprintState",
"ReplicatedUsing = OnRep_ProneState",
"ReplicatedUsing = OnRep_MovementModifierState",
"void OnRep_MovementModifierState();",
"void ServerSetWantsToSprint(bool bNewWantsToSprint);",
"void ServerSetProne(bool bNewProne);",
"void ServerSetTerrainMovementMultiplier(float NewTerrainMovementMultiplier);",
]
EXPECTED_SOURCE_SNIPPETS = [
"bReplicates = true;",
"SetReplicateMovement(true);",
"DOREPLIFETIME(AAgrarianGameCharacter, bWantsToSprint);",
"DOREPLIFETIME(AAgrarianGameCharacter, bIsProne);",
"DOREPLIFETIME(AAgrarianGameCharacter, AgeYears);",
"DOREPLIFETIME(AAgrarianGameCharacter, PhysicalConditionMultiplier);",
"DOREPLIFETIME(AAgrarianGameCharacter, StrengthMultiplier);",
"DOREPLIFETIME(AAgrarianGameCharacter, EnduranceMultiplier);",
"DOREPLIFETIME(AAgrarianGameCharacter, TerrainMovementMultiplier);",
"void AAgrarianGameCharacter::OnRep_SprintState()",
"void AAgrarianGameCharacter::OnRep_ProneState()",
"void AAgrarianGameCharacter::OnRep_MovementModifierState()",
"void AAgrarianGameCharacter::ServerSetWantsToSprint_Implementation",
"void AAgrarianGameCharacter::ServerSetProne_Implementation",
"void AAgrarianGameCharacter::ServerSetTerrainMovementMultiplier_Implementation",
]
def assert_contains(label, text, snippets):
missing = [snippet for snippet in snippets if snippet not in text]
if missing:
raise RuntimeError(f"{label} is missing expected replication snippets: {missing}")
def main():
header_text = HEADER.read_text(encoding="utf-8")
source_text = SOURCE.read_text(encoding="utf-8")
assert_contains("AgrarianGameCharacter.h", header_text, EXPECTED_HEADER_SNIPPETS)
assert_contains("AgrarianGameCharacter.cpp", source_text, EXPECTED_SOURCE_SNIPPETS)
print("Agrarian player core replication verification complete.")
if __name__ == "__main__":
main()
+49
View File
@@ -0,0 +1,49 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
HUD_HEADER = ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.h"
HUD_SOURCE = ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp"
CHARACTER_HEADER = ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.h"
EXPECTED_HUD_HEADER_SNIPPETS = [
"void DrawPlayerStatus(const class AAgrarianGameCharacter* AgrarianCharacter, float X, float& Y);",
]
EXPECTED_HUD_SOURCE_SNIPPETS = [
"#include \"GameFramework/CharacterMovementComponent.h\"",
"DrawPlayerStatus(AgrarianCharacter, X, Y);",
"void AAgrarianDebugHUD::DrawPlayerStatus",
"Role:",
"Location:",
"Speed:",
"Stance:",
"Move Mult:",
"Age %.1f | Cond %.2f | Str %.2f | End %.2f | Terrain %.2f",
]
EXPECTED_CHARACTER_HEADER_SNIPPETS = [
"float GetAgeYears() const",
"float GetPhysicalConditionMultiplier() const",
"float GetStrengthMultiplier() const",
"float GetEnduranceMultiplier() const",
"float GetTerrainMovementMultiplier() const",
]
def assert_contains(label, text, snippets):
missing = [snippet for snippet in snippets if snippet not in text]
if missing:
raise RuntimeError(f"{label} is missing expected debug overlay snippets: {missing}")
def main():
assert_contains("AgrarianDebugHUD.h", HUD_HEADER.read_text(encoding="utf-8"), EXPECTED_HUD_HEADER_SNIPPETS)
assert_contains("AgrarianDebugHUD.cpp", HUD_SOURCE.read_text(encoding="utf-8"), EXPECTED_HUD_SOURCE_SNIPPETS)
assert_contains("AgrarianGameCharacter.h", CHARACTER_HEADER.read_text(encoding="utf-8"), EXPECTED_CHARACTER_HEADER_SNIPPETS)
print("Agrarian player debug overlay verification complete.")
if __name__ == "__main__":
main()
+61
View File
@@ -0,0 +1,61 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FILES = {
"AgrarianTypes.h": ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h",
"AgrarianSurvivalComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h",
"AgrarianSurvivalComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp",
"AgrarianGameCharacter.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.cpp",
"AgrarianDebugHUD.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp",
"AgrarianGamePlayerController.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp",
"PersistenceDesignDocument.md": ROOT / "Docs" / "PersistenceDesignDocument.md",
}
EXPECTED = {
"AgrarianTypes.h": ["float SicknessSeverity = 0.0f;"],
"AgrarianSurvivalComponent.h": [
"float SicknessDamagePerMinute = 1.5f;",
"float SicknessRecoveryPerSecond = 0.02f;",
"void AddSickness(float Severity);",
"void ReduceSickness(float Amount);",
],
"AgrarianSurvivalComponent.cpp": [
"Survival.SicknessSeverity > 0.0f",
"CareHistory.IllnessBurden",
"SicknessDamagePerMinute",
"void UAgrarianSurvivalComponent::AddSickness",
"void UAgrarianSurvivalComponent::ReduceSickness",
"Survival.SicknessSeverity = FMath::Clamp(Survival.SicknessSeverity, 0.0f, 100.0f);",
],
"AgrarianGameCharacter.cpp": [
"const float SicknessMultiplier = FMath::GetMappedRangeValueClamped",
"Survival.SicknessSeverity",
"InjuryMultiplier * SicknessMultiplier * ExhaustionMultiplier",
],
"AgrarianDebugHUD.cpp": ["Sick:", "Survival.SicknessSeverity"],
"AgrarianGamePlayerController.cpp": [
"Sickness %.1f",
"Survival.SicknessSeverity",
"SurvivalComponent->ReduceSickness(100.0f);",
],
"PersistenceDesignDocument.md": ["sickness severity"],
}
def main():
missing = []
for label, path in FILES.items():
text = path.read_text(encoding="utf-8")
for snippet in EXPECTED[label]:
if snippet not in text:
missing.append(f"{label}: {snippet}")
if missing:
raise RuntimeError("Sickness placeholder verification failed: " + "; ".join(missing))
print("Agrarian sickness placeholder verification complete.")
if __name__ == "__main__":
main()
+2 -2
View File
@@ -23,7 +23,7 @@ def mapping_found(context, action, key_name):
def main(): def main():
action = load("/Game/Input/Actions/IA_Sprint") action = load("/Game/Input/Actions/IA_Sprint")
context = load("/Game/Input/IMC_Default") context = load("/Game/Input/IMC_Default")
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
missing = [] missing = []
@@ -33,7 +33,7 @@ def main():
assigned_action = character_cdo.get_editor_property("SprintAction") assigned_action = character_cdo.get_editor_property("SprintAction")
if assigned_action != action: if assigned_action != action:
missing.append("BP_ThirdPersonCharacter SprintAction is not IA_Sprint") missing.append("BP_AgrarianPlayerCharacter SprintAction is not IA_Sprint")
if missing: if missing:
raise RuntimeError("Sprint input verification failed: " + "; ".join(missing)) raise RuntimeError("Sprint input verification failed: " + "; ".join(missing))
+3 -3
View File
@@ -31,7 +31,7 @@ def main():
crouch_action = load("/Game/Input/Actions/IA_Crouch") crouch_action = load("/Game/Input/Actions/IA_Crouch")
prone_action = load("/Game/Input/Actions/IA_Prone") prone_action = load("/Game/Input/Actions/IA_Prone")
context = load("/Game/Input/IMC_Default") context = load("/Game/Input/IMC_Default")
character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") character_bp = load("/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter")
character_cdo = unreal.get_default_object(character_bp.generated_class()) character_cdo = unreal.get_default_object(character_bp.generated_class())
missing = [] missing = []
@@ -45,9 +45,9 @@ def main():
missing.append(f"missing mapping {action.get_name()} -> {key_name}") missing.append(f"missing mapping {action.get_name()} -> {key_name}")
if character_cdo.get_editor_property("CrouchAction") != crouch_action: if character_cdo.get_editor_property("CrouchAction") != crouch_action:
missing.append("BP_ThirdPersonCharacter CrouchAction is not IA_Crouch") missing.append("BP_AgrarianPlayerCharacter CrouchAction is not IA_Crouch")
if character_cdo.get_editor_property("ProneAction") != prone_action: if character_cdo.get_editor_property("ProneAction") != prone_action:
missing.append("BP_ThirdPersonCharacter ProneAction is not IA_Prone") missing.append("BP_AgrarianPlayerCharacter ProneAction is not IA_Prone")
for property_name, expected in STANCE_DEFAULTS.items(): for property_name, expected in STANCE_DEFAULTS.items():
actual = float(character_cdo.get_editor_property(property_name)) actual = float(character_cdo.get_editor_property(property_name))
+70
View File
@@ -0,0 +1,70 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FILES = {
"AgrarianSurvivalComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h",
"AgrarianSurvivalComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp",
"AgrarianPersistenceSubsystem.h": ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.h",
"AgrarianPersistenceSubsystem.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.cpp",
"AgrarianGamePlayerController.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp",
"PersistenceDesignDocument.md": ROOT / "Docs" / "PersistenceDesignDocument.md",
}
EXPECTED = {
"AgrarianSurvivalComponent.h": [
"void ApplySavedState(const FAgrarianSurvivalSnapshot& SavedSurvival, const FAgrarianCareHistorySnapshot& SavedCareHistory);",
],
"AgrarianSurvivalComponent.cpp": [
"void UAgrarianSurvivalComponent::ApplySavedState",
"Survival = SavedSurvival;",
"CareHistory = SavedCareHistory;",
"ClampSurvival();",
"ClampCareHistory();",
],
"AgrarianPersistenceSubsystem.h": [
"int32 CapturePlayers(UAgrarianSaveGame* SaveGame) const;",
"int32 RestorePlayers(const UAgrarianSaveGame* SaveGame) const;",
"void FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const;",
"FString GetPlayerPersistenceId(const AAgrarianGameCharacter* Character) const;",
],
"AgrarianPersistenceSubsystem.cpp": [
"#include \"AgrarianInventoryComponent.h\"",
"int32 UAgrarianPersistenceSubsystem::CapturePlayers",
"SavedPlayer.Survival = SurvivalComponent->Survival;",
"SavedPlayer.CareHistory = SurvivalComponent->CareHistory;",
"SavedPlayer.Inventory = InventoryComponent->Items;",
"int32 UAgrarianPersistenceSubsystem::RestorePlayers",
"SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);",
"InventoryComponent->Items = SavedPlayer->Inventory;",
"CapturePlayers(SaveGame);",
"void UAgrarianPersistenceSubsystem::FindAgrarianPlayers",
"FString UAgrarianPersistenceSubsystem::GetPlayerPersistenceId",
],
"AgrarianGamePlayerController.cpp": [
"const int32 RestoredPlayerCount = Persistence->RestorePlayers(SaveGame);",
"Restored players: %d. Restored actors: %d.",
],
"PersistenceDesignDocument.md": [
"captures live Agrarian player characters",
"RestorePlayers",
],
}
def main():
missing = []
for label, path in FILES.items():
text = path.read_text(encoding="utf-8")
for snippet in EXPECTED[label]:
if snippet not in text:
missing.append(f"{label}: {snippet}")
if missing:
raise RuntimeError("Stat save/load verification failed: " + "; ".join(missing))
print("Agrarian stat save/load verification complete.")
if __name__ == "__main__":
main()
+1 -1
View File
@@ -1,7 +1,7 @@
import unreal import unreal
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson" MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
EXPECTED_PLACEMENTS = { EXPECTED_PLACEMENTS = {
"AGR_WoodResourceNode_01": { "AGR_WoodResourceNode_01": {
+84
View File
@@ -0,0 +1,84 @@
from pathlib import Path
import json
ROOT = Path(__file__).resolve().parents[1]
GAME_STATE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.h"
GAME_STATE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.cpp"
GENERATOR = ROOT / "Scripts" / "generate_tile_solar_metadata.py"
SOLAR_METADATA = ROOT / "Data" / "Tiles" / "tile_solar_metadata.json"
SCHEMA = ROOT / "Data" / "Tiles" / "tile_registry.schema.json"
SQL = ROOT / "Data" / "Tiles" / "tile_registry.sql"
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
EXPECTED = {
GAME_STATE_H: [
"ActiveSolarTileId",
"ActiveTileLatitude",
"ActiveTileLongitude",
"ActiveTileTimeZoneId",
"ActiveTileUtcOffsetHours",
"ActiveDayOfYear",
"SunriseHourLocal",
"SunsetHourLocal",
"SolarNoonHourLocal",
"DayLengthHours",
"ConfigureActiveSolarTile",
],
GAME_STATE_CPP: [
"UpdateSolarTimes();",
"DOREPLIFETIME(AAgrarianGameState, SunriseHourLocal);",
"return WorldHours < SunriseHourLocal || WorldHours > SunsetHourLocal;",
"bool AAgrarianGameState::ConfigureActiveSolarTile",
"void AAgrarianGameState::UpdateSolarTimes()",
"NOAA",
"EquationOfTimeMinutes",
"SolarDeclinationRadians",
],
GENERATOR: [
"SOLAR_READY_STATUSES",
"TIMEZONE_OVERRIDES",
"Only tiles with real source status",
"gz_us_ca_pacifica_utm10n_e544_n4160",
],
SCHEMA: ["solar_metadata", "standard_utc_offset_hours", "daylight_utc_offset_hours"],
SQL: ["terrain_tile_solar_metadata", "time_zone_id", "solar_model"],
ROADMAP: ["Add real local time-zone and sunrise/sunset lookup for Ground Zero"],
}
def assert_contains():
missing = []
for path, snippets in EXPECTED.items():
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
missing.append(f"{path.relative_to(ROOT)}: {snippet}")
if missing:
raise RuntimeError("Tile solar time verification failed: " + "; ".join(missing))
def assert_solar_metadata():
data = json.loads(SOLAR_METADATA.read_text(encoding="utf-8"))
tiles = data.get("tiles", [])
if len(tiles) != 1:
raise RuntimeError(f"Expected exactly one generated solar tile, got {len(tiles)}")
tile = tiles[0]
if tile.get("tile_id") != "gz_us_ca_pacifica_utm10n_e544_n4160":
raise RuntimeError(f"Unexpected solar tile id: {tile.get('tile_id')}")
if tile.get("time_zone_id") != "America/Los_Angeles":
raise RuntimeError("Ground Zero timezone metadata is missing or incorrect")
june = tile.get("sample_solar_hours", {}).get("june_solstice", {})
if not (5.0 <= june.get("sunrise_hour", -1) <= 6.5 and 20.0 <= june.get("sunset_hour", -1) <= 21.0):
raise RuntimeError(f"Ground Zero June solar sample is outside expected range: {june}")
def main():
assert_contains()
assert_solar_metadata()
print("Agrarian tile solar time verification complete.")
if __name__ == "__main__":
main()
+2 -2
View File
@@ -1,8 +1,8 @@
import unreal import unreal
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson" MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
CHARACTER_CLASS_PATH = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter" CHARACTER_CLASS_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
RABBIT_LABEL = "AGR_RabbitWildlife_01" RABBIT_LABEL = "AGR_RabbitWildlife_01"
+117 -2
View File
@@ -5,6 +5,7 @@
#include "AgrarianInventoryComponent.h" #include "AgrarianInventoryComponent.h"
#include "AgrarianSurvivalComponent.h" #include "AgrarianSurvivalComponent.h"
#include "Engine/Canvas.h" #include "Engine/Canvas.h"
#include "GameFramework/CharacterMovementComponent.h"
void AAgrarianDebugHUD::DrawHUD() void AAgrarianDebugHUD::DrawHUD()
{ {
@@ -22,6 +23,7 @@ void AAgrarianDebugHUD::DrawHUD()
} }
DrawInteractionPrompt(AgrarianCharacter); DrawInteractionPrompt(AgrarianCharacter);
DrawCriticalStats(AgrarianCharacter->GetSurvivalComponent());
if (bShowDebugHUD) if (bShowDebugHUD)
{ {
@@ -29,6 +31,7 @@ void AAgrarianDebugHUD::DrawHUD()
constexpr float X = 32.0f; constexpr float X = 32.0f;
DrawLine(TEXT("AGRARIAN DEV HUD"), X, Y, FColor(160, 220, 140)); DrawLine(TEXT("AGRARIAN DEV HUD"), X, Y, FColor(160, 220, 140));
DrawPlayerStatus(AgrarianCharacter, X, Y);
DrawSurvival(AgrarianCharacter->GetSurvivalComponent(), X, Y); DrawSurvival(AgrarianCharacter->GetSurvivalComponent(), X, Y);
DrawInventory(AgrarianCharacter->GetInventoryComponent(), X, Y); DrawInventory(AgrarianCharacter->GetInventoryComponent(), X, Y);
} }
@@ -51,6 +54,108 @@ void AAgrarianDebugHUD::DrawInteractionPrompt(const AAgrarianGameCharacter* Agra
DrawText(Prompt, FColor(245, 245, 225), X, Y, nullptr, PromptTextScale, true); DrawText(Prompt, FColor(245, 245, 225), X, Y, nullptr, PromptTextScale, true);
} }
void AAgrarianDebugHUD::DrawCriticalStats(const UAgrarianSurvivalComponent* SurvivalComponent)
{
if (!bShowCriticalStatsHUD || !SurvivalComponent || !Canvas)
{
return;
}
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
float Y = FMath::Max(32.0f, Canvas->ClipY - (178.0f * CriticalStatsTextScale));
constexpr float X = 32.0f;
const FColor CriticalColor(230, 90, 70);
const FColor WarningColor(245, 190, 80);
const FColor StableColor(225, 235, 220);
const FColor HeaderColor(160, 220, 140);
auto StatusColor = [CriticalColor, WarningColor, StableColor](float Value, bool bHigherIsWorse = false)
{
if (bHigherIsWorse)
{
if (Value >= 75.0f)
{
return CriticalColor;
}
if (Value >= 40.0f)
{
return WarningColor;
}
return StableColor;
}
if (Value <= 20.0f)
{
return CriticalColor;
}
if (Value <= 45.0f)
{
return WarningColor;
}
return StableColor;
};
DrawScaledLine(TEXT("SURVIVAL"), X, Y, CriticalStatsTextScale, HeaderColor);
DrawScaledLine(FString::Printf(TEXT("Health %3.0f"), Survival.Health), X, Y, CriticalStatsTextScale, StatusColor(Survival.Health));
DrawScaledLine(FString::Printf(TEXT("Stamina %3.0f"), Survival.Stamina), X, Y, CriticalStatsTextScale, StatusColor(Survival.Stamina));
DrawScaledLine(FString::Printf(TEXT("Food %3.0f"), Survival.Hunger), X, Y, CriticalStatsTextScale, StatusColor(Survival.Hunger));
DrawScaledLine(FString::Printf(TEXT("Water %3.0f"), Survival.Thirst), X, Y, CriticalStatsTextScale, StatusColor(Survival.Thirst));
DrawScaledLine(FString::Printf(TEXT("Temp %4.1f C"), Survival.BodyTemperature), X, Y, CriticalStatsTextScale, Survival.BodyTemperature < 35.0f ? CriticalColor : StableColor);
DrawScaledLine(FString::Printf(TEXT("Exhaust %3.0f"), Survival.Exhaustion), X, Y, CriticalStatsTextScale, StatusColor(Survival.Exhaustion, true));
DrawScaledLine(FString::Printf(TEXT("Injury %3.0f"), Survival.InjurySeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.InjurySeverity, true));
DrawScaledLine(FString::Printf(TEXT("Sickness %3.0f"), Survival.SicknessSeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.SicknessSeverity, true));
}
void AAgrarianDebugHUD::DrawPlayerStatus(const AAgrarianGameCharacter* AgrarianCharacter, float X, float& Y)
{
if (!AgrarianCharacter)
{
DrawLine(TEXT("Player: unavailable"), X, Y, FColor::Red);
return;
}
const UCharacterMovementComponent* MovementComponent = AgrarianCharacter->GetCharacterMovement();
const FVector Location = AgrarianCharacter->GetActorLocation();
const float HorizontalSpeed = AgrarianCharacter->GetVelocity().Size2D();
const FString MovementMode = MovementComponent
? UEnum::GetValueAsString(MovementComponent->MovementMode)
: TEXT("Unavailable");
DrawLine(FString::Printf(
TEXT("Role: %s | Local: %s"),
AgrarianCharacter->HasAuthority() ? TEXT("Authority") : TEXT("Simulated/Autonomous"),
AgrarianCharacter->IsLocallyControlled() ? TEXT("yes") : TEXT("no")),
X,
Y,
FColor(160, 220, 140));
DrawLine(FString::Printf(TEXT("Location: X %.0f Y %.0f Z %.0f"), Location.X, Location.Y, Location.Z), X, Y);
DrawLine(FString::Printf(TEXT("Speed: %.0f uu/s | Mode: %s"), HorizontalSpeed, *MovementMode), X, Y);
DrawLine(FString::Printf(
TEXT("Stance: %s | Sprint: %s | Camera: %s"),
AgrarianCharacter->IsProne() ? TEXT("Prone") : (AgrarianCharacter->bIsCrouched ? TEXT("Crouched") : TEXT("Standing")),
AgrarianCharacter->IsSprinting() ? TEXT("yes") : TEXT("no"),
AgrarianCharacter->IsFirstPersonCamera() ? TEXT("First") : TEXT("Third")),
X,
Y);
DrawLine(FString::Printf(
TEXT("Move Mult: %.2f | Carry: %.1f"),
AgrarianCharacter->GetCurrentMovementSpeedMultiplier(),
AgrarianCharacter->GetCurrentCarryWeight()),
X,
Y);
DrawLine(FString::Printf(
TEXT("Age %.1f | Cond %.2f | Str %.2f | End %.2f | Terrain %.2f"),
AgrarianCharacter->GetAgeYears(),
AgrarianCharacter->GetPhysicalConditionMultiplier(),
AgrarianCharacter->GetStrengthMultiplier(),
AgrarianCharacter->GetEnduranceMultiplier(),
AgrarianCharacter->GetTerrainMovementMultiplier()),
X,
Y);
Y += 10.0f * TextScale;
}
void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y) void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y)
{ {
if (!SurvivalComponent) if (!SurvivalComponent)
@@ -62,10 +167,15 @@ void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalC
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival; const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
DrawLine(FString::Printf(TEXT("Health: %.0f"), Survival.Health), X, Y); DrawLine(FString::Printf(TEXT("Health: %.0f"), Survival.Health), X, Y);
DrawLine(FString::Printf(TEXT("Stamina: %.0f"), Survival.Stamina), X, Y); DrawLine(FString::Printf(TEXT("Stamina: %.0f"), Survival.Stamina), X, Y);
DrawLine(FString::Printf(TEXT("Exhaust: %.0f"), Survival.Exhaustion), X, Y);
DrawLine(FString::Printf(TEXT("Hunger: %.0f"), Survival.Hunger), X, Y); DrawLine(FString::Printf(TEXT("Hunger: %.0f"), Survival.Hunger), X, Y);
DrawLine(FString::Printf(TEXT("Thirst: %.0f"), Survival.Thirst), X, Y); DrawLine(FString::Printf(TEXT("Thirst: %.0f"), Survival.Thirst), X, Y);
DrawLine(FString::Printf(TEXT("Temp: %.1f C"), Survival.BodyTemperature), X, Y); DrawLine(FString::Printf(TEXT("Temp: %.1f C"), Survival.BodyTemperature), X, Y);
DrawLine(FString::Printf(TEXT("Injury: %.0f"), Survival.InjurySeverity), X, Y); DrawLine(FString::Printf(TEXT("Injury: %.0f"), Survival.InjurySeverity), X, Y);
DrawLine(FString::Printf(TEXT("Sick: %.0f"), Survival.SicknessSeverity), X, Y);
const FAgrarianCareHistorySnapshot& Care = SurvivalComponent->CareHistory;
DrawLine(FString::Printf(TEXT("Care N/S/T: %.2f %.2f %.2f"), Care.NutritionQuality, Care.SleepQuality, Care.TreatmentQuality), X, Y, FColor::Silver);
DrawLine(FString::Printf(TEXT("Burden I/I/S/W: %.2f %.2f %.2f %.2f"), Care.IllnessBurden, Care.InjuryBurden, Care.StressBurden, Care.WorkloadBurden), X, Y, FColor::Silver);
Y += 10.0f * TextScale; Y += 10.0f * TextScale;
} }
@@ -94,6 +204,11 @@ void AAgrarianDebugHUD::DrawInventory(const UAgrarianInventoryComponent* Invento
void AAgrarianDebugHUD::DrawLine(const FString& Text, float X, float& Y, const FColor& Color) void AAgrarianDebugHUD::DrawLine(const FString& Text, float X, float& Y, const FColor& Color)
{ {
DrawText(Text, Color, X, Y, nullptr, TextScale, false); DrawScaledLine(Text, X, Y, TextScale, Color);
Y += 18.0f * TextScale; }
void AAgrarianDebugHUD::DrawScaledLine(const FString& Text, float X, float& Y, float Scale, const FColor& Color)
{
DrawText(Text, Color, X, Y, nullptr, Scale, false);
Y += 18.0f * Scale;
} }
+9
View File
@@ -20,9 +20,15 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD")
bool bShowDebugHUD = true; bool bShowDebugHUD = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD")
bool bShowCriticalStatsHUD = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25"))
float TextScale = 1.0f; float TextScale = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25"))
float CriticalStatsTextScale = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD")
bool bShowInteractionPrompt = true; bool bShowInteractionPrompt = true;
@@ -31,7 +37,10 @@ public:
protected: protected:
void DrawInteractionPrompt(const class AAgrarianGameCharacter* AgrarianCharacter); void DrawInteractionPrompt(const class AAgrarianGameCharacter* AgrarianCharacter);
void DrawCriticalStats(const UAgrarianSurvivalComponent* SurvivalComponent);
void DrawPlayerStatus(const class AAgrarianGameCharacter* AgrarianCharacter, float X, float& Y);
void DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y); void DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y);
void DrawInventory(const UAgrarianInventoryComponent* InventoryComponent, float X, float& Y); void DrawInventory(const UAgrarianInventoryComponent* InventoryComponent, float X, float& Y);
void DrawLine(const FString& Text, float X, float& Y, const FColor& Color = FColor::White); void DrawLine(const FString& Text, float X, float& Y, const FColor& Color = FColor::White);
void DrawScaledLine(const FString& Text, float X, float& Y, float Scale, const FColor& Color = FColor::White);
}; };
+37 -2
View File
@@ -21,6 +21,10 @@
AAgrarianGameCharacter::AAgrarianGameCharacter() AAgrarianGameCharacter::AAgrarianGameCharacter()
{ {
PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
SetReplicateMovement(true);
SetNetUpdateFrequency(30.0f);
SetMinNetUpdateFrequency(10.0f);
// Set size for collision capsule // Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
@@ -286,6 +290,7 @@ bool AAgrarianGameCharacter::CanSprint() const
&& SurvivalComponent->IsAlive() && SurvivalComponent->IsAlive()
&& !bIsProne && !bIsProne
&& !bIsCrouched && !bIsCrouched
&& SurvivalComponent->Survival.Exhaustion < 85.0f
&& SurvivalComponent->Survival.Stamina > MinSprintStamina; && SurvivalComponent->Survival.Stamina > MinSprintStamina;
} }
@@ -329,7 +334,19 @@ void AAgrarianGameCharacter::SetProne(bool bNewProne)
void AAgrarianGameCharacter::SetTerrainMovementMultiplier(float NewTerrainMovementMultiplier) void AAgrarianGameCharacter::SetTerrainMovementMultiplier(float NewTerrainMovementMultiplier)
{ {
TerrainMovementMultiplier = FMath::Clamp(NewTerrainMovementMultiplier, 0.25f, 1.25f); const float ClampedTerrainMovementMultiplier = FMath::Clamp(NewTerrainMovementMultiplier, 0.25f, 1.25f);
if (!HasAuthority())
{
ServerSetTerrainMovementMultiplier(ClampedTerrainMovementMultiplier);
return;
}
if (FMath::IsNearlyEqual(TerrainMovementMultiplier, ClampedTerrainMovementMultiplier))
{
return;
}
TerrainMovementMultiplier = ClampedTerrainMovementMultiplier;
ApplyMovementSpeed(); ApplyMovementSpeed();
} }
@@ -403,8 +420,16 @@ float AAgrarianGameCharacter::CalculateSurvivalMovementMultiplier() const
FVector2D(0.0f, 100.0f), FVector2D(0.0f, 100.0f),
FVector2D(1.0f, 0.5f), FVector2D(1.0f, 0.5f),
Survival.InjurySeverity); Survival.InjurySeverity);
const float SicknessMultiplier = FMath::GetMappedRangeValueClamped(
FVector2D(0.0f, 100.0f),
FVector2D(1.0f, 0.7f),
Survival.SicknessSeverity);
const float ExhaustionMultiplier = FMath::GetMappedRangeValueClamped(
FVector2D(0.0f, 100.0f),
FVector2D(1.0f, 0.55f),
Survival.Exhaustion);
return HungerMultiplier * ThirstMultiplier * InjuryMultiplier; return HungerMultiplier * ThirstMultiplier * InjuryMultiplier * SicknessMultiplier * ExhaustionMultiplier;
} }
float AAgrarianGameCharacter::CalculateCarryWeightMovementMultiplier() const float AAgrarianGameCharacter::CalculateCarryWeightMovementMultiplier() const
@@ -460,6 +485,11 @@ void AAgrarianGameCharacter::OnRep_ProneState()
ApplyMovementSpeed(); ApplyMovementSpeed();
} }
void AAgrarianGameCharacter::OnRep_MovementModifierState()
{
ApplyMovementSpeed();
}
void AAgrarianGameCharacter::DoMove(float Right, float Forward) void AAgrarianGameCharacter::DoMove(float Right, float Forward)
{ {
if (GetController() != nullptr) if (GetController() != nullptr)
@@ -585,6 +615,11 @@ void AAgrarianGameCharacter::ServerSetProne_Implementation(bool bNewProne)
SetProne(bNewProne); SetProne(bNewProne);
} }
void AAgrarianGameCharacter::ServerSetTerrainMovementMultiplier_Implementation(float NewTerrainMovementMultiplier)
{
SetTerrainMovementMultiplier(NewTerrainMovementMultiplier);
}
void AAgrarianGameCharacter::ServerInteract_Implementation(AActor* TargetActor) void AAgrarianGameCharacter::ServerInteract_Implementation(AActor* TargetActor)
{ {
if (!TargetActor || !TargetActor->GetClass()->ImplementsInterface(UAgrarianInteractable::StaticClass())) if (!TargetActor || !TargetActor->GetClass()->ImplementsInterface(UAgrarianInteractable::StaticClass()))
+27 -5
View File
@@ -126,19 +126,19 @@ protected:
float ProneSpeedMultiplier = 0.25f; float ProneSpeedMultiplier = 0.25f;
/** Age hook used by movement until full lifecycle/aging systems own it. */ /** Age hook used by movement until full lifecycle/aging systems own it. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0"))
float AgeYears = 25.0f; float AgeYears = 25.0f;
/** General physical condition scalar reserved for care, illness, sleep, and long-term state. */ /** General physical condition scalar reserved for care, illness, sleep, and long-term state. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25")) UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25"))
float PhysicalConditionMultiplier = 1.0f; float PhysicalConditionMultiplier = 1.0f;
/** Strength scalar that mainly offsets carried-weight penalties. */ /** Strength scalar that mainly offsets carried-weight penalties. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0"))
float StrengthMultiplier = 1.0f; float StrengthMultiplier = 1.0f;
/** Endurance scalar that improves stamina efficiency and movement resilience. */ /** Endurance scalar that improves stamina efficiency and movement resilience. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0"))
float EnduranceMultiplier = 1.0f; float EnduranceMultiplier = 1.0f;
/** Comfort carry capacity before speed penalties, in item-weight units. */ /** Comfort carry capacity before speed penalties, in item-weight units. */
@@ -150,7 +150,7 @@ protected:
float HeavyCarryWeight = 60.0f; float HeavyCarryWeight = 60.0f;
/** Terrain hook for later surface/volume systems. Values below one slow the character. */ /** Terrain hook for later surface/volume systems. Values below one slow the character. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25")) UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25"))
float TerrainMovementMultiplier = 1.0f; float TerrainMovementMultiplier = 1.0f;
/** Third-person spring arm distance used when returning from first person. */ /** Third-person spring arm distance used when returning from first person. */
@@ -238,6 +238,9 @@ protected:
UFUNCTION() UFUNCTION()
void OnRep_ProneState(); void OnRep_ProneState();
UFUNCTION()
void OnRep_MovementModifierState();
public: public:
/** Handles move inputs from either controls or UI interfaces */ /** Handles move inputs from either controls or UI interfaces */
@@ -292,6 +295,21 @@ public:
UFUNCTION(BlueprintPure, Category="Agrarian|Movement") UFUNCTION(BlueprintPure, Category="Agrarian|Movement")
float GetCurrentMovementSpeedMultiplier() const; float GetCurrentMovementSpeedMultiplier() const;
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
float GetAgeYears() const { return AgeYears; }
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
float GetPhysicalConditionMultiplier() const { return PhysicalConditionMultiplier; }
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
float GetStrengthMultiplier() const { return StrengthMultiplier; }
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
float GetEnduranceMultiplier() const { return EnduranceMultiplier; }
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
float GetTerrainMovementMultiplier() const { return TerrainMovementMultiplier; }
UFUNCTION(BlueprintCallable, Category="Agrarian|Movement") UFUNCTION(BlueprintCallable, Category="Agrarian|Movement")
void SetTerrainMovementMultiplier(float NewTerrainMovementMultiplier); void SetTerrainMovementMultiplier(float NewTerrainMovementMultiplier);
@@ -307,6 +325,10 @@ public:
UFUNCTION(Server, Reliable) UFUNCTION(Server, Reliable)
void ServerSetProne(bool bNewProne); void ServerSetProne(bool bNewProne);
/** Server-authoritative terrain movement modifier update. */
UFUNCTION(Server, Reliable)
void ServerSetTerrainMovementMultiplier(float NewTerrainMovementMultiplier);
/** Refreshes local interactable focus and prompt text. */ /** Refreshes local interactable focus and prompt text. */
void UpdateInteractionPrompt(); void UpdateInteractionPrompt();
@@ -104,13 +104,15 @@ void AAgrarianGamePlayerController::AgrarianSurvival()
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival; const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
ClientMessage(FString::Printf( ClientMessage(FString::Printf(
TEXT("Health %.1f | Stamina %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f"), TEXT("Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Sickness %.1f"),
Survival.Health, Survival.Health,
Survival.Stamina, Survival.Stamina,
Survival.Exhaustion,
Survival.Hunger, Survival.Hunger,
Survival.Thirst, Survival.Thirst,
Survival.BodyTemperature, Survival.BodyTemperature,
Survival.InjurySeverity)); Survival.InjurySeverity,
Survival.SicknessSeverity));
} }
void AAgrarianGamePlayerController::AgrarianHeal() void AAgrarianGamePlayerController::AgrarianHeal()
@@ -167,8 +169,9 @@ void AAgrarianGamePlayerController::ServerAgrarianLoadWorld_Implementation()
Persistence->RegisterWorldActorClass(TEXT("primitive_shelter"), AAgrarianShelterActor::StaticClass()); Persistence->RegisterWorldActorClass(TEXT("primitive_shelter"), AAgrarianShelterActor::StaticClass());
const UAgrarianSaveGame* SaveGame = Persistence->LoadOrCreateSave(); const UAgrarianSaveGame* SaveGame = Persistence->LoadOrCreateSave();
const int32 RestoredPlayerCount = Persistence->RestorePlayers(SaveGame);
const int32 RestoredCount = Persistence->RestoreWorldActors(SaveGame); const int32 RestoredCount = Persistence->RestoreWorldActors(SaveGame);
ClientMessage(FString::Printf(TEXT("Agrarian world loaded. Restored actors: %d."), RestoredCount)); ClientMessage(FString::Printf(TEXT("Agrarian world loaded. Restored players: %d. Restored actors: %d."), RestoredPlayerCount, RestoredCount));
} }
void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation() void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
@@ -184,6 +187,8 @@ void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
SurvivalComponent->RestoreHealth(100.0f); SurvivalComponent->RestoreHealth(100.0f);
SurvivalComponent->AddFood(100.0f); SurvivalComponent->AddFood(100.0f);
SurvivalComponent->AddWater(100.0f); SurvivalComponent->AddWater(100.0f);
SurvivalComponent->ReduceExhaustion(100.0f);
SurvivalComponent->ReduceSickness(100.0f);
SurvivalComponent->AddWarmth(37.0f - SurvivalComponent->Survival.BodyTemperature); SurvivalComponent->AddWarmth(37.0f - SurvivalComponent->Survival.BodyTemperature);
ClientMessage(TEXT("Agrarian survival restored.")); ClientMessage(TEXT("Agrarian survival restored."));
} }
+325 -1
View File
@@ -3,10 +3,52 @@
#include "AgrarianGameState.h" #include "AgrarianGameState.h"
#include "Net/UnrealNetwork.h" #include "Net/UnrealNetwork.h"
namespace
{
int32 NormalizeCalendarDay(int32 DayOfYear, int32 DaysPerYear)
{
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerYear);
const int32 ZeroBasedDay = ((DayOfYear - 1) % SafeDaysPerYear + SafeDaysPerYear) % SafeDaysPerYear;
return ZeroBasedDay + 1;
}
bool IsDayInRange(int32 DayOfYear, int32 StartDay, int32 EndDay, int32 DaysPerYear)
{
const int32 Day = NormalizeCalendarDay(DayOfYear, DaysPerYear);
const int32 Start = NormalizeCalendarDay(StartDay, DaysPerYear);
const int32 End = NormalizeCalendarDay(EndDay, DaysPerYear);
if (Start <= End)
{
return Day >= Start && Day <= End;
}
return Day >= Start || Day <= End;
}
}
AAgrarianGameState::AAgrarianGameState() AAgrarianGameState::AAgrarianGameState()
{ {
PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bCanEverTick = true;
bReplicates = true; bReplicates = true;
ActiveGrowingSeason.TileId = ActiveSolarTileId;
ActiveGrowingSeason.GrowingZoneLabel = TEXT("USDA 10a");
ActiveGrowingSeason.GrowingSeasonStartDay = 46;
ActiveGrowingSeason.GrowingSeasonEndDay = 350;
ActiveGrowingSeason.FrostFreeDays = 305;
ActiveGrowingSeason.MinAverageGrowingTempC = 7.0f;
ActiveGrowingSeason.CropSafetyBufferDays = 14;
ActiveGrowingSeason.ClimateProfile = TEXT("coastal_mediterranean_mild");
}
void AAgrarianGameState::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
{
UpdateSolarTimes();
}
} }
void AAgrarianGameState::Tick(float DeltaSeconds) void AAgrarianGameState::Tick(float DeltaSeconds)
@@ -22,6 +64,13 @@ void AAgrarianGameState::Tick(float DeltaSeconds)
while (WorldHours >= 24.0f) while (WorldHours >= 24.0f)
{ {
WorldHours -= 24.0f; WorldHours -= 24.0f;
const int32 PreviousDayOfYear = ActiveDayOfYear;
ActiveDayOfYear = (ActiveDayOfYear % FMath::Max(1, DaysPerAgrarianYear)) + 1;
if (ActiveDayOfYear < PreviousDayOfYear)
{
++ActiveYear;
}
UpdateSolarTimes();
} }
UpdateAmbientTemperature(); UpdateAmbientTemperature();
@@ -33,10 +82,29 @@ void AAgrarianGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& O
DOREPLIFETIME(AAgrarianGameState, WorldHours); DOREPLIFETIME(AAgrarianGameState, WorldHours);
DOREPLIFETIME(AAgrarianGameState, Weather); DOREPLIFETIME(AAgrarianGameState, Weather);
DOREPLIFETIME(AAgrarianGameState, AmbientTemperatureC); DOREPLIFETIME(AAgrarianGameState, AmbientTemperatureC);
DOREPLIFETIME(AAgrarianGameState, DaysPerAgrarianYear);
DOREPLIFETIME(AAgrarianGameState, ActiveSolarTileId);
DOREPLIFETIME(AAgrarianGameState, ActiveTileLatitude);
DOREPLIFETIME(AAgrarianGameState, ActiveTileLongitude);
DOREPLIFETIME(AAgrarianGameState, ActiveTileTimeZoneId);
DOREPLIFETIME(AAgrarianGameState, ActiveTileUtcOffsetHours);
DOREPLIFETIME(AAgrarianGameState, ActiveDayOfYear);
DOREPLIFETIME(AAgrarianGameState, ActiveYear);
DOREPLIFETIME(AAgrarianGameState, ActiveGrowingSeason);
DOREPLIFETIME(AAgrarianGameState, bHasActiveTileSolarData);
DOREPLIFETIME(AAgrarianGameState, SunriseHourLocal);
DOREPLIFETIME(AAgrarianGameState, SunsetHourLocal);
DOREPLIFETIME(AAgrarianGameState, SolarNoonHourLocal);
DOREPLIFETIME(AAgrarianGameState, DayLengthHours);
} }
bool AAgrarianGameState::IsNight() const bool AAgrarianGameState::IsNight() const
{ {
if (bHasActiveTileSolarData)
{
return WorldHours < SunriseHourLocal || WorldHours > SunsetHourLocal;
}
return WorldHours < 6.0f || WorldHours > 20.0f; return WorldHours < 6.0f || WorldHours > 20.0f;
} }
@@ -50,14 +118,270 @@ void AAgrarianGameState::SetWeather(EAgrarianWeatherType NewWeather)
} }
} }
bool AAgrarianGameState::ConfigureActiveSolarTile(FName TileId, float Latitude, float Longitude, const FString& TimeZoneId, float UtcOffsetHours)
{
if (!HasAuthority() || TileId == NAME_None)
{
return false;
}
ActiveSolarTileId = TileId;
ActiveTileLatitude = FMath::Clamp(Latitude, -90.0f, 90.0f);
ActiveTileLongitude = FMath::Clamp(Longitude, -180.0f, 180.0f);
ActiveTileTimeZoneId = TimeZoneId;
ActiveTileUtcOffsetHours = FMath::Clamp(UtcOffsetHours, -12.0f, 14.0f);
ActiveGrowingSeason.TileId = TileId;
UpdateSolarTimes();
UpdateAmbientTemperature();
return bHasActiveTileSolarData;
}
FAgrarianCalendarSnapshot AAgrarianGameState::GetCalendarSnapshot() const
{
FAgrarianCalendarSnapshot Snapshot;
Snapshot.DayOfYear = NormalizeCalendarDay(ActiveDayOfYear, DaysPerAgrarianYear);
Snapshot.Year = FMath::Max(1, ActiveYear);
Snapshot.AbsoluteDay = GetAbsoluteAgrarianDay();
Snapshot.DayOfSeason = GetDayOfSeason(Snapshot.DayOfYear);
Snapshot.Season = GetSeasonForDay(Snapshot.DayOfYear);
Snapshot.HourOfDay = FMath::Clamp(WorldHours, 0.0f, 24.0f);
Snapshot.bInsideGrowingSeason = IsDayInsideActiveGrowingSeason(Snapshot.DayOfYear);
Snapshot.GrowingSeasonDaysRemaining = GetDaysRemainingInActiveGrowingSeason(Snapshot.DayOfYear);
return Snapshot;
}
int32 AAgrarianGameState::GetAbsoluteAgrarianDay() const
{
const int32 SafeYear = FMath::Max(1, ActiveYear);
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
return ((SafeYear - 1) * SafeDaysPerYear) + NormalizeCalendarDay(ActiveDayOfYear, SafeDaysPerYear);
}
EAgrarianSeason AAgrarianGameState::GetSeasonForDay(int32 DayOfYear) const
{
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
const int32 SeasonalDay = ActiveTileLatitude < 0.0f
? NormalizeCalendarDay(DayOfYear + (SafeDaysPerYear / 2), SafeDaysPerYear)
: NormalizeCalendarDay(DayOfYear, SafeDaysPerYear);
if (SeasonalDay >= 355 || SeasonalDay < 80)
{
return EAgrarianSeason::Winter;
}
if (SeasonalDay < 172)
{
return EAgrarianSeason::Spring;
}
if (SeasonalDay < 264)
{
return EAgrarianSeason::Summer;
}
return EAgrarianSeason::Autumn;
}
int32 AAgrarianGameState::GetDayOfSeason(int32 DayOfYear) const
{
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
const int32 SeasonalDay = ActiveTileLatitude < 0.0f
? NormalizeCalendarDay(DayOfYear + (SafeDaysPerYear / 2), SafeDaysPerYear)
: NormalizeCalendarDay(DayOfYear, SafeDaysPerYear);
switch (GetSeasonForDay(DayOfYear))
{
case EAgrarianSeason::Spring:
return SeasonalDay - 79;
case EAgrarianSeason::Summer:
return SeasonalDay - 171;
case EAgrarianSeason::Autumn:
return SeasonalDay - 263;
default:
return SeasonalDay >= 355 ? SeasonalDay - 354 : SeasonalDay + 12;
}
}
float AAgrarianGameState::ConvertAgrarianDaysToRealHours(float AgrarianDays) const
{
const float SafeGameHoursPerRealMinute = FMath::Max(0.001f, GameHoursPerRealMinute);
return FMath::Max(0.0f, AgrarianDays) * (24.0f / SafeGameHoursPerRealMinute) / 60.0f;
}
float AAgrarianGameState::ConvertRealHoursToAgrarianDays(float RealHours) const
{
return (FMath::Max(0.0f, RealHours) * 60.0f * FMath::Max(0.001f, GameHoursPerRealMinute)) / 24.0f;
}
float AAgrarianGameState::GetLongTaskProgress(int32 StartAbsoluteDay, float StartHourOfDay, float DurationAgrarianDays) const
{
const float SafeDuration = FMath::Max(0.001f, DurationAgrarianDays);
const float CurrentAgrarianDay = static_cast<float>(GetAbsoluteAgrarianDay() - 1) + (WorldHours / 24.0f);
const float StartAgrarianDay = static_cast<float>(FMath::Max(1, StartAbsoluteDay) - 1)
+ (FMath::Clamp(StartHourOfDay, 0.0f, 24.0f) / 24.0f);
return FMath::Clamp((CurrentAgrarianDay - StartAgrarianDay) / SafeDuration, 0.0f, 1.0f);
}
bool AAgrarianGameState::IsDayInsideActiveGrowingSeason(int32 DayOfYear) const
{
return IsDayInRange(
DayOfYear,
ActiveGrowingSeason.GrowingSeasonStartDay,
ActiveGrowingSeason.GrowingSeasonEndDay,
DaysPerAgrarianYear);
}
int32 AAgrarianGameState::GetDaysRemainingInActiveGrowingSeason(int32 PlantDayOfYear) const
{
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
const int32 Day = NormalizeCalendarDay(PlantDayOfYear, SafeDaysPerYear);
const int32 Start = NormalizeCalendarDay(ActiveGrowingSeason.GrowingSeasonStartDay, SafeDaysPerYear);
const int32 End = NormalizeCalendarDay(ActiveGrowingSeason.GrowingSeasonEndDay, SafeDaysPerYear);
if (Start <= End)
{
if (Day < Start)
{
return End - Start + 1;
}
if (Day > End)
{
return 0;
}
return End - Day + 1;
}
if (Day >= Start)
{
return (SafeDaysPerYear - Day + 1) + End;
}
if (Day <= End)
{
return End - Day + 1;
}
return 0;
}
FAgrarianCropSeasonAssessment AAgrarianGameState::AssessCropForActiveGrowingSeason(int32 CropMaturityDays, int32 PlantDayOfYear) const
{
FAgrarianCropSeasonAssessment Assessment;
Assessment.DaysAvailable = GetDaysRemainingInActiveGrowingSeason(PlantDayOfYear);
Assessment.SafetyBufferDays = FMath::Max(0, ActiveGrowingSeason.CropSafetyBufferDays);
Assessment.DaysRequired = FMath::Max(0, CropMaturityDays) + Assessment.SafetyBufferDays;
Assessment.GrowingZoneLabel = ActiveGrowingSeason.GrowingZoneLabel;
Assessment.bCanPlantToday = false;
if (CropMaturityDays <= 0)
{
Assessment.Fit = EAgrarianCropSeasonFit::Unknown;
Assessment.Reason = FText::FromString(TEXT("Crop maturity days must be greater than zero."));
return Assessment;
}
if (!IsDayInsideActiveGrowingSeason(PlantDayOfYear))
{
Assessment.Fit = EAgrarianCropSeasonFit::OutOfSeason;
Assessment.Reason = FText::FromString(TEXT("Planting day is outside the active tile growing season."));
return Assessment;
}
if (Assessment.DaysRequired > ActiveGrowingSeason.FrostFreeDays)
{
Assessment.Fit = EAgrarianCropSeasonFit::TooLong;
Assessment.Reason = FText::FromString(TEXT("Crop maturity is too long for this tile's frost-free growing window."));
return Assessment;
}
if (Assessment.DaysRequired > Assessment.DaysAvailable)
{
Assessment.Fit = EAgrarianCropSeasonFit::Marginal;
Assessment.Reason = FText::FromString(TEXT("Crop can grow in this zone, but this planting date is too late for a reliable harvest."));
return Assessment;
}
Assessment.Fit = EAgrarianCropSeasonFit::FitsSeason;
Assessment.bCanPlantToday = true;
Assessment.Reason = FText::FromString(TEXT("Crop maturity fits the active tile growing season."));
return Assessment;
}
void AAgrarianGameState::OnRep_Weather() void AAgrarianGameState::OnRep_Weather()
{ {
UpdateAmbientTemperature(); UpdateAmbientTemperature();
} }
void AAgrarianGameState::UpdateSolarTimes()
{
if (ActiveSolarTileId == NAME_None)
{
bHasActiveTileSolarData = false;
SunriseHourLocal = 6.0f;
SunsetHourLocal = 20.0f;
SolarNoonHourLocal = 13.0f;
DayLengthHours = 14.0f;
return;
}
const float ClampedLatitude = FMath::Clamp(ActiveTileLatitude, -89.8f, 89.8f);
const float ClampedLongitude = FMath::Clamp(ActiveTileLongitude, -180.0f, 180.0f);
const int32 ClampedDayOfYear = FMath::Clamp(ActiveDayOfYear, 1, 366);
// NOAA approximate sunrise/sunset model; good enough for MVP regional light timing.
const double Gamma = (2.0 * PI / 365.0) * (static_cast<double>(ClampedDayOfYear) - 1.0);
const double EquationOfTimeMinutes = 229.18 * (
0.000075
+ 0.001868 * FMath::Cos(Gamma)
- 0.032077 * FMath::Sin(Gamma)
- 0.014615 * FMath::Cos(2.0 * Gamma)
- 0.040849 * FMath::Sin(2.0 * Gamma));
const double SolarDeclinationRadians =
0.006918
- 0.399912 * FMath::Cos(Gamma)
+ 0.070257 * FMath::Sin(Gamma)
- 0.006758 * FMath::Cos(2.0 * Gamma)
+ 0.000907 * FMath::Sin(2.0 * Gamma)
- 0.002697 * FMath::Cos(3.0 * Gamma)
+ 0.00148 * FMath::Sin(3.0 * Gamma);
const double LatitudeRadians = FMath::DegreesToRadians(static_cast<double>(ClampedLatitude));
const double SolarZenithRadians = FMath::DegreesToRadians(90.833);
const double HourAngleArgument =
(FMath::Cos(SolarZenithRadians) / (FMath::Cos(LatitudeRadians) * FMath::Cos(SolarDeclinationRadians)))
- FMath::Tan(LatitudeRadians) * FMath::Tan(SolarDeclinationRadians);
if (HourAngleArgument <= -1.0 || HourAngleArgument >= 1.0)
{
bHasActiveTileSolarData = true;
SolarNoonHourLocal = FMath::Fmod(12.0f + ActiveTileUtcOffsetHours - (ClampedLongitude / 15.0f), 24.0f);
if (SolarNoonHourLocal < 0.0f)
{
SolarNoonHourLocal += 24.0f;
}
DayLengthHours = HourAngleArgument <= -1.0 ? 24.0f : 0.0f;
SunriseHourLocal = DayLengthHours >= 24.0f ? 0.0f : SolarNoonHourLocal;
SunsetHourLocal = DayLengthHours >= 24.0f ? 24.0f : SolarNoonHourLocal;
return;
}
const double HourAngleDegrees = FMath::RadiansToDegrees(FMath::Acos(HourAngleArgument));
const double SolarNoonMinutes = 720.0 - (4.0 * ClampedLongitude) - EquationOfTimeMinutes + (ActiveTileUtcOffsetHours * 60.0);
const double SunriseMinutes = SolarNoonMinutes - (HourAngleDegrees * 4.0);
const double SunsetMinutes = SolarNoonMinutes + (HourAngleDegrees * 4.0);
auto NormalizeHour = [](double Minutes)
{
double Hours = FMath::Fmod(Minutes / 60.0, 24.0);
if (Hours < 0.0)
{
Hours += 24.0;
}
return static_cast<float>(Hours);
};
bHasActiveTileSolarData = true;
SunriseHourLocal = NormalizeHour(SunriseMinutes);
SunsetHourLocal = NormalizeHour(SunsetMinutes);
SolarNoonHourLocal = NormalizeHour(SolarNoonMinutes);
DayLengthHours = static_cast<float>((SunsetMinutes - SunriseMinutes) / 60.0);
}
void AAgrarianGameState::UpdateAmbientTemperature() void AAgrarianGameState::UpdateAmbientTemperature()
{ {
const float DayWarmth = FMath::Sin((WorldHours / 24.0f) * 2.0f * PI - (PI * 0.5f)) * 8.0f; const float WarmestHour = bHasActiveTileSolarData ? FMath::Fmod(SolarNoonHourLocal + 3.0f, 24.0f) : 14.0f;
const float DayWarmth = FMath::Sin(((WorldHours - WarmestHour) / 24.0f) * 2.0f * PI + (PI * 0.5f)) * 8.0f;
float WeatherModifier = 0.0f; float WeatherModifier = 0.0f;
switch (Weather) switch (Weather)
+77
View File
@@ -15,6 +15,7 @@ class AAgrarianGameState : public AGameStateBase
public: public:
AAgrarianGameState(); AAgrarianGameState();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override; virtual void Tick(float DeltaSeconds) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
@@ -24,21 +25,97 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|World", meta = (ClampMin = "0.1")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|World", meta = (ClampMin = "0.1"))
float GameHoursPerRealMinute = 0.1f; float GameHoursPerRealMinute = 0.1f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Calendar", meta = (ClampMin = "1", ClampMax = "366"))
int32 DaysPerAgrarianYear = 366;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Weather, Category = "Agrarian|World") UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Weather, Category = "Agrarian|World")
EAgrarianWeatherType Weather = EAgrarianWeatherType::Clear; EAgrarianWeatherType Weather = EAgrarianWeatherType::Clear;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World") UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World")
float AmbientTemperatureC = 12.0f; float AmbientTemperatureC = 12.0f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
FName ActiveSolarTileId = TEXT("gz_us_ca_pacifica_utm10n_e544_n4160");
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
float ActiveTileLatitude = 37.5925f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
float ActiveTileLongitude = -122.4995f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
FString ActiveTileTimeZoneId = TEXT("America/Los_Angeles");
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
float ActiveTileUtcOffsetHours = -7.0f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar", meta = (ClampMin = "1", ClampMax = "366"))
int32 ActiveDayOfYear = 172;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Calendar", meta = (ClampMin = "1"))
int32 ActiveYear = 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Growing Season")
FAgrarianGrowingSeasonProfile ActiveGrowingSeason;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
bool bHasActiveTileSolarData = false;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
float SunriseHourLocal = 6.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
float SunsetHourLocal = 20.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
float SolarNoonHourLocal = 13.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
float DayLengthHours = 14.0f;
UFUNCTION(BlueprintCallable, Category = "Agrarian|World") UFUNCTION(BlueprintCallable, Category = "Agrarian|World")
bool IsNight() const; bool IsNight() const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|World") UFUNCTION(BlueprintCallable, Category = "Agrarian|World")
void SetWeather(EAgrarianWeatherType NewWeather); void SetWeather(EAgrarianWeatherType NewWeather);
UFUNCTION(BlueprintCallable, Category = "Agrarian|World|Tile Solar")
bool ConfigureActiveSolarTile(FName TileId, float Latitude, float Longitude, const FString& TimeZoneId, float UtcOffsetHours);
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
FAgrarianCalendarSnapshot GetCalendarSnapshot() const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
int32 GetAbsoluteAgrarianDay() const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
EAgrarianSeason GetSeasonForDay(int32 DayOfYear) const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
int32 GetDayOfSeason(int32 DayOfYear) const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
float ConvertAgrarianDaysToRealHours(float AgrarianDays) const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
float ConvertRealHoursToAgrarianDays(float RealHours) const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
float GetLongTaskProgress(int32 StartAbsoluteDay, float StartHourOfDay, float DurationAgrarianDays) const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Growing Season")
bool IsDayInsideActiveGrowingSeason(int32 DayOfYear) const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Growing Season")
int32 GetDaysRemainingInActiveGrowingSeason(int32 PlantDayOfYear) const;
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Growing Season")
FAgrarianCropSeasonAssessment AssessCropForActiveGrowingSeason(int32 CropMaturityDays, int32 PlantDayOfYear) const;
protected: protected:
UFUNCTION() UFUNCTION()
void OnRep_Weather(); void OnRep_Weather();
void UpdateSolarTimes();
void UpdateAmbientTemperature(); void UpdateAmbientTemperature();
}; };
@@ -1,10 +1,14 @@
// Copyright Pacificao. All Rights Reserved. // Copyright Pacificao. All Rights Reserved.
#include "AgrarianPersistenceSubsystem.h" #include "AgrarianPersistenceSubsystem.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianInventoryComponent.h"
#include "AgrarianPersistentActorComponent.h" #include "AgrarianPersistentActorComponent.h"
#include "AgrarianSaveGame.h" #include "AgrarianSaveGame.h"
#include "AgrarianSurvivalComponent.h"
#include "EngineUtils.h" #include "EngineUtils.h"
#include "Engine/World.h" #include "Engine/World.h"
#include "GameFramework/PlayerState.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
UAgrarianSaveGame* UAgrarianPersistenceSubsystem::CreateEmptySave() const UAgrarianSaveGame* UAgrarianPersistenceSubsystem::CreateEmptySave() const
@@ -116,6 +120,87 @@ int32 UAgrarianPersistenceSubsystem::RestoreWorldActors(const UAgrarianSaveGame*
return RestoredCount; return RestoredCount;
} }
int32 UAgrarianPersistenceSubsystem::CapturePlayers(UAgrarianSaveGame* SaveGame) const
{
if (!SaveGame)
{
return 0;
}
TArray<AAgrarianGameCharacter*> Players;
FindAgrarianPlayers(Players);
SaveGame->Players.Reset();
for (const AAgrarianGameCharacter* Character : Players)
{
const UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
if (!Character || !SurvivalComponent)
{
continue;
}
FAgrarianSavedPlayer SavedPlayer;
SavedPlayer.PlayerId = GetPlayerPersistenceId(Character);
SavedPlayer.Transform = Character->GetActorTransform();
SavedPlayer.Survival = SurvivalComponent->Survival;
SavedPlayer.CareHistory = SurvivalComponent->CareHistory;
if (const UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
{
SavedPlayer.Inventory = InventoryComponent->Items;
}
SaveGame->Players.Add(SavedPlayer);
}
return SaveGame->Players.Num();
}
int32 UAgrarianPersistenceSubsystem::RestorePlayers(const UAgrarianSaveGame* SaveGame) const
{
if (!SaveGame)
{
return 0;
}
TArray<AAgrarianGameCharacter*> Players;
FindAgrarianPlayers(Players);
int32 RestoredCount = 0;
for (AAgrarianGameCharacter* Character : Players)
{
UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
if (!Character || !SurvivalComponent)
{
continue;
}
const FString PlayerId = GetPlayerPersistenceId(Character);
const FAgrarianSavedPlayer* SavedPlayer = SaveGame->Players.FindByPredicate(
[&PlayerId](const FAgrarianSavedPlayer& Candidate)
{
return Candidate.PlayerId == PlayerId;
});
if (!SavedPlayer)
{
continue;
}
Character->SetActorTransform(SavedPlayer->Transform, false, nullptr, ETeleportType::TeleportPhysics);
SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);
if (UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
{
InventoryComponent->Items = SavedPlayer->Inventory;
}
RestoredCount++;
}
return RestoredCount;
}
bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const
{ {
UAgrarianSaveGame* SaveGame = LoadOrCreateSave(); UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
@@ -124,6 +209,7 @@ bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const
return false; return false;
} }
CapturePlayers(SaveGame);
CaptureWorldActors(SaveGame); CaptureWorldActors(SaveGame);
return WriteSave(SaveGame); return WriteSave(SaveGame);
} }
@@ -152,3 +238,42 @@ void UAgrarianPersistenceSubsystem::FindPersistentComponents(TArray<UAgrarianPer
} }
} }
} }
void UAgrarianPersistenceSubsystem::FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const
{
OutPlayers.Reset();
UWorld* World = GetWorld();
if (!World)
{
return;
}
for (TActorIterator<AAgrarianGameCharacter> ActorIt(World); ActorIt; ++ActorIt)
{
AAgrarianGameCharacter* Character = *ActorIt;
if (Character && !Character->IsPendingKillPending())
{
OutPlayers.Add(Character);
}
}
}
FString UAgrarianPersistenceSubsystem::GetPlayerPersistenceId(const AAgrarianGameCharacter* Character) const
{
if (!Character)
{
return TEXT("UnknownPlayer");
}
if (const APlayerState* PlayerState = Character->GetPlayerState())
{
const FString PlayerName = PlayerState->GetPlayerName();
if (!PlayerName.IsEmpty())
{
return PlayerName;
}
}
return Character->GetName();
}
@@ -8,6 +8,7 @@
class UAgrarianSaveGame; class UAgrarianSaveGame;
class UAgrarianPersistentActorComponent; class UAgrarianPersistentActorComponent;
class AAgrarianGameCharacter;
UCLASS() UCLASS()
class UAgrarianPersistenceSubsystem : public UGameInstanceSubsystem class UAgrarianPersistenceSubsystem : public UGameInstanceSubsystem
@@ -45,9 +46,17 @@ public:
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence") UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
int32 RestoreWorldActors(const UAgrarianSaveGame* SaveGame, bool bClearExistingActors = true) const; int32 RestoreWorldActors(const UAgrarianSaveGame* SaveGame, bool bClearExistingActors = true) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
int32 CapturePlayers(UAgrarianSaveGame* SaveGame) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
int32 RestorePlayers(const UAgrarianSaveGame* SaveGame) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence") UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
bool SaveCurrentWorld() const; bool SaveCurrentWorld() const;
protected: protected:
void FindPersistentComponents(TArray<UAgrarianPersistentActorComponent*>& OutComponents) const; void FindPersistentComponents(TArray<UAgrarianPersistentActorComponent*>& OutComponents) const;
void FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const;
FString GetPlayerPersistenceId(const AAgrarianGameCharacter* Character) const;
}; };
+3
View File
@@ -21,6 +21,9 @@ struct FAgrarianSavedPlayer
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save")
FAgrarianSurvivalSnapshot Survival; FAgrarianSurvivalSnapshot Survival;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save")
FAgrarianCareHistorySnapshot CareHistory;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save")
TArray<FAgrarianItemStack> Inventory; TArray<FAgrarianItemStack> Inventory;
}; };
@@ -15,6 +15,7 @@ void UAgrarianSurvivalComponent::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
ClampSurvival(); ClampSurvival();
ClampCareHistory();
BroadcastSurvivalChanged(); BroadcastSurvivalChanged();
} }
@@ -32,6 +33,26 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT
Survival.Thirst -= ThirstDecayPerMinute * Minutes; Survival.Thirst -= ThirstDecayPerMinute * Minutes;
Survival.Stamina += StaminaRecoveryPerSecond * DeltaTime; Survival.Stamina += StaminaRecoveryPerSecond * DeltaTime;
if (Survival.Stamina <= LowStaminaExhaustionThreshold)
{
Survival.Exhaustion += ExhaustionGainPerLowStaminaSecond * DeltaTime;
}
else if (Survival.Hunger > 10.0f && Survival.Thirst > 10.0f && Survival.BodyTemperature >= 35.0f)
{
Survival.Exhaustion -= ExhaustionRecoveryPerSecond * DeltaTime;
}
if (Survival.SicknessSeverity > 0.0f)
{
Survival.Exhaustion += (Survival.SicknessSeverity / 100.0f) * 0.08f * DeltaTime;
CareHistory.IllnessBurden += (Survival.SicknessSeverity / 100.0f) * 0.001f * DeltaTime;
if (Survival.Hunger > 20.0f && Survival.Thirst > 20.0f && Survival.BodyTemperature >= 35.0f)
{
Survival.SicknessSeverity -= SicknessRecoveryPerSecond * DeltaTime * FMath::Max(0.25f, CareHistory.TreatmentQuality);
}
}
if (const UWorld* World = GetWorld()) if (const UWorld* World = GetWorld())
{ {
if (const AAgrarianGameState* AgrarianGameState = World->GetGameState<AAgrarianGameState>()) if (const AAgrarianGameState* AgrarianGameState = World->GetGameState<AAgrarianGameState>())
@@ -56,7 +77,13 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT
Survival.Health -= ColdDamagePerMinute * Minutes; Survival.Health -= ColdDamagePerMinute * Minutes;
} }
if (Survival.SicknessSeverity >= 60.0f)
{
Survival.Health -= SicknessDamagePerMinute * (Survival.SicknessSeverity / 100.0f) * Minutes;
}
ClampSurvival(); ClampSurvival();
ClampCareHistory();
BroadcastSurvivalChanged(); BroadcastSurvivalChanged();
} }
@@ -64,6 +91,7 @@ void UAgrarianSurvivalComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProp
{ {
Super::GetLifetimeReplicatedProps(OutLifetimeProps); Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UAgrarianSurvivalComponent, Survival); DOREPLIFETIME(UAgrarianSurvivalComponent, Survival);
DOREPLIFETIME(UAgrarianSurvivalComponent, CareHistory);
} }
bool UAgrarianSurvivalComponent::IsAlive() const bool UAgrarianSurvivalComponent::IsAlive() const
@@ -126,8 +154,45 @@ void UAgrarianSurvivalComponent::AddInjury(float Severity)
if (GetOwner() && GetOwner()->HasAuthority()) if (GetOwner() && GetOwner()->HasAuthority())
{ {
Survival.InjurySeverity += FMath::Max(0.0f, Severity); Survival.InjurySeverity += FMath::Max(0.0f, Severity);
CareHistory.InjuryBurden += FMath::Max(0.0f, Severity) / 100.0f;
Survival.Health -= Severity * 5.0f; Survival.Health -= Severity * 5.0f;
ClampSurvival(); ClampSurvival();
ClampCareHistory();
BroadcastSurvivalChanged();
}
}
void UAgrarianSurvivalComponent::AddSickness(float Severity)
{
if (GetOwner() && GetOwner()->HasAuthority())
{
const float PositiveSeverity = FMath::Max(0.0f, Severity);
Survival.SicknessSeverity += PositiveSeverity;
CareHistory.IllnessBurden += PositiveSeverity / 100.0f;
ClampSurvival();
ClampCareHistory();
BroadcastSurvivalChanged();
}
}
void UAgrarianSurvivalComponent::ReduceSickness(float Amount)
{
if (GetOwner() && GetOwner()->HasAuthority())
{
Survival.SicknessSeverity -= FMath::Max(0.0f, Amount);
ClampSurvival();
BroadcastSurvivalChanged();
}
}
void UAgrarianSurvivalComponent::ApplySavedState(const FAgrarianSurvivalSnapshot& SavedSurvival, const FAgrarianCareHistorySnapshot& SavedCareHistory)
{
if (GetOwner() && GetOwner()->HasAuthority())
{
Survival = SavedSurvival;
CareHistory = SavedCareHistory;
ClampSurvival();
ClampCareHistory();
BroadcastSurvivalChanged(); BroadcastSurvivalChanged();
} }
} }
@@ -136,7 +201,29 @@ void UAgrarianSurvivalComponent::SpendStamina(float Amount)
{ {
if (GetOwner() && GetOwner()->HasAuthority()) if (GetOwner() && GetOwner()->HasAuthority())
{ {
Survival.Stamina -= FMath::Max(0.0f, Amount); const float PositiveAmount = FMath::Max(0.0f, Amount);
Survival.Stamina -= PositiveAmount;
Survival.Exhaustion += PositiveAmount * 0.05f;
ClampSurvival();
BroadcastSurvivalChanged();
}
}
void UAgrarianSurvivalComponent::AddExhaustion(float Amount)
{
if (GetOwner() && GetOwner()->HasAuthority())
{
Survival.Exhaustion += FMath::Max(0.0f, Amount);
ClampSurvival();
BroadcastSurvivalChanged();
}
}
void UAgrarianSurvivalComponent::ReduceExhaustion(float Amount)
{
if (GetOwner() && GetOwner()->HasAuthority())
{
Survival.Exhaustion -= FMath::Max(0.0f, Amount);
ClampSurvival(); ClampSurvival();
BroadcastSurvivalChanged(); BroadcastSurvivalChanged();
} }
@@ -147,14 +234,34 @@ void UAgrarianSurvivalComponent::OnRep_Survival()
BroadcastSurvivalChanged(); BroadcastSurvivalChanged();
} }
void UAgrarianSurvivalComponent::OnRep_CareHistory()
{
ClampCareHistory();
BroadcastSurvivalChanged();
}
void UAgrarianSurvivalComponent::ClampSurvival() void UAgrarianSurvivalComponent::ClampSurvival()
{ {
Survival.Health = FMath::Clamp(Survival.Health, 0.0f, 100.0f); Survival.Health = FMath::Clamp(Survival.Health, 0.0f, 100.0f);
Survival.Stamina = FMath::Clamp(Survival.Stamina, 0.0f, 100.0f); Survival.Stamina = FMath::Clamp(Survival.Stamina, 0.0f, 100.0f);
Survival.Exhaustion = FMath::Clamp(Survival.Exhaustion, 0.0f, 100.0f);
Survival.Hunger = FMath::Clamp(Survival.Hunger, 0.0f, 100.0f); Survival.Hunger = FMath::Clamp(Survival.Hunger, 0.0f, 100.0f);
Survival.Thirst = FMath::Clamp(Survival.Thirst, 0.0f, 100.0f); Survival.Thirst = FMath::Clamp(Survival.Thirst, 0.0f, 100.0f);
Survival.BodyTemperature = FMath::Clamp(Survival.BodyTemperature, 30.0f, 42.0f); Survival.BodyTemperature = FMath::Clamp(Survival.BodyTemperature, 30.0f, 42.0f);
Survival.InjurySeverity = FMath::Clamp(Survival.InjurySeverity, 0.0f, 100.0f); Survival.InjurySeverity = FMath::Clamp(Survival.InjurySeverity, 0.0f, 100.0f);
Survival.SicknessSeverity = FMath::Clamp(Survival.SicknessSeverity, 0.0f, 100.0f);
}
void UAgrarianSurvivalComponent::ClampCareHistory()
{
CareHistory.NutritionQuality = FMath::Clamp(CareHistory.NutritionQuality, 0.0f, 1.0f);
CareHistory.IllnessBurden = FMath::Clamp(CareHistory.IllnessBurden, 0.0f, 1.0f);
CareHistory.InjuryBurden = FMath::Clamp(CareHistory.InjuryBurden, 0.0f, 1.0f);
CareHistory.SleepQuality = FMath::Clamp(CareHistory.SleepQuality, 0.0f, 1.0f);
CareHistory.ShelterQuality = FMath::Clamp(CareHistory.ShelterQuality, 0.0f, 1.0f);
CareHistory.StressBurden = FMath::Clamp(CareHistory.StressBurden, 0.0f, 1.0f);
CareHistory.WorkloadBurden = FMath::Clamp(CareHistory.WorkloadBurden, 0.0f, 1.0f);
CareHistory.TreatmentQuality = FMath::Clamp(CareHistory.TreatmentQuality, 0.0f, 1.0f);
} }
void UAgrarianSurvivalComponent::BroadcastSurvivalChanged() void UAgrarianSurvivalComponent::BroadcastSurvivalChanged()
@@ -27,6 +27,9 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Survival, Category = "Agrarian|Survival") UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Survival, Category = "Agrarian|Survival")
FAgrarianSurvivalSnapshot Survival; FAgrarianSurvivalSnapshot Survival;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_CareHistory, Category = "Agrarian|Survival")
FAgrarianCareHistorySnapshot CareHistory;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float HungerDecayPerMinute = 0.55f; float HungerDecayPerMinute = 0.55f;
@@ -36,6 +39,15 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float StaminaRecoveryPerSecond = 14.0f; float StaminaRecoveryPerSecond = 14.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float ExhaustionGainPerLowStaminaSecond = 0.35f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float ExhaustionRecoveryPerSecond = 0.08f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0", ClampMax = "100"))
float LowStaminaExhaustionThreshold = 20.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float StarvationDamagePerMinute = 3.0f; float StarvationDamagePerMinute = 3.0f;
@@ -45,6 +57,12 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float ColdDamagePerMinute = 4.0f; float ColdDamagePerMinute = 4.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float SicknessDamagePerMinute = 1.5f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
float SicknessRecoveryPerSecond = 0.02f;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
bool IsAlive() const; bool IsAlive() const;
@@ -66,13 +84,32 @@ public:
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
void AddInjury(float Severity); void AddInjury(float Severity);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
void AddSickness(float Severity);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
void ReduceSickness(float Amount);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
void ApplySavedState(const FAgrarianSurvivalSnapshot& SavedSurvival, const FAgrarianCareHistorySnapshot& SavedCareHistory);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
void SpendStamina(float Amount); void SpendStamina(float Amount);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
void AddExhaustion(float Amount);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
void ReduceExhaustion(float Amount);
protected: protected:
UFUNCTION() UFUNCTION()
void OnRep_Survival(); void OnRep_Survival();
UFUNCTION()
void OnRep_CareHistory();
void ClampSurvival(); void ClampSurvival();
void ClampCareHistory();
void BroadcastSurvivalChanged(); void BroadcastSurvivalChanged();
}; };
+142
View File
@@ -14,6 +14,112 @@ enum class EAgrarianWeatherType : uint8
Storm UMETA(DisplayName = "Storm") Storm UMETA(DisplayName = "Storm")
}; };
UENUM(BlueprintType)
enum class EAgrarianSeason : uint8
{
Winter UMETA(DisplayName = "Winter"),
Spring UMETA(DisplayName = "Spring"),
Summer UMETA(DisplayName = "Summer"),
Autumn UMETA(DisplayName = "Autumn")
};
UENUM(BlueprintType)
enum class EAgrarianCropSeasonFit : uint8
{
Unknown UMETA(DisplayName = "Unknown"),
FitsSeason UMETA(DisplayName = "Fits Season"),
Marginal UMETA(DisplayName = "Marginal"),
TooLong UMETA(DisplayName = "Too Long"),
OutOfSeason UMETA(DisplayName = "Out Of Season")
};
USTRUCT(BlueprintType)
struct FAgrarianCalendarSnapshot
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
int32 DayOfYear = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
int32 Year = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
int32 AbsoluteDay = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
int32 DayOfSeason = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
EAgrarianSeason Season = EAgrarianSeason::Winter;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
float HourOfDay = 8.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
bool bInsideGrowingSeason = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
int32 GrowingSeasonDaysRemaining = 0;
};
USTRUCT(BlueprintType)
struct FAgrarianGrowingSeasonProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
FName TileId = NAME_None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
FString GrowingZoneLabel = TEXT("unknown");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "1", ClampMax = "366"))
int32 GrowingSeasonStartDay = 80;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "1", ClampMax = "366"))
int32 GrowingSeasonEndDay = 300;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "0", ClampMax = "366"))
int32 FrostFreeDays = 220;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
float MinAverageGrowingTempC = 7.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "0", ClampMax = "90"))
int32 CropSafetyBufferDays = 14;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
FString ClimateProfile = TEXT("unknown");
};
USTRUCT(BlueprintType)
struct FAgrarianCropSeasonAssessment
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
EAgrarianCropSeasonFit Fit = EAgrarianCropSeasonFit::Unknown;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
int32 DaysAvailable = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
int32 DaysRequired = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
int32 SafetyBufferDays = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
bool bCanPlantToday = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
FString GrowingZoneLabel = TEXT("unknown");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
FText Reason;
};
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FAgrarianItemStack struct FAgrarianItemStack
{ {
@@ -125,6 +231,9 @@ struct FAgrarianSurvivalSnapshot
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
float Stamina = 100.0f; float Stamina = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
float Exhaustion = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
float Hunger = 100.0f; float Hunger = 100.0f;
@@ -136,4 +245,37 @@ struct FAgrarianSurvivalSnapshot
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
float InjurySeverity = 0.0f; float InjurySeverity = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
float SicknessSeverity = 0.0f;
};
USTRUCT(BlueprintType)
struct FAgrarianCareHistorySnapshot
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float NutritionQuality = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float IllnessBurden = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float InjuryBurden = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float SleepQuality = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float ShelterQuality = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float StressBurden = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float WorkloadBurden = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
float TreatmentQuality = 1.0f;
}; };