class_name MBWorldProjectIO extends RefCounted ## Manages serialization and deserialization of local .mbworld project files ## and cloud sync with ModBison backend. const RECENT_PROJECTS_PATH: String = "user://recent_projects.json" static func save_project(path: String, project_data: Dictionary) -> bool: var fa := FileAccess.open(path, FileAccess.WRITE) if fa == null: push_error("Failed to open %s for write" % path) return false var json_str := JSON.stringify(project_data, " ") fa.store_string(json_str) fa.close() record_recent_project(path) return true static func load_project(path: String) -> Dictionary: if not FileAccess.file_exists(path): push_error("Project file does not exist: %s" % path) return {} var fa := FileAccess.open(path, FileAccess.READ) if fa == null: return {} var text := fa.get_as_text() fa.close() var parsed = JSON.parse_string(text) if parsed is Dictionary: record_recent_project(path) return parsed return {} static func create_template(template_name: String) -> Dictionary: var base := { "format_version": 1, "title": "Untitled World", "template": template_name, "created_at": Time.get_datetime_string_from_system(), "objects": [], "tile_layers": [ { "name": "ground", "width": 32, "height": 32, "tiles": [] } ], "settings": { "ambient_color": [0.4, 0.4, 0.45], "sky_color": [0.3, 0.6, 0.9], "gravity": 9.8 } } match template_name.to_lower(): "baseplate": base["title"] = "Baseplate World" base["objects"] = [ { "id": "spawn_pad", "catalog_id": "crate_small", "asset_id": "props/crate_small.glb", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "scale": [2, 2, 0.2], "properties": {"display_name": "Spawn Pad", "catalog_id": "crate_small"} }, { "id": "modbison_mascot", "catalog_id": "bison", "asset_id": "props/bison.glb", "position": [0, 4, 0], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1], "properties": {"display_name": "ModBison", "catalog_id": "bison"} }, { "id": "crate_base", "catalog_id": "crate_small", "asset_id": "props/crate_small.glb", "position": [4, 0, 0], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1], "properties": {"display_name": "Wooden Crate", "catalog_id": "crate_small"} }, { "id": "crate_stacked", "catalog_id": "crate_small", "asset_id": "props/crate_small.glb", "position": [4, 0, 1], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1], "properties": {"display_name": "Stacked Crate", "catalog_id": "crate_small", "parent_id": "crate_base"} }, { "id": "lamp_01", "catalog_id": "lamp_post_01", "asset_id": "props/lamp_post_01.glb", "position": [-3, 3, 0], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1], "properties": {"display_name": "Street Lamp", "catalog_id": "lamp_post_01"} } ] "terrain": base["title"] = "Rolling Hills World" base["objects"] = [ { "id": "tree_01", "catalog_id": "tree_pine", "asset_id": "props/tree_pine_01.glb", "position": [4, 4, 0], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1], "properties": {"display_name": "Pine Tree"} }, { "id": "bison_01", "catalog_id": "bison", "asset_id": "props/bison.glb", "position": [0, 6, 0], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1], "properties": {"display_name": "Wild Bison"} } ] "obby", "platformer": base["title"] = "Obby Parkour World" base["objects"] = [ { "id": "start_platform", "catalog_id": "crate_small", "asset_id": "props/crate_small.glb", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "scale": [3, 3, 0.5], "properties": {"display_name": "Start Platform"} }, { "id": "moving_plat_01", "catalog_id": "crate_small", "asset_id": "props/crate_small.glb", "position": [0, 6, 1], "rotation": [0, 0, 0, 1], "scale": [2, 2, 0.3], "properties": { "display_name": "Moving Elevator", "motion": { "motion_type": "waypoint", "move_speed": 3.0, "waypoints": [[0, 6, 1], [0, 6, 6]] } } }, { "id": "goal_platform", "catalog_id": "crate_small", "asset_id": "props/crate_small.glb", "position": [0, 14, 6], "rotation": [0, 0, 0, 1], "scale": [3, 3, 0.5], "properties": {"display_name": "Finish Goal"} } ] return base static func record_recent_project(path: String) -> void: var recents: Array = get_recent_projects() if path in recents: recents.erase(path) recents.push_front(path) if recents.size() > 10: recents.resize(10) var fa := FileAccess.open(RECENT_PROJECTS_PATH, FileAccess.WRITE) if fa != null: fa.store_string(JSON.stringify(recents)) fa.close() static func get_recent_projects() -> Array: if not FileAccess.file_exists(RECENT_PROJECTS_PATH): return [] var fa := FileAccess.open(RECENT_PROJECTS_PATH, FileAccess.READ) if fa == null: return [] var parsed = JSON.parse_string(fa.get_as_text()) fa.close() if parsed is Array: return parsed return []