import unreal BLUEPRINT_ROOT = "/Game/Agrarian/Blueprints" RESOURCE_FOLDER = f"{BLUEPRINT_ROOT}/Resources" STRUCTURE_FOLDER = f"{BLUEPRINT_ROOT}/Structures" WILDLIFE_FOLDER = f"{BLUEPRINT_ROOT}/Wildlife" WORLD_FOLDER = f"{BLUEPRINT_ROOT}/World" WOOD_ITEM_PATH = "/Game/Agrarian/DataAssets/Items/DA_Item_Wood" FIBER_ITEM_PATH = "/Game/Agrarian/DataAssets/Items/DA_Item_Fiber" STONE_ITEM_PATH = "/Game/Agrarian/DataAssets/Items/DA_Item_Stone" FOOD_ITEM_PATH = "/Game/Agrarian/DataAssets/Items/DA_Item_Food" PLACEHOLDER_MESH_FOLDER = "/Game/Agrarian/Environment/PlaceholderMeshes" PLACEHOLDER_MESH_SOURCES = { "SM_AGR_Placeholder_Cube": "/Game/LevelPrototyping/Meshes/SM_Cube", "SM_AGR_Placeholder_Cylinder": "/Game/LevelPrototyping/Meshes/SM_Cylinder", } PLACEHOLDER_MESHES = { name: f"{PLACEHOLDER_MESH_FOLDER}/{name}" for name in PLACEHOLDER_MESH_SOURCES } MESH_CUBE_PATH = PLACEHOLDER_MESHES["SM_AGR_Placeholder_Cube"] MESH_CYLINDER_PATH = PLACEHOLDER_MESHES["SM_AGR_Placeholder_Cylinder"] BLUEPRINTS = [ { "asset": "BP_WoodResourceNode", "folder": RESOURCE_FOLDER, "parent": unreal.AgrarianResourceNode, "defaults": { "yield_item_definition": WOOD_ITEM_PATH, "remaining_harvests": 16, "quantity_per_harvest": 2, "required_tool_item_id": "basic_tool", "allow_bare_hand_gathering": True, "tool_quantity_bonus": 1, "respawns_for_mvp": True, "respawn_delay_seconds": 900.0, "max_harvests": 16, }, "mesh": MESH_CUBE_PATH, "scale": unreal.Vector(1.0, 1.0, 1.5), }, { "asset": "BP_FiberResourceNode", "folder": RESOURCE_FOLDER, "parent": unreal.AgrarianResourceNode, "defaults": { "yield_item_definition": FIBER_ITEM_PATH, "remaining_harvests": 10, "quantity_per_harvest": 3, "required_tool_item_id": "basic_tool", "allow_bare_hand_gathering": True, "tool_quantity_bonus": 1, "respawns_for_mvp": True, "respawn_delay_seconds": 600.0, "max_harvests": 10, }, "mesh": MESH_CYLINDER_PATH, "scale": unreal.Vector(0.8, 0.8, 1.0), }, { "asset": "BP_StoneResourceNode", "folder": RESOURCE_FOLDER, "parent": unreal.AgrarianResourceNode, "defaults": { "yield_item_definition": STONE_ITEM_PATH, "remaining_harvests": 12, "quantity_per_harvest": 2, "required_tool_item_id": "basic_tool", "allow_bare_hand_gathering": True, "tool_quantity_bonus": 1, "respawns_for_mvp": False, "respawn_delay_seconds": 1800.0, "max_harvests": 12, }, "mesh": MESH_CUBE_PATH, "scale": unreal.Vector(0.9, 0.75, 0.45), }, { "asset": "BP_EdiblePlantResourceNode", "folder": RESOURCE_FOLDER, "parent": unreal.AgrarianResourceNode, "defaults": { "yield_item_definition": FOOD_ITEM_PATH, "remaining_harvests": 8, "quantity_per_harvest": 1, "allow_bare_hand_gathering": True, "tool_quantity_bonus": 0, "respawns_for_mvp": True, "respawn_delay_seconds": 1200.0, "max_harvests": 8, }, "mesh": MESH_CYLINDER_PATH, "scale": unreal.Vector(0.65, 0.65, 0.85), }, { "asset": "BP_Campfire", "folder": STRUCTURE_FOLDER, "parent": unreal.AgrarianCampfire, "defaults": { "fuel_seconds": 180.0, "warmth_radius": 650.0, "warmth_per_second": 0.03, }, "mesh": MESH_CYLINDER_PATH, "scale": unreal.Vector(1.3, 1.3, 0.25), }, { "asset": "BP_FreshWaterSource", "folder": WORLD_FOLDER, "parent": unreal.AgrarianWaterSource, "defaults": { "water_restore_amount": 45.0, "display_name": "Fresh Water Spring", }, "mesh": MESH_CYLINDER_PATH, "scale": unreal.Vector(2.2, 2.2, 0.12), }, { "asset": "BP_PrimitiveShelter", "folder": STRUCTURE_FOLDER, "parent": unreal.AgrarianShelterActor, "defaults": { "weather_protection": 0.7, }, "mesh": MESH_CUBE_PATH, "scale": unreal.Vector(3.0, 2.0, 1.4), }, { "asset": "BP_RabbitWildlife", "folder": WILDLIFE_FOLDER, "parent": unreal.AgrarianWildlifeBase, "defaults": { "wildlife_id": "rabbit", "display_name": "Rabbit", "max_health": 12.0, "health": 12.0, "wander_radius": 900.0, "wander_speed": 160.0, "flee_speed": 520.0, "aggro_radius": 0.0, "flee_radius": 750.0, "decision_interval_seconds": 1.5, "harvest_yields": [ { "item_id": "meat", "display_name": "Meat", "quantity": 1, "unit_weight": 0.5, }, { "item_id": "hide", "display_name": "Hide", "quantity": 2, "unit_weight": 0.7, }, ], }, }, ] 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 create_or_load_blueprint(asset_name, folder, parent_class): path = f"{folder}/{asset_name}" existing = unreal.EditorAssetLibrary.load_asset(path) if existing: return existing factory = unreal.BlueprintFactory() factory.set_editor_property("parent_class", parent_class) asset_tools = unreal.AssetToolsHelpers.get_asset_tools() blueprint = asset_tools.create_asset(asset_name, folder, None, factory) if not blueprint: raise RuntimeError(f"Could not create {path}") return blueprint def make_stack(data): stack = unreal.AgrarianItemStack() stack.set_editor_property("item_id", data["item_id"]) stack.set_editor_property("display_name", data["display_name"]) stack.set_editor_property("quantity", data["quantity"]) stack.set_editor_property("unit_weight", data["unit_weight"]) return stack def apply_defaults(blueprint, config): blueprint_path = f"{config['folder']}/{config['asset']}" unreal.BlueprintEditorLibrary.compile_blueprint(blueprint) generated_class = unreal.EditorAssetLibrary.load_blueprint_class(blueprint_path) if not generated_class: raise RuntimeError(f"Could not load generated class for {blueprint_path}") cdo = unreal.get_default_object(generated_class) for property_name, value in config.get("defaults", {}).items(): if property_name == "yield_item_definition": value = load_required_asset(value) elif property_name == "harvest_yields": value = [make_stack(stack_data) for stack_data in value] cdo.set_editor_property(property_name, value) mesh_path = config.get("mesh") if mesh_path: mesh_component = cdo.get_editor_property("mesh") mesh_component.set_editor_property("static_mesh", load_required_asset(mesh_path)) mesh_component.set_editor_property("relative_scale3d", config.get("scale", unreal.Vector(1.0, 1.0, 1.0))) unreal.BlueprintEditorLibrary.compile_blueprint(blueprint) unreal.EditorAssetLibrary.save_loaded_asset(blueprint) def main(): ensure_native_placeholder_meshes() for folder in (RESOURCE_FOLDER, STRUCTURE_FOLDER, WILDLIFE_FOLDER, WORLD_FOLDER): unreal.EditorAssetLibrary.make_directory(folder) for config in BLUEPRINTS: blueprint = create_or_load_blueprint(config["asset"], config["folder"], config["parent"]) apply_defaults(blueprint, config) unreal.log(f"Configured Blueprint: {config['folder']}/{config['asset']}") unreal.log("Agrarian playable Blueprint setup complete.") main()