Complete early roadmap foundation and calendar helpers
This commit is contained in:
@@ -63,7 +63,7 @@ call "%RUN_UAT%" BuildCookRun ^
|
||||
-pak ^
|
||||
-archive ^
|
||||
-archivedirectory="%ARCHIVE_DIR%" ^
|
||||
-map=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test+/Game/ThirdPerson/Lvl_ThirdPerson ^
|
||||
-map=/Game/Agrarian/Maps/L_GroundZeroTerrain_Test ^
|
||||
-prereqs ^
|
||||
-utf8output ^
|
||||
-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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -77,7 +77,7 @@ def main():
|
||||
map_key(context, toggle_action, "Gamepad_RightThumbstick")
|
||||
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.set_editor_property("ToggleCameraAction", toggle_action)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(character_bp)
|
||||
|
||||
@@ -75,7 +75,7 @@ def main():
|
||||
map_key(context, interact_action, "Gamepad_FaceButton_Left")
|
||||
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.set_editor_property("InteractAction", interact_action)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(character_bp)
|
||||
|
||||
@@ -9,7 +9,7 @@ def load(path):
|
||||
|
||||
|
||||
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())
|
||||
hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD")
|
||||
if not hud_class:
|
||||
|
||||
@@ -24,7 +24,7 @@ def load(path):
|
||||
|
||||
|
||||
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())
|
||||
|
||||
for property_name, value in MOVEMENT_DEFAULTS.items():
|
||||
|
||||
@@ -77,7 +77,7 @@ def main():
|
||||
map_key(context, sprint_action, "Gamepad_LeftThumbstick")
|
||||
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.set_editor_property("SprintAction", sprint_action)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(character_bp)
|
||||
|
||||
@@ -87,7 +87,7 @@ def main():
|
||||
map_key(context, prone_action, "Gamepad_LeftShoulder")
|
||||
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.set_editor_property("CrouchAction", crouch_action)
|
||||
character_cdo.set_editor_property("ProneAction", prone_action)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import unreal
|
||||
|
||||
|
||||
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson"
|
||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||
|
||||
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()
|
||||
@@ -23,7 +23,7 @@ def mapping_found(context, action, key_name):
|
||||
def main():
|
||||
action = load("/Game/Input/Actions/IA_ToggleCamera")
|
||||
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())
|
||||
|
||||
missing = []
|
||||
@@ -33,7 +33,7 @@ def main():
|
||||
|
||||
assigned_action = character_cdo.get_editor_property("ToggleCameraAction")
|
||||
if assigned_action != action:
|
||||
missing.append("BP_ThirdPersonCharacter ToggleCameraAction is not IA_ToggleCamera")
|
||||
missing.append("BP_AgrarianPlayerCharacter ToggleCameraAction is not IA_ToggleCamera")
|
||||
|
||||
if missing:
|
||||
raise RuntimeError("Camera toggle input verification failed: " + "; ".join(missing))
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -23,7 +23,7 @@ def mapping_found(context, action, key_name):
|
||||
def main():
|
||||
action = load("/Game/Input/Actions/IA_Interact")
|
||||
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())
|
||||
|
||||
missing = []
|
||||
@@ -33,7 +33,7 @@ def main():
|
||||
|
||||
assigned_action = character_cdo.get_editor_property("InteractAction")
|
||||
if assigned_action != action:
|
||||
missing.append("BP_ThirdPersonCharacter InteractAction is not IA_Interact")
|
||||
missing.append("BP_AgrarianPlayerCharacter InteractAction is not IA_Interact")
|
||||
|
||||
if missing:
|
||||
raise RuntimeError("Interact input verification failed: " + "; ".join(missing))
|
||||
|
||||
@@ -9,7 +9,7 @@ def load(path):
|
||||
|
||||
|
||||
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())
|
||||
expected_hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD")
|
||||
character_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianGameCharacter")
|
||||
@@ -20,7 +20,7 @@ def main():
|
||||
if not expected_hud_class:
|
||||
missing.append("could not load AgrarianDebugHUD 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:
|
||||
if not bool(hud_cdo.get_editor_property("bShowInteractionPrompt")):
|
||||
missing.append("AgrarianDebugHUD bShowInteractionPrompt is disabled")
|
||||
|
||||
@@ -25,7 +25,7 @@ def load(path):
|
||||
|
||||
|
||||
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())
|
||||
|
||||
mismatches = []
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import unreal
|
||||
|
||||
|
||||
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson"
|
||||
CHARACTER_CLASS_PATH = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"
|
||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||
CHARACTER_CLASS_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
|
||||
SHELTER_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveShelter"
|
||||
FRAME_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveFrame"
|
||||
WALL_PANEL_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveWallPanel"
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -23,7 +23,7 @@ def mapping_found(context, action, key_name):
|
||||
def main():
|
||||
action = load("/Game/Input/Actions/IA_Sprint")
|
||||
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())
|
||||
|
||||
missing = []
|
||||
@@ -33,7 +33,7 @@ def main():
|
||||
|
||||
assigned_action = character_cdo.get_editor_property("SprintAction")
|
||||
if assigned_action != action:
|
||||
missing.append("BP_ThirdPersonCharacter SprintAction is not IA_Sprint")
|
||||
missing.append("BP_AgrarianPlayerCharacter SprintAction is not IA_Sprint")
|
||||
|
||||
if missing:
|
||||
raise RuntimeError("Sprint input verification failed: " + "; ".join(missing))
|
||||
|
||||
@@ -31,7 +31,7 @@ def main():
|
||||
crouch_action = load("/Game/Input/Actions/IA_Crouch")
|
||||
prone_action = load("/Game/Input/Actions/IA_Prone")
|
||||
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())
|
||||
|
||||
missing = []
|
||||
@@ -45,9 +45,9 @@ def main():
|
||||
missing.append(f"missing mapping {action.get_name()} -> {key_name}")
|
||||
|
||||
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:
|
||||
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():
|
||||
actual = float(character_cdo.get_editor_property(property_name))
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
import unreal
|
||||
|
||||
|
||||
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson"
|
||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||
|
||||
EXPECTED_PLACEMENTS = {
|
||||
"AGR_WoodResourceNode_01": {
|
||||
|
||||
@@ -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()
|
||||
@@ -1,8 +1,8 @@
|
||||
import unreal
|
||||
|
||||
|
||||
MAP_PATH = "/Game/ThirdPerson/Lvl_ThirdPerson"
|
||||
CHARACTER_CLASS_PATH = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"
|
||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||
CHARACTER_CLASS_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
|
||||
RABBIT_LABEL = "AGR_RabbitWildlife_01"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user