package vn import ( "fmt" "os" "path/filepath" "sort" "strings" ) // FlightCheckResult contains script integrity validation results. type FlightCheckResult struct { Errors []string } func (r *FlightCheckResult) OK() bool { return len(r.Errors) == 0 } func (r *FlightCheckResult) Summary(texts *Texts) string { if r.OK() { return texts.FlightCheckPassed } if len(r.Errors) == 1 { return fmt.Sprintf(texts.FlightCheckOneFmt, r.Errors[0]) } return fmt.Sprintf(texts.FlightCheckManyFmt, len(r.Errors)) } func RunFlightCheck(script *Script, texts *Texts) *FlightCheckResult { result := &FlightCheckResult{} seenFiles := map[string]bool{} for _, file := range script.LoadedFiles { if seenFiles[file] { continue } seenFiles[file] = true if _, err := os.Stat(file); err != nil { result.Errors = append(result.Errors, fmt.Sprintf(texts.FlightMissingScriptFmt, file)) } } sceneByID := map[string]Scene{} for _, scene := range script.Scenes { if _, ok := sceneByID[scene.ID]; ok { result.Errors = append(result.Errors, fmt.Sprintf(texts.FlightDuplicateSceneFmt, scene.ID)) continue } sceneByID[scene.ID] = scene } if _, ok := sceneByID[script.Start]; !ok { result.Errors = append(result.Errors, fmt.Sprintf(texts.FlightStartMissingFmt, script.Start)) } assets := map[string]bool{} var walk func(sceneID string, cmds []Command) walk = func(sceneID string, cmds []Command) { for _, cmd := range cmds { switch strings.ToLower(cmd.Type) { case "goto": if cmd.Target != "" { if _, ok := sceneByID[cmd.Target]; !ok { result.Errors = append(result.Errors, fmt.Sprintf(texts.FlightGotoMissingFmt, sceneID, cmd.Target)) } } case "choice": for _, c := range cmd.Choices { if _, ok := sceneByID[c.Target]; !ok { result.Errors = append(result.Errors, fmt.Sprintf(texts.FlightChoiceMissingFmt, sceneID, c.Target)) } } case "if": walk(sceneID, cmd.Then) walk(sceneID, cmd.Else) case "menu": for _, item := range cmd.MenuItems { if item.Sprite != "" { assets[item.Sprite] = true } walk(sceneID, item.Commands) } } if cmd.BackgroundImage != "" { assets[cmd.BackgroundImage] = true } if looksLikeImagePath(cmd.Background) { assets[cmd.Background] = true } if cmd.Sprite != "" { assets[cmd.Sprite] = true } } } for _, scene := range script.Scenes { walk(scene.ID, scene.Commands) } keys := make([]string, 0, len(assets)) for a := range assets { keys = append(keys, a) } sort.Strings(keys) for _, rel := range keys { if strings.Contains(rel, "{") { continue } path := rel if !filepath.IsAbs(path) { path = filepath.Join(script.BaseDir, rel) } if _, err := os.Stat(path); err != nil { result.Errors = append(result.Errors, fmt.Sprintf(texts.FlightMissingAssetFmt, rel)) } } return result }