package config import ( "fmt" "path/filepath" "strings" "gitea.maximumdirect.net/eric/narratio/internal/fileops" ) const maxPartyDocumentBytes = 8 << 20 func resolvePartyInput(input ResolvedInputFile) (ResolvedParty, error) { configuredPath := strings.TrimSpace(input.Path) if configuredPath == "" { return ResolvedParty{}, fmt.Errorf("party input path is required") } configPath := strings.TrimSpace(input.ConfigPath) if configPath == "" { return ResolvedParty{}, fmt.Errorf("party input %q has no declaring configuration path", configuredPath) } path := configuredPath if !filepath.IsAbs(path) { path = filepath.Join(filepath.Dir(configPath), path) } path = filepath.Clean(path) raw, err := fileops.ReadRegularFile(path, maxPartyDocumentBytes) if err != nil { return ResolvedParty{}, fmt.Errorf("read party input %q: %w", path, err) } document, err := ParseParty(raw) if err != nil { return ResolvedParty{}, fmt.Errorf("parse party input %q: %w", path, err) } resolved := ResolvedParty{ Mode: document.Mode, Source: PartySource{ Path: path, ConfigPath: configPath, Source: input.Source, }, Canonical: document.Canonical, } if document.IsCanonical() { return resolved, nil } return resolveLegacyParty(resolved) } func campaignPartyInput(campaignPath string, campaign *CampaignConfig) (ResolvedInputFile, error) { if campaign == nil { return ResolvedInputFile{}, fmt.Errorf("campaign config is required") } if strings.TrimSpace(campaign.Inputs.PartyFile) == "" { return ResolvedInputFile{}, fmt.Errorf("campaign.inputs.party_file is required") } return ResolvedInputFile{ Path: campaign.Inputs.PartyFile, ConfigPath: campaignPath, Source: "campaign_config", }, nil } func resolveCampaignParty(campaignPath string, campaign *CampaignConfig) (ResolvedParty, error) { input, err := campaignPartyInput(campaignPath, campaign) if err != nil { return ResolvedParty{}, err } return resolvePartyInput(input) } func validateCanonicalPartySelection(campaign *CampaignConfig, session *SessionConfig) error { if campaign == nil { return fmt.Errorf("campaign config is required") } if strings.TrimSpace(campaign.Inputs.PlayersFile) != "" { return fmt.Errorf("campaign.inputs.players_file is not allowed with a canonical party") } if session == nil { return nil } if strings.TrimSpace(session.Inputs.PlayersFile) != "" { return fmt.Errorf("session.inputs.players_file is not allowed with a canonical party") } if strings.TrimSpace(session.Inputs.PartyFile) != "" { return fmt.Errorf("session.inputs.party_file cannot override a canonical campaign party") } return nil } func virtualPlayersInput() ResolvedInputFile { return ResolvedInputFile{Source: "derived_from_party"} }