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

This commit is contained in:
2026-08-27 18:30:57 +02:00
parent 3a58f55bbf
commit 942be8eaeb
160 changed files with 1035 additions and 491 deletions

View File

@@ -0,0 +1,3 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View 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();
}

View 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>([]));
}

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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>())

View File

@@ -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)

View File

@@ -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";
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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));

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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));

View File

@@ -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);
}
}
}

View File

@@ -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));

View File

@@ -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%
}
}

View File

@@ -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%
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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);

View File

@@ -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%
}
}

View File

@@ -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);
}

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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))

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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));

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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());
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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))
{

View File

@@ -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;

View File

@@ -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);

View File

@@ -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.

View File

@@ -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());
}

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -16,18 +16,19 @@ public class GrassPledge : 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 == "fire_pledge"));
(mc.ChosenMove.MoveData.Name == MoveNames.WaterPledge ||
mc.ChosenMove.MoveData.Name == MoveNames.FirePledge));
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 GrassWaterPledgeMove());
}
else if (pledgeMove.ChosenMove.MoveData.Name == "fire_pledge")
else if (pledgeMove.ChosenMove.MoveData.Name == MoveNames.FirePledge)
{
pledgeMove.Volatile.Add(new FireGrassPledgeMove());
}

View File

@@ -14,7 +14,7 @@ public class HealBell : Script, IScriptOnSecondaryEffect
foreach (var pokemon in party.Party.WhereNotNull())
{
if (pokemon.BattleData?.IsOnBattlefield == true && pokemon.ActiveAbility?.Name == "soundproof")
if (pokemon.BattleData?.IsOnBattlefield == true && pokemon.ActiveAbility?.Name == AbilityNames.Soundproof)
continue;
pokemon.ClearStatus();
}

View File

@@ -6,7 +6,7 @@ public class Hex : Script, IScriptChangeBasePower
/// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
if (!target.StatusScript.IsEmpty || target.ActiveAbility?.Name == "comatose")
if (!target.StatusScript.IsEmpty || target.ActiveAbility?.Name == AbilityNames.Comatose)
{
basePower = basePower.MultiplyOrMax(2);
}

View File

@@ -21,12 +21,9 @@ public class HyperspaceFury : Script, IScriptFailMove, IScriptOnSecondaryEffect
move.User.ChangeStatBoost(Statistic.Defense, -1, true, false);
}
private static StringKey HoopaName = "hoopa";
private static StringKey UnboundName = "unbound";
public void FailMove(IExecutingMove move, ref bool fail)
{
if (move.User.Species.Name != HoopaName || move.User.Form.Name != UnboundName)
if (move.User.Species.Name != SpeciesNames.Hoopa || move.User.Form.Name != FormNames.Unbound)
{
fail = true;
}

View File

@@ -17,7 +17,7 @@ public class IceBurn : BaseChargeMove<RequireChargeEffect>
if (battleData.Battle.Random.EffectChance(30, move, target, hit))
{
target.SetStatus("burned", move.User);
target.SetStatus(ScriptUtils.ResolveName<Status.Burned>(), move.User);
}
}
}

View File

@@ -14,7 +14,7 @@ public class IceFang : Script, IScriptOnSecondaryEffect
var random = battleData.Battle.Random;
if (random.EffectChance(10, move, target, hit))
{
target.SetStatus("frozen", move.User);
target.SetStatus(ScriptUtils.ResolveName<Status.Frozen>(), move.User);
}
// It also has an independent 10% chance of causing the target to flinch, if the user attacks before the target.

View File

