Exemple #1
0
        /// <summary>The event called after a save slot is loaded.</summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void OnSaveLoaded(object sender, SaveLoadedEventArgs e)
        {
            // load legacy data
            if (Context.IsMainPlayer)
            {
                this.LoadLegacyData();
            }

            // check if mod should be enabled for the current player
            this.IsEnabled = Context.IsMainPlayer;
            if (!this.IsEnabled)
            {
                ISemanticVersion hostVersion = this.Helper.Multiplayer.GetConnectedPlayer(Game1.MasterPlayer.UniqueMultiplayerID)?.GetMod(this.ModManifest.UniqueID)?.Version;
                if (hostVersion == null)
                {
                    this.IsEnabled = false;
                    this.Monitor.Log("This mod is disabled because the host player doesn't have it installed.", LogLevel.Warn);
                }
                else if (hostVersion.IsOlderThan(this.MinHostVersion))
                {
                    this.IsEnabled = false;
                    this.Monitor.Log($"This mod is disabled because the host player has {this.ModManifest.Name} {hostVersion}, but the minimum compatible version is {this.MinHostVersion}.", LogLevel.Warn);
                }
                else
                {
                    this.IsEnabled = true;
                }
            }
        }
Exemple #2
0
        /*********
        ** Public methods
        *********/
        /// <summary>Set backwards-compatible fields.</summary>
        /// <param name="version">The requested API version.</param>
        public void SetBackwardsCompatibility(ISemanticVersion version)
        {
            if (version.IsOlderThan("2.6-beta.19"))
            {
                this.Version = this.Main?.Version?.ToString();
                this.Url     = this.Main?.Url;

                this.PreviewVersion = this.Optional?.Version?.ToString();
                this.PreviewUrl     = this.Optional?.Url;
            }
        }
Exemple #3
0
        /// <summary>
        /// Apply rewrites for fields in patches
        /// </summary>
        /// <param name="change"></param>
        /// <param name="formatVersion"></param>
        /// <returns></returns>
        private List <string> ApplyPatchRewrites(LegacyChanges change, ISemanticVersion formatVersion)
        {
            List <string> notices = new List <string>();

            if (formatVersion.IsOlderThan("1.2") && !string.IsNullOrEmpty(change.Locale))
            {
                // Locales exists in format version 1.2 and newer. For older formats is locale undefined
                change.Locale = null;
                notices.Add($"Ignore field `Locale` in format version `{formatVersion}`");
            }

            if (formatVersion.IsOlderThan("1.2") && !string.IsNullOrEmpty(change.LogName))
            {
                // Locales exists in format version 1.2 and newer. For older formats is locale undefined
                change.LogName = null;
                notices.Add($"Ignore field `LogName` in format version `{formatVersion}`");
            }

            if (!formatVersion.IsOlderThan("1.3") && string.IsNullOrEmpty(change.Action))
            {
                change.Action = "Patch"; // Action patch is a default action in format >=1.3
            }

            if (formatVersion.IsOlderThan("1.3") && (change.Action == "Load" || change.Action == "Edit"))
            {
                var replace = change.Action == "Load" ? "Replace" : "Patch";

                notices.Add($"Rewrite action `{change.Action}` -> `{replace}`");
                change.Action = replace;
            }

            if (change.Action == "Replace")
            {
                notices.Add($"Detected content replacer `{change.LogName}` for `{change.Target}`");
            }

            return(notices);
        }
Exemple #4
0
        /*********
        ** Public methods
        *********/
        /// <summary>Get whether the specified version is compatible according to this metadata.</summary>
        /// <param name="version">The current version of the matching mod.</param>
        public bool IsCompatible(ISemanticVersion version)
        {
            ISemanticVersion lowerVersion = this.LowerVersion != null ? new SemanticVersion(this.LowerVersion) : null;
            ISemanticVersion upperVersion = new SemanticVersion(this.UpperVersion);

            // ignore versions not in range
            if (lowerVersion != null && version.IsOlderThan(lowerVersion))
            {
                return(true);
            }
            if (version.IsNewerThan(upperVersion))
            {
                return(true);
            }

            // allow versions matching override
            return(!string.IsNullOrWhiteSpace(this.ForceCompatibleVersion) && Regex.IsMatch(version.ToString(), this.ForceCompatibleVersion, RegexOptions.IgnoreCase));
        }
        /// <summary>Register API stuff for Generic Mod Config Menu.</summary>
        internal static void SetUpMenu()
        {
            var api = Helper.ModRegistry.GetApi <GenericModConfigMenu.IApi>
                          ("spacechase0.GenericModConfigMenu");

            if (api == null)
            {
                return;
            }

            ISemanticVersion gmcm = Helper.ModRegistry.Get("spacechase0.GenericModConfigMenu").Manifest.Version;
            ISemanticVersion req  = new SemanticVersion("1.1.0");

            if (gmcm.IsOlderThan(req))
            {
                Monitor.Log($"Installed version {gmcm} of GMCM is not supported. Please update Generic Mod Config Menu to version {req} or newer to enable its features for Dynamic Conversation Topics mod.", LogLevel.Warn);
                return;
            }

            var manifest = ModEntry.Instance.ModManifest;

            api.RegisterModConfig(manifest, Reset, Save);

            api.RegisterLabel(manifest,
                              //i18n.Get("DialogueOptions.title"),
                              "Debugging Tools",
                              "");

            api.RegisterSimpleOption(manifest,
                                     "Debug mode",                                                                  //i18n.Get("GenderNeutrality.name"),
                                     "Visible console logging, conversation topic alerts, and dialogue box labels", //i18n.Get("GenderNeutrality.description"),
                                     () => Instance.DebugMode,
                                     (bool val) => Instance.DebugMode = val);

            Monitor.Log("Added DCT config to GMCM", LogLevel.Info);
        }
