import math import random import struct from pathlib import Path import unreal MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test" PROJECT_ROOT = Path(unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir())) TILE_ID = "gz_us_ca_pacifica_utm10n_e544_n4160" HEIGHTMAP_PATH = PROJECT_ROOT / "Data" / "Terrain" / "Unreal" / TILE_ID / f"{TILE_ID}_unreal_1009.r16" LANDSCAPE_SIZE = 1009 XY_SCALE_CM = 100000.0 / (LANDSCAPE_SIZE - 1) Z_SCALE_CM = 100.0 LANDSCAPE_MIN_XY = -50000.0 FOLIAGE_LABEL = "AGR_GroundZeroFoliage_FirstPass" FOLIAGE_RANDOM_SEED = 4160544 PLACEHOLDER_MESH_FOLDER = "/Game/Agrarian/Environment/PlaceholderMeshes" VEGETATION_MESH_FOLDER = "/Game/Agrarian/Environment/Vegetation" VEGETATION_SOURCE_FOLDER = PROJECT_ROOT / "Saved" / "CodexGenerated" / "Vegetation" PLACEHOLDER_MESH_SOURCES = { "SM_AGR_Placeholder_Cube": "/Game/LevelPrototyping/Meshes/SM_Cube", "SM_AGR_Placeholder_ChamferCube": "/Game/LevelPrototyping/Meshes/SM_ChamferCube", "SM_AGR_Placeholder_Cylinder": "/Game/LevelPrototyping/Meshes/SM_Cylinder", "SM_AGR_Placeholder_QuarterCylinder": "/Game/LevelPrototyping/Meshes/SM_QuarterCylinder", "SM_AGR_Placeholder_Plane": "/Game/LevelPrototyping/Meshes/SM_Plane", } PLACEHOLDER_MESHES = { name: f"{PLACEHOLDER_MESH_FOLDER}/{name}" for name in PLACEHOLDER_MESH_SOURCES } FOLIAGE_MESHES = { "tree": f"{VEGETATION_MESH_FOLDER}/SM_AGR_GZ_CoastalOak_Proxy", "shrub": f"{VEGETATION_MESH_FOLDER}/SM_AGR_GZ_CoyoteBrush_Proxy", "grass": f"{VEGETATION_MESH_FOLDER}/SM_AGR_GZ_DryGrassClump_Proxy", } VARIATION_MESHES = { "cube": PLACEHOLDER_MESHES["SM_AGR_Placeholder_Cube"], "chamfer_cube": PLACEHOLDER_MESHES["SM_AGR_Placeholder_ChamferCube"], "cylinder": PLACEHOLDER_MESHES["SM_AGR_Placeholder_Cylinder"], "quarter_cylinder": PLACEHOLDER_MESHES["SM_AGR_Placeholder_QuarterCylinder"], "plane": PLACEHOLDER_MESHES["SM_AGR_Placeholder_Plane"], "coastal_oak": FOLIAGE_MESHES["tree"], "coyote_brush": FOLIAGE_MESHES["shrub"], "dry_grass_clump": FOLIAGE_MESHES["grass"], } MATERIAL_FOLDER = "/Game/Agrarian/Materials" ENVIRONMENT_MATERIALS = { "terrain": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Terrain_CoastalScrub", "color": unreal.LinearColor(0.16, 0.23, 0.12, 1.0), "dry_soil_color": unreal.LinearColor(0.28, 0.24, 0.16, 1.0), "scrub_green_color": unreal.LinearColor(0.12, 0.22, 0.10, 1.0), "sandy_path_color": unreal.LinearColor(0.42, 0.36, 0.23, 1.0), "noise_scale": 42.0, "roughness": 0.92, }, "tree": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Tree_CoastalOak", "color": unreal.LinearColor(0.07, 0.18, 0.06, 1.0), "variation_color": unreal.LinearColor(0.14, 0.24, 0.09, 1.0), "roughness": 0.88, "used_with_instanced_static_meshes": True, }, "shrub": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Shrub_CoyoteBrush", "color": unreal.LinearColor(0.15, 0.28, 0.10, 1.0), "variation_color": unreal.LinearColor(0.24, 0.34, 0.15, 1.0), "roughness": 0.9, "used_with_instanced_static_meshes": True, "two_sided": True, }, "grass": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Grass_DryCoastal", "color": unreal.LinearColor(0.32, 0.34, 0.13, 1.0), "variation_color": unreal.LinearColor(0.52, 0.45, 0.22, 1.0), "roughness": 0.95, "used_with_instanced_static_meshes": True, "two_sided": True, }, "wood_resource": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Wood_Resource", "color": unreal.LinearColor(0.26, 0.16, 0.08, 1.0), "roughness": 0.86, }, "fiber_resource": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Fiber_Resource", "color": unreal.LinearColor(0.55, 0.49, 0.28, 1.0), "roughness": 0.93, }, "edible_plant_resource": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_EdiblePlant_Resource", "color": unreal.LinearColor(0.22, 0.46, 0.18, 1.0), "roughness": 0.9, }, "stone_resource": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Stone_Sandstone", "color": unreal.LinearColor(0.43, 0.40, 0.35, 1.0), "roughness": 0.97, }, "fresh_water": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_FreshWater", "color": unreal.LinearColor(0.02, 0.16, 0.30, 1.0), "roughness": 0.35, }, } RESOURCE_MATERIAL_BY_LABEL_PREFIX = { "AGR_GZ_Wood": "wood_resource", "AGR_GZ_Fiber": "fiber_resource", "AGR_GZ_EdiblePlant": "edible_plant_resource", "AGR_GZ_Stone": "stone_resource", "AGR_DemoWoodResource": "wood_resource", "AGR_DemoFiberResource": "fiber_resource", "AGR_GZ_FreshWaterSource": "fresh_water", } SAFE_SPAWN_CONFIG = { "player_start_label": "AGR_DemoPlayerStart", "safe_z_offset": 220.0, "min_elevation_m": 2.0, "max_slope_degrees": 8.0, "slope_sample_distance_cm": 500.0, "min_water_distance_cm": 10000.0, "min_resource_distance_cm": 5000.0, "fallback_location_xy": unreal.Vector(-22000.0, -3500.0, 0.0), "candidate_locations_xy": [ unreal.Vector(-22000.0, -3500.0, 0.0), unreal.Vector(-25000.0, -4000.0, 0.0), unreal.Vector(-20500.0, -10500.0, 0.0), unreal.Vector(-28000.0, -9000.0, 0.0), unreal.Vector(-30000.0, -12000.0, 0.0), unreal.Vector(-35000.0, 0.0, 0.0), ], } MAP_BOUNDARY_CONFIG = { "label": "AGR_GroundZeroMapBoundary", "boundary_id": "ground_zero_mvp_tile", "location": unreal.Vector(0.0, 0.0, 10000.0), "extent": unreal.Vector(50000.0, 50000.0, 25000.0), "padding_cm": 250.0, "warning_distance_cm": 3000.0, } DEMO_ACTORS = [ { "label": "AGR_DemoPlayerStart", "class": unreal.PlayerStart, "location_xy": unreal.Vector(-18500.0, -6400.0, 0.0), "z_offset": 220.0, "rotation": unreal.Rotator(0.0, 72.0, 0.0), }, { "label": "AGR_DemoWoodResource_01", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_WoodResourceNode", "location_xy": unreal.Vector(-16800.0, -5400.0, 0.0), "z_offset": 80.0, "rotation": unreal.Rotator(0.0, 25.0, 0.0), }, { "label": "AGR_DemoFiberResource_01", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_FiberResourceNode", "location_xy": unreal.Vector(-15600.0, -7850.0, 0.0), "z_offset": 80.0, "rotation": unreal.Rotator(0.0, -15.0, 0.0), }, { "label": "AGR_DemoCampfire_01", "class_path": "/Game/Agrarian/Blueprints/Structures/BP_Campfire", "location_xy": unreal.Vector(-13200.0, -6350.0, 0.0), "z_offset": 90.0, "rotation": unreal.Rotator(0.0, 0.0, 0.0), }, { "label": "AGR_DemoPrimitiveShelter_01", "class_path": "/Game/Agrarian/Blueprints/Structures/BP_PrimitiveShelter", "location_xy": unreal.Vector(-11500.0, -7750.0, 0.0), "z_offset": 120.0, "rotation": unreal.Rotator(0.0, -30.0, 0.0), }, { "label": "AGR_DemoRabbitWildlife_01", "class_path": "/Game/Agrarian/Blueprints/Wildlife/BP_RabbitWildlife", "location_xy": unreal.Vector(-19800.0, -8550.0, 0.0), "z_offset": 120.0, "rotation": unreal.Rotator(0.0, 135.0, 0.0), }, { "label": "AGR_DemoSkyLightingController", "class": unreal.AgrarianSkyLightingController, "location_xy": unreal.Vector(-18000.0, -7000.0, 0.0), "fixed_z": 12000.0, "rotation": unreal.Rotator(-42.0, -35.0, 0.0), }, { "label": "AGR_DemoWeatherAudioController", "class": unreal.AgrarianWeatherAudioController, "location_xy": unreal.Vector(-18000.0, -7000.0, 0.0), "fixed_z": 11800.0, "rotation": unreal.Rotator(0.0, 0.0, 0.0), }, { "label": "AGR_DemoNoticeActor", "class": unreal.AgrarianDemoNoticeActor, "location_xy": unreal.Vector(-18500.0, -6400.0, 0.0), "fixed_z": 1600.0, "rotation": unreal.Rotator(0.0, 0.0, 0.0), }, ] LEGACY_DEMO_LIGHTING_LABELS = { "AGR_DemoSun", "AGR_DemoSkyLight", "AGR_DemoFog", } BIOME_RESOURCE_ACTORS = [ { "label": "AGR_GZ_Wood_CoastalScrub_01", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_WoodResourceNode", "location_xy": unreal.Vector(-28600.0, 7400.0, 0.0), "z_offset": 70.0, "rotation": unreal.Rotator(0.0, 18.0, 0.0), }, { "label": "AGR_GZ_Wood_CoastalScrub_02", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_WoodResourceNode", "location_xy": unreal.Vector(-5400.0, 21400.0, 0.0), "z_offset": 70.0, "rotation": unreal.Rotator(0.0, -42.0, 0.0), }, { "label": "AGR_GZ_Wood_Hillside_03", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_WoodResourceNode", "location_xy": unreal.Vector(18400.0, 32200.0, 0.0), "z_offset": 70.0, "rotation": unreal.Rotator(0.0, 86.0, 0.0), }, { "label": "AGR_GZ_Fiber_Grassland_01", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_FiberResourceNode", "location_xy": unreal.Vector(-33400.0, -16200.0, 0.0), "z_offset": 65.0, "rotation": unreal.Rotator(0.0, 6.0, 0.0), }, { "label": "AGR_GZ_Fiber_Grassland_02", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_FiberResourceNode", "location_xy": unreal.Vector(8200.0, -18200.0, 0.0), "z_offset": 65.0, "rotation": unreal.Rotator(0.0, -28.0, 0.0), }, { "label": "AGR_GZ_Fiber_Scrub_03", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_FiberResourceNode", "location_xy": unreal.Vector(29200.0, -4200.0, 0.0), "z_offset": 65.0, "rotation": unreal.Rotator(0.0, 54.0, 0.0), }, { "label": "AGR_GZ_Fiber_DrainageCandidate_04", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_FiberResourceNode", "location_xy": unreal.Vector(-6200.0, 9200.0, 0.0), "z_offset": 65.0, "rotation": unreal.Rotator(0.0, 114.0, 0.0), }, { "label": "AGR_GZ_EdiblePlant_CoastalScrub_01", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_EdiblePlantResourceNode", "location_xy": unreal.Vector(-31400.0, -7600.0, 0.0), "z_offset": 58.0, "rotation": unreal.Rotator(0.0, 21.0, 0.0), }, { "label": "AGR_GZ_EdiblePlant_Grassland_02", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_EdiblePlantResourceNode", "location_xy": unreal.Vector(6200.0, -26800.0, 0.0), "z_offset": 58.0, "rotation": unreal.Rotator(0.0, -48.0, 0.0), }, { "label": "AGR_GZ_EdiblePlant_DrainageCandidate_03", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_EdiblePlantResourceNode", "location_xy": unreal.Vector(-9400.0, 12800.0, 0.0), "z_offset": 58.0, "rotation": unreal.Rotator(0.0, 96.0, 0.0), }, { "label": "AGR_GZ_Stone_Slope_01", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_StoneResourceNode", "location_xy": unreal.Vector(22600.0, 17600.0, 0.0), "z_offset": 45.0, "rotation": unreal.Rotator(0.0, 12.0, 0.0), }, { "label": "AGR_GZ_Stone_Slope_02", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_StoneResourceNode", "location_xy": unreal.Vector(37600.0, 28600.0, 0.0), "z_offset": 45.0, "rotation": unreal.Rotator(0.0, -36.0, 0.0), }, { "label": "AGR_GZ_Stone_ExposedTerrain_03", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_StoneResourceNode", "location_xy": unreal.Vector(11800.0, 38200.0, 0.0), "z_offset": 45.0, "rotation": unreal.Rotator(0.0, 71.0, 0.0), }, { "label": "AGR_GZ_Stone_ValleyEdge_04", "class_path": "/Game/Agrarian/Blueprints/Resources/BP_StoneResourceNode", "location_xy": unreal.Vector(-24800.0, 28600.0, 0.0), "z_offset": 45.0, "rotation": unreal.Rotator(0.0, 133.0, 0.0), }, ] WATER_SOURCE_ACTORS = [ { "label": "AGR_GZ_FreshWaterSource_01", "class_path": "/Game/Agrarian/Blueprints/World/BP_FreshWaterSource", "location_xy": unreal.Vector(-7200.0, 10400.0, 0.0), "z_offset": 36.0, "rotation": unreal.Rotator(0.0, 0.0, 0.0), }, ] WEATHER_EXPOSURE_ZONES = [ { "label": "AGR_GZ_WeatherExposure_Ridge_01", "zone_id": "ground_zero_ridge_exposed", "location_xy": unreal.Vector(36000.0, 29200.0, 0.0), "z_offset": 450.0, "extent": unreal.Vector(11500.0, 9200.0, 2200.0), "exposure_multiplier": 1.45, "temperature_offset_c": -1.8, }, { "label": "AGR_GZ_WeatherExposure_CoastalWind_01", "zone_id": "ground_zero_coastal_wind", "location_xy": unreal.Vector(-31500.0, -14600.0, 0.0), "z_offset": 350.0, "extent": unreal.Vector(10500.0, 7600.0, 1600.0), "exposure_multiplier": 1.25, "temperature_offset_c": -0.9, }, { "label": "AGR_GZ_WeatherExposure_DrainageCool_01", "zone_id": "ground_zero_drainage_cool", "location_xy": unreal.Vector(-7200.0, 10400.0, 0.0), "z_offset": 260.0, "extent": unreal.Vector(7200.0, 5400.0, 1100.0), "exposure_multiplier": 1.1, "temperature_offset_c": -1.2, }, ] ENVIRONMENT_VARIATION_ACTORS = [ { "label": "AGR_GZ_EnvVar_Tree_Canopy_01", "mesh_key": "coastal_oak", "material_key": "tree", "location_xy": unreal.Vector(-27500.0, 6900.0, 0.0), "z_offset": 390.0, "scale": unreal.Vector(2.5, 2.0, 1.6), "rotation": unreal.Rotator(0.0, 32.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Tree_Trunk_01", "mesh_key": "cylinder", "material_key": "wood_resource", "location_xy": unreal.Vector(-27500.0, 6900.0, 0.0), "z_offset": 170.0, "scale": unreal.Vector(0.35, 0.35, 3.6), "rotation": unreal.Rotator(0.0, 32.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Tree_Canopy_02", "mesh_key": "coastal_oak", "material_key": "tree", "location_xy": unreal.Vector(17600.0, 31800.0, 0.0), "z_offset": 430.0, "scale": unreal.Vector(2.0, 2.8, 1.45), "rotation": unreal.Rotator(0.0, -18.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Tree_Trunk_02", "mesh_key": "cylinder", "material_key": "wood_resource", "location_xy": unreal.Vector(17600.0, 31800.0, 0.0), "z_offset": 190.0, "scale": unreal.Vector(0.3, 0.3, 4.1), "rotation": unreal.Rotator(0.0, -18.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Bush_Rounded_01", "mesh_key": "coyote_brush", "material_key": "shrub", "location_xy": unreal.Vector(-33400.0, -15200.0, 0.0), "z_offset": 70.0, "scale": unreal.Vector(1.9, 1.4, 0.7), "rotation": unreal.Rotator(0.0, 11.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Bush_Rounded_02", "mesh_key": "coyote_brush", "material_key": "shrub", "location_xy": unreal.Vector(30400.0, -3900.0, 0.0), "z_offset": 75.0, "scale": unreal.Vector(1.45, 2.1, 0.85), "rotation": unreal.Rotator(0.0, 73.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Grass_Mat_01", "mesh_key": "plane", "material_key": "grass", "location_xy": unreal.Vector(8200.0, -17100.0, 0.0), "z_offset": 14.0, "scale": unreal.Vector(3.6, 2.2, 1.0), "rotation": unreal.Rotator(0.0, -34.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Grass_Mat_02", "mesh_key": "plane", "material_key": "grass", "location_xy": unreal.Vector(-6200.0, 8300.0, 0.0), "z_offset": 14.0, "scale": unreal.Vector(2.7, 4.0, 1.0), "rotation": unreal.Rotator(0.0, 41.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Rock_Slab_01", "mesh_key": "chamfer_cube", "material_key": "stone_resource", "location_xy": unreal.Vector(22900.0, 18300.0, 0.0), "z_offset": 45.0, "scale": unreal.Vector(1.8, 1.05, 0.45), "rotation": unreal.Rotator(0.0, 24.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Rock_Slab_02", "mesh_key": "quarter_cylinder", "material_key": "stone_resource", "location_xy": unreal.Vector(38100.0, 29300.0, 0.0), "z_offset": 55.0, "scale": unreal.Vector(1.2, 1.8, 0.75), "rotation": unreal.Rotator(0.0, -47.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Water_Surface_01", "mesh_key": "plane", "material_key": "fresh_water", "location_xy": unreal.Vector(-7200.0, 10400.0, 0.0), "z_offset": 18.0, "scale": unreal.Vector(2.6, 1.6, 1.0), "rotation": unreal.Rotator(0.0, -15.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Tree_Canopy_03", "mesh_key": "chamfer_cube", "material_key": "tree", "location_xy": unreal.Vector(-8200.0, 12600.0, 0.0), "z_offset": 360.0, "scale": unreal.Vector(1.85, 2.25, 1.35), "rotation": unreal.Rotator(0.0, 61.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Tree_Trunk_03", "mesh_key": "cylinder", "material_key": "wood_resource", "location_xy": unreal.Vector(-8200.0, 12600.0, 0.0), "z_offset": 155.0, "scale": unreal.Vector(0.24, 0.24, 3.25), "rotation": unreal.Rotator(0.0, 61.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Brush_Coyote_01", "mesh_key": "chamfer_cube", "material_key": "shrub", "location_xy": unreal.Vector(-12600.0, -5100.0, 0.0), "z_offset": 72.0, "scale": unreal.Vector(1.7, 1.15, 0.62), "rotation": unreal.Rotator(0.0, 7.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Brush_Coyote_02", "mesh_key": "chamfer_cube", "material_key": "shrub", "location_xy": unreal.Vector(-10400.0, -8500.0, 0.0), "z_offset": 74.0, "scale": unreal.Vector(1.2, 1.85, 0.7), "rotation": unreal.Rotator(0.0, -38.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Brush_Coyote_03", "mesh_key": "chamfer_cube", "material_key": "shrub", "location_xy": unreal.Vector(-15800.0, -9800.0, 0.0), "z_offset": 78.0, "scale": unreal.Vector(1.55, 1.35, 0.58), "rotation": unreal.Rotator(0.0, 47.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Grass_Mat_03", "mesh_key": "plane", "material_key": "grass", "location_xy": unreal.Vector(-14600.0, -4200.0, 0.0), "z_offset": 14.0, "scale": unreal.Vector(3.2, 1.8, 1.0), "rotation": unreal.Rotator(0.0, 16.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Grass_Mat_04", "mesh_key": "plane", "material_key": "grass", "location_xy": unreal.Vector(-11800.0, -10600.0, 0.0), "z_offset": 14.0, "scale": unreal.Vector(2.1, 3.1, 1.0), "rotation": unreal.Rotator(0.0, -27.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Rock_Slab_03", "mesh_key": "quarter_cylinder", "material_key": "stone_resource", "location_xy": unreal.Vector(-9200.0, -7600.0, 0.0), "z_offset": 48.0, "scale": unreal.Vector(0.95, 1.45, 0.55), "rotation": unreal.Rotator(0.0, 34.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Rock_Slab_04", "mesh_key": "chamfer_cube", "material_key": "stone_resource", "location_xy": unreal.Vector(-18200.0, -5000.0, 0.0), "z_offset": 42.0, "scale": unreal.Vector(1.05, 0.72, 0.32), "rotation": unreal.Rotator(0.0, -64.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Water_Bank_01", "mesh_key": "quarter_cylinder", "material_key": "stone_resource", "location_xy": unreal.Vector(-6150.0, 9700.0, 0.0), "z_offset": 42.0, "scale": unreal.Vector(1.4, 0.75, 0.45), "rotation": unreal.Rotator(0.0, 22.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Water_Reed_01", "mesh_key": "cylinder", "material_key": "grass", "location_xy": unreal.Vector(-8050.0, 9600.0, 0.0), "z_offset": 74.0, "scale": unreal.Vector(0.08, 0.08, 1.35), "rotation": unreal.Rotator(0.0, 11.0, 0.0), }, { "label": "AGR_GZ_EnvVar_Water_Reed_02", "mesh_key": "cylinder", "material_key": "grass", "location_xy": unreal.Vector(-6600.0, 11700.0, 0.0), "z_offset": 68.0, "scale": unreal.Vector(0.07, 0.07, 1.15), "rotation": unreal.Rotator(0.0, -19.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_OakCanopy_01", "mesh_key": "chamfer_cube", "material_key": "tree", "location_xy": unreal.Vector(-26200.0, -7200.0, 0.0), "z_offset": 410.0, "scale": unreal.Vector(2.2, 2.6, 1.55), "rotation": unreal.Rotator(0.0, 24.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_OakTrunk_01", "mesh_key": "cylinder", "material_key": "wood_resource", "location_xy": unreal.Vector(-26200.0, -7200.0, 0.0), "z_offset": 180.0, "scale": unreal.Vector(0.28, 0.28, 3.7), "rotation": unreal.Rotator(0.0, 24.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_Brush_01", "mesh_key": "chamfer_cube", "material_key": "shrub", "location_xy": unreal.Vector(-19100.0, -8350.0, 0.0), "z_offset": 72.0, "scale": unreal.Vector(1.65, 1.25, 0.68), "rotation": unreal.Rotator(0.0, -14.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_Brush_02", "mesh_key": "chamfer_cube", "material_key": "shrub", "location_xy": unreal.Vector(-23900.0, 1300.0, 0.0), "z_offset": 76.0, "scale": unreal.Vector(1.25, 1.95, 0.72), "rotation": unreal.Rotator(0.0, 39.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_GrassMat_01", "mesh_key": "plane", "material_key": "grass", "location_xy": unreal.Vector(-20500.0, -6400.0, 0.0), "z_offset": 15.0, "scale": unreal.Vector(3.8, 2.4, 1.0), "rotation": unreal.Rotator(0.0, 18.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_GrassMat_02", "mesh_key": "plane", "material_key": "grass", "location_xy": unreal.Vector(-24200.0, -1800.0, 0.0), "z_offset": 15.0, "scale": unreal.Vector(2.9, 3.7, 1.0), "rotation": unreal.Rotator(0.0, -31.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_Rock_01", "mesh_key": "quarter_cylinder", "material_key": "stone_resource", "location_xy": unreal.Vector(-18400.0, -2400.0, 0.0), "z_offset": 45.0, "scale": unreal.Vector(1.15, 1.55, 0.52), "rotation": unreal.Rotator(0.0, 66.0, 0.0), }, { "label": "AGR_GZ_EnvVar_FirstLook_Rock_02", "mesh_key": "chamfer_cube", "material_key": "stone_resource", "location_xy": unreal.Vector(-27900.0, -3600.0, 0.0), "z_offset": 42.0, "scale": unreal.Vector(0.98, 0.82, 0.36), "rotation": unreal.Rotator(0.0, -52.0, 0.0), }, ] RUIN_PLACEHOLDER_ACTORS = [ { "label": "AGR_GZ_Landmark_Ruin_Foundation_01", "mesh_key": "chamfer_cube", "material_key": "stone_resource", "location_xy": unreal.Vector(6400.0, 24700.0, 0.0), "z_offset": 38.0, "scale": unreal.Vector(4.8, 3.2, 0.22), "rotation": unreal.Rotator(0.0, -22.0, 0.0), }, { "label": "AGR_GZ_Landmark_Ruin_Wall_01", "mesh_key": "chamfer_cube", "material_key": "stone_resource", "location_xy": unreal.Vector(5750.0, 25350.0, 0.0), "z_offset": 165.0, "scale": unreal.Vector(0.42, 2.35, 1.65), "rotation": unreal.Rotator(0.0, -22.0, 0.0), }, { "label": "AGR_GZ_Landmark_Ruin_Wall_02", "mesh_key": "chamfer_cube", "material_key": "stone_resource", "location_xy": unreal.Vector(7250.0, 24250.0, 0.0), "z_offset": 118.0, "scale": unreal.Vector(0.36, 1.7, 1.05), "rotation": unreal.Rotator(0.0, 68.0, 0.0), }, { "label": "AGR_GZ_Landmark_Ruin_Cairn_01", "mesh_key": "cylinder", "material_key": "stone_resource", "location_xy": unreal.Vector(7900.0, 25300.0, 0.0), "z_offset": 92.0, "scale": unreal.Vector(0.9, 0.9, 1.15), "rotation": unreal.Rotator(0.0, 11.0, 0.0), }, { "label": "AGR_GZ_Landmark_Ruin_Threshold_01", "mesh_key": "quarter_cylinder", "material_key": "stone_resource", "location_xy": unreal.Vector(6550.0, 23550.0, 0.0), "z_offset": 70.0, "scale": unreal.Vector(1.8, 0.55, 0.5), "rotation": unreal.Rotator(0.0, -22.0, 0.0), }, ] FOLIAGE_ZONES = { "trees": { "count": 96, "x_range": (-25000.0, 42000.0), "y_range": (-12000.0, 42000.0), "min_elevation_m": 18.0, "avoid_radius_cm": 3600.0, "scale_range": (0.75, 1.35), }, "shrubs": { "count": 220, "x_range": (-42000.0, 45000.0), "y_range": (-38000.0, 45000.0), "min_elevation_m": 7.0, "avoid_radius_cm": 2600.0, "scale_range": (0.45, 1.05), }, "grass": { "count": 420, "x_range": (-46000.0, 46000.0), "y_range": (-46000.0, 46000.0), "min_elevation_m": 4.0, "avoid_radius_cm": 1800.0, "scale_range": (0.35, 0.9), }, } SIGHTLINE_TUNING_CONFIG = { "critical_clearance_cm": { "trees": 5200.0, "shrubs": 3400.0, "grass": 1250.0, }, "corridor_clearance_cm": { "trees": 3600.0, "shrubs": 2100.0, "grass": 850.0, }, "corridor_sample_spacing_cm": 1600.0, "critical_actor_labels": { "AGR_DemoPlayerStart", "AGR_DemoWoodResource_01", "AGR_DemoFiberResource_01", "AGR_DemoCampfire_01", "AGR_DemoPrimitiveShelter_01", "AGR_DemoRabbitWildlife_01", "AGR_GZ_FreshWaterSource_01", }, "sightline_pairs": [ ("AGR_DemoPlayerStart", "AGR_DemoWoodResource_01"), ("AGR_DemoPlayerStart", "AGR_DemoFiberResource_01"), ("AGR_DemoPlayerStart", "AGR_DemoCampfire_01"), ("AGR_DemoPlayerStart", "AGR_DemoPrimitiveShelter_01"), ("AGR_DemoPlayerStart", "AGR_DemoRabbitWildlife_01"), ("AGR_DemoPlayerStart", "AGR_GZ_FreshWaterSource_01"), ], } def get_actor_label(actor): try: return actor.get_actor_label() except Exception: return actor.get_name() def set_actor_label(actor, label): try: actor.set_actor_label(label, mark_dirty=True) except TypeError: actor.set_actor_label(label) def load_blueprint_class(path): generated_class = unreal.EditorAssetLibrary.load_blueprint_class(path) if not generated_class: raise RuntimeError(f"Could not load Blueprint class: {path}") return generated_class def load_required_asset(path): asset = unreal.EditorAssetLibrary.load_asset(path) if not asset: raise RuntimeError(f"Required asset not found: {path}") return asset def ensure_native_placeholder_meshes(): unreal.EditorAssetLibrary.make_directory(PLACEHOLDER_MESH_FOLDER) for asset_name, source_path in PLACEHOLDER_MESH_SOURCES.items(): destination_path = f"{PLACEHOLDER_MESH_FOLDER}/{asset_name}" if unreal.EditorAssetLibrary.does_asset_exist(destination_path): continue if not unreal.EditorAssetLibrary.duplicate_asset(source_path, destination_path): raise RuntimeError(f"Could not create native placeholder mesh {destination_path} from {source_path}") unreal.EditorAssetLibrary.save_asset(destination_path) unreal.log(f"Created native placeholder mesh: {destination_path}") def write_obj_mesh(path, vertices, faces): path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8") as handle: handle.write("# Agrarian generated coastal scrub vegetation proxy\n") for vertex in vertices: handle.write(f"v {vertex[0]:.3f} {vertex[1]:.3f} {vertex[2]:.3f}\n") for face in faces: handle.write("f " + " ".join(str(index + 1) for index in face) + "\n") def add_box(vertices, faces, center, size): cx, cy, cz = center sx, sy, sz = size[0] * 0.5, size[1] * 0.5, size[2] * 0.5 base = len(vertices) vertices.extend( [ (cx - sx, cy - sy, cz - sz), (cx + sx, cy - sy, cz - sz), (cx + sx, cy + sy, cz - sz), (cx - sx, cy + sy, cz - sz), (cx - sx, cy - sy, cz + sz), (cx + sx, cy - sy, cz + sz), (cx + sx, cy + sy, cz + sz), (cx - sx, cy + sy, cz + sz), ] ) faces.extend( [ (base + 0, base + 1, base + 2, base + 3), (base + 4, base + 7, base + 6, base + 5), (base + 0, base + 4, base + 5, base + 1), (base + 1, base + 5, base + 6, base + 2), (base + 2, base + 6, base + 7, base + 3), (base + 3, base + 7, base + 4, base + 0), ] ) def add_leaf_card(vertices, faces, center, width, height, yaw_degrees, lean_degrees=0.0): yaw = math.radians(yaw_degrees) lean = math.radians(lean_degrees) right = (math.cos(yaw) * width * 0.5, math.sin(yaw) * width * 0.5, 0.0) up_offset = (math.sin(lean) * height * 0.35, 0.0, math.cos(lean) * height) cx, cy, cz = center base = len(vertices) vertices.extend( [ (cx - right[0], cy - right[1], cz), (cx + right[0], cy + right[1], cz), (cx + right[0] + up_offset[0], cy + right[1] + up_offset[1], cz + up_offset[2]), (cx - right[0] + up_offset[0], cy - right[1] + up_offset[1], cz + up_offset[2]), ] ) faces.append((base + 0, base + 1, base + 2, base + 3)) def add_low_poly_ellipsoid(vertices, faces, center, radius_x, radius_y, radius_z, segments=10): cx, cy, cz = center top_index = len(vertices) vertices.append((cx, cy, cz + radius_z)) upper = [] lower = [] for ring_z, ring_radius_scale, target in ((0.18, 0.9, upper), (-0.25, 1.0, lower)): for index in range(segments): angle = (math.tau * index) / segments target.append(len(vertices)) vertices.append( ( cx + math.cos(angle) * radius_x * ring_radius_scale, cy + math.sin(angle) * radius_y * ring_radius_scale, cz + (radius_z * ring_z), ) ) bottom_index = len(vertices) vertices.append((cx, cy, cz - radius_z * 0.45)) for index in range(segments): next_index = (index + 1) % segments faces.append((top_index, upper[index], upper[next_index])) faces.append((upper[index], lower[index], lower[next_index], upper[next_index])) faces.append((bottom_index, lower[next_index], lower[index])) def coastal_oak_mesh(): vertices = [] faces = [] add_box(vertices, faces, (0.0, 0.0, 140.0), (36.0, 30.0, 280.0)) add_box(vertices, faces, (-34.0, 14.0, 250.0), (18.0, 16.0, 125.0)) add_box(vertices, faces, (42.0, -12.0, 275.0), (18.0, 16.0, 115.0)) add_low_poly_ellipsoid(vertices, faces, (0.0, 0.0, 350.0), 150.0, 125.0, 105.0) add_low_poly_ellipsoid(vertices, faces, (-95.0, 30.0, 320.0), 105.0, 82.0, 78.0) add_low_poly_ellipsoid(vertices, faces, (95.0, -20.0, 330.0), 112.0, 88.0, 82.0) return vertices, faces def coyote_brush_mesh(): vertices = [] faces = [] for yaw in (0.0, 35.0, 82.0, 128.0, 171.0): add_leaf_card(vertices, faces, (0.0, 0.0, 0.0), 190.0, 145.0, yaw, 8.0) add_low_poly_ellipsoid(vertices, faces, (-32.0, 18.0, 78.0), 92.0, 68.0, 56.0, 8) add_low_poly_ellipsoid(vertices, faces, (48.0, -16.0, 70.0), 86.0, 70.0, 50.0, 8) add_low_poly_ellipsoid(vertices, faces, (0.0, 42.0, 62.0), 78.0, 52.0, 45.0, 8) return vertices, faces def dry_grass_clump_mesh(): vertices = [] faces = [] for index, yaw in enumerate((0.0, 22.0, 47.0, 76.0, 111.0, 146.0, 178.0)): width = 18.0 + (index % 3) * 4.0 height = 95.0 + (index % 4) * 14.0 add_leaf_card(vertices, faces, (0.0, 0.0, 0.0), width, height, yaw, -5.0 + (index % 3) * 5.0) return vertices, faces def import_static_mesh_obj(obj_path, destination_folder, asset_name): task = unreal.AssetImportTask() task.set_editor_property("filename", str(obj_path)) task.set_editor_property("destination_path", destination_folder) task.set_editor_property("destination_name", asset_name) task.set_editor_property("automated", True) task.set_editor_property("replace_existing", True) task.set_editor_property("save", True) unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) asset_path = f"{destination_folder}/{asset_name}" asset = unreal.EditorAssetLibrary.load_asset(asset_path) if not asset: raise RuntimeError(f"Could not import vegetation mesh asset: {asset_path}") return asset def ensure_ground_zero_vegetation_meshes(): if not unreal.EditorAssetLibrary.does_directory_exist(VEGETATION_MESH_FOLDER): unreal.EditorAssetLibrary.make_directory(VEGETATION_MESH_FOLDER) mesh_builders = { "SM_AGR_GZ_CoastalOak_Proxy": coastal_oak_mesh, "SM_AGR_GZ_CoyoteBrush_Proxy": coyote_brush_mesh, "SM_AGR_GZ_DryGrassClump_Proxy": dry_grass_clump_mesh, } for asset_name, builder in mesh_builders.items(): vertices, faces = builder() obj_path = VEGETATION_SOURCE_FOLDER / f"{asset_name}.obj" write_obj_mesh(obj_path, vertices, faces) import_static_mesh_obj(obj_path, VEGETATION_MESH_FOLDER, asset_name) unreal.log(f"Created or refreshed Ground Zero vegetation mesh: {VEGETATION_MESH_FOLDER}/{asset_name}") def ensure_environment_materials(): if not unreal.EditorAssetLibrary.does_directory_exist(MATERIAL_FOLDER): unreal.EditorAssetLibrary.make_directory(MATERIAL_FOLDER) asset_tools = unreal.AssetToolsHelpers.get_asset_tools() created_or_loaded = {} for key, spec in ENVIRONMENT_MATERIALS.items(): material = unreal.EditorAssetLibrary.load_asset(spec["path"]) if not material: asset_name = spec["path"].rsplit("/", 1)[-1] material = asset_tools.create_asset(asset_name, MATERIAL_FOLDER, unreal.Material, unreal.MaterialFactoryNew()) if not material: raise RuntimeError(f"Could not create material: {spec['path']}") base_color = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionConstant3Vector, -420, -120 ) base_color.set_editor_property("constant", spec["color"]) unreal.MaterialEditingLibrary.connect_material_property( base_color, "", unreal.MaterialProperty.MP_BASE_COLOR ) roughness = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionConstant, -420, 80 ) roughness.set_editor_property("r", spec["roughness"]) unreal.MaterialEditingLibrary.connect_material_property( roughness, "", unreal.MaterialProperty.MP_ROUGHNESS ) unreal.MaterialEditingLibrary.recompile_material(material) unreal.EditorAssetLibrary.save_asset(spec["path"]) unreal.log(f"Created Ground Zero environment material: {spec['path']}") else: base_color = unreal.MaterialEditingLibrary.get_material_property_input_node( material, unreal.MaterialProperty.MP_BASE_COLOR ) if base_color and hasattr(base_color, "set_editor_property"): try: base_color.set_editor_property("constant", spec["color"]) except Exception: pass roughness = unreal.MaterialEditingLibrary.get_material_property_input_node( material, unreal.MaterialProperty.MP_ROUGHNESS ) if roughness and hasattr(roughness, "set_editor_property"): try: roughness.set_editor_property("r", spec["roughness"]) except Exception: pass unreal.MaterialEditingLibrary.recompile_material(material) unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False) if spec.get("used_with_instanced_static_meshes"): material.set_editor_property("used_with_instanced_static_meshes", True) unreal.MaterialEditingLibrary.recompile_material(material) unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False) if key == "terrain": rebuild_ground_zero_terrain_material(material, spec) if key in {"tree", "shrub", "grass"}: rebuild_ground_zero_foliage_material(material, spec) created_or_loaded[key] = material return created_or_loaded def set_expression_property(expression, property_name, value): try: expression.set_editor_property(property_name, value) return True except Exception: return False def connect_expression(source, source_output, target, target_input): try: unreal.MaterialEditingLibrary.connect_material_expressions(source, source_output, target, target_input) return True except Exception as exc: unreal.log_warning(f"Could not connect material expression input {target_input}: {exc}") return False def create_constant_color(material, color, x, y): expression = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionConstant3Vector, x, y ) expression.set_editor_property("constant", color) return expression def create_constant_scalar(material, value, x, y): expression = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionConstant, x, y ) expression.set_editor_property("r", value) return expression def rebuild_ground_zero_terrain_material(material, spec): """Build a simple procedural material so the landscape no longer reads as a flat color.""" if hasattr(unreal.MaterialEditingLibrary, "delete_all_material_expressions"): unreal.MaterialEditingLibrary.delete_all_material_expressions(material) dry_soil = create_constant_color(material, spec["dry_soil_color"], -900, -260) scrub_green = create_constant_color(material, spec["scrub_green_color"], -900, -80) sandy_path = create_constant_color(material, spec["sandy_path_color"], -900, 100) broad_noise = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionNoise, -560, -160 ) set_expression_property(broad_noise, "scale", spec.get("noise_scale", 42.0)) set_expression_property(broad_noise, "quality", 3) set_expression_property(broad_noise, "levels", 5) fine_noise = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionNoise, -560, 100 ) set_expression_property(fine_noise, "scale", 145.0) set_expression_property(fine_noise, "quality", 2) set_expression_property(fine_noise, "levels", 3) scrub_blend = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionLinearInterpolate, -260, -180 ) connect_expression(dry_soil, "", scrub_blend, "A") connect_expression(scrub_green, "", scrub_blend, "B") connect_expression(broad_noise, "", scrub_blend, "Alpha") final_blend = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionLinearInterpolate, 40, -70 ) connect_expression(scrub_blend, "", final_blend, "A") connect_expression(sandy_path, "", final_blend, "B") connect_expression(fine_noise, "", final_blend, "Alpha") unreal.MaterialEditingLibrary.connect_material_property( final_blend, "", unreal.MaterialProperty.MP_BASE_COLOR ) roughness = create_constant_scalar(material, spec["roughness"], -260, 160) unreal.MaterialEditingLibrary.connect_material_property( roughness, "", unreal.MaterialProperty.MP_ROUGHNESS ) specular = create_constant_scalar(material, 0.18, -260, 260) unreal.MaterialEditingLibrary.connect_material_property( specular, "", unreal.MaterialProperty.MP_SPECULAR ) material.set_editor_property("use_material_attributes", False) unreal.MaterialEditingLibrary.recompile_material(material) unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False) unreal.log("Rebuilt Ground Zero terrain material with coastal scrub color variation and procedural noise.") def rebuild_ground_zero_foliage_material(material, spec): """Use per-instance color variation so repeated foliage clumps do not tile visually.""" if hasattr(unreal.MaterialEditingLibrary, "delete_all_material_expressions"): unreal.MaterialEditingLibrary.delete_all_material_expressions(material) color_a = create_constant_color(material, spec["color"], -720, -160) color_b = create_constant_color(material, spec["variation_color"], -720, 20) blend = unreal.MaterialEditingLibrary.create_material_expression( material, unreal.MaterialExpressionLinearInterpolate, -360, -80 ) connect_expression(color_a, "", blend, "A") connect_expression(color_b, "", blend, "B") random_expression_class = getattr(unreal, "MaterialExpressionPerInstanceRandom", None) if random_expression_class: random_expression = unreal.MaterialEditingLibrary.create_material_expression( material, random_expression_class, -600, 170 ) connect_expression(random_expression, "", blend, "Alpha") unreal.MaterialEditingLibrary.connect_material_property( blend, "", unreal.MaterialProperty.MP_BASE_COLOR ) roughness = create_constant_scalar(material, spec["roughness"], -360, 140) unreal.MaterialEditingLibrary.connect_material_property( roughness, "", unreal.MaterialProperty.MP_ROUGHNESS ) material.set_editor_property("use_material_attributes", False) material.set_editor_property("used_with_instanced_static_meshes", True) if spec.get("two_sided"): material.set_editor_property("two_sided", True) unreal.MaterialEditingLibrary.recompile_material(material) unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False) def apply_material_to_actor_meshes(actor, material): applied_count = 0 for component in actor.get_components_by_class(unreal.StaticMeshComponent): slot_count = component.get_num_materials() if slot_count == 0: component.set_material(0, material) applied_count += 1 continue for slot_index in range(slot_count): component.set_material(slot_index, material) applied_count += 1 return applied_count def apply_water_source_materials(actor, materials): component_materials = { "Mesh": materials["stone_resource"], "StoneBankProxy": materials["stone_resource"], "WaterSurfaceProxy": materials["fresh_water"], "CollectMarkerProxy": materials["fresh_water"], } for component in actor.get_components_by_class(unreal.StaticMeshComponent): material = component_materials.get(component.get_name()) if material: component.set_material(0, material) def material_key_for_actor_label(label): for prefix, material_key in RESOURCE_MATERIAL_BY_LABEL_PREFIX.items(): if label.startswith(prefix): return material_key return None def apply_landscape_material(material): applied = 0 for actor in unreal.EditorLevelLibrary.get_all_level_actors(): if isinstance(actor, unreal.Landscape): actor.set_editor_property("landscape_material", material) applied += 1 unreal.log(f"Applied Ground Zero terrain material to {applied} landscape actor(s).") return applied def load_heightmap(): raw = HEIGHTMAP_PATH.read_bytes() expected_bytes = LANDSCAPE_SIZE * LANDSCAPE_SIZE * 2 if len(raw) != expected_bytes: raise RuntimeError(f"Unexpected heightmap size: {len(raw)} != {expected_bytes}") return struct.unpack(f"<{LANDSCAPE_SIZE * LANDSCAPE_SIZE}H", raw) def terrain_z_cm(height_values, x_cm, y_cm): sample_x = max(0, min(LANDSCAPE_SIZE - 1, round((x_cm - LANDSCAPE_MIN_XY) / XY_SCALE_CM))) sample_y = max(0, min(LANDSCAPE_SIZE - 1, round((y_cm - LANDSCAPE_MIN_XY) / XY_SCALE_CM))) height_value = height_values[int(sample_y) * LANDSCAPE_SIZE + int(sample_x)] elevation_m = (float(height_value) - 32768.0) * Z_SCALE_CM / (100.0 * 128.0) return elevation_m * 100.0 def terrain_elevation_m(height_values, x_cm, y_cm): return terrain_z_cm(height_values, x_cm, y_cm) / 100.0 def terrain_slope_degrees(height_values, x_cm, y_cm, sample_distance_cm): dz_dx_m = ( terrain_elevation_m(height_values, x_cm + sample_distance_cm, y_cm) - terrain_elevation_m(height_values, x_cm - sample_distance_cm, y_cm) ) / ((sample_distance_cm * 2.0) / 100.0) dz_dy_m = ( terrain_elevation_m(height_values, x_cm, y_cm + sample_distance_cm) - terrain_elevation_m(height_values, x_cm, y_cm - sample_distance_cm) ) / ((sample_distance_cm * 2.0) / 100.0) return math.degrees(math.atan(math.sqrt((dz_dx_m * dz_dx_m) + (dz_dy_m * dz_dy_m)))) def remove_existing_demo_actors(labels): for actor in unreal.EditorLevelLibrary.get_all_level_actors(): if get_actor_label(actor) in labels: unreal.EditorLevelLibrary.destroy_actor(actor) def distance_2d(a, b): return math.hypot(a.x - b.x, a.y - b.y) def reserved_point(point, source_label, clearances): return { "point": point, "source_label": source_label, "clearances": clearances, } def reserved_point_clearance(reservation, foliage_family): return reservation["clearances"].get(foliage_family, 0.0) def add_sightline_corridor_reservations(reservations, label_points): spacing_cm = SIGHTLINE_TUNING_CONFIG["corridor_sample_spacing_cm"] corridor_clearances = SIGHTLINE_TUNING_CONFIG["corridor_clearance_cm"] for start_label, end_label in SIGHTLINE_TUNING_CONFIG["sightline_pairs"]: start = label_points.get(start_label) end = label_points.get(end_label) if not start or not end: continue length_cm = distance_2d(start, end) sample_count = max(1, int(length_cm / spacing_cm)) for index in range(1, sample_count): alpha = index / sample_count point = unreal.Vector( start.x + ((end.x - start.x) * alpha), start.y + ((end.y - start.y) * alpha), 0.0, ) reservations.append( reserved_point(point, f"{start_label}->{end_label}", corridor_clearances) ) def validate_safe_spawn_location(height_values, location_xy, resource_points, water_points): failures = [] elevation_m = terrain_elevation_m(height_values, location_xy.x, location_xy.y) slope_degrees = terrain_slope_degrees( height_values, location_xy.x, location_xy.y, SAFE_SPAWN_CONFIG["slope_sample_distance_cm"], ) min_resource_distance_cm = min(distance_2d(location_xy, point) for point in resource_points) min_water_distance_cm = min(distance_2d(location_xy, point) for point in water_points) if elevation_m < SAFE_SPAWN_CONFIG["min_elevation_m"]: failures.append(f"elevation {elevation_m:.2f}m below minimum") if slope_degrees > SAFE_SPAWN_CONFIG["max_slope_degrees"]: failures.append(f"slope {slope_degrees:.2f}deg above maximum") if min_resource_distance_cm < SAFE_SPAWN_CONFIG["min_resource_distance_cm"]: failures.append(f"resource distance {min_resource_distance_cm:.0f}cm below minimum") if min_water_distance_cm < SAFE_SPAWN_CONFIG["min_water_distance_cm"]: failures.append(f"water distance {min_water_distance_cm:.0f}cm below minimum") return failures def select_safe_spawn_location(height_values): resource_points = [spec["location_xy"] for spec in BIOME_RESOURCE_ACTORS] resource_points.extend( spec["location_xy"] for spec in DEMO_ACTORS if spec["label"] in {"AGR_DemoWoodResource_01", "AGR_DemoFiberResource_01"} ) water_points = [spec["location_xy"] for spec in WATER_SOURCE_ACTORS] candidates = list(SAFE_SPAWN_CONFIG["candidate_locations_xy"]) fallback = SAFE_SPAWN_CONFIG["fallback_location_xy"] if all(distance_2d(fallback, candidate) > 1.0 for candidate in candidates): candidates.append(fallback) rejected = [] for candidate in candidates: failures = validate_safe_spawn_location(height_values, candidate, resource_points, water_points) if not failures: unreal.log( "Selected safe Ground Zero spawn at " f"({candidate.x:.0f}, {candidate.y:.0f}) " f"elevation {terrain_elevation_m(height_values, candidate.x, candidate.y):.2f}m " f"slope {terrain_slope_degrees(height_values, candidate.x, candidate.y, SAFE_SPAWN_CONFIG['slope_sample_distance_cm']):.2f}deg" ) return candidate rejected.append(f"({candidate.x:.0f}, {candidate.y:.0f}): {', '.join(failures)}") raise RuntimeError("No safe Ground Zero spawn candidate found. Rejected: " + "; ".join(rejected)) def make_foliage_transform(height_values, x, y, yaw, z_offset, scale_xy, scale_z): return unreal.Transform( location=unreal.Vector(x, y, terrain_z_cm(height_values, x, y) + z_offset), rotation=unreal.Rotator(0.0, yaw, 0.0), scale=unreal.Vector(scale_xy, scale_xy, scale_z), ) def configure_foliage_meshes(foliage_actor): component_meshes = { "tree_instances": load_required_asset(FOLIAGE_MESHES["tree"]), "shrub_instances": load_required_asset(FOLIAGE_MESHES["shrub"]), "grass_instances": load_required_asset(FOLIAGE_MESHES["grass"]), } for property_name, mesh in component_meshes.items(): component = foliage_actor.get_editor_property(property_name) component.set_editor_property("static_mesh", mesh) def apply_foliage_materials(foliage_actor, materials): component_materials = { "tree_instances": materials["tree"], "shrub_instances": materials["shrub"], "grass_instances": materials["grass"], } for property_name, material in component_materials.items(): component = foliage_actor.get_editor_property(property_name) component.set_material(0, material) def choose_foliage_points(height_values, family_name, zone, reservations, existing_points): rng = random.Random(FOLIAGE_RANDOM_SEED + len(existing_points) + int(zone["count"])) chosen = [] attempts = 0 max_attempts = zone["count"] * 80 while len(chosen) < zone["count"] and attempts < max_attempts: attempts += 1 x = rng.uniform(zone["x_range"][0], zone["x_range"][1]) y = rng.uniform(zone["y_range"][0], zone["y_range"][1]) point = unreal.Vector(x, y, 0.0) if terrain_elevation_m(height_values, x, y) < zone["min_elevation_m"]: continue if any( distance_2d(point, reservation["point"]) < reserved_point_clearance(reservation, family_name) for reservation in reservations ): continue if any(distance_2d(point, existing) < zone["avoid_radius_cm"] * 0.55 for existing in existing_points): continue chosen.append(point) existing_points.append(point) if len(chosen) != zone["count"]: unreal.log_warning(f"Placed {len(chosen)} of {zone['count']} requested foliage instances for zone.") return chosen def build_foliage_reservations(safe_spawn_location_xy): label_points = {} specs = DEMO_ACTORS + BIOME_RESOURCE_ACTORS + WATER_SOURCE_ACTORS for spec in specs: label = spec["label"] location_xy = spec["location_xy"] if label == SAFE_SPAWN_CONFIG["player_start_label"] and safe_spawn_location_xy is not None: location_xy = safe_spawn_location_xy label_points[label] = location_xy reservations = [] critical_clearances = SIGHTLINE_TUNING_CONFIG["critical_clearance_cm"] for label in SIGHTLINE_TUNING_CONFIG["critical_actor_labels"]: point = label_points.get(label) if point: reservations.append(reserved_point(point, label, critical_clearances)) for spec in BIOME_RESOURCE_ACTORS: reservations.append(reserved_point(spec["location_xy"], spec["label"], critical_clearances)) add_sightline_corridor_reservations(reservations, label_points) unreal.log(f"Prepared {len(reservations)} Ground Zero foliage sightline reservation point(s).") return reservations def spawn_foliage_actor(height_values, materials, safe_spawn_location_xy): reservations = build_foliage_reservations(safe_spawn_location_xy) foliage_actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world( unreal.AgrarianFoliagePatch, unreal.Vector(0.0, 0.0, 0.0), unreal.Rotator(0.0, 0.0, 0.0), FOLIAGE_LABEL, ) if not foliage_actor: raise RuntimeError("Could not spawn first-pass foliage actor.") set_actor_label(foliage_actor, FOLIAGE_LABEL) configure_foliage_meshes(foliage_actor) apply_foliage_materials(foliage_actor, materials) foliage_actor.clear_foliage() existing_points = [] rng = random.Random(FOLIAGE_RANDOM_SEED) for point in choose_foliage_points(height_values, "trees", FOLIAGE_ZONES["trees"], reservations, existing_points): scale = rng.uniform(*FOLIAGE_ZONES["trees"]["scale_range"]) foliage_actor.add_tree_instance( make_foliage_transform(height_values, point.x, point.y, rng.uniform(0.0, 360.0), 5.0, scale, scale * 5.5) ) for point in choose_foliage_points(height_values, "shrubs", FOLIAGE_ZONES["shrubs"], reservations, existing_points): scale = rng.uniform(*FOLIAGE_ZONES["shrubs"]["scale_range"]) foliage_actor.add_shrub_instance( make_foliage_transform(height_values, point.x, point.y, rng.uniform(0.0, 360.0), 4.0, scale * 2.2, scale * 0.85) ) for point in choose_foliage_points(height_values, "grass", FOLIAGE_ZONES["grass"], reservations, existing_points): scale = rng.uniform(*FOLIAGE_ZONES["grass"]["scale_range"]) foliage_actor.add_grass_instance( make_foliage_transform(height_values, point.x, point.y, rng.uniform(0.0, 360.0), 2.0, scale * 0.28, scale * 1.6) ) unreal.log( "Placed first-pass Ground Zero foliage: " f"{foliage_actor.get_tree_instance_count()} trees, " f"{foliage_actor.get_shrub_instance_count()} shrubs, " f"{foliage_actor.get_grass_instance_count()} grass clumps." ) return foliage_actor def spawn_demo_actor(spec, height_values, materials, safe_spawn_location_xy=None): location_xy = spec["location_xy"] if spec["label"] == SAFE_SPAWN_CONFIG["player_start_label"] and safe_spawn_location_xy is not None: location_xy = safe_spawn_location_xy z = spec.get("fixed_z") if z is None: z = terrain_z_cm(height_values, location_xy.x, location_xy.y) + spec.get("z_offset", 0.0) actor_class = spec.get("class") if actor_class is None: actor_class = load_blueprint_class(spec["class_path"]) actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world( actor_class, unreal.Vector(location_xy.x, location_xy.y, z), spec["rotation"], spec["label"], ) if not actor: raise RuntimeError(f"Could not spawn {spec['label']}") set_actor_label(actor, spec["label"]) try: actor.set_editor_property("persistence_node_id", spec["label"]) except Exception: pass if spec["label"].startswith("AGR_GZ_FreshWaterSource"): apply_water_source_materials(actor, materials) else: material_key = material_key_for_actor_label(spec["label"]) if material_key: apply_material_to_actor_meshes(actor, materials[material_key]) unreal.log(f"Placed {spec['label']} at {actor.get_actor_location()}") return actor def spawn_environment_variation_actor(spec, height_values, materials): location_xy = spec["location_xy"] z = terrain_z_cm(height_values, location_xy.x, location_xy.y) + spec.get("z_offset", 0.0) actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world( unreal.StaticMeshActor, unreal.Vector(location_xy.x, location_xy.y, z), spec["rotation"], spec["label"], ) if not actor: raise RuntimeError(f"Could not spawn {spec['label']}") set_actor_label(actor, spec["label"]) actor.set_actor_scale3d(spec["scale"]) mesh_component = actor.static_mesh_component mesh_component.set_editor_property("static_mesh", load_required_asset(VARIATION_MESHES[spec["mesh_key"]])) mesh_component.set_material(0, materials[spec["material_key"]]) unreal.log(f"Placed {spec['label']} at {actor.get_actor_location()}") return actor def spawn_weather_exposure_zone(spec, height_values): location_xy = spec["location_xy"] z = terrain_z_cm(height_values, location_xy.x, location_xy.y) + spec.get("z_offset", 0.0) actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world( unreal.AgrarianWeatherExposureZone, unreal.Vector(location_xy.x, location_xy.y, z), unreal.Rotator(0.0, 0.0, 0.0), spec["label"], ) if not actor: raise RuntimeError(f"Could not spawn {spec['label']}") set_actor_label(actor, spec["label"]) actor.set_editor_property("exposure_zone_id", spec["zone_id"]) actor.set_editor_property("exposure_multiplier", spec["exposure_multiplier"]) actor.set_editor_property("temperature_offset_c", spec["temperature_offset_c"]) actor.exposure_volume.set_box_extent(spec["extent"], True) unreal.log( f"Placed {spec['label']} at {actor.get_actor_location()} " f"exposure x{spec['exposure_multiplier']} temp {spec['temperature_offset_c']} C" ) return actor def spawn_map_boundary_volume(): actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world( unreal.AgrarianMapBoundaryVolume, MAP_BOUNDARY_CONFIG["location"], unreal.Rotator(0.0, 0.0, 0.0), MAP_BOUNDARY_CONFIG["label"], ) if not actor: raise RuntimeError(f"Could not spawn {MAP_BOUNDARY_CONFIG['label']}") set_actor_label(actor, MAP_BOUNDARY_CONFIG["label"]) actor.set_editor_property("boundary_id", MAP_BOUNDARY_CONFIG["boundary_id"]) actor.set_editor_property("boundary_padding_cm", MAP_BOUNDARY_CONFIG["padding_cm"]) actor.set_editor_property("warning_distance_cm", MAP_BOUNDARY_CONFIG["warning_distance_cm"]) actor.set_editor_property("clamp_players_at_boundary", True) actor.boundary_volume.set_box_extent(MAP_BOUNDARY_CONFIG["extent"], True) unreal.log( f"Placed {MAP_BOUNDARY_CONFIG['label']} at {actor.get_actor_location()} " f"extent {MAP_BOUNDARY_CONFIG['extent']}" ) return actor def main(): if not unreal.EditorLevelLibrary.load_level(MAP_PATH): raise RuntimeError(f"Could not load map: {MAP_PATH}") ensure_native_placeholder_meshes() ensure_ground_zero_vegetation_meshes() labels = {spec["label"] for spec in DEMO_ACTORS} labels.update(LEGACY_DEMO_LIGHTING_LABELS) labels.update(spec["label"] for spec in BIOME_RESOURCE_ACTORS) labels.update(spec["label"] for spec in WATER_SOURCE_ACTORS) labels.update(spec["label"] for spec in WEATHER_EXPOSURE_ZONES) labels.update(spec["label"] for spec in ENVIRONMENT_VARIATION_ACTORS) labels.update(spec["label"] for spec in RUIN_PLACEHOLDER_ACTORS) labels.add(FOLIAGE_LABEL) labels.add(MAP_BOUNDARY_CONFIG["label"]) remove_existing_demo_actors(labels) materials = ensure_environment_materials() apply_landscape_material(materials["terrain"]) height_values = load_heightmap() safe_spawn_location_xy = select_safe_spawn_location(height_values) spawn_foliage_actor(height_values, materials, safe_spawn_location_xy) for spec in BIOME_RESOURCE_ACTORS: spawn_demo_actor(spec, height_values, materials) for spec in WATER_SOURCE_ACTORS: spawn_demo_actor(spec, height_values, materials) for spec in WEATHER_EXPOSURE_ZONES: spawn_weather_exposure_zone(spec, height_values) for spec in ENVIRONMENT_VARIATION_ACTORS: spawn_environment_variation_actor(spec, height_values, materials) for spec in RUIN_PLACEHOLDER_ACTORS: spawn_environment_variation_actor(spec, height_values, materials) spawn_map_boundary_volume() for spec in DEMO_ACTORS: spawn_demo_actor(spec, height_values, materials, safe_spawn_location_xy) unreal.EditorLevelLibrary.save_current_level() unreal.log("Ground Zero demo map setup complete.") main()