/**************************************************************************/ /* template_manager.cpp */ /**************************************************************************/ /* This file is part of: */ /* AETHEX ENGINE */ /* https://aethex.foundation */ /**************************************************************************/ /* Copyright (c) 2026-present AeThex Labs. */ /**************************************************************************/ #include "template_manager.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/io/json.h" #include "core/os/os.h" #include "core/variant/typed_array.h" const String AeThexTemplateManager::TEMPLATES_API_URL = "https://api.aethex.dev/templates/v1"; const String AeThexTemplateManager::TEMPLATES_CACHE_PATH = "user://aethex_templates/"; AeThexTemplateManager *AeThexTemplateManager::singleton = nullptr; void AeThexTemplateManager::_bind_methods() { ClassDB::bind_method(D_METHOD("get_all_templates"), &AeThexTemplateManager::get_all_templates); ClassDB::bind_method(D_METHOD("get_builtin_templates"), &AeThexTemplateManager::get_builtin_templates); ClassDB::bind_method(D_METHOD("get_downloaded_templates"), &AeThexTemplateManager::get_downloaded_templates); ClassDB::bind_method(D_METHOD("get_online_templates"), &AeThexTemplateManager::get_online_templates); ClassDB::bind_method(D_METHOD("get_template_by_id", "id"), &AeThexTemplateManager::get_template_by_id); ClassDB::bind_method(D_METHOD("get_templates_by_category", "category"), &AeThexTemplateManager::get_templates_by_category); ClassDB::bind_method(D_METHOD("get_templates_by_platform", "platform"), &AeThexTemplateManager::get_templates_by_platform); ClassDB::bind_method(D_METHOD("get_templates_by_difficulty", "difficulty"), &AeThexTemplateManager::get_templates_by_difficulty); ClassDB::bind_method(D_METHOD("search_templates", "query"), &AeThexTemplateManager::search_templates); ClassDB::bind_method(D_METHOD("fetch_online_templates"), &AeThexTemplateManager::fetch_online_templates); ClassDB::bind_method(D_METHOD("download_template", "template_id"), &AeThexTemplateManager::download_template); ClassDB::bind_method(D_METHOD("is_template_downloaded", "template_id"), &AeThexTemplateManager::is_template_downloaded); ClassDB::bind_method(D_METHOD("create_project_from_template", "template", "project_path", "variables"), &AeThexTemplateManager::create_project_from_template); ClassDB::bind_method(D_METHOD("process_template_string", "template", "variables"), &AeThexTemplateManager::process_template_string); ADD_SIGNAL(MethodInfo("templates_fetched", PropertyInfo(Variant::ARRAY, "templates"))); ADD_SIGNAL(MethodInfo("template_downloaded", PropertyInfo(Variant::STRING, "template_id"))); ADD_SIGNAL(MethodInfo("template_download_failed", PropertyInfo(Variant::STRING, "template_id"), PropertyInfo(Variant::STRING, "error"))); ADD_SIGNAL(MethodInfo("project_created", PropertyInfo(Variant::STRING, "project_path"))); ADD_SIGNAL(MethodInfo("project_creation_failed", PropertyInfo(Variant::STRING, "error"))); } AeThexTemplateManager *AeThexTemplateManager::get_singleton() { return singleton; } AeThexTemplateManager::AeThexTemplateManager() { singleton = this; _init_builtin_templates(); _load_downloaded_templates(); } AeThexTemplateManager::~AeThexTemplateManager() { if (http_client) { memdelete(http_client); } singleton = nullptr; } void AeThexTemplateManager::_init_builtin_templates() { builtin_templates.push_back(AeThexBuiltinTemplates::create_empty_project()); builtin_templates.push_back(AeThexBuiltinTemplates::create_2d_platformer()); builtin_templates.push_back(AeThexBuiltinTemplates::create_3d_fps()); builtin_templates.push_back(AeThexBuiltinTemplates::create_rpg_starter()); builtin_templates.push_back(AeThexBuiltinTemplates::create_multiplayer_game()); builtin_templates.push_back(AeThexBuiltinTemplates::create_roblox_experience()); builtin_templates.push_back(AeThexBuiltinTemplates::create_uefn_island()); builtin_templates.push_back(AeThexBuiltinTemplates::create_unity_mobile()); builtin_templates.push_back(AeThexBuiltinTemplates::create_web_game()); builtin_templates.push_back(AeThexBuiltinTemplates::create_crossplatform_game()); } void AeThexTemplateManager::_load_downloaded_templates() { Ref dir = DirAccess::open(TEMPLATES_CACHE_PATH); if (dir.is_null()) { return; } dir->list_dir_begin(); String file_name = dir->get_next(); while (!file_name.is_empty()) { if (file_name.ends_with(".aethex_template")) { Ref f = FileAccess::open(TEMPLATES_CACHE_PATH + file_name, FileAccess::READ); if (f.is_valid()) { String json_str = f->get_as_text(); JSON json; if (json.parse(json_str) == OK) { Ref tmpl = AeThexTemplate::from_json(json.get_data()); tmpl->set_is_downloaded(true); tmpl->set_local_path(TEMPLATES_CACHE_PATH + file_name); downloaded_templates.push_back(tmpl); } } } file_name = dir->get_next(); } } void AeThexTemplateManager::_cache_template(const Ref &p_template) { Ref dir = DirAccess::open("user://"); if (dir.is_valid()) { dir->make_dir_recursive(TEMPLATES_CACHE_PATH.substr(7)); // Remove "user://" } String file_path = TEMPLATES_CACHE_PATH + p_template->get_id() + ".aethex_template"; Ref f = FileAccess::open(file_path, FileAccess::WRITE); if (f.is_valid()) { f->store_string(JSON::stringify(p_template->to_json(), "\t")); } } TypedArray AeThexTemplateManager::get_all_templates() const { TypedArray all; for (const Ref &t : builtin_templates) all.push_back(t); for (const Ref &t : downloaded_templates) all.push_back(t); for (const Ref &t : online_templates) all.push_back(t); return all; } TypedArray AeThexTemplateManager::get_builtin_templates() const { TypedArray result; for (const Ref &t : builtin_templates) result.push_back(t); return result; } TypedArray AeThexTemplateManager::get_downloaded_templates() const { TypedArray result; for (const Ref &t : downloaded_templates) result.push_back(t); return result; } TypedArray AeThexTemplateManager::get_online_templates() const { TypedArray result; for (const Ref &t : online_templates) result.push_back(t); return result; } Ref AeThexTemplateManager::get_template_by_id(const String &p_id) const { for (const Ref &tmpl : builtin_templates) { if (tmpl->get_id() == p_id) return tmpl; } for (const Ref &tmpl : downloaded_templates) { if (tmpl->get_id() == p_id) return tmpl; } for (const Ref &tmpl : online_templates) { if (tmpl->get_id() == p_id) return tmpl; } return Ref(); } TypedArray AeThexTemplateManager::get_templates_by_category(AeThexTemplate::TemplateCategory p_category) const { TypedArray result; for (const Ref &tmpl : builtin_templates) { if (tmpl->get_category() == p_category) result.push_back(tmpl); } for (const Ref &tmpl : downloaded_templates) { if (tmpl->get_category() == p_category) result.push_back(tmpl); } for (const Ref &tmpl : online_templates) { if (tmpl->get_category() == p_category) result.push_back(tmpl); } return result; } TypedArray AeThexTemplateManager::get_templates_by_platform(AeThexTemplate::TargetPlatform p_platform) const { TypedArray result; for (const Ref &tmpl : builtin_templates) { if (tmpl->supports_platform(p_platform)) result.push_back(tmpl); } for (const Ref &tmpl : downloaded_templates) { if (tmpl->supports_platform(p_platform)) result.push_back(tmpl); } for (const Ref &tmpl : online_templates) { if (tmpl->supports_platform(p_platform)) result.push_back(tmpl); } return result; } TypedArray AeThexTemplateManager::get_templates_by_difficulty(AeThexTemplate::Difficulty p_difficulty) const { TypedArray result; for (const Ref &tmpl : builtin_templates) { if (tmpl->get_difficulty() == p_difficulty) result.push_back(tmpl); } for (const Ref &tmpl : downloaded_templates) { if (tmpl->get_difficulty() == p_difficulty) result.push_back(tmpl); } for (const Ref &tmpl : online_templates) { if (tmpl->get_difficulty() == p_difficulty) result.push_back(tmpl); } return result; } TypedArray AeThexTemplateManager::search_templates(const String &p_query) const { TypedArray result; String query_lower = p_query.to_lower(); Vector> all; all.append_array(builtin_templates); all.append_array(downloaded_templates); all.append_array(online_templates); for (const Ref &tmpl : all) { if (tmpl->get_template_name().to_lower().contains(query_lower) || tmpl->get_description().to_lower().contains(query_lower)) { result.push_back(tmpl); continue; } // Check tags for (const String &tag : tmpl->get_tags()) { if (tag.to_lower().contains(query_lower)) { result.push_back(tmpl); break; } } } return result; } void AeThexTemplateManager::fetch_online_templates() { // Would make HTTP request to TEMPLATES_API_URL // For now, emit empty signal is_fetching = true; // ... HTTP request logic ... } void AeThexTemplateManager::download_template(const String &p_template_id) { Ref tmpl = get_template_by_id(p_template_id); if (tmpl.is_null()) { emit_signal("template_download_failed", p_template_id, "Template not found"); return; } // Would download template package // For now, just cache the metadata _cache_template(tmpl); emit_signal("template_downloaded", p_template_id); } bool AeThexTemplateManager::is_template_downloaded(const String &p_template_id) const { for (const Ref &tmpl : downloaded_templates) { if (tmpl->get_id() == p_template_id) { return true; } } // Builtin templates are always "downloaded" for (const Ref &tmpl : builtin_templates) { if (tmpl->get_id() == p_template_id) { return true; } } return false; } Error AeThexTemplateManager::create_project_from_template(const Ref &p_template, const String &p_project_path, const Dictionary &p_variables) { if (p_template.is_null()) { emit_signal("project_creation_failed", "Invalid template"); return ERR_INVALID_PARAMETER; } // Create project directory Ref dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); Error err = dir->make_dir_recursive(p_project_path); if (err != OK) { emit_signal("project_creation_failed", "Failed to create project directory"); return err; } // Process file structure Dictionary file_structure = p_template->get_file_structure(); Array keys = file_structure.keys(); for (int i = 0; i < keys.size(); i++) { String rel_path = keys[i]; String content = file_structure[rel_path]; // Process template variables content = process_template_string(content, p_variables); rel_path = process_template_string(rel_path, p_variables); String full_path = p_project_path.path_join(rel_path); // Create subdirectories if needed String dir_path = full_path.get_base_dir(); dir->make_dir_recursive(dir_path); // Write file Ref f = FileAccess::open(full_path, FileAccess::WRITE); if (f.is_valid()) { f->store_string(content); } else { WARN_PRINT("Failed to create file: " + full_path); } } // Create project.aethex file with AeThex configuration String project_file = p_project_path.path_join("project.aethex"); if (!FileAccess::exists(project_file)) { String project_name = p_variables.get("project_name", "New AeThex Project"); String project_content = _create_project_godot(project_name, p_template); Ref f = FileAccess::open(project_file, FileAccess::WRITE); if (f.is_valid()) { f->store_string(project_content); } } emit_signal("project_created", p_project_path); return OK; } String AeThexTemplateManager::process_template_string(const String &p_template, const Dictionary &p_variables) { String result = p_template; Array keys = p_variables.keys(); for (int i = 0; i < keys.size(); i++) { String key = keys[i]; String value = p_variables[key]; // Replace {{variable}} patterns result = result.replace("{{" + key + "}}", value); // Replace ${variable} patterns result = result.replace("${" + key + "}", value); // Replace %variable% patterns result = result.replace("%" + key + "%", value); } return result; } String AeThexTemplateManager::_create_project_godot(const String &p_name, const Ref &p_template) { String content; content += "; Engine configuration file.\n"; content += "; Generated by AeThex Engine Template System\n\n"; content += "[application]\n\n"; content += "config/name=\"" + p_name + "\"\n"; content += "config/description=\"Created with AeThex Engine\"\n"; content += "config/icon=\"res://icon.svg\"\n\n"; content += "[aethex]\n\n"; content += "template/id=\"" + p_template->get_id() + "\"\n"; content += "template/version=\"" + p_template->get_version() + "\"\n"; // Add platform targets uint32_t platforms = p_template->get_platforms(); PackedStringArray platform_names; if (platforms & AeThexTemplate::PLATFORM_ROBLOX) platform_names.push_back("roblox"); if (platforms & AeThexTemplate::PLATFORM_UEFN) platform_names.push_back("uefn"); if (platforms & AeThexTemplate::PLATFORM_UNITY) platform_names.push_back("unity"); if (platforms & AeThexTemplate::PLATFORM_WEB) platform_names.push_back("web"); if (platforms & AeThexTemplate::PLATFORM_AETHEX) platform_names.push_back("aethex"); content += "export/targets=["; for (int i = 0; i < platform_names.size(); i++) { if (i > 0) content += ", "; content += "\"" + platform_names[i] + "\""; } content += "]\n\n"; content += "[rendering]\n\n"; content += "renderer/rendering_method=\"forward_plus\"\n"; return content; } // =========================================================== // Built-in Template Factory Functions // =========================================================== namespace AeThexBuiltinTemplates { Ref create_empty_project() { Ref t; t.instantiate(); t->set_id("builtin_empty"); t->set_template_name("Empty Project"); t->set_description("A minimal project with just the essentials. Perfect for starting from scratch."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_ALL); t->set_difficulty(AeThexTemplate::DIFFICULTY_BEGINNER); t->set_is_builtin(true); PackedStringArray tags; tags.push_back("empty"); tags.push_back("starter"); tags.push_back("minimal"); t->set_tags(tags); Dictionary files; files["main.aethex"] = R"(// AeThex Main Script // Your cross-platform game starts here! reality Game { journey start() { reveal("Hello from AeThex!") } } )"; files["icon.svg"] = R"()"; t->set_file_structure(files); return t; } Ref create_2d_platformer() { Ref t; t.instantiate(); t->set_id("builtin_2d_platformer"); t->set_template_name("2D Platformer"); t->set_description("Classic 2D platformer with player movement, jumping, and basic level design."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_ALL); t->set_difficulty(AeThexTemplate::DIFFICULTY_BEGINNER); t->set_is_builtin(true); PackedStringArray tags; tags.push_back("2d"); tags.push_back("platformer"); tags.push_back("sidescroller"); t->set_tags(tags); PackedStringArray features; features.push_back("Player movement & jumping"); features.push_back("Tilemap-based levels"); features.push_back("Collectibles"); features.push_back("Basic enemies"); t->set_features(features); Dictionary files; files["player.aethex"] = R"(// Player Controller - Cross-platform reality Player { beacon position: Vector2 = (0, 0) beacon velocity: Vector2 = (0, 0) beacon speed: Number = 300 beacon jump_force: Number = -600 beacon gravity: Number = 1200 beacon is_grounded: Boolean = false journey update(delta: Number) { // Horizontal movement artifact move_dir = get_input_axis("move_left", "move_right") velocity.x = move_dir * speed // Gravity velocity.y += gravity * delta // Jump if is_grounded and is_action_pressed("jump") { velocity.y = jump_force } // Apply movement sync across { position += velocity * delta } } } )"; files["level.aethex"] = R"(// Level Controller reality Level { beacon player: Player beacon collectibles: Array = [] beacon score: Number = 0 journey start() { player = spawn(Player, (100, 300)) setup_tilemap() } journey on_collectible_picked(item) { score += item.value notify("score_changed", score) } } )"; t->set_file_structure(files); return t; } Ref create_3d_fps() { Ref t; t.instantiate(); t->set_id("builtin_3d_fps"); t->set_template_name("3D First Person"); t->set_description("First-person 3D template with mouselook, movement, and basic shooting mechanics."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_AETHEX | AeThexTemplate::PLATFORM_UEFN | AeThexTemplate::PLATFORM_UNITY); t->set_difficulty(AeThexTemplate::DIFFICULTY_INTERMEDIATE); t->set_is_builtin(true); PackedStringArray tags; tags.push_back("3d"); tags.push_back("fps"); tags.push_back("shooter"); t->set_tags(tags); return t; } Ref create_rpg_starter() { Ref t; t.instantiate(); t->set_id("builtin_rpg_starter"); t->set_template_name("RPG Starter Kit"); t->set_description("Complete RPG foundation with inventory, dialogue, and combat systems."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_ALL); t->set_difficulty(AeThexTemplate::DIFFICULTY_ADVANCED); t->set_is_builtin(true); return t; } Ref create_multiplayer_game() { Ref t; t.instantiate(); t->set_id("builtin_multiplayer"); t->set_template_name("Multiplayer Game"); t->set_description("Network-enabled game template with lobby, matchmaking, and sync."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_ALL); t->set_difficulty(AeThexTemplate::DIFFICULTY_ADVANCED); t->set_is_builtin(true); return t; } Ref create_roblox_experience() { Ref t; t.instantiate(); t->set_id("builtin_roblox_experience"); t->set_template_name("Roblox Experience"); t->set_description("Template optimized for Roblox with AeThex-to-Luau compilation."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_ROBLOX | AeThexTemplate::PLATFORM_AETHEX); t->set_difficulty(AeThexTemplate::DIFFICULTY_BEGINNER); t->set_is_builtin(true); Dictionary files; files["game.aethex"] = R"(// Roblox Experience - AeThex Script // Compiles to Luau for Roblox deployment reality RobloxGame { beacon players: Array = [] beacon game_started: Boolean = false journey on_player_join(player) { players.add(player) reveal("Welcome, " + player.name + "!") if players.length >= 2 and not game_started { start_game() } } journey start_game() { game_started = true notify("game_started") // Cross-platform compatible! sync across { reveal("Game starting!") } } } )"; t->set_file_structure(files); return t; } Ref create_uefn_island() { Ref t; t.instantiate(); t->set_id("builtin_uefn_island"); t->set_template_name("UEFN Island"); t->set_description("Fortnite Creative island template with AeThex-to-Verse compilation."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_UEFN | AeThexTemplate::PLATFORM_AETHEX); t->set_difficulty(AeThexTemplate::DIFFICULTY_BEGINNER); t->set_is_builtin(true); return t; } Ref create_unity_mobile() { Ref t; t.instantiate(); t->set_id("builtin_unity_mobile"); t->set_template_name("Unity Mobile"); t->set_description("Mobile game template targeting Unity export with touch controls."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_UNITY | AeThexTemplate::PLATFORM_AETHEX); t->set_difficulty(AeThexTemplate::DIFFICULTY_INTERMEDIATE); t->set_is_builtin(true); return t; } Ref create_web_game() { Ref t; t.instantiate(); t->set_id("builtin_web_game"); t->set_template_name("Web Game"); t->set_description("Browser-based game with HTML5 export and JavaScript compilation."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_WEB | AeThexTemplate::PLATFORM_AETHEX); t->set_difficulty(AeThexTemplate::DIFFICULTY_BEGINNER); t->set_is_builtin(true); return t; } Ref create_crossplatform_game() { Ref t; t.instantiate(); t->set_id("builtin_crossplatform"); t->set_template_name("Cross-Platform Game"); t->set_description("Ultimate template supporting ALL platforms: Roblox, UEFN, Unity, and Web."); t->set_author("AeThex Labs"); t->set_version("1.0.0"); t->set_category(AeThexTemplate::CATEGORY_GAME); t->set_platforms(AeThexTemplate::PLATFORM_ALL); t->set_difficulty(AeThexTemplate::DIFFICULTY_INTERMEDIATE); t->set_is_builtin(true); PackedStringArray features; features.push_back("Write once, deploy everywhere"); features.push_back("Platform-specific optimizations"); features.push_back("Unified networking layer"); features.push_back("Asset pipeline for all platforms"); t->set_features(features); Dictionary files; files["main.aethex"] = R"(// Cross-Platform AeThex Game // This code runs on Roblox, UEFN, Unity, Web, and native AeThex! reality CrossPlatformGame { beacon score: Number = 0 beacon player_name: String = "Player" journey start() { reveal("Welcome to Cross-Platform Gaming!") reveal("Running on: " + get_platform()) setup_game() } journey setup_game() { // Platform-agnostic game logic sync across { spawn_player() load_level(1) } } journey add_score(points: Number) { score += points notify("score_updated", score) // Sync across all platforms and clients sync across { update_leaderboard(player_name, score) } } } )"; files["player.aethex"] = R"(// Universal Player Controller reality Player { beacon health: Number = 100 beacon position: Vector3 = (0, 0, 0) journey take_damage(amount: Number) { health -= amount if health <= 0 { on_death() } } journey on_death() { notify("player_died") respawn() } } )"; t->set_file_structure(files); return t; } } // namespace AeThexBuiltinTemplates