Exemple #6
0
        /****
        ** Condition parsing
        ****/
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="formatVersion">The format version specified by the content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        public bool TryParseConditions(InvariantDictionary <string> raw, ISemanticVersion formatVersion, ISemanticVersion latestFormatVersion, out ConditionDictionary conditions, out string error)
        {
            // no conditions
            if (raw == null || !raw.Any())
            {
                conditions = this.ConditionFactory.BuildEmpty();
                error      = null;
                return(true);
            }

            // parse conditions
            conditions = this.ConditionFactory.BuildEmpty();
            foreach (KeyValuePair <string, string> pair in raw)
            {
                // parse condition key
                if (!ConditionKey.TryParse(pair.Key, out ConditionKey key))
                {
                    error      = $"'{pair.Key}' isn't a valid condition; must be one of {string.Join(", ", Enum.GetValues(typeof(ConditionType)))}";
                    conditions = null;
                    return(false);
                }

                // validate types which require an ID
                if (this.TypesRequireID.Contains(key.Type) && string.IsNullOrWhiteSpace(key.ForID))
                {
                    error      = $"{key.Type} conditions must specify a separate ID (see readme for usage)";
                    conditions = null;
                    return(false);
                }

                // check compatibility
                foreach (var versionPair in this.MinimumVersions)
                {
                    if (formatVersion.IsOlderThan(versionPair.Key) && versionPair.Value.Contains(key.Type))
                    {
                        error      = $"{key} isn't available with format version {formatVersion} (change the {nameof(ContentConfig.Format)} field to {latestFormatVersion} to use newer features)";
                        conditions = null;
                        return(false);
                    }
                }

                // parse values
                InvariantHashSet values = this.ParseCommaDelimitedField(pair.Value);
                if (!values.Any())
                {
                    error      = $"{key} can't be empty";
                    conditions = null;
                    return(false);
                }

                // restrict to allowed values
                string[] rawValidValues = this.ConditionFactory.GetValidValues(key)?.ToArray();
                if (rawValidValues?.Any() == true)
                {
                    InvariantHashSet validValues = new InvariantHashSet(rawValidValues);
                    {
                        string[] invalidValues = values.Except(validValues, StringComparer.InvariantCultureIgnoreCase).ToArray();
                        if (invalidValues.Any())
                        {
                            error      = $"invalid {key} values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                            conditions = null;
                            return(false);
                        }
                    }
                }

                // create condition
                conditions[key] = new Condition(key, values);
            }

            // return parsed conditions
            error = null;
            return(true);
        }
