package dynamic import ( "encoding/json" "fmt" "os" "strings" components "github.com/DegustatorPonos/RuinesOfRafdolon/Components" descriptors "github.com/DegustatorPonos/RuinesOfRafdolon/Dynamic/Descriptors" settings "github.com/DegustatorPonos/RuinesOfRafdolon/Settings" utils "github.com/DegustatorPonos/RuinesOfRafdolon/Utils" rl "github.com/gen2brain/raylib-go/raylib" ) // The defenition of the package type PackageDescription struct { Name string `json:"name"` Version *settings.AppVersion `json:"version"` Type string `json:"type"` MinimumGameVersion *settings.AppVersion MaximumGameVersion *settings.AppVersion } func (base *PackageDescription) IsValid() error { if base.Name == "" { return fmt.Errorf("Invalid package descriptor: the name cannot be empty") } if !base.Version.IsValid() { return fmt.Errorf("Invalid package descriptor: the version cannot be 0") } if base.MinimumGameVersion == nil { return fmt.Errorf("Invalid package descriptor: The package must specify the minimal version") } if !base.MinimumGameVersion.IsCompatible() || (base.MaximumGameVersion != nil && !base.MaximumGameVersion.IsLessThan(&settings.Current.Version)){ return fmt.Errorf("Invalid package descriptor: The package is made for the newer or older version") } return nil } // The dynamic collection of the things type Package struct { Description PackageDescription Textures *descriptors.TexturesDescriptor Objects map[string]*descriptors.ObjectDescriptor Worlds map[string]*descriptors.WorldDescriptor // Dynamic menus don't have descriptors - they self-reload Menus map[string]*DynamicMenu location string } func (base *Package) String() string { var outp, jsonErr = json.Marshal(base) if jsonErr != nil { return fmt.Sprintf("Failed to parse package: %s", jsonErr.Error()) } return string(outp) } func ReadPackage(dir os.DirEntry) (*Package, error) { var dirPath = utils.JoinFileLocation(settings.Current.PackagesLocation, dir.Name()) var desc = PackageDescription{} if descErr := ReadValidJSONfromFile(utils.JoinFileLocation(dirPath, "Description.json"), &desc); descErr != nil { return nil, descErr } return &Package{ Description: desc, }, nil } // Loads package in the memory func (base *Package) Load() { // Do not change the order - it will break inner dependencies base.LoadTextures() base.LoadObjects() base.LoadWorlds() base.LoadMenus() } func loadAssetsFromDir[T Validatable](dirLocation string, InitializeFunc func()T) ([]T, error) { var files, err = os.ReadDir(dirLocation) if err != nil { return nil, err } var outp = make([]T, 0) for _, v := range files { var new = InitializeFunc() var loadErr = ReadValidJSONfromFile(utils.JoinFileLocation(dirLocation, v.Name()), new) if loadErr != nil { rl.TraceLog(rl.LogWarning, "Failed to load the asset %s: %s", v.Name(), loadErr) continue } outp = append(outp, new) } return outp, nil } func (base *Package) ReadTextures() { var textures = &descriptors.TexturesDescriptor{} var texturesLocation = utils.JoinFileLocation(base.location, descriptors.TexturesDescriptorDirectoryName) var loadErr = utils.ReadJSONfromFile(utils.JoinFileLocation(texturesLocation, descriptors.TexturesDescriptorFileName), textures) if loadErr != nil { rl.TraceLog(rl.LogWarning, "Failed to load texture description from the module %s: %s", base.location, loadErr) } base.Textures = textures } // Loads textures in the resource manager func (base *Package) LoadTextures() { for _, v := range base.Textures.Avaliable { var location = base.Textures.GetTexturePath(base.location, v) var displayName = fmt.Sprintf("%v/%v", base.Description.Name, v) components.Resources.Textures.LoadTexture(location, displayName) } } func (base *Package) ReadWorlds() { if base.Description.Type != "story" { return } var worlds, loadErr = loadWorldsFromDir(utils.JoinFileLocation(base.location, descriptors.WorldsDirName)) if loadErr != nil { rl.TraceLog(rl.LogWarning, "Failed to load world from the module %s: %s", base.location, loadErr) } else { base.Worlds = worlds } } // Loads worlds in the resource manager func (base *Package) LoadWorlds() { if base.Description.Type != "story" { return } for _, v := range base.Worlds { var parsed = v.Parse() components.Resources.Scenes[v.Name] = &parsed } } func (base *Package) ReadObjects() { var loaded, err = loadObjectsFromDir(utils.JoinFileLocation(base.location, descriptors.ObjectsDirName)) if err != nil { rl.TraceLog(rl.LogWarning, "Failed to load objects from the module %s: %s", base.location, err) return } base.Objects = loaded } // Loads objects in the resource manager func (base *Package) LoadObjects() { rl.TraceLog(rl.LogInfo, "%s", base.Objects) for _, v := range base.Objects { var parsed = v.Parse() var err = components.Resources.RegisterObject(base.Description.Name, v.Name, &parsed) if err != nil { rl.TraceLog(rl.LogWarning, "Failed to register object %s: %s", v.Name, err.Error()) } } } func loadWorldsFromDir(dirLocation string) (map[string]*descriptors.WorldDescriptor, error) { var loaded, err = loadAssetsFromDir(dirLocation, func() *descriptors.WorldDescriptor{ return &descriptors.WorldDescriptor{} }) if err != nil { return nil, err } return descriptors.MapWorldDescriptors(loaded), nil } func loadObjectsFromDir(dir string) (map[string]*descriptors.ObjectDescriptor, error) { var loaded, err = loadAssetsFromDir(dir, func() *descriptors.ObjectDescriptor { return &descriptors.ObjectDescriptor {} }) if err != nil { return nil, err } return descriptors.MapObjectDescriptors(loaded), nil } func (base *Package) ReadMenus() { var loaded, err = loadMenusFromDir(utils.JoinFileLocation(base.location, MenusDirName)) if err != nil { rl.TraceLog(rl.LogWarning, "Failed to load menus from the module %s: %s", base.location, err) return } base.Menus = loaded } func loadMenusFromDir(dir string) (map[string]*DynamicMenu, error) { var files, err = os.ReadDir(dir) if err != nil { return nil, err } var outp = make(map[string]*DynamicMenu) for _, v := range files { if v.IsDir() { rl.TraceLog(rl.LogWarning, "Failed to load menu %s: the file is a directory", v.Name()) continue } var menuName = strings.TrimSuffix(v.Name(), ".json") outp[menuName] = &DynamicMenu{ FileLocation: utils.JoinFileLocation(dir, v.Name()), } } return outp, nil } func (base *Package) LoadMenus() { for k, v := range base.Menus { components.Resources.Scenes[fmt.Sprintf("%s/%s", base.Description.Name, k)] = v } }