133 lines
5.0 KiB
Python
133 lines
5.0 KiB
Python
import math
|
|
from pathlib import Path
|
|
|
|
import unreal
|
|
|
|
|
|
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
|
WATER_SOURCE_LABEL = "AGR_GZ_FreshWaterSource_01"
|
|
EXPECTED_WATER_LOCATION = unreal.Vector(-7200.0, 10400.0, 0.0)
|
|
MAX_WATER_LOCATION_DISTANCE_CM = 500.0
|
|
MAX_DRESSING_DISTANCE_CM = 3500.0
|
|
FRESH_WATER_MATERIAL = "/Game/Agrarian/Materials/M_AGR_GZ_FreshWater"
|
|
STONE_MATERIAL = "/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone"
|
|
REQUIRED_COMPONENTS = {
|
|
"Mesh",
|
|
"WaterSurfaceProxy",
|
|
"StoneBankProxy",
|
|
"CollectMarkerProxy",
|
|
}
|
|
REQUIRED_NEARBY_DRESSING = {
|
|
"AGR_GZ_EnvVar_Water_Surface_01",
|
|
"AGR_GZ_EnvVar_Water_Bank_01",
|
|
"AGR_GZ_EnvVar_Water_Reed_01",
|
|
"AGR_GZ_EnvVar_Water_Reed_02",
|
|
}
|
|
|
|
|
|
def get_actor_label(actor):
|
|
try:
|
|
return actor.get_actor_label()
|
|
except Exception:
|
|
return actor.get_name()
|
|
|
|
|
|
def material_path(material):
|
|
if not material:
|
|
return ""
|
|
return material.get_path_name().split(".", 1)[0]
|
|
|
|
|
|
def distance_2d(a, b):
|
|
return math.hypot(a.x - b.x, a.y - b.y)
|
|
|
|
|
|
def component_by_name(actor, component_name):
|
|
for component in actor.get_components_by_class(unreal.StaticMeshComponent):
|
|
if component.get_name() == component_name:
|
|
return component
|
|
return None
|
|
|
|
|
|
def main():
|
|
if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
|
|
raise RuntimeError(f"Could not load map: {MAP_PATH}")
|
|
|
|
actors = unreal.EditorLevelLibrary.get_all_level_actors()
|
|
failures = []
|
|
|
|
water_sources = [actor for actor in actors if get_actor_label(actor) == WATER_SOURCE_LABEL]
|
|
if len(water_sources) != 1:
|
|
raise RuntimeError(f"Expected exactly one {WATER_SOURCE_LABEL}, found {len(water_sources)}")
|
|
|
|
water_source = water_sources[0]
|
|
if not isinstance(water_source, unreal.AgrarianWaterSource):
|
|
failures.append(f"{WATER_SOURCE_LABEL} is not an AgrarianWaterSource")
|
|
|
|
components = {
|
|
component_name: component_by_name(water_source, component_name)
|
|
for component_name in REQUIRED_COMPONENTS
|
|
}
|
|
for component_name, component in components.items():
|
|
if component is None:
|
|
failures.append(f"{WATER_SOURCE_LABEL} missing {component_name}")
|
|
|
|
water_surface = components.get("WaterSurfaceProxy")
|
|
if water_surface:
|
|
if material_path(water_surface.get_material(0)) != FRESH_WATER_MATERIAL:
|
|
failures.append("WaterSurfaceProxy does not use the freshwater material")
|
|
scale = water_surface.get_editor_property("relative_scale3d")
|
|
if scale.x < 2.0 or scale.y < 1.3:
|
|
failures.append(f"WaterSurfaceProxy scale is too small: {scale}")
|
|
|
|
collect_marker = components.get("CollectMarkerProxy")
|
|
if collect_marker and material_path(collect_marker.get_material(0)) != FRESH_WATER_MATERIAL:
|
|
failures.append("CollectMarkerProxy does not use the freshwater material")
|
|
|
|
for component_name in ("Mesh", "StoneBankProxy"):
|
|
component = components.get(component_name)
|
|
if component and material_path(component.get_material(0)) != STONE_MATERIAL:
|
|
failures.append(f"{component_name} does not use the sandstone material")
|
|
|
|
water_location = water_source.get_actor_location()
|
|
if distance_2d(water_location, EXPECTED_WATER_LOCATION) > MAX_WATER_LOCATION_DISTANCE_CM:
|
|
failures.append(f"{WATER_SOURCE_LABEL} moved too far from drainage-candidate placement: {water_location}")
|
|
|
|
nearby_labels = {}
|
|
for actor in actors:
|
|
label = get_actor_label(actor)
|
|
if label in REQUIRED_NEARBY_DRESSING:
|
|
nearby_labels[label] = actor
|
|
|
|
missing_dressing = REQUIRED_NEARBY_DRESSING.difference(nearby_labels)
|
|
if missing_dressing:
|
|
failures.append("missing water dressing actors: " + ", ".join(sorted(missing_dressing)))
|
|
|
|
for label, actor in nearby_labels.items():
|
|
if distance_2d(water_location, actor.get_actor_location()) > MAX_DRESSING_DISTANCE_CM:
|
|
failures.append(f"{label} is too far from the freshwater source")
|
|
|
|
project_root = Path(unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()))
|
|
docs = project_root / "Docs" / "Terrain" / "GroundZeroWaterSourceVisualPass.md"
|
|
roadmap = project_root / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
|
for path, snippets in {
|
|
docs: ["Ground Zero Water Source Visual Pass", "WaterSurfaceProxy", "stone bank/edge treatment"],
|
|
roadmap: ["[x] Add a real water-source visual pass", "surface material, edge treatment, scale, and placement"],
|
|
}.items():
|
|
text = path.read_text(encoding="utf-8")
|
|
for snippet in snippets:
|
|
if snippet not in text:
|
|
failures.append(f"{path} missing {snippet!r}")
|
|
|
|
if failures:
|
|
raise RuntimeError("Ground Zero water-source visual verification failed: " + "; ".join(failures))
|
|
|
|
unreal.log(
|
|
"Ground Zero water-source visual verification complete: "
|
|
f"{WATER_SOURCE_LABEL}, {len(REQUIRED_COMPONENTS)} native visual components, "
|
|
f"{len(REQUIRED_NEARBY_DRESSING)} nearby dressing actors."
|
|
)
|
|
|
|
|
|
main()
|