@@ -5,6 +5,27 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "judgement")]
public class Judgement : Script, IScriptChangeMoveType
{
private static readonly Dictionary<StringKey, StringKey> PlateTypes = new()
{
{ ItemNames.DreadPlate, TypeNames.Dark },
{ ItemNames.EarthPlate, TypeNames.Ground },
{ ItemNames.FistPlate, TypeNames.Fighting },
{ ItemNames.FlamePlate, TypeNames.Fire },
{ ItemNames.IciclePlate, TypeNames.Ice },
{ ItemNames.InsectPlate, TypeNames.Bug },
{ ItemNames.IronPlate, TypeNames.Steel },
{ ItemNames.MeadowPlate, TypeNames.Grass },
{ ItemNames.MindPlate, TypeNames.Psychic },
{ ItemNames.PixiePlate, TypeNames.Fairy },
{ ItemNames.SkyPlate, TypeNames.Flying },
{ ItemNames.SpookyPlate, TypeNames.Ghost },
{ ItemNames.StonePlate, TypeNames.Rock },
{ ItemNames.ToxicPlate, TypeNames.Poison },
{ ItemNames.ZapPlate, TypeNames.Electric },
{ ItemNames.DracoPlate, TypeNames.Dragon },
{ ItemNames.SplashPlate, TypeNames.Water },
};
/// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType)
{
@@ -19,26 +40,10 @@ public class Judgement : Script, IScriptChangeMoveType
return;
var typeLibrary = target.Library.StaticLibrary.Types;
moveType = heldItem.Name.ToString().ToLowerInvariant() switch
if (PlateTypes.TryGetValue(heldItem.Name, out var typeName) &&
typeLibrary.TryGetTypeIdentifier(typeName, out var type))
{
"dread_plate" when typeLibrary.TryGetTypeIdentifier("dark", out var dark) => dark,
"earth_plate" when typeLibrary.TryGetTypeIdentifier("ground", out var ground) => ground,
"fist_plate" when typeLibrary.TryGetTypeIdentifier("fighting", out var fighting) => fighting,
"flame_plate" when typeLibrary.TryGetTypeIdentifier("fire", out var fire) => fire,
"icicle_plate" when typeLibrary.TryGetTypeIdentifier("ice", out var ice) => ice,
"insect_plate" when typeLibrary.TryGetTypeIdentifier("bug", out var bug) => bug,
"iron_plate" when typeLibrary.TryGetTypeIdentifier("steel", out var steel) => steel,
"meadow_plate" when typeLibrary.TryGetTypeIdentifier("grass", out var grass) => grass,
"mind_plate" when typeLibrary.TryGetTypeIdentifier("psychic", out var psychic) => psychic,
"pixie_plate" when typeLibrary.TryGetTypeIdentifier("fairy", out var fairy) => fairy,
"sky_plate" when typeLibrary.TryGetTypeIdentifier("flying", out var flying) => flying,
"spooky_plate" when typeLibrary.TryGetTypeIdentifier("ghost", out var ghost) => ghost,
"stone_plate" when typeLibrary.TryGetTypeIdentifier("rock", out var rock) => rock,
"toxic_plate" when typeLibrary.TryGetTypeIdentifier("poison", out var poison) => poison,
"zap_plate" when typeLibrary.TryGetTypeIdentifier("electric", out var electric) => electric,
"draco_plate" when typeLibrary.TryGetTypeIdentifier("dragon", out var dragon) => dragon,
"splash_plate" when typeLibrary.TryGetTypeIdentifier("water", out var water) => water,
_ => moveType,
};
moveType = type;
}
}
}

View File

@@ -14,7 +14,7 @@ public class KingsShield : ProtectionScript
if (move.GetHitData(target, hit).HasFailed)
return;
// Default form is shield form
if (move.User.Species.Name == "aegislash" && move.User.Form.Name != "default")
if (move.User.Species.Name == SpeciesNames.Aegislash && move.User.Form.Name != FormNames.Default)
{
move.User.ChangeForm(move.User.Species.GetDefaultForm());
}

View File

@@ -12,7 +12,7 @@ public class LastResort : Script, IScriptPreventMoveSelection
prevent = true;
return;
}
var userMoves = choice.User.Moves.WhereNotNull().Where(x => x.MoveData.Name != "last_resort").ToList();
var userMoves = choice.User.Moves.WhereNotNull().Where(x => x.MoveData.Name != MoveNames.LastResort).ToList();
if (userMoves.Count == 0)
{
prevent = true;

Some files were not shown because too many files have changed in this diff Show More