/// <summary>Validate whether a token referenced in a content pack field is valid.</summary> /// <param name="lexToken">The lexical token reference to check.</param> /// <param name="assumeModIds">Mod IDs to assume are installed for purposes of token validation.</param> /// <param name="error">An error phrase indicating why validation failed (if applicable).</param> public bool TryValidateToken(LexTokenToken lexToken, InvariantHashSet assumeModIds, out string error) { // token doesn't exist IToken token = this.Context.GetToken(lexToken.Name, enforceContext: false); if (token == null) { // special case: requires a missing mod that's checked via HasMod string requiredModId = lexToken.GetProviderModId(); if (assumeModIds?.Contains(requiredModId) == true && !this.InstalledMods.Contains(requiredModId)) { error = null; return(true); } // else error error = $"'{lexToken}' can't be used as a token because that token could not be found."; return(false); } // token requires dependency if (token is ModProvidedToken modToken && !this.ForMod.HasDependency(modToken.Mod.UniqueID, canBeOptional: false)) { if (assumeModIds == null || !assumeModIds.Contains(modToken.Mod.UniqueID)) { error = $"'{lexToken}' can't be used because it's provided by {modToken.Mod.Name} (ID: {modToken.Mod.UniqueID}), but {this.ForMod.Name} doesn't list it as a dependency and the patch doesn't have an immutable {ConditionType.HasMod} condition for that mod."; return(false); } } // no error found error = null; return(true); }
/// <summary>Parse a boolean <see cref="PatchConfig.Enabled"/> value from a string which can contain tokens, and validate that it's valid.</summary> /// <param name="rawValue">The raw string which may contain tokens.</param> /// <param name="tokenContext">The tokens available for this content pack.</param> /// <param name="migrator">The migrator which validates and migrates content pack data.</param> /// <param name="error">An error phrase indicating why parsing failed (if applicable).</param> /// <param name="parsed">The parsed value.</param> private bool TryParseEnabled(string rawValue, IContext tokenContext, IMigration migrator, out string error, out bool parsed) { parsed = false; // analyse string if (!this.TryParseStringTokens(rawValue, tokenContext, migrator, out error, out ITokenString tokenString)) { return(false); } // validate & extract tokens string text = rawValue; if (tokenString.HasAnyTokens) { // only one token allowed if (!tokenString.IsSingleTokenOnly) { error = "can't be treated as a true/false value because it contains multiple tokens."; return(false); } // parse token LexTokenToken lexToken = tokenString.GetTokenPlaceholders(recursive: false).Single(); IToken token = tokenContext.GetToken(lexToken.Name, enforceContext: false); ITokenString input = new TokenString(lexToken.InputArg, tokenContext); // check token options InvariantHashSet allowedValues = token?.GetAllowedValues(input); if (token == null || token.IsMutable || !token.IsReady) { error = $"can only use static tokens in this field, consider using a {nameof(PatchConfig.When)} condition instead."; return(false); } if (allowedValues == null || !allowedValues.All(p => bool.TryParse(p, out _))) { error = "that token isn't restricted to 'true' or 'false'."; return(false); } if (token.CanHaveMultipleValues(input)) { error = "can't be treated as a true/false value because that token can have multiple values."; return(false); } text = token.GetValues(input).First(); } // parse text if (!bool.TryParse(text, out parsed)) { error = $"can't parse {tokenString.Raw} as a true/false value."; return(false); } return(true); }
/// <inheritdoc /> public override bool TryMigrate(ref ILexToken lexToken, [NotNullWhen(false)] out string?error) { if (!base.TryMigrate(ref lexToken, out error)) { return(false); } // ignore non-tokens if (lexToken is not LexTokenToken token || !Enum.TryParse(token.Name, ignoreCase: true, out ConditionType type)) { return(true); } // 1.24 changes input arguments for player tokens if (token.HasInputArgs()) { bool wasPlayerToken = this.IsOldPlayerToken(type); bool isNewPlayerToken = !wasPlayerToken && this.IsNewPlayerToken(type); if (wasPlayerToken || isNewPlayerToken) { var input = new InputArguments(new LiteralString(token.InputArgs.ToString(), new LogPathBuilder())); // can't use player ID before 1.24 if (wasPlayerToken && long.TryParse(input.PositionalArgs.FirstOrDefault(), out _)) { error = this.GetNounPhraseError($"using {type} token with a player ID"); return(false); } // didn't accept input before 1.24 if (isNewPlayerToken && input.PositionalArgs.Any()) { error = this.GetNounPhraseError($"using {type} token with input arguments"); return(false); } } } // 1.24 splits {{Roommate}} token out of {{Spouse}} if (type == ConditionType.Spouse) { lexToken = new LexTokenToken( name: nameof(ConditionType.Merge), inputArgs: new LexTokenInput(new ILexToken[] { new LexTokenToken(nameof(ConditionType.Roommate), null, impliedBraces: false), new LexTokenLiteral(","), new LexTokenToken(nameof(ConditionType.Spouse), token.InputArgs, impliedBraces: false) }), impliedBraces: token.ImpliedBraces ); } return(true); }
/// <inheritdoc /> public override bool TryMigrate(ref ILexToken lexToken, [NotNullWhen(false)] out string?error) { if (!base.TryMigrate(ref lexToken, out error)) { return(false); } string weatherName = nameof(ConditionType.Weather); // 1.20 adds input arguments for Weather token, and changes default from valley to current context if (lexToken is LexTokenToken token && token.Name.EqualsIgnoreCase(weatherName)) { // can't add positional input args before 1.20 if (token.HasInputArgs()) { var input = new InputArguments(new LiteralString(token.InputArgs.ToString(), new LogPathBuilder())); if (input.HasPositionalArgs) { error = this.GetNounPhraseError($"using {weatherName} token with an input argument"); return(false); } } // default to valley ILexToken[] valleyArg = new ILexToken[] { new LexTokenLiteral(LocationContext.Valley.ToString()) }; ILexToken[]? inputParts = token.InputArgs?.Parts; lexToken = new LexTokenToken( name: token.Name, inputArgs: new LexTokenInput( inputParts?.Any() == true ? valleyArg.Concat(inputParts).ToArray() : valleyArg ), impliedBraces: true ); } return(true); }