Replace all raw string comparisons with StringKey, source generator that creates cache keys for all names for Gen7 plugin
All checks were successful
Build / Build (push) Successful in 2m21s
All checks were successful
Build / Build (push) Successful in 2m21s
This commit is contained in:
@@ -3,6 +3,7 @@ using System.CommandLine.Parsing;
|
||||
using PkmnLib.Dynamic.AI;
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Plugin.Gen7;
|
||||
using PkmnLib.Static.Utils;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
@@ -93,7 +94,8 @@ internal static class Program
|
||||
return;
|
||||
}
|
||||
|
||||
var ai = _availableAIs!.FirstOrDefault(a => a.Name == aiName);
|
||||
StringKey aiKey = aiName;
|
||||
var ai = _availableAIs!.FirstOrDefault(a => a.Name == aiKey);
|
||||
if (ai == null)
|
||||
{
|
||||
result.AddError(
|
||||
|
||||
@@ -20,5 +20,14 @@
|
||||
<PackageVersion Include="TUnit" Version="1.58.0" />
|
||||
<PackageVersion Include="PolySharp" Version="1.15" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2026.2.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -18,6 +18,7 @@ public partial class ExplicitAI
|
||||
private static readonly StringKey ShieldsDownName = "shields_down";
|
||||
private static readonly StringKey HarshSunlightName = "harsh_sunlight";
|
||||
private static readonly StringKey DesolateLandsName = "desolate_lands";
|
||||
private static readonly StringKey PrimordialSeaName = "primordial_sea";
|
||||
private static readonly StringKey BulletproofName = "bulletproof";
|
||||
private static readonly StringKey FlashFireName = "flash_fire";
|
||||
private static readonly StringKey LightningRodName = "lightning_rod";
|
||||
@@ -33,6 +34,8 @@ public partial class ExplicitAI
|
||||
private static readonly StringKey FireName = "fire";
|
||||
private static readonly StringKey ElectricName = "electric";
|
||||
private static readonly StringKey WaterName = "water";
|
||||
private static readonly StringKey KomalaName = "komala";
|
||||
private static readonly StringKey MiniorName = "minior";
|
||||
|
||||
private static bool CanBePoisoned(IPokemon pokemon, IBattle battle)
|
||||
{
|
||||
@@ -49,9 +52,9 @@ public partial class ExplicitAI
|
||||
if ((pokemon.ActiveAbility?.Name == LeafGuardName && battle.WeatherName == HarshSunlightName) ||
|
||||
battle.WeatherName == DesolateLandsName)
|
||||
return false;
|
||||
if (pokemon.ActiveAbility?.Name == ComatoseName && pokemon.Species.Name == "komala")
|
||||
if (pokemon.ActiveAbility?.Name == ComatoseName && pokemon.Species.Name == KomalaName)
|
||||
return false;
|
||||
if (pokemon.ActiveAbility?.Name == ShieldsDownName && pokemon.Species.Name == "minior" &&
|
||||
if (pokemon.ActiveAbility?.Name == ShieldsDownName && pokemon.Species.Name == MiniorName &&
|
||||
pokemon.Form.Name.Contains("-meteor"))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
@@ -313,9 +313,9 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
|
||||
}
|
||||
|
||||
// Primal weather
|
||||
if (battle.WeatherName == "primordial_sea" && aiMove.Move.MoveType.Name == "fire")
|
||||
if (battle.WeatherName == PrimordialSeaName && aiMove.Move.MoveType.Name == FireName)
|
||||
return true;
|
||||
if (battle.WeatherName == "desolate_lands" && aiMove.Move.MoveType.Name == "water")
|
||||
if (battle.WeatherName == DesolateLandsName && aiMove.Move.MoveType.Name == WaterName)
|
||||
return true;
|
||||
|
||||
// Check if the move will fail based on the handlers
|
||||
@@ -453,13 +453,16 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
|
||||
return true;
|
||||
}
|
||||
|
||||
private static readonly StringKey TruantName = "truant";
|
||||
private static readonly StringKey TruantEffectName = "truant_effect";
|
||||
|
||||
private static bool CanAttack(IPokemon pokemon)
|
||||
{
|
||||
if (pokemon.Volatile.Contains("requires_recharge"))
|
||||
return false;
|
||||
if (pokemon.HasStatus("frozen") || pokemon.HasStatus("sleep"))
|
||||
return false;
|
||||
if (pokemon.ActiveAbility?.Name == "truant" && pokemon.Volatile.Contains("truant_effect"))
|
||||
if (pokemon.ActiveAbility?.Name == TruantName && pokemon.Volatile.Contains(TruantEffectName))
|
||||
return false;
|
||||
if (pokemon.Volatile.Contains("flinch_effect"))
|
||||
return false;
|
||||
|
||||
@@ -467,7 +467,7 @@ public class BattleImpl : ScriptSource, IBattle
|
||||
var oldWeatherName = WeatherScript.Script?.Name;
|
||||
if (weatherName.HasValue)
|
||||
{
|
||||
if (weatherName == oldWeatherName)
|
||||
if (oldWeatherName == weatherName.Value)
|
||||
{
|
||||
// Extend duration of existing weather
|
||||
if (_weatherScript.Script is ILimitedTurnsScript existingWeatherScript)
|
||||
|
||||
@@ -1482,12 +1482,14 @@ public class PokemonImpl : ScriptSource, IPokemon
|
||||
return true;
|
||||
}
|
||||
|
||||
private static readonly StringKey FlyingTypeName = "flying";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsFloating
|
||||
{
|
||||
get
|
||||
{
|
||||
var isFloating = Types.Any(x => x.Name == "flying");
|
||||
var isFloating = Types.Any(x => x.Name == FlyingTypeName);
|
||||
this.RunScriptHook<IScriptIsFloating>(x => x.IsFloating(this, ref isFloating));
|
||||
return isFloating;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/Plugins/">
|
||||
<Project Path="Plugins\PkmnLib.Plugin.Gen7.SourceGen\PkmnLib.Plugin.Gen7.SourceGen.csproj" Type="Classic C#" />
|
||||
<Project Path="Plugins\PkmnLib.Plugin.Gen7.Tests\PkmnLib.Plugin.Gen7.Tests.csproj" Type="Classic C#" />
|
||||
<Project Path="Plugins\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj" Type="Classic C#" />
|
||||
</Folder>
|
||||
|
||||
@@ -58,7 +58,7 @@ public readonly struct StringKey : IEquatable<StringKey>, IEquatable<string>
|
||||
return obj switch
|
||||
{
|
||||
StringKey other => Equals(other),
|
||||
string str => Equals(str),
|
||||
string str => string.Equals(_key, str, StringComparison.InvariantCultureIgnoreCase),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
@@ -67,16 +67,22 @@ public readonly struct StringKey : IEquatable<StringKey>, IEquatable<string>
|
||||
public bool Equals(StringKey other) => _hashCode == other._hashCode;
|
||||
|
||||
/// <inheritdoc cref="Equals(StringKey)"/>
|
||||
[Obsolete("Comparing a StringKey to a string uses a slow culture-aware string comparison. " +
|
||||
"Compare against a cached StringKey (e.g. a generated constant) instead.")]
|
||||
public bool Equals(string other) => string.Equals(_key, other, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => _hashCode;
|
||||
|
||||
/// <inheritdoc cref="Equals(StringKey)"/>
|
||||
[Obsolete("Comparing a StringKey to a string uses a slow culture-aware string comparison. " +
|
||||
"Compare against a cached StringKey (e.g. a generated constant) instead.")]
|
||||
public static bool operator ==(StringKey? left, string? right) =>
|
||||
(left is null && right is null) || (right != null && (left?.Equals(right) ?? false));
|
||||
|
||||
/// <inheritdoc cref="Equals(StringKey)"/>
|
||||
[Obsolete("Comparing a StringKey to a string uses a slow culture-aware string comparison. " +
|
||||
"Compare against a cached StringKey (e.g. a generated constant) instead.")]
|
||||
public static bool operator !=(StringKey? left, string? right) => !(left == right);
|
||||
|
||||
/// <inheritdoc cref="Equals(StringKey)"/>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
|
||||
using CSPath;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.Models.Choices;
|
||||
using PkmnLib.Static.Utils;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace PkmnLib.Tests.Integration.Models;
|
||||
@@ -39,7 +40,8 @@ public class SetMoveChoiceAction : IntegrationTestAction
|
||||
{
|
||||
var user = battle.Sides[Place[0]].Pokemon[Place[1]];
|
||||
await Assert.That(user).IsNotNull();
|
||||
var move = user!.Moves.First(m => m?.MoveData.Name == Move);
|
||||
StringKey moveName = Move;
|
||||
var move = user!.Moves.First(m => m?.MoveData.Name == moveName);
|
||||
await Assert.That(move).IsNotNull();
|
||||
var res = battle.TrySetChoice(new MoveChoice(user, move!, Target[0], Target[1]));
|
||||
await Assert.That(res).IsTrue();
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
; Shipped analyzer releases
|
||||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
; Unshipped analyzer release
|
||||
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||
|
||||
### New Rules
|
||||
|
||||
Rule ID | Category | Severity | Notes
|
||||
--------|----------|----------|-------
|
||||
PKMN7G000 | PkmnLib.SourceGen | Error | DataConstantsGenerator
|
||||
PKMN7G001 | PkmnLib.SourceGen | Error | DataConstantsGenerator
|
||||
PKMN7G002 | PkmnLib.SourceGen | Error | DataConstantsGenerator
|
||||
@@ -0,0 +1,59 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using PkmnLib.Plugin.Gen7.SourceGen.Emit;
|
||||
using PkmnLib.Plugin.Gen7.SourceGen.Parsing;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.SourceGen;
|
||||
|
||||
/// <summary>
|
||||
/// Generates <c>public static readonly StringKey</c> constant classes (move names, type names,
|
||||
/// move flags, etc.) from the Gen 7 data files, which are exposed to the generator as
|
||||
/// <c>AdditionalFiles</c>.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public sealed class DataConstantsGenerator : IIncrementalGenerator
|
||||
{
|
||||
private static readonly DiagnosticDescriptor ParseFailure = new("PKMN7G000", "Data file parse failure", "{0}",
|
||||
"PkmnLib.SourceGen", DiagnosticSeverity.Error, true);
|
||||
|
||||
private static readonly DiagnosticDescriptor DuplicateIdentifier = new("PKMN7G001", "Duplicate constant identifier",
|
||||
"{0}", "PkmnLib.SourceGen", DiagnosticSeverity.Error, true);
|
||||
|
||||
private static readonly DiagnosticDescriptor UnusableName = new("PKMN7G002", "Unusable constant name", "{0}",
|
||||
"PkmnLib.SourceGen", DiagnosticSeverity.Error, true);
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var results = context.AdditionalTextsProvider
|
||||
.Where(static text => DataExtractor.IsKnownFile(Path.GetFileName(text.Path))).Select(static (text, ct) =>
|
||||
DataExtractor.Extract(Path.GetFileName(text.Path), text.GetText(ct)?.ToString()));
|
||||
|
||||
context.RegisterSourceOutput(results, static (context, result) =>
|
||||
{
|
||||
foreach (var diagnostic in result.Diagnostics)
|
||||
{
|
||||
Report(context, diagnostic);
|
||||
}
|
||||
foreach (var spec in result.Classes)
|
||||
{
|
||||
var diagnostics = new List<ExtractionDiagnostic>();
|
||||
var source = ConstantClassEmitter.Emit(spec, diagnostics);
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
Report(context, diagnostic);
|
||||
}
|
||||
context.AddSource($"{spec.ClassName}.g.cs", source);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void Report(SourceProductionContext context, ExtractionDiagnostic diagnostic)
|
||||
{
|
||||
var descriptor = diagnostic.Id switch
|
||||
{
|
||||
"PKMN7G001" => DuplicateIdentifier,
|
||||
"PKMN7G002" => UnusableName,
|
||||
_ => ParseFailure,
|
||||
};
|
||||
context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, diagnostic.Message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Text;
|
||||
using PkmnLib.Plugin.Gen7.SourceGen.Parsing;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.SourceGen.Emit;
|
||||
|
||||
/// <summary>
|
||||
/// Emits a constants class of <c>public static readonly StringKey</c> fields for a
|
||||
/// <see cref="ClassSpec"/>.
|
||||
/// </summary>
|
||||
public static class ConstantClassEmitter
|
||||
{
|
||||
public static string Emit(ClassSpec spec, List<ExtractionDiagnostic> diagnostics)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"// <auto-generated from Data/{spec.SourceFile} by PkmnLib.Plugin.Gen7.SourceGen />");
|
||||
sb.AppendLine("#nullable enable");
|
||||
sb.AppendLine("using PkmnLib.Static.Utils;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("namespace PkmnLib.Plugin.Gen7.Common;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"public static class {spec.ClassName}");
|
||||
sb.AppendLine("{");
|
||||
var seen = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (var name in spec.Names)
|
||||
{
|
||||
var identifier = Sanitize(name);
|
||||
if (identifier.Length == 0)
|
||||
{
|
||||
diagnostics.Add(new ExtractionDiagnostic("PKMN7G002",
|
||||
$"Name '{name}' in {spec.ClassName} does not produce a usable identifier and was skipped."));
|
||||
continue;
|
||||
}
|
||||
if (seen.TryGetValue(identifier, out var existing))
|
||||
{
|
||||
diagnostics.Add(new ExtractionDiagnostic("PKMN7G001",
|
||||
$"Names '{existing}' and '{name}' in {spec.ClassName} both map to identifier '{identifier}'; '{name}' was skipped."));
|
||||
continue;
|
||||
}
|
||||
seen.Add(identifier, name);
|
||||
sb.AppendLine($" public static readonly StringKey {identifier} = \"{name}\";");
|
||||
}
|
||||
sb.AppendLine("}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a raw data name to a PascalCase identifier: segments split on <c>_</c>/<c>-</c>
|
||||
/// runs, non-alphanumeric characters stripped, and a leading <c>_</c> added when the result
|
||||
/// would start with a digit.
|
||||
/// </summary>
|
||||
public static string Sanitize(string raw)
|
||||
{
|
||||
var sb = new StringBuilder(raw.Length);
|
||||
var newSegment = true;
|
||||
foreach (var c in raw)
|
||||
{
|
||||
if (c is '_' or '-')
|
||||
{
|
||||
newSegment = true;
|
||||
continue;
|
||||
}
|
||||
if (!char.IsLetterOrDigit(c))
|
||||
continue;
|
||||
sb.Append(newSegment ? char.ToUpperInvariant(c) : c);
|
||||
newSegment = false;
|
||||
}
|
||||
if (sb.Length == 0)
|
||||
return string.Empty;
|
||||
if (char.IsDigit(sb[0]))
|
||||
sb.Insert(0, '_');
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
52
Plugins/PkmnLib.Plugin.Gen7.SourceGen/EquatableArray.cs
Normal file
52
Plugins/PkmnLib.Plugin.Gen7.SourceGen/EquatableArray.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.SourceGen;
|
||||
|
||||
/// <summary>
|
||||
/// An immutable array wrapper with sequence-based equality, so that incremental generator pipeline
|
||||
/// values containing collections cache correctly.
|
||||
/// </summary>
|
||||
public readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IReadOnlyList<T> where T : IEquatable<T>
|
||||
{
|
||||
private readonly T[]? _array;
|
||||
|
||||
public EquatableArray(T[] array)
|
||||
{
|
||||
_array = array;
|
||||
}
|
||||
|
||||
public int Count => _array?.Length ?? 0;
|
||||
|
||||
public T this[int index] => _array![index];
|
||||
|
||||
public bool Equals(EquatableArray<T> other)
|
||||
{
|
||||
if (Count != other.Count)
|
||||
return false;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (!this[i].Equals(other[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is EquatableArray<T> other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
hash = hash * 31 + this[i].GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)(_array ?? [])).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
196
Plugins/PkmnLib.Plugin.Gen7.SourceGen/Parsing/DataExtractor.cs
Normal file
196
Plugins/PkmnLib.Plugin.Gen7.SourceGen/Parsing/DataExtractor.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.SourceGen.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// A single generated constants class: its name and the raw data names it should contain.
|
||||
/// </summary>
|
||||
public sealed record ClassSpec(string ClassName, string SourceFile, EquatableArray<string> Names);
|
||||
|
||||
/// <summary>
|
||||
/// A diagnostic produced during extraction, kept as plain data so pipeline values stay equatable.
|
||||
/// </summary>
|
||||
public sealed record ExtractionDiagnostic(string Id, string Message);
|
||||
|
||||
/// <summary>
|
||||
/// The result of extracting all constant classes from a single data file.
|
||||
/// </summary>
|
||||
public sealed record ExtractionResult(
|
||||
EquatableArray<ClassSpec> Classes,
|
||||
EquatableArray<ExtractionDiagnostic> Diagnostics);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the names for the generated constant classes from the Gen 7 data files.
|
||||
/// </summary>
|
||||
public static class DataExtractor
|
||||
{
|
||||
private static readonly HashSet<string> KnownFiles = new(StringComparer.Ordinal)
|
||||
{
|
||||
"Moves.jsonc", "Abilities.jsonc", "Items.json", "Pokemon.json", "Types.csv", "Natures.csv",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Matches the runtime's <c>JsonOptions</c>: the data files contain <c>//</c> comments
|
||||
/// (including Items.json, despite its extension) and may contain trailing commas.
|
||||
/// </summary>
|
||||
private static readonly JsonDocumentOptions JsonOptions = new()
|
||||
{
|
||||
CommentHandling = JsonCommentHandling.Skip,
|
||||
AllowTrailingCommas = true,
|
||||
};
|
||||
|
||||
public static bool IsKnownFile(string fileName) => KnownFiles.Contains(fileName);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the constant classes for a data file. Never throws: a generator that throws is
|
||||
/// silently disabled by IDE hosts, so failures are reported as diagnostics instead.
|
||||
/// </summary>
|
||||
public static ExtractionResult Extract(string fileName, string? text)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
return Error(fileName, "file content could not be read");
|
||||
}
|
||||
try
|
||||
{
|
||||
return fileName switch
|
||||
{
|
||||
"Moves.jsonc" => ExtractMoves(fileName, text),
|
||||
"Abilities.jsonc" => ExtractTopLevelKeys(fileName, text, "AbilityNames"),
|
||||
"Items.json" => ExtractItems(fileName, text),
|
||||
"Pokemon.json" => ExtractSpecies(fileName, text),
|
||||
"Types.csv" => ExtractTypes(fileName, text),
|
||||
"Natures.csv" => ExtractNatures(fileName, text),
|
||||
_ => new ExtractionResult(new EquatableArray<ClassSpec>([]),
|
||||
new EquatableArray<ExtractionDiagnostic>([])),
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Error(fileName, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static ExtractionResult Error(string fileName, string message) =>
|
||||
new(new EquatableArray<ClassSpec>([]), new EquatableArray<ExtractionDiagnostic>([
|
||||
new ExtractionDiagnostic("PKMN7G000", $"Failed to parse data file '{fileName}': {message}"),
|
||||
]));
|
||||
|
||||
private static ExtractionResult ExtractMoves(string fileName, string text)
|
||||
{
|
||||
var moveNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
var flags = new HashSet<string>(StringComparer.Ordinal);
|
||||
var effectNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
using var doc = JsonDocument.Parse(text, JsonOptions);
|
||||
foreach (var move in doc.RootElement.GetProperty("data").EnumerateArray())
|
||||
{
|
||||
Add(moveNames, move.GetProperty("name").GetString());
|
||||
if (move.TryGetProperty("flags", out var flagsElement))
|
||||
{
|
||||
foreach (var flag in flagsElement.EnumerateArray())
|
||||
{
|
||||
Add(flags, flag.GetString());
|
||||
}
|
||||
}
|
||||
if (move.TryGetProperty("effect", out var effect) && effect.TryGetProperty("name", out var effectName))
|
||||
{
|
||||
Add(effectNames, effectName.GetString());
|
||||
}
|
||||
}
|
||||
return Result(Class("MoveNames", fileName, moveNames), Class("MoveFlags", fileName, flags),
|
||||
Class("MoveEffectNames", fileName, effectNames));
|
||||
}
|
||||
|
||||
private static ExtractionResult ExtractTopLevelKeys(string fileName, string text, string className)
|
||||
{
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
using var doc = JsonDocument.Parse(text, JsonOptions);
|
||||
foreach (var property in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
if (!property.Name.StartsWith("$", StringComparison.Ordinal))
|
||||
Add(names, property.Name);
|
||||
}
|
||||
return Result(Class(className, fileName, names));
|
||||
}
|
||||
|
||||
private static ExtractionResult ExtractItems(string fileName, string text)
|
||||
{
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
using var doc = JsonDocument.Parse(text, JsonOptions);
|
||||
foreach (var item in doc.RootElement.EnumerateArray())
|
||||
{
|
||||
Add(names, item.GetProperty("name").GetString());
|
||||
}
|
||||
return Result(Class("ItemNames", fileName, names));
|
||||
}
|
||||
|
||||
private static ExtractionResult ExtractSpecies(string fileName, string text)
|
||||
{
|
||||
var speciesNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
var formNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
using var doc = JsonDocument.Parse(text, JsonOptions);
|
||||
foreach (var property in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
if (property.Name.StartsWith("$", StringComparison.Ordinal))
|
||||
continue;
|
||||
Add(speciesNames, property.Name);
|
||||
if (property.Value.TryGetProperty("formes", out var formes))
|
||||
{
|
||||
foreach (var forme in formes.EnumerateObject())
|
||||
{
|
||||
Add(formNames, forme.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result(Class("SpeciesNames", fileName, speciesNames), Class("FormNames", fileName, formNames));
|
||||
}
|
||||
|
||||
private static ExtractionResult ExtractTypes(string fileName, string text)
|
||||
{
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
var header = FirstLine(text);
|
||||
var cells = header.Split(header.Contains('|') ? '|' : ',');
|
||||
for (var i = 1; i < cells.Length; i++)
|
||||
{
|
||||
Add(names, cells[i]);
|
||||
}
|
||||
return Result(Class("TypeNames", fileName, names));
|
||||
}
|
||||
|
||||
private static ExtractionResult ExtractNatures(string fileName, string text)
|
||||
{
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
var lines = text.Split('\n');
|
||||
for (var i = 1; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
if (line.Length == 0)
|
||||
continue;
|
||||
Add(names, line.Split(line.Contains('|') ? '|' : ',')[0]);
|
||||
}
|
||||
return Result(Class("NatureNames", fileName, names));
|
||||
}
|
||||
|
||||
private static string FirstLine(string text)
|
||||
{
|
||||
var index = text.IndexOf('\n');
|
||||
return (index < 0 ? text : text.Substring(0, index)).Trim();
|
||||
}
|
||||
|
||||
private static void Add(HashSet<string> names, string? raw)
|
||||
{
|
||||
var name = raw?.Trim().ToLowerInvariant();
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
names.Add(name!);
|
||||
}
|
||||
|
||||
private static ClassSpec Class(string className, string fileName, HashSet<string> names)
|
||||
{
|
||||
var sorted = names.ToArray();
|
||||
Array.Sort(sorted, StringComparer.Ordinal);
|
||||
return new ClassSpec(className, fileName, new EquatableArray<string>(sorted));
|
||||
}
|
||||
|
||||
private static ExtractionResult Result(params ClassSpec[] classes) =>
|
||||
new(new EquatableArray<ClassSpec>(classes), new EquatableArray<ExtractionDiagnostic>([]));
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<!-- Ensures the dependency dlls below are handed to the consuming compilation as analyzer
|
||||
references; a ProjectReference'd analyzer only passes its own assembly by default. -->
|
||||
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all"/>
|
||||
<PackageReference Include="System.Text.Json" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="System.Text.Encodings.Web" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="System.Memory" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="System.Buffers" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="System.Numerics.Vectors" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" PrivateAssets="all" GeneratePathProperty="true"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
|
||||
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="GetDependencyTargetPaths">
|
||||
<ItemGroup>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Json)/lib/netstandard2.0/System.Text.Json.dll" IncludeRuntimeDependency="false"/>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Encodings_Web)/lib/netstandard2.0/System.Text.Encodings.Web.dll" IncludeRuntimeDependency="false"/>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Memory)/lib/netstandard2.0/System.Memory.dll" IncludeRuntimeDependency="false"/>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Buffers)/lib/netstandard2.0/System.Buffers.dll" IncludeRuntimeDependency="false"/>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Numerics_Vectors)/lib/netstandard2.0/System.Numerics.Vectors.dll" IncludeRuntimeDependency="false"/>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Runtime_CompilerServices_Unsafe)/lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll" IncludeRuntimeDependency="false"/>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Threading_Tasks_Extensions)/lib/netstandard2.0/System.Threading.Tasks.Extensions.dll" IncludeRuntimeDependency="false"/>
|
||||
<TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_Bcl_AsyncInterfaces)/lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll" IncludeRuntimeDependency="false"/>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,105 @@
|
||||
using PkmnLib.Dynamic.Libraries;
|
||||
using PkmnLib.Plugin.Gen7.Common;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Tests.DataTests;
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the source-generated constant classes stay in sync with the data files: every
|
||||
/// generated constant must resolve in the loaded library, and every library entry must have a
|
||||
/// generated constant.
|
||||
/// </summary>
|
||||
public class GeneratedConstantsTests
|
||||
{
|
||||
private static readonly IDynamicLibrary Library = LibraryHelpers.LoadLibrary();
|
||||
|
||||
private static HashSet<StringKey> ConstantsOf(Type type) => type.GetFields()
|
||||
.Where(x => x is { IsStatic: true, IsPublic: true } && x.FieldType == typeof(StringKey))
|
||||
.Select(x => (StringKey)x.GetValue(null)!).ToHashSet();
|
||||
|
||||
[Test]
|
||||
public async Task MoveNamesMatchMoveLibrary()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(MoveNames));
|
||||
var libraryNames = Library.StaticLibrary.Moves.Select(x => x.Name).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AbilityNamesMatchAbilityLibrary()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(AbilityNames));
|
||||
var libraryNames = Library.StaticLibrary.Abilities.Select(x => x.Name).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ItemNamesMatchItemLibrary()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(ItemNames));
|
||||
var libraryNames = Library.StaticLibrary.Items.Select(x => x.Name).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SpeciesNamesMatchSpeciesLibrary()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(SpeciesNames));
|
||||
var libraryNames = Library.StaticLibrary.Species.Select(x => x.Name).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FormNamesMatchSpeciesForms()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(FormNames));
|
||||
var libraryNames = Library.StaticLibrary.Species.SelectMany(x => x.Forms.Keys).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NatureNamesMatchNatureLibrary()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(NatureNames));
|
||||
var libraryNames = Library.StaticLibrary.Natures.Select(x => x.Name).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TypeNamesMatchTypeLibrary()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(TypeNames));
|
||||
var libraryNames = new HashSet<StringKey>();
|
||||
for (byte index = 1; Library.StaticLibrary.Types.TryGetTypeIdentifierFromIndex(index, out var type); index++)
|
||||
{
|
||||
libraryNames.Add(type.Name);
|
||||
}
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task MoveFlagsMatchMoveData()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(MoveFlags));
|
||||
var libraryNames = Library.StaticLibrary.Moves.SelectMany(x => x.Flags).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task MoveEffectNamesMatchMoveData()
|
||||
{
|
||||
var constants = ConstantsOf(typeof(MoveEffectNames));
|
||||
var libraryNames = Library.StaticLibrary.Moves.Where(x => x.SecondaryEffect != null)
|
||||
.Select(x => x.SecondaryEffect!.Name).ToHashSet();
|
||||
await Assert.That(constants.Where(x => !libraryNames.Contains(x))).IsEmpty();
|
||||
await Assert.That(libraryNames.Where(x => !constants.Contains(x))).IsEmpty();
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public class MoveDataTests
|
||||
var moveLibrary = library.StaticLibrary.Moves;
|
||||
foreach (var move in moveLibrary)
|
||||
{
|
||||
if (move.SecondaryEffect?.Name != "set_status")
|
||||
if (move.SecondaryEffect?.Name != MoveEffectNames.SetStatus)
|
||||
continue;
|
||||
yield return () => new SetStatusTestCaseData(library, move);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class DreamEaterTests
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, target, _) = CreateTestSetup(100);
|
||||
target.HasStatus(new StringKey("asleep")).Returns(false);
|
||||
target.HasStatus(new StringKey("sleep")).Returns(false);
|
||||
var block = false;
|
||||
|
||||
// Act
|
||||
@@ -77,7 +77,7 @@ public class DreamEaterTests
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, target, _) = CreateTestSetup(100);
|
||||
target.HasStatus(new StringKey("asleep")).Returns(true);
|
||||
target.HasStatus(new StringKey("sleep")).Returns(true);
|
||||
var block = false;
|
||||
|
||||
// Act
|
||||
@@ -96,7 +96,7 @@ public class DreamEaterTests
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, target, _) = CreateTestSetup(100);
|
||||
target.HasStatus(new StringKey("asleep")).Returns(false);
|
||||
target.HasStatus(new StringKey("sleep")).Returns(false);
|
||||
var comatose = Substitute.For<IAbility>();
|
||||
comatose.Name.Returns(new StringKey("comatose"));
|
||||
target.ActiveAbility.Returns(comatose);
|
||||
|
||||
@@ -27,7 +27,7 @@ public static class AIDamageFunctions
|
||||
{
|
||||
score += 10;
|
||||
if ((option.Move.Move.HasFlag(MoveFlags.MultiHit) && target.CurrentHealth == target.MaxHealth &&
|
||||
target.ActiveAbility?.Name == "sturdy") || target.HasHeldItem("focus_sash"))
|
||||
target.ActiveAbility?.Name == AbilityNames.Sturdy) || target.HasHeldItem(ItemNames.FocusSash))
|
||||
{
|
||||
score += 8;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public static class AIHelperFunctions
|
||||
if (move.User.BattleData?.SideIndex != target.BattleData?.SideIndex)
|
||||
desireMult = -1;
|
||||
|
||||
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == "contrary")
|
||||
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == AbilityNames.Contrary)
|
||||
{
|
||||
if (desireMult > 0 && wholeEffect)
|
||||
{
|
||||
@@ -39,10 +39,10 @@ public static class AIHelperFunctions
|
||||
if (expectedEndOfTurnDamage >= target.CurrentHealth)
|
||||
return wholeEffect ? ExplicitAI.MoveUselessScore : score;
|
||||
|
||||
if (!move.User.HasMoveWithEffect("power_trip"))
|
||||
if (!move.User.HasMoveWithEffect(MoveEffectNames.PowerTrip))
|
||||
{
|
||||
var foeIsAware = target.BattleData?.BattleSide.Pokemon.Any(x => x?.ActiveAbility?.Name == "unaware") !=
|
||||
true;
|
||||
var foeIsAware =
|
||||
target.BattleData?.BattleSide.Pokemon.Any(x => x?.ActiveAbility?.Name == AbilityNames.Unaware) != true;
|
||||
if (!foeIsAware)
|
||||
{
|
||||
return wholeEffect ? ExplicitAI.MoveUselessScore : score;
|
||||
@@ -56,7 +56,7 @@ public static class AIHelperFunctions
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!fixedChange && target.ActiveAbility?.Name == "simple")
|
||||
if (!fixedChange && target.ActiveAbility?.Name == AbilityNames.Simple)
|
||||
{
|
||||
increment *= 2;
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public static class AIHelperFunctions
|
||||
var desireMult = -1;
|
||||
if (move.User.BattleData?.SideIndex == target.BattleData?.SideIndex)
|
||||
desireMult = 1;
|
||||
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == "contrary")
|
||||
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == AbilityNames.Contrary)
|
||||
{
|
||||
if (desireMult > 0 && wholeEffect)
|
||||
{
|
||||
@@ -109,7 +109,7 @@ public static class AIHelperFunctions
|
||||
return wholeEffect ? ExplicitAI.MoveUselessScore : score;
|
||||
|
||||
var foeIsAware = false;
|
||||
if (target.BattleData?.BattleSide.Pokemon.All(x => x?.ActiveAbility?.Name != "unaware") == true)
|
||||
if (target.BattleData?.BattleSide.Pokemon.All(x => x?.ActiveAbility?.Name != AbilityNames.Unaware) == true)
|
||||
{
|
||||
foeIsAware = true;
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public static class AIHelperFunctions
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!fixedChange && target.ActiveAbility?.Name == "simple")
|
||||
if (!fixedChange && target.ActiveAbility?.Name == AbilityNames.Simple)
|
||||
{
|
||||
decrement *= 2;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public static class AIHelperFunctions
|
||||
{
|
||||
if (!fixedChange && pokemon.StatBoost.GetStatistic(stat) == StatBoostStatisticSet.MaxStatBoost)
|
||||
return false;
|
||||
if (!pokemon.HasMoveWithEffect("power_trip", "baton_pass"))
|
||||
if (!pokemon.HasMoveWithEffect(MoveEffectNames.PowerTrip, MoveEffectNames.BatonPass))
|
||||
return true;
|
||||
|
||||
switch (stat)
|
||||
@@ -160,7 +160,8 @@ public static class AIHelperFunctions
|
||||
case Statistic.Attack:
|
||||
{
|
||||
if (!pokemon.Moves.WhereNotNull().Any(x => x.MoveData.Category == MoveCategory.Physical &&
|
||||
x.MoveData.SecondaryEffect?.Name != "foul_play"))
|
||||
x.MoveData.SecondaryEffect?.Name !=
|
||||
MoveEffectNames.FoulPlay))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -170,7 +171,8 @@ public static class AIHelperFunctions
|
||||
{
|
||||
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
|
||||
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
|
||||
y.MoveData.Category == MoveCategory.Physical || y.MoveData.SecondaryEffect?.Name == "psyshock"));
|
||||
y.MoveData.Category == MoveCategory.Physical ||
|
||||
y.MoveData.SecondaryEffect?.Name == MoveEffectNames.Psyshock));
|
||||
}
|
||||
case Statistic.SpecialAttack:
|
||||
{
|
||||
@@ -184,11 +186,12 @@ public static class AIHelperFunctions
|
||||
{
|
||||
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
|
||||
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
|
||||
y.MoveData.Category == MoveCategory.Special && y.MoveData.SecondaryEffect?.Name != "psyshock"));
|
||||
y.MoveData.Category == MoveCategory.Special &&
|
||||
y.MoveData.SecondaryEffect?.Name != MoveEffectNames.Psyshock));
|
||||
}
|
||||
case Statistic.Speed:
|
||||
{
|
||||
if (!pokemon.HasMoveWithEffect("electro_ball", "power_trip"))
|
||||
if (!pokemon.HasMoveWithEffect(MoveEffectNames.ElectroBall, MoveEffectNames.PowerTrip))
|
||||
{
|
||||
var targetSpeed = pokemon.BoostedStats.Speed;
|
||||
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
|
||||
@@ -228,13 +231,15 @@ public static class AIHelperFunctions
|
||||
case Statistic.Attack:
|
||||
{
|
||||
return pokemon.Moves.WhereNotNull().Any(x => x.MoveData.Category == MoveCategory.Physical &&
|
||||
x.MoveData.SecondaryEffect?.Name != FoulPlayAbilityName);
|
||||
x.MoveData.SecondaryEffect?.Name !=
|
||||
MoveEffectNames.FoulPlay);
|
||||
}
|
||||
case Statistic.Defense:
|
||||
{
|
||||
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
|
||||
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
|
||||
y.MoveData.Category == MoveCategory.Physical || y.MoveData.SecondaryEffect?.Name == "psyshock"));
|
||||
y.MoveData.Category == MoveCategory.Physical ||
|
||||
y.MoveData.SecondaryEffect?.Name == MoveEffectNames.Psyshock));
|
||||
}
|
||||
case Statistic.SpecialAttack:
|
||||
{
|
||||
@@ -244,11 +249,12 @@ public static class AIHelperFunctions
|
||||
{
|
||||
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
|
||||
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y =>
|
||||
y.MoveData.Category == MoveCategory.Special && y.MoveData.SecondaryEffect?.Name != "psyshock"));
|
||||
y.MoveData.Category == MoveCategory.Special &&
|
||||
y.MoveData.SecondaryEffect?.Name != MoveEffectNames.Psyshock));
|
||||
}
|
||||
case Statistic.Speed:
|
||||
{
|
||||
if (!pokemon.HasMoveWithEffect("electro_ball"))
|
||||
if (!pokemon.HasMoveWithEffect(MoveEffectNames.ElectroBall))
|
||||
{
|
||||
var targetSpeed = pokemon.BoostedStats.Speed;
|
||||
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
|
||||
@@ -266,8 +272,6 @@ public static class AIHelperFunctions
|
||||
return true;
|
||||
}
|
||||
|
||||
private static readonly StringKey FoulPlayAbilityName = "foul_play";
|
||||
|
||||
private static void GetTargetStatRaiseScoreOne(ref int score, IPokemon target, Statistic stat, sbyte increment,
|
||||
AIMoveState move, float desireMult = 1)
|
||||
{
|
||||
@@ -311,7 +315,7 @@ public static class AIHelperFunctions
|
||||
{
|
||||
var hasPhysicalMoves = target.Moves.WhereNotNull().Any(x =>
|
||||
x.MoveData.Category == MoveCategory.Physical &&
|
||||
x.MoveData.SecondaryEffect?.Name != FoulPlayAbilityName);
|
||||
x.MoveData.SecondaryEffect?.Name != MoveEffectNames.FoulPlay);
|
||||
var inc = hasPhysicalMoves ? 8 : 12;
|
||||
score += (int)(inc * incMult);
|
||||
}
|
||||
@@ -340,15 +344,15 @@ public static class AIHelperFunctions
|
||||
else
|
||||
score += (int)(8 * incMult);
|
||||
}
|
||||
if (target.HasMoveWithEffect("electro_ball", "power_trip"))
|
||||
if (target.HasMoveWithEffect(MoveEffectNames.ElectroBall, MoveEffectNames.PowerTrip))
|
||||
{
|
||||
score += (int)(5 * incMult);
|
||||
}
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect("gyro_ball")))
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect(MoveEffectNames.GyroBall)))
|
||||
{
|
||||
score -= (int)(5 * incMult);
|
||||
}
|
||||
if (target.ActiveAbility?.Name == "speed_boost")
|
||||
if (target.ActiveAbility?.Name == AbilityNames.SpeedBoost)
|
||||
{
|
||||
score -= (int)(15 * (target.Opposes(move.User) ? 1 : desireMult));
|
||||
}
|
||||
@@ -393,11 +397,11 @@ public static class AIHelperFunctions
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (target.HasMoveWithEffect("power_trip"))
|
||||
if (target.HasMoveWithEffect(MoveEffectNames.PowerTrip))
|
||||
{
|
||||
score += (int)(5 * increment * desireMult);
|
||||
}
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect("punishment")))
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect(MoveEffectNames.Punishment)))
|
||||
{
|
||||
score -= (int)(5 * increment * desireMult);
|
||||
}
|
||||
@@ -445,7 +449,7 @@ public static class AIHelperFunctions
|
||||
{
|
||||
var hasPhysicalMoves = target.Moves.WhereNotNull().Any(x =>
|
||||
x.MoveData.Category == MoveCategory.Physical &&
|
||||
x.MoveData.SecondaryEffect?.Name != FoulPlayAbilityName);
|
||||
x.MoveData.SecondaryEffect?.Name != MoveEffectNames.FoulPlay);
|
||||
var dec = hasPhysicalMoves ? 8 : 12;
|
||||
score += (int)(dec * decMult);
|
||||
}
|
||||
@@ -475,11 +479,11 @@ public static class AIHelperFunctions
|
||||
score += (int)(8 * decMult);
|
||||
break;
|
||||
}
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect("electro_ball")))
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect(MoveEffectNames.ElectroBall)))
|
||||
{
|
||||
score += (int)(5 * decMult);
|
||||
}
|
||||
if (target.ActiveAbility?.Name == "speed_boost")
|
||||
if (target.ActiveAbility?.Name == AbilityNames.SpeedBoost)
|
||||
{
|
||||
score -= (int)(15 * (target.Opposes(move.User) ? 1 : desireMult));
|
||||
}
|
||||
@@ -502,11 +506,11 @@ public static class AIHelperFunctions
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (target.HasMoveWithEffect("power_trip"))
|
||||
if (target.HasMoveWithEffect(MoveEffectNames.PowerTrip))
|
||||
{
|
||||
score += (int)(5 * decrement * desireMult);
|
||||
}
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect("punishment")))
|
||||
if (opponentSide.Pokemon.WhereNotNull().Any(x => x.HasMoveWithEffect(MoveEffectNames.Punishment)))
|
||||
{
|
||||
score -= (int)(5 * decrement * desireMult);
|
||||
}
|
||||
@@ -545,13 +549,13 @@ public static class AIHelperFunctions
|
||||
{
|
||||
if (move.Move.SecondaryEffect is null)
|
||||
return 0;
|
||||
if (move.User.ActiveAbility?.Name == "sheer_force")
|
||||
if (move.User.ActiveAbility?.Name == AbilityNames.SheerForce)
|
||||
return -999;
|
||||
if (target is not null && target.BattleData?.Position != move.User.BattleData?.Position &&
|
||||
target.ActiveAbility?.Name == "shield_dust")
|
||||
target.ActiveAbility?.Name == AbilityNames.ShieldDust)
|
||||
return -999;
|
||||
|
||||
if ((move.Move.SecondaryEffect.Chance < 100 && move.User.ActiveAbility?.Name == "serene_grace") ||
|
||||
if ((move.Move.SecondaryEffect.Chance < 100 && move.User.ActiveAbility?.Name == AbilityNames.SereneGrace) ||
|
||||
move.User.BattleData?.BattleSide.VolatileScripts.Contains<RainbowEffect>() == true)
|
||||
{
|
||||
return 5;
|
||||
@@ -574,38 +578,38 @@ public static class AIHelperFunctions
|
||||
return true;
|
||||
if (pokemon.ActiveAbility != null)
|
||||
{
|
||||
if (pokemon.ActiveAbility.Name == "guts" && status != ScriptUtils.ResolveName<Sleep>() &&
|
||||
if (pokemon.ActiveAbility.Name == AbilityNames.Guts && status != ScriptUtils.ResolveName<Sleep>() &&
|
||||
status != ScriptUtils.ResolveName<Frozen>() &&
|
||||
IsStatRaiseWorthwhile(pokemon, Statistic.Attack, 1, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pokemon.ActiveAbility.Name == "marvel_scale" &&
|
||||
if (pokemon.ActiveAbility.Name == AbilityNames.MarvelScale &&
|
||||
IsStatRaiseWorthwhile(pokemon, Statistic.Defense, 1, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pokemon.ActiveAbility.Name == "quick_feet" && status != ScriptUtils.ResolveName<Sleep>() &&
|
||||
if (pokemon.ActiveAbility.Name == AbilityNames.QuickFeet && status != ScriptUtils.ResolveName<Sleep>() &&
|
||||
status != ScriptUtils.ResolveName<Frozen>() && IsStatRaiseWorthwhile(pokemon, Statistic.Speed, 1, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pokemon.ActiveAbility.Name == "flare_boost" && status == ScriptUtils.ResolveName<Burned>() &&
|
||||
if (pokemon.ActiveAbility.Name == AbilityNames.FlareBoost && status == ScriptUtils.ResolveName<Burned>() &&
|
||||
IsStatRaiseWorthwhile(pokemon, Statistic.SpecialAttack, 1, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pokemon.ActiveAbility.Name == "toxic_boost" &&
|
||||
if (pokemon.ActiveAbility.Name == AbilityNames.ToxicBoost &&
|
||||
(status == ScriptUtils.ResolveName<Poisoned>() || status == ScriptUtils.ResolveName<BadlyPoisoned>()) &&
|
||||
IsStatRaiseWorthwhile(pokemon, Statistic.Attack, 1, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pokemon.ActiveAbility.Name == "poison_heal" && status == ScriptUtils.ResolveName<Poisoned>())
|
||||
if (pokemon.ActiveAbility.Name == AbilityNames.PoisonHeal && status == ScriptUtils.ResolveName<Poisoned>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pokemon.ActiveAbility.Name == "magic_guard")
|
||||
if (pokemon.ActiveAbility.Name == AbilityNames.MagicGuard)
|
||||
{
|
||||
if (status != ScriptUtils.ResolveName<Poisoned>() &&
|
||||
status != ScriptUtils.ResolveName<BadlyPoisoned>() && status != ScriptUtils.ResolveName<Burned>())
|
||||
|
||||
@@ -26,8 +26,6 @@ public static class AISwitchFunctions
|
||||
return effect.Turns <= 1;
|
||||
}
|
||||
|
||||
private static readonly StringKey PoisonHealAbilityName = "poison_heal";
|
||||
|
||||
/// <summary>
|
||||
/// Switch out if the Pokémon is expected to take significant end-of-turn damage.
|
||||
/// </summary>
|
||||
@@ -50,7 +48,7 @@ public static class AISwitchFunctions
|
||||
return true;
|
||||
var statusScript = pokemon.StatusScript.Script;
|
||||
if (statusScript is BadlyPoisoned { Turns: > 0 } badlyPoisoned &&
|
||||
pokemon.ActiveAbility?.Name != PoisonHealAbilityName)
|
||||
pokemon.ActiveAbility?.Name != AbilityNames.PoisonHeal)
|
||||
{
|
||||
var poisonDamage = pokemon.MaxHealth / 8;
|
||||
var nextToxicDamage = pokemon.MaxHealth * badlyPoisoned.GetPoisonMultiplier();
|
||||
@@ -92,28 +90,18 @@ public static class AISwitchFunctions
|
||||
return bigThreat;
|
||||
}
|
||||
|
||||
private static readonly StringKey ImmunityAbilityName = "immunity";
|
||||
private static readonly StringKey InsomniaAbilityName = "insomnia";
|
||||
private static readonly StringKey LimberAbilityName = "limber";
|
||||
private static readonly StringKey MagmaArmorAbilityName = "magma_armor";
|
||||
private static readonly StringKey VitalSpiritAbilityName = "vital_spirit";
|
||||
private static readonly StringKey WaterBubbleAbilityName = "water_bubble";
|
||||
private static readonly StringKey WaterVeilAbilityName = "water_veil";
|
||||
private static readonly StringKey NaturalCureAbilityName = "natural_cure";
|
||||
private static readonly StringKey RegeneratorAbilityName = "regenerator";
|
||||
|
||||
private static readonly Dictionary<StringKey, List<StringKey>> StatusCureAbilities = new()
|
||||
{
|
||||
{
|
||||
ImmunityAbilityName,
|
||||
AbilityNames.Immunity,
|
||||
[ScriptUtils.ResolveName<Poisoned>(), ScriptUtils.ResolveName<BadlyPoisoned>()]
|
||||
},
|
||||
{ InsomniaAbilityName, [ScriptUtils.ResolveName<Sleep>()] },
|
||||
{ LimberAbilityName, [ScriptUtils.ResolveName<Paralyzed>()] },
|
||||
{ MagmaArmorAbilityName, [ScriptUtils.ResolveName<Frozen>()] },
|
||||
{ VitalSpiritAbilityName, [ScriptUtils.ResolveName<Sleep>()] },
|
||||
{ WaterBubbleAbilityName, [ScriptUtils.ResolveName<Burned>()] },
|
||||
{ WaterVeilAbilityName, [ScriptUtils.ResolveName<Burned>()] },
|
||||
{ AbilityNames.Insomnia, [ScriptUtils.ResolveName<Sleep>()] },
|
||||
{ AbilityNames.Limber, [ScriptUtils.ResolveName<Paralyzed>()] },
|
||||
{ AbilityNames.MagmaArmor, [ScriptUtils.ResolveName<Frozen>()] },
|
||||
{ AbilityNames.VitalSpirit, [ScriptUtils.ResolveName<Sleep>()] },
|
||||
{ AbilityNames.WaterBubble, [ScriptUtils.ResolveName<Burned>()] },
|
||||
{ AbilityNames.WaterVeil, [ScriptUtils.ResolveName<Burned>()] },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -138,7 +126,7 @@ public static class AISwitchFunctions
|
||||
|
||||
// Check abilities that cure specific status conditions
|
||||
var canCureStatus = false;
|
||||
if (abilityName == NaturalCureAbilityName)
|
||||
if (abilityName == AbilityNames.NaturalCure)
|
||||
{
|
||||
canCureStatus = true;
|
||||
}
|
||||
@@ -161,7 +149,7 @@ public static class AISwitchFunctions
|
||||
|
||||
// Don't bother curing a poisoning if Toxic Spikes will just re-poison
|
||||
if (pokemon.StatusScript.Script is Poisoned or BadlyPoisoned &&
|
||||
!reserves.Any(p => p.Types.Any(t => t.Name == "poison")))
|
||||
!reserves.Any(p => p.Types.Any(t => t.Name == TypeNames.Poison)))
|
||||
{
|
||||
if (pokemon.BattleData!.BattleSide.VolatileScripts.TryGet<ToxicSpikesEffect>(out _))
|
||||
{
|
||||
@@ -177,7 +165,7 @@ public static class AISwitchFunctions
|
||||
if (ai.Random.GetInt(100) < 70)
|
||||
return true;
|
||||
}
|
||||
else if (abilityName == RegeneratorAbilityName)
|
||||
else if (abilityName == AbilityNames.Regenerator)
|
||||
{
|
||||
// Not worth healing if battler would lose more HP from switching back in later
|
||||
if (entryHazardDamage >= pokemon.MaxHealth / 3)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
namespace PkmnLib.Plugin.Gen7.Common;
|
||||
|
||||
public static class MoveFlags
|
||||
{
|
||||
public static readonly StringKey Ballistics = "ballistics";
|
||||
public static readonly StringKey Bite = "bite";
|
||||
public static readonly StringKey CantRepeat = "cant_repeat";
|
||||
public static readonly StringKey Charge = "charge";
|
||||
public static readonly StringKey Contact = "contact";
|
||||
public static readonly StringKey Dance = "dance";
|
||||
public static readonly StringKey Defrost = "defrost";
|
||||
public static readonly StringKey Distance = "distance";
|
||||
public static readonly StringKey EffectiveAgainstFly = "effective_against_fly";
|
||||
public static readonly StringKey EffectiveAgainstUnderground = "effective_against_underground";
|
||||
public static readonly StringKey EffectiveAgainstUnderwater = "effective_against_underwater";
|
||||
public static readonly StringKey Gravity = "gravity";
|
||||
public static readonly StringKey Heal = "heal";
|
||||
public static readonly StringKey HitFlying = "hit_flying";
|
||||
public static readonly StringKey HitUnderground = "hit_underground";
|
||||
public static readonly StringKey HitUnderwater = "hit_underwater";
|
||||
public static readonly StringKey IgnoreSubstitute = "ignore_substitute";
|
||||
public static readonly StringKey LimitMoveChoice = "limit_move_choice";
|
||||
public static readonly StringKey Mental = "mental";
|
||||
public static readonly StringKey Mirror = "mirror";
|
||||
public static readonly StringKey MultiHit = "multi_hit";
|
||||
public static readonly StringKey NoRedirection = "no_redirection";
|
||||
public static readonly StringKey NonSkyBattle = "non_sky_battle";
|
||||
public static readonly StringKey NotSketchable = "not_sketchable";
|
||||
public static readonly StringKey Powder = "powder";
|
||||
public static readonly StringKey Protect = "protect";
|
||||
public static readonly StringKey Pulse = "pulse";
|
||||
public static readonly StringKey Punch = "punch";
|
||||
public static readonly StringKey Recoil = "recoil";
|
||||
public static readonly StringKey Recharge = "recharge";
|
||||
public static readonly StringKey Reflectable = "reflectable";
|
||||
public static readonly StringKey Snatch = "snatch";
|
||||
public static readonly StringKey Sound = "sound";
|
||||
public static readonly StringKey UsableWhileAsleep = "usable_while_asleep";
|
||||
}
|
||||
@@ -7,9 +7,12 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj"/>
|
||||
<ProjectReference Include="..\..\PkmnLib.Static\PkmnLib.Static.csproj"/>
|
||||
<ProjectReference Include="..\PkmnLib.Plugin.Gen7.SourceGen\PkmnLib.Plugin.Gen7.SourceGen.csproj"
|
||||
OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Data\*.*"/>
|
||||
<AdditionalFiles Include="Data\*.*"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
[Script(ScriptCategory.Ability, "aftermath")]
|
||||
public class Aftermath : Script, IScriptOnIncomingHit, IScriptOnFaint
|
||||
{
|
||||
private static readonly StringKey DampAbilityName = new("damp");
|
||||
|
||||
private IExecutingMove? _lastAttack;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -35,7 +33,7 @@ public class Aftermath : Script, IScriptOnIncomingHit, IScriptOnFaint
|
||||
// Aftermath does not trigger if a Pokémon with Damp is on the field
|
||||
var battle = user.BattleData.Battle;
|
||||
var hasDamp = battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull()
|
||||
.Any(p => p.ActiveAbility?.Name == DampAbilityName);
|
||||
.Any(p => p.ActiveAbility?.Name == AbilityNames.Damp);
|
||||
if (hasDamp)
|
||||
return;
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ public class Anticipation : Script, IScriptOnOpponentSwitchIn
|
||||
// Either the move is super effective against the owner or
|
||||
typeLibrary.GetEffectiveness(move.MoveData.MoveType, _owner.Types) > 1.0f ||
|
||||
// the move is a OHKO move
|
||||
move.MoveData.SecondaryEffect?.Name == "one_hit_ko" ||
|
||||
move.MoveData.SecondaryEffect?.Name == MoveEffectNames.OneHitKo ||
|
||||
// the move is a self-destruct move
|
||||
move.MoveData.SecondaryEffect?.Name == "self_destruct" ||
|
||||
move.MoveData.SecondaryEffect?.Name == "explosion");
|
||||
move.MoveData.SecondaryEffect?.Name == MoveEffectNames.SelfDestruct ||
|
||||
move.MoveData.SecondaryEffect?.Name == MoveEffectNames.Explosion);
|
||||
|
||||
if (relevantMoves)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ public class AuraBreak : Script, IScriptCustomTrigger
|
||||
return;
|
||||
|
||||
var typeName = auraArgs.Move.UseMove.MoveType.Name;
|
||||
if (typeName == "dark" || typeName == "fairy")
|
||||
if (typeName == TypeNames.Dark || typeName == TypeNames.Fairy)
|
||||
{
|
||||
// Reverse the aura effect by reducing power by 25%
|
||||
auraArgs.AuraEffect *= 0.75f;
|
||||
|
||||
@@ -12,8 +12,8 @@ public class BattleBond : Script, IScriptChangeNumberOfHits, IScriptOnOpponentFa
|
||||
/// <inheritdoc />
|
||||
public void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.User.Species.Name == "greninja" && move.User.Form.Name != "ash" &&
|
||||
move.User.Species.TryGetForm("ash", out var ashForm))
|
||||
if (move.User.Species.Name == SpeciesNames.Greninja && move.User.Form.Name != FormNames.Ash &&
|
||||
move.User.Species.TryGetForm(FormNames.Ash, out var ashForm))
|
||||
{
|
||||
move.User.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
|
||||
move.User.ChangeForm(ashForm);
|
||||
@@ -23,14 +23,14 @@ public class BattleBond : Script, IScriptChangeNumberOfHits, IScriptOnOpponentFa
|
||||
/// <inheritdoc />
|
||||
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.UseMove.Name == "water_shuriken" && move.User.Form.Name == "ash")
|
||||
if (move.UseMove.Name == MoveNames.WaterShuriken && move.User.Form.Name == FormNames.Ash)
|
||||
basePower = 20;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits)
|
||||
{
|
||||
if (choice.ChosenMove.MoveData.Name == "water_shuriken" && choice.User.Form.Name == "ash")
|
||||
if (choice.ChosenMove.MoveData.Name == MoveNames.WaterShuriken && choice.User.Form.Name == FormNames.Ash)
|
||||
{
|
||||
numberOfHits = 3;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class Damp : Script, IScriptFailIncomingMove
|
||||
/// <inheritdoc />
|
||||
public void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail)
|
||||
{
|
||||
if (move.UseMove.Name == "self_destruct" || move.UseMove.Name == "explosion")
|
||||
if (move.UseMove.Name == MoveNames.SelfDestruct || move.UseMove.Name == MoveNames.Explosion)
|
||||
{
|
||||
fail = true;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public class DarkAura : Script, IScriptChangeDamageModifier
|
||||
/// <inheritdoc />
|
||||
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "dark")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Dark)
|
||||
{
|
||||
var auraModifier = 5448f / 4096f;
|
||||
var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier);
|
||||
|
||||
@@ -19,17 +19,17 @@ public class Disguise : Script, IScriptChangeIncomingDamage
|
||||
return;
|
||||
if (source is not DamageSource.MoveDamage and not DamageSource.Confusion)
|
||||
return;
|
||||
if (pokemon.Form.Name == "busted" || pokemon.Form.Name == "totem-busted")
|
||||
if (pokemon.Form.Name == FormNames.Busted || pokemon.Form.Name == FormNames.TotemBusted)
|
||||
return;
|
||||
IForm form;
|
||||
if (pokemon.Form.Name == "default")
|
||||
if (pokemon.Form.Name == FormNames.Default)
|
||||
{
|
||||
if (!pokemon.Species.TryGetForm("busted", out form!))
|
||||
if (!pokemon.Species.TryGetForm(FormNames.Busted, out form!))
|
||||
return;
|
||||
}
|
||||
else if (pokemon.Form.Name == "totem-disguised")
|
||||
else if (pokemon.Form.Name == FormNames.TotemDisguised)
|
||||
{
|
||||
if (!pokemon.Species.TryGetForm("totem-busted", out form!))
|
||||
if (!pokemon.Species.TryGetForm(FormNames.TotemBusted, out form!))
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -26,11 +26,11 @@ public class DrySkin : Script, IScriptChangeDamageModifier, IScriptOnEndTurn, IA
|
||||
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
var hitType = move.GetHitData(target, hit).Type;
|
||||
if (hitType?.Name == "fire")
|
||||
if (hitType?.Name == TypeNames.Fire)
|
||||
{
|
||||
modifier *= 1.25f;
|
||||
}
|
||||
else if (hitType?.Name == "water")
|
||||
else if (hitType?.Name == TypeNames.Water)
|
||||
{
|
||||
modifier = 0;
|
||||
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
|
||||
@@ -13,11 +13,11 @@ public class EffectSpore : Script, IScriptOnIncomingHit
|
||||
/// <inheritdoc />
|
||||
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.User.Types.Any(x => x.Name == "grass"))
|
||||
if (move.User.Types.Any(x => x.Name == TypeNames.Grass))
|
||||
return;
|
||||
if (move.User.ActiveAbility?.Name == "effect_spore")
|
||||
if (move.User.ActiveAbility?.Name == AbilityNames.EffectSpore)
|
||||
return;
|
||||
if (move.User.HasHeldItem("safety_goggles"))
|
||||
if (move.User.HasHeldItem(ItemNames.SafetyGoggles))
|
||||
return;
|
||||
if (!move.GetHitData(target, hit).IsContact)
|
||||
return;
|
||||
|
||||
@@ -13,7 +13,7 @@ public class FairyAura : Script, IScriptChangeDamageModifier
|
||||
/// <inheritdoc />
|
||||
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "fairy")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Fairy)
|
||||
{
|
||||
var auraModifier = 5448f / 4096f;
|
||||
var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier);
|
||||
|
||||
@@ -16,7 +16,7 @@ public class FlashFire : Script, IScriptChangeIncomingEffectiveness
|
||||
public void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex,
|
||||
ref float effectiveness)
|
||||
{
|
||||
if (executingMove.GetHitData(target, hitIndex).Type?.Name != "fire")
|
||||
if (executingMove.GetHitData(target, hitIndex).Type?.Name != TypeNames.Fire)
|
||||
return;
|
||||
effectiveness = 0f;
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ public class FlowerGift : Script, IScriptOnWeatherChange
|
||||
return;
|
||||
if (weatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>())
|
||||
return;
|
||||
if (_pokemon.Species.Name != "cherrim")
|
||||
if (_pokemon.Species.Name != SpeciesNames.Cherrim)
|
||||
return;
|
||||
EventBatchId batchId = new();
|
||||
if (_pokemon.Species.TryGetForm("sunshine", out var form) && _pokemon.Form != form)
|
||||
if (_pokemon.Species.TryGetForm(FormNames.Sunshine, out var form) && _pokemon.Form != form)
|
||||
{
|
||||
_pokemon.ChangeForm(form, batchId);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class FlowerGift : Script, IScriptOnWeatherChange
|
||||
script.OnRemoved(_pokemon);
|
||||
}
|
||||
|
||||
if (_pokemon.Species.Name != "cherrim")
|
||||
if (_pokemon.Species.Name != SpeciesNames.Cherrim)
|
||||
return;
|
||||
|
||||
EventBatchId batchId = new();
|
||||
|
||||
@@ -12,7 +12,7 @@ public class Fluffy : Script, IScriptChangeDamageModifier
|
||||
/// <inheritdoc />
|
||||
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "fire")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Fire)
|
||||
{
|
||||
modifier *= 2f;
|
||||
}
|
||||
|
||||
@@ -45,21 +45,21 @@ public class Forecast : Script, IScriptOnSwitchIn, IScriptOnWeatherChange
|
||||
|
||||
private static void ChangeForm(IPokemon pokemon, StringKey? weather)
|
||||
{
|
||||
if (pokemon.Species.Name != "castform")
|
||||
if (pokemon.Species.Name != SpeciesNames.Castform)
|
||||
return;
|
||||
|
||||
if (weather == ScriptUtils.ResolveName<Weather.HarshSunlight>() &&
|
||||
pokemon.Species.TryGetForm("sunny", out var sunnyForm) && pokemon.Form != sunnyForm)
|
||||
pokemon.Species.TryGetForm(FormNames.Sunny, out var sunnyForm) && pokemon.Form != sunnyForm)
|
||||
{
|
||||
pokemon.ChangeForm(sunnyForm);
|
||||
}
|
||||
else if (weather == ScriptUtils.ResolveName<Weather.Rain>() &&
|
||||
pokemon.Species.TryGetForm("rainy", out var rainyForm) && pokemon.Form != rainyForm)
|
||||
pokemon.Species.TryGetForm(FormNames.Rainy, out var rainyForm) && pokemon.Form != rainyForm)
|
||||
{
|
||||
pokemon.ChangeForm(rainyForm);
|
||||
}
|
||||
else if (weather == ScriptUtils.ResolveName<Weather.Hail>() &&
|
||||
pokemon.Species.TryGetForm("snowy", out var snowyForm) && pokemon.Form != snowyForm)
|
||||
pokemon.Species.TryGetForm(FormNames.Snowy, out var snowyForm) && pokemon.Form != snowyForm)
|
||||
{
|
||||
pokemon.ChangeForm(snowyForm);
|
||||
}
|
||||
|
||||
@@ -37,28 +37,64 @@ public class Forewarn : Script, IScriptOnSwitchIn
|
||||
});
|
||||
}
|
||||
|
||||
private static readonly Dictionary<StringKey, byte> FixedBasePowers = new()
|
||||
{
|
||||
// 150 BP: Specific moves
|
||||
[MoveNames.BlastBurn] = 150,
|
||||
[MoveNames.Eruption] = 150,
|
||||
[MoveNames.WaterSpout] = 150,
|
||||
[MoveNames.Fissure] = 150,
|
||||
[MoveNames.Guillotine] = 150,
|
||||
[MoveNames.HornDrill] = 150,
|
||||
[MoveNames.SheerCold] = 150,
|
||||
// 120 BP: Counter, Metal Burst, Mirror Coat
|
||||
[MoveNames.Counter] = 120,
|
||||
[MoveNames.MetalBurst] = 120,
|
||||
[MoveNames.MirrorCoat] = 120,
|
||||
// 80 BP: List of variable power and fixed-damage moves
|
||||
[MoveNames.CrushGrip] = 80,
|
||||
[MoveNames.DragonRage] = 80,
|
||||
[MoveNames.ElectroBall] = 80,
|
||||
[MoveNames.Endeavor] = 80,
|
||||
[MoveNames.FinalGambit] = 80,
|
||||
[MoveNames.Flail] = 80,
|
||||
[MoveNames.Fling] = 80,
|
||||
[MoveNames.Frustration] = 80,
|
||||
[MoveNames.GrassKnot] = 80,
|
||||
[MoveNames.GuardianOfAlola] = 80,
|
||||
[MoveNames.GyroBall] = 80,
|
||||
[MoveNames.HeatCrash] = 80,
|
||||
[MoveNames.HeavySlam] = 80,
|
||||
[MoveNames.HiddenPower] = 80,
|
||||
[MoveNames.LowKick] = 80,
|
||||
[MoveNames.NaturalGift] = 80,
|
||||
[MoveNames.NaturesMadness] = 80,
|
||||
[MoveNames.NightShade] = 80,
|
||||
[MoveNames.Present] = 80,
|
||||
[MoveNames.Psywave] = 80,
|
||||
[MoveNames.Punishment] = 80,
|
||||
[MoveNames.Return] = 80,
|
||||
[MoveNames.Reversal] = 80,
|
||||
[MoveNames.SeismicToss] = 80,
|
||||
[MoveNames.SonicBoom] = 80,
|
||||
[MoveNames.SpitUp] = 80,
|
||||
[MoveNames.SuperFang] = 80,
|
||||
[MoveNames.TrumpCard] = 80,
|
||||
[MoveNames.WringOut] = 80,
|
||||
// 20 BP: Stored Power, Power Trip
|
||||
[MoveNames.StoredPower] = 20,
|
||||
[MoveNames.PowerTrip] = 20,
|
||||
};
|
||||
|
||||
private static byte GetBasePower(IMoveData moveData)
|
||||
{
|
||||
// OHKO moves (handled by secondary effect)
|
||||
if (moveData.SecondaryEffect?.Name == "one_hit_ko")
|
||||
if (moveData.SecondaryEffect?.Name == MoveEffectNames.OneHitKo)
|
||||
return 150;
|
||||
|
||||
return moveData.Name.ToString() switch
|
||||
{
|
||||
// 150 BP: Specific moves
|
||||
"blast_burn" or "eruption" or "water_spout" or "fissure" or "guillotine" or "horn_drill"
|
||||
or "sheer_cold" => 150,
|
||||
// 120 BP: Counter, Metal Burst, Mirror Coat
|
||||
"counter" or "metal_burst" or "mirror_coat" => 120,
|
||||
// 80 BP: List of variable power and fixed-damage moves
|
||||
"crush_grip" or "dragon_rage" or "electro_ball" or "endeavor" or "final_gambit" or "flail" or "fling"
|
||||
or "frustration" or "grass_knot" or "guardian_of_alola" or "gyro_ball" or "heat_crash" or "heavy_slam"
|
||||
or "hidden_power" or "low_kick" or "natural_gift" or "natures_madness" or "night_shade" or "present"
|
||||
or "psywave" or "punishment" or "return" or "reversal" or "seismic_toss" or "sonic_boom" or "spit_up"
|
||||
or "super_fang" or "trump_card" or "wring_out" => 80,
|
||||
// 20 BP: Stored Power, Power Trip
|
||||
"stored_power" or "power_trip" => 20,
|
||||
_ => moveData.BasePower == 0 ? (byte)1 : moveData.BasePower,
|
||||
};
|
||||
if (FixedBasePowers.TryGetValue(moveData.Name, out var fixedBasePower))
|
||||
return fixedBasePower;
|
||||
|
||||
return moveData.BasePower == 0 ? (byte)1 : moveData.BasePower;
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ public class Galvanize : Script, IScriptChangeMoveType, IScriptChangeDamageModif
|
||||
/// <inheritdoc />
|
||||
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
|
||||
{
|
||||
if (typeIdentifier?.Name == "normal" &&
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType))
|
||||
if (typeIdentifier?.Name == TypeNames.Normal &&
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Electric, out var electricType))
|
||||
{
|
||||
typeIdentifier = electricType;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class Galvanize : Script, IScriptChangeMoveType, IScriptChangeDamageModif
|
||||
/// <inheritdoc />
|
||||
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "electric")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Electric)
|
||||
modifier *= 1.2f;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class Heatproof : Script, IScriptChangeBasePower
|
||||
/// <inheritdoc />
|
||||
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "fire")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Fire)
|
||||
{
|
||||
basePower = (ushort)(basePower / 2);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class Justified : Script, IScriptOnIncomingHit
|
||||
/// <inheritdoc />
|
||||
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name != "dark")
|
||||
if (move.GetHitData(target, hit).Type?.Name != TypeNames.Dark)
|
||||
return;
|
||||
|
||||
EventBatchId batchId = new();
|
||||
|
||||
@@ -11,7 +11,7 @@ public class LightningRod : Script, IScriptChangeIncomingTargets, IScriptChangeE
|
||||
/// <inheritdoc />
|
||||
public void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
|
||||
{
|
||||
if (moveChoice.ChosenMove.MoveData.MoveType.Name == "electric" && targets.Count == 1)
|
||||
if (moveChoice.ChosenMove.MoveData.MoveType.Name == TypeNames.Electric && targets.Count == 1)
|
||||
{
|
||||
targets = [moveChoice.User];
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class LightningRod : Script, IScriptChangeIncomingTargets, IScriptChangeE
|
||||
/// <inheritdoc />
|
||||
public void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name != "electric")
|
||||
if (move.GetHitData(target, hit).Type?.Name != TypeNames.Electric)
|
||||
return;
|
||||
|
||||
effectiveness = 0f;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class LiquidVoice : Script, IScriptChangeMoveType
|
||||
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
|
||||
{
|
||||
if (move.UseMove.HasFlag(MoveFlags.Sound) &&
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("water", out var waterType))
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Water, out var waterType))
|
||||
{
|
||||
typeIdentifier = waterType;
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ public class MagnetPull : Script, IScriptPreventOpponentRunAway, IScriptPreventO
|
||||
/// <inheritdoc />
|
||||
public void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent)
|
||||
{
|
||||
if (choice.User.Types.Any(x => x.Name == "steel"))
|
||||
if (choice.User.Types.Any(x => x.Name == TypeNames.Steel))
|
||||
prevent = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent)
|
||||
{
|
||||
if (choice.User.Types.Any(x => x.Name == "steel"))
|
||||
if (choice.User.Types.Any(x => x.Name == TypeNames.Steel))
|
||||
prevent = true;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ public class Minus : Script, IScriptChangeOffensiveStatValue
|
||||
var battleData = move.User.BattleData;
|
||||
if (battleData is null)
|
||||
return;
|
||||
if (battleData.BattleSide.Pokemon.WhereNotNull().Any(x => x.IsUsable && x.ActiveAbility?.Name == "plus"))
|
||||
if (battleData.BattleSide.Pokemon.WhereNotNull()
|
||||
.Any(x => x.IsUsable && x.ActiveAbility?.Name == AbilityNames.Plus))
|
||||
{
|
||||
value = value.MultiplyOrMax(1.5f);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class MotorDrive : Script, IScriptIsInvulnerableToMove
|
||||
/// <inheritdoc />
|
||||
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
|
||||
{
|
||||
if (move.UseMove.MoveType.Name != "electric")
|
||||
if (move.UseMove.MoveType.Name != TypeNames.Electric)
|
||||
return;
|
||||
invulnerable = true;
|
||||
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
|
||||
@@ -8,72 +8,40 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
|
||||
[Script(ScriptCategory.Ability, "multitype")]
|
||||
public class Multitype : Script, IScriptOnAfterHeldItemChange
|
||||
{
|
||||
private static readonly Dictionary<StringKey, StringKey> PlateForms = new()
|
||||
{
|
||||
[ItemNames.FistPlate] = FormNames.ArceusFighting,
|
||||
[ItemNames.FlamePlate] = FormNames.ArceusFire,
|
||||
[ItemNames.ZapPlate] = FormNames.ArceusElectric,
|
||||
[ItemNames.DracoPlate] = FormNames.ArceusDragon,
|
||||
[ItemNames.DreadPlate] = FormNames.ArceusDark,
|
||||
[ItemNames.EarthPlate] = FormNames.ArceusGround,
|
||||
[ItemNames.IciclePlate] = FormNames.ArceusIce,
|
||||
[ItemNames.InsectPlate] = FormNames.ArceusBug,
|
||||
[ItemNames.IronPlate] = FormNames.ArceusSteel,
|
||||
[ItemNames.MeadowPlate] = FormNames.ArceusGrass,
|
||||
[ItemNames.MindPlate] = FormNames.ArceusPsychic,
|
||||
[ItemNames.PixiePlate] = FormNames.ArceusFairy,
|
||||
[ItemNames.SkyPlate] = FormNames.ArceusFlying,
|
||||
[ItemNames.SplashPlate] = FormNames.ArceusWater,
|
||||
[ItemNames.SpookyPlate] = FormNames.ArceusGhost,
|
||||
[ItemNames.StonePlate] = FormNames.ArceusRock,
|
||||
[ItemNames.ToxicPlate] = FormNames.ArceusPoison,
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item)
|
||||
{
|
||||
if (pokemon.Species.Name != "arceus")
|
||||
if (pokemon.Species.Name != SpeciesNames.Arceus)
|
||||
return;
|
||||
if (item is null && pokemon.Form.Name != "default")
|
||||
if (item is null && pokemon.Form.Name != FormNames.Default)
|
||||
{
|
||||
pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
|
||||
}
|
||||
else if (item is not null && item.Name.ToString().EndsWith("_plate", StringComparison.OrdinalIgnoreCase))
|
||||
else if (item is not null && PlateForms.TryGetValue(item.Name, out var formName) &&
|
||||
pokemon.Species.TryGetForm(formName, out var form))
|
||||
{
|
||||
var platePrefix = item.Name.ToString().Replace("_plate", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
switch (platePrefix)
|
||||
{
|
||||
case "fist" when pokemon.Species.TryGetForm("arceus_fighting", out var fightingForm):
|
||||
pokemon.ChangeForm(fightingForm);
|
||||
break;
|
||||
case "flame" when pokemon.Species.TryGetForm("arceus_fire", out var fireForm):
|
||||
pokemon.ChangeForm(fireForm);
|
||||
break;
|
||||
case "shock" when pokemon.Species.TryGetForm("arceus_electric", out var electricForm):
|
||||
pokemon.ChangeForm(electricForm);
|
||||
break;
|
||||
case "draco" when pokemon.Species.TryGetForm("arceus_dragon", out var dragonForm):
|
||||
pokemon.ChangeForm(dragonForm);
|
||||
break;
|
||||
case "dread" when pokemon.Species.TryGetForm("arceus_dark", out var darkForm):
|
||||
pokemon.ChangeForm(darkForm);
|
||||
break;
|
||||
case "earth" when pokemon.Species.TryGetForm("arceus_ground", out var groundForm):
|
||||
pokemon.ChangeForm(groundForm);
|
||||
break;
|
||||
case "icicle" when pokemon.Species.TryGetForm("arceus_ice", out var iceForm):
|
||||
pokemon.ChangeForm(iceForm);
|
||||
break;
|
||||
case "insect" when pokemon.Species.TryGetForm("arceus_bug", out var bugForm):
|
||||
pokemon.ChangeForm(bugForm);
|
||||
break;
|
||||
case "iron" when pokemon.Species.TryGetForm("arceus_steel", out var steelForm):
|
||||
pokemon.ChangeForm(steelForm);
|
||||
break;
|
||||
case "meadow" when pokemon.Species.TryGetForm("arceus_grass", out var grassForm):
|
||||
pokemon.ChangeForm(grassForm);
|
||||
break;
|
||||
case "mind" when pokemon.Species.TryGetForm("arceus_psychic", out var psychicForm):
|
||||
pokemon.ChangeForm(psychicForm);
|
||||
break;
|
||||
case "pixie" when pokemon.Species.TryGetForm("arceus_fairy", out var fairyForm):
|
||||
pokemon.ChangeForm(fairyForm);
|
||||
break;
|
||||
case "sky" when pokemon.Species.TryGetForm("arceus_flying", out var flyingForm):
|
||||
pokemon.ChangeForm(flyingForm);
|
||||
break;
|
||||
case "splash" when pokemon.Species.TryGetForm("arceus_water", out var waterForm):
|
||||
pokemon.ChangeForm(waterForm);
|
||||
break;
|
||||
case "spooky" when pokemon.Species.TryGetForm("arceus_ghost", out var ghostForm):
|
||||
pokemon.ChangeForm(ghostForm);
|
||||
break;
|
||||
case "stone" when pokemon.Species.TryGetForm("arceus_rock", out var rockForm):
|
||||
pokemon.ChangeForm(rockForm);
|
||||
break;
|
||||
case "toxic" when pokemon.Species.TryGetForm("arceus_poison", out var poisonForm):
|
||||
pokemon.ChangeForm(poisonForm);
|
||||
break;
|
||||
}
|
||||
pokemon.ChangeForm(form);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ public class Mummy : Script, IScriptOnIncomingHit
|
||||
/// <inheritdoc />
|
||||
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (!move.GetHitData(target, hit).IsContact || move.User.ActiveAbility?.Name == "mummy" ||
|
||||
!move.Battle.Library.StaticLibrary.Abilities.TryGet("mummy", out var mummyAbility))
|
||||
if (!move.GetHitData(target, hit).IsContact || move.User.ActiveAbility?.Name == AbilityNames.Mummy ||
|
||||
!move.Battle.Library.StaticLibrary.Abilities.TryGet(AbilityNames.Mummy, out var mummyAbility))
|
||||
return;
|
||||
|
||||
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
|
||||
@@ -11,14 +11,14 @@ public class Normalize : Script, IScriptChangeMoveType, IScriptChangeBasePower
|
||||
/// <inheritdoc />
|
||||
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
|
||||
{
|
||||
if (move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("normal", out var normalType))
|
||||
if (move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Normal, out var normalType))
|
||||
typeIdentifier = normalType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "normal")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Normal)
|
||||
basePower = (ushort)(basePower * 1.2f); // Boost Normal-type moves by 30%
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,15 @@ public class Pixilate : Script, IScriptChangeMoveType, IScriptChangeBasePower
|
||||
/// <inheritdoc />
|
||||
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
|
||||
{
|
||||
if (typeIdentifier?.Name == "normal" &&
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("fairy", out var fairyType))
|
||||
if (typeIdentifier?.Name == TypeNames.Normal &&
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Fairy, out var fairyType))
|
||||
typeIdentifier = fairyType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "fairy")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Fairy)
|
||||
basePower = (ushort)(basePower * 1.2f); // Boost Normal-type moves by 30%
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ public class Plus : Script, IScriptChangeOffensiveStatValue
|
||||
var battleData = move.User.BattleData;
|
||||
if (battleData is null)
|
||||
return;
|
||||
if (battleData.BattleSide.Pokemon.WhereNotNull().Any(x => x.IsUsable && x.ActiveAbility?.Name == "minus"))
|
||||
if (battleData.BattleSide.Pokemon.WhereNotNull()
|
||||
.Any(x => x.IsUsable && x.ActiveAbility?.Name == AbilityNames.Minus))
|
||||
{
|
||||
value = value.MultiplyOrMax(1.5f);
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ public class PowerConstruct : Script, IScriptOnEndTurn
|
||||
{
|
||||
if (_pokemon?.BattleData?.Battle == null)
|
||||
return;
|
||||
if (_pokemon.Species.Name != "zygarde")
|
||||
if (_pokemon.Species.Name != SpeciesNames.Zygarde)
|
||||
return;
|
||||
if (_pokemon.CurrentHealth > _pokemon.BoostedStats.Hp / 2)
|
||||
return;
|
||||
if (_pokemon.Form.Name != "10" || _pokemon.Form.Name != "50")
|
||||
if (_pokemon.Form.Name != FormNames._10 || _pokemon.Form.Name != FormNames._50)
|
||||
return;
|
||||
if (!_pokemon.Species.TryGetForm("complete", out var completeForm))
|
||||
if (!_pokemon.Species.TryGetForm(FormNames.Complete, out var completeForm))
|
||||
return;
|
||||
|
||||
_pokemon.ChangeForm(completeForm);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class QuickFeet : Script, IScriptChangeSpeed
|
||||
{
|
||||
if (choice.User.StatusScript.IsEmpty)
|
||||
return;
|
||||
if (choice.User.StatusScript.Script?.Name == "paralyzed")
|
||||
if (choice.User.StatusScript.Script?.Name == ScriptUtils.ResolveName<Status.Paralyzed>())
|
||||
speed = speed.MultiplyOrMax(2);
|
||||
speed = speed.MultiplyOrMax(1.5f);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ public class RksSystem : Script, IScriptOnAfterHeldItemChange
|
||||
/// <inheritdoc />
|
||||
public void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item)
|
||||
{
|
||||
if (pokemon.Species.Name != "silvally")
|
||||
if (pokemon.Species.Name != SpeciesNames.Silvally)
|
||||
return;
|
||||
if (item is null && pokemon.Form.Name != "default")
|
||||
if (item is null && pokemon.Form.Name != FormNames.Default)
|
||||
{
|
||||
pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public class Rattled : Script, IScriptOnIncomingHit
|
||||
var type = move.GetHitData(target, hit).Type;
|
||||
if (type is null)
|
||||
return;
|
||||
if (type.Value.Name != "bug" && type.Value.Name != "ghost" && type.Value.Name != "dark")
|
||||
if (type.Value.Name != TypeNames.Bug && type.Value.Name != TypeNames.Ghost && type.Value.Name != TypeNames.Dark)
|
||||
return;
|
||||
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
target.ChangeStatBoost(Statistic.Speed, 1, true, false);
|
||||
|
||||
@@ -11,15 +11,15 @@ public class Refrigerate : Script, IScriptChangeMoveType, IScriptChangeBasePower
|
||||
/// <inheritdoc />
|
||||
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
|
||||
{
|
||||
if (typeIdentifier?.Name == "normal" &&
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("ice", out var iceType))
|
||||
if (typeIdentifier?.Name == TypeNames.Normal &&
|
||||
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Ice, out var iceType))
|
||||
typeIdentifier = iceType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "ice")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Ice)
|
||||
basePower = (ushort)(basePower * 1.2f); // Boost Normal-type moves by 30%
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ public class SandForce : Script, IScriptChangeBasePower, IScriptCustomTrigger
|
||||
if (move.Battle.WeatherName == ScriptUtils.ResolveName<Weather.Sandstorm>())
|
||||
{
|
||||
var type = move.GetHitData(target, hit).Type;
|
||||
if (type != null &&
|
||||
(type.Value.Name == "rock" || type.Value.Name == "ground" || type.Value.Name == "steel"))
|
||||
if (type != null && (type.Value.Name == TypeNames.Rock || type.Value.Name == TypeNames.Ground ||
|
||||
type.Value.Name == TypeNames.Steel))
|
||||
{
|
||||
basePower = basePower.MultiplyOrMax(1.3f);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class SapSipper : Script, IScriptIsInvulnerableToMove
|
||||
/// <inheritdoc />
|
||||
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
|
||||
{
|
||||
if (move.GetHitData(target, 0).Type?.Name == "grass")
|
||||
if (move.GetHitData(target, 0).Type?.Name == TypeNames.Grass)
|
||||
{
|
||||
invulnerable = true;
|
||||
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
|
||||
@@ -29,15 +29,15 @@ public class Schooling : Script, IScriptOnEndTurn, IScriptOnSwitchIn
|
||||
if (pokemon is null)
|
||||
return;
|
||||
|
||||
if (pokemon.Species.Name != "wishiwashi" || pokemon.BattleData?.Battle == null)
|
||||
if (pokemon.Species.Name != SpeciesNames.Wishiwashi || pokemon.BattleData?.Battle == null)
|
||||
return;
|
||||
// If Wishiwashi has less than 25% health, change to Solo form
|
||||
if (pokemon.CurrentHealth < pokemon.MaxHealth / 4 && pokemon.Form.Name != "default")
|
||||
if (pokemon.CurrentHealth < pokemon.MaxHealth / 4 && pokemon.Form.Name != FormNames.Default)
|
||||
{
|
||||
pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
|
||||
}
|
||||
else if (pokemon.CurrentHealth >= pokemon.MaxHealth / 4 && pokemon.Form.Name != "school" &&
|
||||
pokemon.Species.TryGetForm("school", out var schoolForm))
|
||||
else if (pokemon.CurrentHealth >= pokemon.MaxHealth / 4 && pokemon.Form.Name != FormNames.School &&
|
||||
pokemon.Species.TryGetForm(FormNames.School, out var schoolForm))
|
||||
{
|
||||
pokemon.ChangeForm(schoolForm);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ public class Scrappy : Script, IScriptChangeTypesForMove
|
||||
IList<TypeIdentifier> types)
|
||||
{
|
||||
var hitType = executingMove.GetHitData(target, hitIndex).Type;
|
||||
if (hitType?.Name != "normal" && hitType?.Name != "fighting")
|
||||
if (hitType?.Name != TypeNames.Normal && hitType?.Name != TypeNames.Fighting)
|
||||
return;
|
||||
if (types.Any(x => x.Name == "ghost"))
|
||||
types.RemoveAll(x => x.Name == "ghost");
|
||||
if (types.Any(x => x.Name == TypeNames.Ghost))
|
||||
types.RemoveAll(x => x.Name == TypeNames.Ghost);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class ShieldsDown : Script, IScriptOnEndTurn, IScriptOnSwitchIn
|
||||
if (pokemon is null)
|
||||
return;
|
||||
|
||||
if (pokemon.Species.Name != "minior" || pokemon.BattleData?.Battle == null)
|
||||
if (pokemon.Species.Name != SpeciesNames.Minior || pokemon.BattleData?.Battle == null)
|
||||
return;
|
||||
if (pokemon.CurrentHealth < pokemon.MaxHealth / 2 && pokemon.Form.Name.ToString().EndsWith("-meteor"))
|
||||
{
|
||||
@@ -35,7 +35,7 @@ public class ShieldsDown : Script, IScriptOnEndTurn, IScriptOnSwitchIn
|
||||
pokemon.Form.Name.ToString().EndsWith("-meteor") == false)
|
||||
{
|
||||
var baseFormName = pokemon.Form.Name;
|
||||
if (baseFormName == "default")
|
||||
if (baseFormName == FormNames.Default)
|
||||
baseFormName = "blue";
|
||||
var meteorFormName = baseFormName + "-meteor";
|
||||
if (pokemon.Species.TryGetForm(meteorFormName, out var meteorFormData))
|
||||
|
||||
@@ -13,11 +13,11 @@ public class StanceChange : Script, IScriptOnBeforeMove
|
||||
/// <inheritdoc />
|
||||
public void OnBeforeMove(IExecutingMove move)
|
||||
{
|
||||
if (move.User.Species.Name != "aegislash")
|
||||
if (move.User.Species.Name != SpeciesNames.Aegislash)
|
||||
return;
|
||||
|
||||
if (move.UseMove.Category is not (MoveCategory.Physical or MoveCategory.Special) ||
|
||||
move.User.Form.Name == "blade" || !move.User.Species.TryGetForm("blade", out var bladeForm))
|
||||
move.User.Form.Name == FormNames.Blade || !move.User.Species.TryGetForm(FormNames.Blade, out var bladeForm))
|
||||
return;
|
||||
EventBatchId batchId = new();
|
||||
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)
|
||||
|
||||
@@ -12,7 +12,7 @@ public class Steelworker : Script, IScriptChangeOffensiveStatValue
|
||||
public void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
|
||||
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "steel")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Steel)
|
||||
{
|
||||
value = value.MultiplyOrMax(1.5f);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class StormDrain : Script, IScriptChangeIncomingTargets, IScriptChangeEff
|
||||
/// <inheritdoc />
|
||||
public void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
|
||||
{
|
||||
if (moveChoice.ChosenMove.MoveData.MoveType.Name == "water" && targets.Count == 1)
|
||||
if (moveChoice.ChosenMove.MoveData.MoveType.Name == TypeNames.Water && targets.Count == 1)
|
||||
{
|
||||
targets = [moveChoice.User];
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class StormDrain : Script, IScriptChangeIncomingTargets, IScriptChangeEff
|
||||
/// <inheritdoc />
|
||||
public void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name != "water")
|
||||
if (move.GetHitData(target, hit).Type?.Name != TypeNames.Water)
|
||||
return;
|
||||
|
||||
effectiveness = 0f;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class ThickFat : Script, IScriptChangeMoveDamage
|
||||
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
|
||||
{
|
||||
var type = move.GetHitData(target, hit).Type;
|
||||
if (type is not null && (type.Value.Name == "ice" || type.Value.Name == "fire"))
|
||||
if (type is not null && (type.Value.Name == TypeNames.Ice || type.Value.Name == TypeNames.Fire))
|
||||
{
|
||||
damage /= 2;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class VoltAbsorb : Script, IScriptIsInvulnerableToMove
|
||||
/// <inheritdoc />
|
||||
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
|
||||
{
|
||||
if (move.GetHitData(target, 0).Type?.Name != "electric")
|
||||
if (move.GetHitData(target, 0).Type?.Name != TypeNames.Electric)
|
||||
return;
|
||||
invulnerable = true;
|
||||
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
|
||||
@@ -11,7 +11,7 @@ public class WaterAbsorb : Script, IScriptIsInvulnerableToMove
|
||||
/// <inheritdoc />
|
||||
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
|
||||
{
|
||||
if (move.GetHitData(target, 0).Type?.Name != "water")
|
||||
if (move.GetHitData(target, 0).Type?.Name != TypeNames.Water)
|
||||
return;
|
||||
invulnerable = true;
|
||||
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
|
||||
@@ -18,14 +18,14 @@ public class WaterBubble : Script, IScriptChangeIncomingMoveDamage, IScriptChang
|
||||
/// <inheritdoc />
|
||||
public void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "fire")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Fire)
|
||||
damage /= 2;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name == "water")
|
||||
if (move.GetHitData(target, hit).Type?.Name == TypeNames.Water)
|
||||
damage *= 2;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class WaterCompaction : Script, IScriptOnIncomingHit
|
||||
/// <inheritdoc />
|
||||
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.GetHitData(target, hit).Type?.Name != "water")
|
||||
if (move.GetHitData(target, hit).Type?.Name != TypeNames.Water)
|
||||
return;
|
||||
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
|
||||
target.ChangeStatBoost(Statistic.Defense, 2, true, false);
|
||||
|
||||
@@ -19,14 +19,14 @@ public class ZenMode : Script, IScriptOnEndTurn, IScriptOnSwitchIn
|
||||
if (pokemon is null)
|
||||
return;
|
||||
|
||||
if (pokemon.Species.Name != "darmanitan" || pokemon.BattleData?.Battle == null)
|
||||
if (pokemon.Species.Name != SpeciesNames.Darmanitan || pokemon.BattleData?.Battle == null)
|
||||
return;
|
||||
if (pokemon.CurrentHealth < pokemon.MaxHealth / 2 && pokemon.Form.Name != "zen" &&
|
||||
pokemon.Species.TryGetForm("zen", out var zenForm))
|
||||
if (pokemon.CurrentHealth < pokemon.MaxHealth / 2 && pokemon.Form.Name != FormNames.Zen &&
|
||||
pokemon.Species.TryGetForm(FormNames.Zen, out var zenForm))
|
||||
{
|
||||
pokemon.ChangeForm(zenForm);
|
||||
}
|
||||
else if (pokemon.CurrentHealth >= pokemon.MaxHealth / 2 && pokemon.Form.Name == "zen")
|
||||
else if (pokemon.CurrentHealth >= pokemon.MaxHealth / 2 && pokemon.Form.Name == FormNames.Zen)
|
||||
{
|
||||
pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@ public class FairyLockEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAw
|
||||
/// <inheritdoc />
|
||||
public void PreventSelfRunAway(IFleeChoice choice, ref bool prevent)
|
||||
{
|
||||
if (choice.User.Types.All(x => x.Name != "ghost"))
|
||||
if (choice.User.Types.All(x => x.Name != TypeNames.Ghost))
|
||||
prevent = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent)
|
||||
{
|
||||
if (choice.User.Types.All(x => x.Name != "ghost"))
|
||||
if (choice.User.Types.All(x => x.Name != TypeNames.Ghost))
|
||||
prevent = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public class Gravity : Script, IScriptFailIncomingMove, IScriptOnEndTurn, IScrip
|
||||
{
|
||||
var typeLibrary = target.Library.StaticLibrary.Types;
|
||||
|
||||
if (executingMove.UseMove.MoveType.Name != "ground")
|
||||
if (executingMove.UseMove.MoveType.Name != TypeNames.Ground)
|
||||
return;
|
||||
// Remove all types that are immune to ground moves
|
||||
types.RemoveAll(x => typeLibrary.GetSingleEffectiveness(executingMove.UseMove.MoveType, x) == 0);
|
||||
|
||||
@@ -6,8 +6,8 @@ public class IonDelugeEffect : Script, IScriptChangeMoveType, IScriptOnEndTurn
|
||||
/// <inheritdoc />
|
||||
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType)
|
||||
{
|
||||
if (moveType?.Name == "normal" &&
|
||||
target.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType))
|
||||
if (moveType?.Name == TypeNames.Normal &&
|
||||
target.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Electric, out var electricType))
|
||||
{
|
||||
moveType = electricType;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ public class MudSportEffect : Script, IScriptChangeBasePower, IScriptOnEndTurn
|
||||
/// <inheritdoc />
|
||||
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
|
||||
{
|
||||
if (move.UseMove.MoveType.Name == "electric")
|
||||
if (move.UseMove.MoveType.Name == TypeNames.Electric)
|
||||
{
|
||||
basePower = basePower.MultiplyOrMax(1352 / 4096f);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class UproarEffect : Script, IScriptOnBeforeTurnStart, IScriptOnSecondary
|
||||
/// <inheritdoc />
|
||||
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.User == _placer && move.UseMove.Name == "uproar")
|
||||
if (move.User == _placer && move.UseMove.Name == MoveNames.Uproar)
|
||||
_hasUsedUproar = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ public class WaterSportEffect : Script, IScriptChangeMoveDamage, IScriptOnSwitch
|
||||
/// <inheritdoc />
|
||||
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
|
||||
{
|
||||
if (move.UseMove.MoveType.Name == "fire")
|
||||
if (move.UseMove.MoveType.Name == TypeNames.Fire)
|
||||
{
|
||||
damage = damage / 2;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ public class MoonBall : PokeballScript
|
||||
{
|
||||
switch (x)
|
||||
{
|
||||
case ItemUseEvolution itemUseEvolution when itemUseEvolution.Item == "moon_ball":
|
||||
case ItemGenderEvolution itemGenderEvolution when itemGenderEvolution.Item == "moon_ball":
|
||||
case ItemUseEvolution itemUseEvolution when itemUseEvolution.Item == ItemNames.MoonBall:
|
||||
case ItemGenderEvolution itemGenderEvolution when itemGenderEvolution.Item == ItemNames.MoonBall:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -8,13 +8,10 @@ public class NetBall : PokeballScript
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly StringKey WaterType = "water";
|
||||
private static readonly StringKey BugType = "bug";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
|
||||
{
|
||||
if (target.Types.Any(x => x.Name == WaterType || x.Name == BugType))
|
||||
if (target.Types.Any(x => x.Name == TypeNames.Water || x.Name == TypeNames.Bug))
|
||||
{
|
||||
catchRate = catchRate.MultiplyOrMax(3.5f);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||
[Script(ScriptCategory.Move, "block")]
|
||||
public class Block : Script, IScriptOnSecondaryEffect
|
||||
{
|
||||
private static StringKey GhostTypeName => new("ghost");
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (target.Types.Any(x => x.Name == GhostTypeName))
|
||||
if (target.Types.Any(x => x.Name == TypeNames.Ghost))
|
||||
return;
|
||||
target.Volatile.Add(new BlockEffect());
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class Bounce : Script, IScriptPreventMove, IScriptOnBeforeMove, IScriptOn
|
||||
var random = battle.Random;
|
||||
if (random.EffectChance(30, move, target, hit))
|
||||
{
|
||||
target.SetStatus("paralyzed", move.User);
|
||||
target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), move.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public class BurnUp : Script, IScriptOnSecondaryEffect
|
||||
if (battleData == null)
|
||||
return;
|
||||
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
|
||||
if (!typeLibrary.TryGetTypeIdentifier("fire", out var fireType))
|
||||
if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Fire, out var fireType))
|
||||
return;
|
||||
if (!move.User.Types.Contains(fireType))
|
||||
{
|
||||
@@ -18,7 +18,7 @@ public class BurnUp : Script, IScriptOnSecondaryEffect
|
||||
return;
|
||||
}
|
||||
|
||||
if (move.User.HasStatus("frozen"))
|
||||
if (move.User.HasStatus(ScriptUtils.ResolveName<Status.Frozen>()))
|
||||
move.User.ClearStatus();
|
||||
move.User.RemoveType(fireType);
|
||||
}
|
||||
|
||||
@@ -20,23 +20,23 @@ public class Camouflage : Script, IScriptOnSecondaryEffect
|
||||
var environmentCategory = battle.GetEnvironmentCategory();
|
||||
return environmentCategory switch
|
||||
{
|
||||
EnvironmentHelper.EnvironmentCategory.Electric when typesLibrary.TryGetTypeIdentifier("electric",
|
||||
EnvironmentHelper.EnvironmentCategory.Electric when typesLibrary.TryGetTypeIdentifier(TypeNames.Electric,
|
||||
out var electricType) => electricType,
|
||||
EnvironmentHelper.EnvironmentCategory.Fairy when typesLibrary.TryGetTypeIdentifier("fairy",
|
||||
EnvironmentHelper.EnvironmentCategory.Fairy when typesLibrary.TryGetTypeIdentifier(TypeNames.Fairy,
|
||||
out var fairyType) => fairyType,
|
||||
EnvironmentHelper.EnvironmentCategory.Grass when typesLibrary.TryGetTypeIdentifier("grass",
|
||||
EnvironmentHelper.EnvironmentCategory.Grass when typesLibrary.TryGetTypeIdentifier(TypeNames.Grass,
|
||||
out var grassType) => grassType,
|
||||
EnvironmentHelper.EnvironmentCategory.Psychic when typesLibrary.TryGetTypeIdentifier("psychic",
|
||||
EnvironmentHelper.EnvironmentCategory.Psychic when typesLibrary.TryGetTypeIdentifier(TypeNames.Psychic,
|
||||
out var psychicType) => psychicType,
|
||||
EnvironmentHelper.EnvironmentCategory.Rock when typesLibrary.TryGetTypeIdentifier("rock", out var rockType)
|
||||
=> rockType,
|
||||
EnvironmentHelper.EnvironmentCategory.Ground when typesLibrary.TryGetTypeIdentifier("ground",
|
||||
EnvironmentHelper.EnvironmentCategory.Rock when typesLibrary.TryGetTypeIdentifier(TypeNames.Rock,
|
||||
out var rockType) => rockType,
|
||||
EnvironmentHelper.EnvironmentCategory.Ground when typesLibrary.TryGetTypeIdentifier(TypeNames.Ground,
|
||||
out var groundType) => groundType,
|
||||
EnvironmentHelper.EnvironmentCategory.Ice when typesLibrary.TryGetTypeIdentifier("ice", out var iceType) =>
|
||||
iceType,
|
||||
EnvironmentHelper.EnvironmentCategory.Water when typesLibrary.TryGetTypeIdentifier("water",
|
||||
EnvironmentHelper.EnvironmentCategory.Ice when typesLibrary.TryGetTypeIdentifier(TypeNames.Ice,
|
||||
out var iceType) => iceType,
|
||||
EnvironmentHelper.EnvironmentCategory.Water when typesLibrary.TryGetTypeIdentifier(TypeNames.Water,
|
||||
out var waterType) => waterType,
|
||||
EnvironmentHelper.EnvironmentCategory.Normal when typesLibrary.TryGetTypeIdentifier("normal",
|
||||
EnvironmentHelper.EnvironmentCategory.Normal when typesLibrary.TryGetTypeIdentifier(TypeNames.Normal,
|
||||
out var normalType) => normalType,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ public class Captivate : Script, IScriptOnSecondaryEffect
|
||||
move.GetHitData(target, hit).Fail();
|
||||
return;
|
||||
}
|
||||
if (target.ActiveAbility?.Name == "oblivious")
|
||||
if (target.ActiveAbility?.Name == AbilityNames.Oblivious)
|
||||
{
|
||||
move.GetHitData(target, hit).Fail();
|
||||
return;
|
||||
|
||||
@@ -3,11 +3,14 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||
[Script(ScriptCategory.Move, "conversion_2")]
|
||||
public class Conversion2 : Script, IScriptOnSecondaryEffect
|
||||
{
|
||||
// The typeless "none" type is not part of the type data, so no generated constant exists for it.
|
||||
private static readonly StringKey NoneTypeName = "none";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
var lastMoveByTarget = target.BattleData?.LastMoveChoice;
|
||||
if (lastMoveByTarget == null || lastMoveByTarget.ChosenMove.MoveData.MoveType.Name == "none")
|
||||
if (lastMoveByTarget == null || lastMoveByTarget.ChosenMove.MoveData.MoveType.Name == NoneTypeName)
|
||||
{
|
||||
move.GetHitData(target, hit).Fail();
|
||||
return;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class Curse : Script, IScriptOnSecondaryEffect
|
||||
if (battleData == null)
|
||||
return;
|
||||
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
|
||||
if (!typeLibrary.TryGetTypeIdentifier("ghost", out var ghostType))
|
||||
if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Ghost, out var ghostType))
|
||||
return;
|
||||
if (move.User.Types.Contains(ghostType))
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ public class Drain : Script, IScriptOnInitialize, IScriptOnSecondaryEffect
|
||||
var user = move.User;
|
||||
var damage = move.GetHitData(target, hit).Damage;
|
||||
var healed = (uint)(damage * DrainModifier);
|
||||
if (move.User.HasHeldItem("big_root"))
|
||||
if (move.User.HasHeldItem(ItemNames.BigRoot))
|
||||
healed = (uint)(healed * 1.3f);
|
||||
var invert = false;
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||
[Script(ScriptCategory.Move, "dream_eater")]
|
||||
public class DreamEater : Script, IScriptOnSecondaryEffect, IScriptBlockOutgoingHit
|
||||
{
|
||||
private static readonly StringKey AsleepStatus = new("asleep");
|
||||
private static readonly StringKey ComatoseAbility = new("comatose");
|
||||
|
||||
/// <inheritdoc />
|
||||
public void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
|
||||
{
|
||||
if (!target.HasStatus(AsleepStatus) && target.ActiveAbility?.Name != ComatoseAbility)
|
||||
if (!target.HasStatus(ScriptUtils.ResolveName<Status.Sleep>()) &&
|
||||
target.ActiveAbility?.Name != AbilityNames.Comatose)
|
||||
block = true;
|
||||
}
|
||||
|
||||
@@ -19,7 +17,7 @@ public class DreamEater : Script, IScriptOnSecondaryEffect, IScriptBlockOutgoing
|
||||
var user = move.User;
|
||||
var damage = move.GetHitData(target, hit).Damage;
|
||||
var healed = (uint)(damage * 0.5f);
|
||||
if (move.User.HasHeldItem("big_root"))
|
||||
if (move.User.HasHeldItem(ItemNames.BigRoot))
|
||||
healed = (uint)(healed * 1.3f);
|
||||
|
||||
var args = new CustomTriggers.ModifyDrainArgs(move, target, hit, damage, healed, false);
|
||||
|
||||
@@ -14,7 +14,7 @@ public class FireFang : Script, IScriptOnSecondaryEffect
|
||||
var random = battleData.Battle.Random;
|
||||
if (random.EffectChance(10, move, target, hit))
|
||||
{
|
||||
target.SetStatus("burned", move.User);
|
||||
target.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), move.User);
|
||||
}
|
||||
|
||||
// It also has an independent 10% chance of causing the target to flinch, if the user attacks before the target.
|
||||
|
||||
@@ -16,18 +16,19 @@ public class FirePledge : Script, IScriptStopBeforeMove
|
||||
|
||||
var pledgeMove = (IMoveChoice?)move.Battle.ChoiceQueue?.FirstOrDefault(x =>
|
||||
x is IMoveChoice mc && mc.User.BattleData?.SideIndex == move.User.BattleData?.SideIndex &&
|
||||
(mc.ChosenMove.MoveData.Name == "water_pledge" || mc.ChosenMove.MoveData.Name == "grass_pledge"));
|
||||
(mc.ChosenMove.MoveData.Name == MoveNames.WaterPledge ||
|
||||
mc.ChosenMove.MoveData.Name == MoveNames.GrassPledge));
|
||||
if (pledgeMove is null)
|
||||
return;
|
||||
|
||||
// If a pledge move is already queued, we stop the current move.
|
||||
stop = true;
|
||||
|
||||
if (pledgeMove.ChosenMove.MoveData.Name == "water_pledge")
|
||||
if (pledgeMove.ChosenMove.MoveData.Name == MoveNames.WaterPledge)
|
||||
{
|
||||
pledgeMove.Volatile.Add(new FireWaterPledgeMove());
|
||||
}
|
||||
else if (pledgeMove.ChosenMove.MoveData.Name == "grass_pledge")
|
||||
else if (pledgeMove.ChosenMove.MoveData.Name == MoveNames.GrassPledge)
|
||||
{
|
||||
pledgeMove.Volatile.Add(new FireGrassPledgeMove());
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ public class FlameWheel : Script, IScriptOnInitialize, IScriptOnSecondaryEffect
|
||||
/// <inheritdoc />
|
||||
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
|
||||
{
|
||||
if (move.User.HasStatus("frozen"))
|
||||
if (move.User.HasStatus(ScriptUtils.ResolveName<Status.Frozen>()))
|
||||
{
|
||||
move.User.ClearStatus();
|
||||
}
|
||||
|
||||
if (move.Battle.Random.EffectChance(_burnChance, move, target, hit))
|
||||
{
|
||||
target.SetStatus("burned", move.User);
|
||||
target.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), move.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class FlareBlitz : Script, IScriptOnSecondaryEffect
|
||||
|
||||
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
|
||||
{
|
||||
target.SetStatus("burned", move.User);
|
||||
target.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), move.User);
|
||||
}
|
||||
|
||||
var triggerArgs = new CustomTriggers.ModifyRecoilArgs(move, target, hit, hitData.Damage, (uint)recoilDamage);
|
||||
|
||||
@@ -11,7 +11,7 @@ public class FlowerShield : Script, IScriptOnSecondaryEffect
|
||||
var battleData = target.BattleData;
|
||||
if (battleData == null)
|
||||
return;
|
||||
if (!battleData.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("grass", out var grassType))
|
||||
if (!battleData.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Grass, out var grassType))
|
||||
return;
|
||||
|
||||
var batchId = new EventBatchId();
|
||||
|
||||
@@ -8,7 +8,7 @@ public class FlyingPress : Script, IScriptChangeTypesForMove
|
||||
IList<TypeIdentifier> types)
|
||||
{
|
||||
var typeLibrary = executingMove.User.Library.StaticLibrary.Types;
|
||||
if (!typeLibrary.TryGetTypeIdentifier("flying", out var flyingType))
|
||||
if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Flying, out var flyingType))
|
||||
return;
|
||||
types.Add(flyingType);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public class ForestsCurse : Script, IScriptOnSecondaryEffect
|
||||
if (battleData == null)
|
||||
return;
|
||||
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
|
||||
if (!typeLibrary.TryGetTypeIdentifier("grass", out var grassType) || target.Types.Contains(grassType))
|
||||
if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Grass, out var grassType) || target.Types.Contains(grassType))
|
||||
{
|
||||
move.GetHitData(target, hit).Fail();
|
||||
return;
|
||||
|
||||
@@ -11,7 +11,7 @@ public class FreezeDry : Script, IScriptChangeEffectiveness, IScriptOnSecondaryE
|
||||
return;
|
||||
|
||||
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
|
||||
if (!typeLibrary.TryGetTypeIdentifier("water", out var waterType))
|
||||
if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Water, out var waterType))
|
||||
return;
|
||||
|
||||
var hitDataType = move.GetHitData(target, hit).Type;
|
||||
@@ -32,7 +32,7 @@ public class FreezeDry : Script, IScriptChangeEffectiveness, IScriptOnSecondaryE
|
||||
|
||||
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
|
||||
{
|
||||
target.SetStatus("frozen", move.User);
|
||||
target.SetStatus(ScriptUtils.ResolveName<Status.Frozen>(), move.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class FreezeShock : BaseChargeMove<RequireChargeEffect>
|
||||
|
||||
if (battleData.Battle.Random.EffectChance(30, move, target, hit))
|
||||
{
|
||||
target.SetStatus("paralyzed", move.User);
|
||||
target.SetStatus(ScriptUtils.ResolveName<Status.Paralyzed>(), move.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class FusionBolt : Script, IScriptChangeDamageModifier
|
||||
.OfType<MoveChoice>().LastOrDefault(x => !x.HasFailed);
|
||||
|
||||
// If Fusion Flare was used immediately preceding, Fusion Bolt's power is doubled.
|
||||
if (choice is not null && choice.ChosenMove.MoveData.Name == "fusion_flare")
|
||||
if (choice is not null && choice.ChosenMove.MoveData.Name == MoveNames.FusionFlare)
|
||||
{
|
||||
modifier *= 2;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public class FusionFlare : Script, IScriptChangeDamageModifier
|
||||
.OfType<MoveChoice>().LastOrDefault(x => !x.HasFailed);
|
||||
|
||||
// If Fusion Bolt was used immediately preceding, Fusion Flare's power is doubled.
|
||||
if (choice is not null && choice.ChosenMove.MoveData.Name == "fusion_bolt")
|
||||
if (choice is not null && choice.ChosenMove.MoveData.Name == MoveNames.FusionBolt)
|
||||
{
|
||||
modifier *= 2;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class GearUp : Script, IScriptOnSecondaryEffect
|
||||
foreach (var pokemon in side.Pokemon.WhereNotNull())
|
||||
{
|
||||
var ability = pokemon.ActiveAbility?.Name;
|
||||
if (ability != "plus" && ability != "minus")
|
||||
if (ability != AbilityNames.Plus && ability != AbilityNames.Minus)
|
||||
continue;
|
||||
pokemon.ChangeStatBoost(Statistic.Attack, 1, pokemon == move.User, false, evtBatchId);
|
||||
pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, pokemon == move.User, false, evtBatchId);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user