Exemple #7
0
 /// <summary>Get whether a <paramref name="current"/> version is newer than an <paramref name="other"/> version.</summary>
 /// <param name="current">The current version.</param>
 /// <param name="other">The other version.</param>
 private bool IsNewer(ISemanticVersion current, ISemanticVersion other)
 {
     return(current != null && (other == null || other.IsOlderThan(current)));
 }
 /// <summary>Get whether a given version is contained within this compatibility range.</summary>
 /// <param name="version">The version to check.</param>
 public bool MatchesVersion(ISemanticVersion version)
 {
     return
         ((this.LowerVersion == null || !version.IsOlderThan(this.LowerVersion)) &&
          (this.UpperVersion == null || !version.IsNewerThan(this.UpperVersion)));
 }
        /*********
        ** Private methods
        *********/
        /// <summary>Parse a raw config schema for a content pack.</summary>
        /// <param name="rawSchema">The raw config schema.</param>
        /// <param name="logWarning">The callback to invoke on each validation warning, passed the field name and reason respectively.</param>
        /// <param name="formatVersion">The content format version.</param>
        private InvariantDictionary <ConfigField> LoadConfigSchema(InvariantDictionary <ConfigSchemaFieldConfig?>?rawSchema, Action <string, string> logWarning, ISemanticVersion formatVersion)
        {
            InvariantDictionary <ConfigField> schema = new();

            if (rawSchema == null || !rawSchema.Any())
            {
                return(schema);
            }

            foreach (string rawKey in rawSchema.Keys)
            {
                ConfigSchemaFieldConfig?field = rawSchema[rawKey];
                if (field is null)
                {
                    continue;
                }

                // validate format
                if (string.IsNullOrWhiteSpace(rawKey))
                {
                    logWarning(rawKey, "the config field name can't be empty.");
                    continue;
                }
                if (rawKey.Contains(InternalConstants.PositionalInputArgSeparator) || rawKey.Contains(InternalConstants.NamedInputArgSeparator))
                {
                    logWarning(rawKey, $"the name '{rawKey}' can't have input arguments ({InternalConstants.PositionalInputArgSeparator} or {InternalConstants.NamedInputArgSeparator} character).");
                    continue;
                }

                // validate reserved keys
                if (Enum.TryParse <ConditionType>(rawKey, true, out _))
                {
                    logWarning(rawKey, $"can't use {rawKey} as a config field, because it's a reserved condition key.");
                    continue;
                }

                // read allowed/default values
                IInvariantSet allowValues   = this.ParseCommaDelimitedField(field.AllowValues);
                IInvariantSet defaultValues = this.ParseCommaDelimitedField(field.Default);

                // pre-1.7 behaviour
                if (formatVersion.IsOlderThan("1.7"))
                {
                    // allowed values are required
                    if (!allowValues.Any())
                    {
                        logWarning(rawKey, $"no {nameof(ConfigSchemaFieldConfig.AllowValues)} specified (and format version is less than 1.7).");
                        continue;
                    }

                    // inject default if needed
                    if (!defaultValues.Any() && !field.AllowBlank)
                    {
                        defaultValues = InvariantSets.FromValue(allowValues.First());
                    }
                }

                // validate allowed values
                if (!field.AllowBlank && !defaultValues.Any())
                {
                    logWarning(rawKey, $"if {nameof(field.AllowBlank)} is false, you must specify {nameof(field.Default)}.");
                    continue;
                }
                if (allowValues.Any() && defaultValues.Any())
                {
                    IInvariantSet invalidValues = defaultValues.GetWithout(allowValues);
                    if (invalidValues.Any())
                    {
                        logWarning(rawKey, $"default values '{string.Join(", ", invalidValues)}' are not allowed according to {nameof(ConfigSchemaFieldConfig.AllowValues)}.");
                        continue;
                    }
                }

                // validate allow multiple
                if (!field.AllowMultiple && defaultValues.Count > 1)
                {
                    logWarning(rawKey, $"can't have multiple default values because {nameof(ConfigSchemaFieldConfig.AllowMultiple)} is false.");
                    continue;
                }

                // add to schema
                schema[rawKey] = new ConfigField(
                    allowValues: allowValues,
                    defaultValues: defaultValues,
                    value: InvariantSets.Empty,
                    allowBlank: field.AllowBlank,
                    allowMultiple: field.AllowMultiple,
                    description: field.Description,
                    section: field.Section
                    );
            }

            return(schema);
        }
Exemple #10
0
        /****
        ** Condition parsing
        ****/
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="formatVersion">The format version specified by the content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        public bool TryParseConditions(InvariantDictionary <string> raw, ISemanticVersion formatVersion, ISemanticVersion latestFormatVersion, out ConditionDictionary conditions, out string error)
        {
            // no conditions
            if (raw == null || !raw.Any())
            {
                conditions = this.ConditionFactory.BuildEmpty();
                error      = null;
                return(true);
            }

            // parse conditions
            conditions = this.ConditionFactory.BuildEmpty();
            foreach (KeyValuePair <string, string> pair in raw)
            {
                // parse condition key
                if (!Enum.TryParse(pair.Key, true, out ConditionKey key))
                {
                    error      = $"'{pair.Key}' isn't a valid condition; must be one of {string.Join(", ", this.ConditionFactory.GetValidConditions())}";
                    conditions = null;
                    return(false);
                }

                // check compatibility
                if (formatVersion.IsOlderThan("1.4"))
                {
                    if (key == ConditionKey.DayEvent || key == ConditionKey.HasFlag || key == ConditionKey.HasSeenEvent || key == ConditionKey.Spouse)
                    {
                        error      = $"{key} isn't available with format version {formatVersion} (change the {nameof(ContentConfig.Format)} field to {latestFormatVersion} to use newer features)";
                        conditions = null;
                        return(false);
                    }
                }

                // parse values
                InvariantHashSet values = this.ParseCommaDelimitedField(pair.Value);
                if (!values.Any())
                {
                    error      = $"{key} can't be empty";
                    conditions = null;
                    return(false);
                }

                // restrict to allowed values
                string[] rawValidValues = this.ConditionFactory.GetValidValues(key)?.ToArray();
                if (rawValidValues?.Any() == true)
                {
                    InvariantHashSet validValues = new InvariantHashSet(rawValidValues);
                    {
                        string[] invalidValues = values.Except(validValues, StringComparer.InvariantCultureIgnoreCase).ToArray();
                        if (invalidValues.Any())
                        {
                            error      = $"invalid {key} values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                            conditions = null;
                            return(false);
                        }
                    }
                }

                // create condition
                conditions[key] = new Condition(key, values);
            }

            // return parsed conditions
            error = null;
            return(true);
        }