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

@@ -3,6 +3,7 @@ using System.CommandLine.Parsing;
using PkmnLib.Dynamic.AI; using PkmnLib.Dynamic.AI;
using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Libraries;
using PkmnLib.Plugin.Gen7; using PkmnLib.Plugin.Gen7;
using PkmnLib.Static.Utils;
using Serilog; using Serilog;
using Serilog.Core; using Serilog.Core;
using Serilog.Events; using Serilog.Events;
@@ -93,7 +94,8 @@ internal static class Program
return; return;
} }
var ai = _availableAIs!.FirstOrDefault(a => a.Name == aiName); StringKey aiKey = aiName;
var ai = _availableAIs!.FirstOrDefault(a => a.Name == aiKey);
if (ai == null) if (ai == null)
{ {
result.AddError( result.AddError(

View File

@@ -20,5 +20,14 @@
<PackageVersion Include="TUnit" Version="1.58.0" /> <PackageVersion Include="TUnit" Version="1.58.0" />
<PackageVersion Include="PolySharp" Version="1.15" /> <PackageVersion Include="PolySharp" Version="1.15" />
<PackageVersion Include="JetBrains.Annotations" Version="2026.2.0" /> <PackageVersion Include="JetBrains.Annotations" Version="2026.2.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Buffers" Version="4.5.1" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -18,6 +18,7 @@ public partial class ExplicitAI
private static readonly StringKey ShieldsDownName = "shields_down"; private static readonly StringKey ShieldsDownName = "shields_down";
private static readonly StringKey HarshSunlightName = "harsh_sunlight"; private static readonly StringKey HarshSunlightName = "harsh_sunlight";
private static readonly StringKey DesolateLandsName = "desolate_lands"; private static readonly StringKey DesolateLandsName = "desolate_lands";
private static readonly StringKey PrimordialSeaName = "primordial_sea";
private static readonly StringKey BulletproofName = "bulletproof"; private static readonly StringKey BulletproofName = "bulletproof";
private static readonly StringKey FlashFireName = "flash_fire"; private static readonly StringKey FlashFireName = "flash_fire";
private static readonly StringKey LightningRodName = "lightning_rod"; private static readonly StringKey LightningRodName = "lightning_rod";
@@ -33,6 +34,8 @@ public partial class ExplicitAI
private static readonly StringKey FireName = "fire"; private static readonly StringKey FireName = "fire";
private static readonly StringKey ElectricName = "electric"; private static readonly StringKey ElectricName = "electric";
private static readonly StringKey WaterName = "water"; private static readonly StringKey WaterName = "water";
private static readonly StringKey KomalaName = "komala";
private static readonly StringKey MiniorName = "minior";
private static bool CanBePoisoned(IPokemon pokemon, IBattle battle) private static bool CanBePoisoned(IPokemon pokemon, IBattle battle)
{ {
@@ -49,9 +52,9 @@ public partial class ExplicitAI
if ((pokemon.ActiveAbility?.Name == LeafGuardName && battle.WeatherName == HarshSunlightName) || if ((pokemon.ActiveAbility?.Name == LeafGuardName && battle.WeatherName == HarshSunlightName) ||
battle.WeatherName == DesolateLandsName) battle.WeatherName == DesolateLandsName)
return false; return false;
if (pokemon.ActiveAbility?.Name == ComatoseName && pokemon.Species.Name == "komala") if (pokemon.ActiveAbility?.Name == ComatoseName && pokemon.Species.Name == KomalaName)
return false; return false;
if (pokemon.ActiveAbility?.Name == ShieldsDownName && pokemon.Species.Name == "minior" && if (pokemon.ActiveAbility?.Name == ShieldsDownName && pokemon.Species.Name == MiniorName &&
pokemon.Form.Name.Contains("-meteor")) pokemon.Form.Name.Contains("-meteor"))
return false; return false;
return true; return true;

View File

@@ -313,9 +313,9 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
} }
// Primal weather // Primal weather
if (battle.WeatherName == "primordial_sea" && aiMove.Move.MoveType.Name == "fire") if (battle.WeatherName == PrimordialSeaName && aiMove.Move.MoveType.Name == FireName)
return true; return true;
if (battle.WeatherName == "desolate_lands" && aiMove.Move.MoveType.Name == "water") if (battle.WeatherName == DesolateLandsName && aiMove.Move.MoveType.Name == WaterName)
return true; return true;
// Check if the move will fail based on the handlers // Check if the move will fail based on the handlers
@@ -453,13 +453,16 @@ public partial class ExplicitAI : PokemonAI, IExplicitAI
return true; return true;
} }
private static readonly StringKey TruantName = "truant";
private static readonly StringKey TruantEffectName = "truant_effect";
private static bool CanAttack(IPokemon pokemon) private static bool CanAttack(IPokemon pokemon)
{ {
if (pokemon.Volatile.Contains("requires_recharge")) if (pokemon.Volatile.Contains("requires_recharge"))
return false; return false;
if (pokemon.HasStatus("frozen") || pokemon.HasStatus("sleep")) if (pokemon.HasStatus("frozen") || pokemon.HasStatus("sleep"))
return false; return false;
if (pokemon.ActiveAbility?.Name == "truant" && pokemon.Volatile.Contains("truant_effect")) if (pokemon.ActiveAbility?.Name == TruantName && pokemon.Volatile.Contains(TruantEffectName))
return false; return false;
if (pokemon.Volatile.Contains("flinch_effect")) if (pokemon.Volatile.Contains("flinch_effect"))
return false; return false;

View File

@@ -467,7 +467,7 @@ public class BattleImpl : ScriptSource, IBattle
var oldWeatherName = WeatherScript.Script?.Name; var oldWeatherName = WeatherScript.Script?.Name;
if (weatherName.HasValue) if (weatherName.HasValue)
{ {
if (weatherName == oldWeatherName) if (oldWeatherName == weatherName.Value)
{ {
// Extend duration of existing weather // Extend duration of existing weather
if (_weatherScript.Script is ILimitedTurnsScript existingWeatherScript) if (_weatherScript.Script is ILimitedTurnsScript existingWeatherScript)

View File

@@ -1482,12 +1482,14 @@ public class PokemonImpl : ScriptSource, IPokemon
return true; return true;
} }
private static readonly StringKey FlyingTypeName = "flying";
/// <inheritdoc /> /// <inheritdoc />
public bool IsFloating public bool IsFloating
{ {
get get
{ {
var isFloating = Types.Any(x => x.Name == "flying"); var isFloating = Types.Any(x => x.Name == FlyingTypeName);
this.RunScriptHook<IScriptIsFloating>(x => x.IsFloating(this, ref isFloating)); this.RunScriptHook<IScriptIsFloating>(x => x.IsFloating(this, ref isFloating));
return isFloating; return isFloating;
} }

View File

@@ -15,6 +15,7 @@
</Project> </Project>
</Folder> </Folder>
<Folder Name="/Plugins/"> <Folder Name="/Plugins/">
<Project Path="Plugins\PkmnLib.Plugin.Gen7.SourceGen\PkmnLib.Plugin.Gen7.SourceGen.csproj" Type="Classic C#" />
<Project Path="Plugins\PkmnLib.Plugin.Gen7.Tests\PkmnLib.Plugin.Gen7.Tests.csproj" Type="Classic C#" /> <Project Path="Plugins\PkmnLib.Plugin.Gen7.Tests\PkmnLib.Plugin.Gen7.Tests.csproj" Type="Classic C#" />
<Project Path="Plugins\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj" Type="Classic C#" /> <Project Path="Plugins\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj" Type="Classic C#" />
</Folder> </Folder>

View File

@@ -58,7 +58,7 @@ public readonly struct StringKey : IEquatable<StringKey>, IEquatable<string>
return obj switch return obj switch
{ {
StringKey other => Equals(other), StringKey other => Equals(other),
string str => Equals(str), string str => string.Equals(_key, str, StringComparison.InvariantCultureIgnoreCase),
_ => false, _ => false,
}; };
} }
@@ -67,16 +67,22 @@ public readonly struct StringKey : IEquatable<StringKey>, IEquatable<string>
public bool Equals(StringKey other) => _hashCode == other._hashCode; public bool Equals(StringKey other) => _hashCode == other._hashCode;
/// <inheritdoc cref="Equals(StringKey)"/> /// <inheritdoc cref="Equals(StringKey)"/>
[Obsolete("Comparing a StringKey to a string uses a slow culture-aware string comparison. " +
"Compare against a cached StringKey (e.g. a generated constant) instead.")]
public bool Equals(string other) => string.Equals(_key, other, StringComparison.InvariantCultureIgnoreCase); public bool Equals(string other) => string.Equals(_key, other, StringComparison.InvariantCultureIgnoreCase);
/// <inheritdoc /> /// <inheritdoc />
public override int GetHashCode() => _hashCode; public override int GetHashCode() => _hashCode;
/// <inheritdoc cref="Equals(StringKey)"/> /// <inheritdoc cref="Equals(StringKey)"/>
[Obsolete("Comparing a StringKey to a string uses a slow culture-aware string comparison. " +
"Compare against a cached StringKey (e.g. a generated constant) instead.")]
public static bool operator ==(StringKey? left, string? right) => public static bool operator ==(StringKey? left, string? right) =>
(left is null && right is null) || (right != null && (left?.Equals(right) ?? false)); (left is null && right is null) || (right != null && (left?.Equals(right) ?? false));
/// <inheritdoc cref="Equals(StringKey)"/> /// <inheritdoc cref="Equals(StringKey)"/>
[Obsolete("Comparing a StringKey to a string uses a slow culture-aware string comparison. " +
"Compare against a cached StringKey (e.g. a generated constant) instead.")]
public static bool operator !=(StringKey? left, string? right) => !(left == right); public static bool operator !=(StringKey? left, string? right) => !(left == right);
/// <inheritdoc cref="Equals(StringKey)"/> /// <inheritdoc cref="Equals(StringKey)"/>

View File

@@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
using CSPath; using CSPath;
using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Static.Utils;
using JsonSerializer = System.Text.Json.JsonSerializer; using JsonSerializer = System.Text.Json.JsonSerializer;
namespace PkmnLib.Tests.Integration.Models; namespace PkmnLib.Tests.Integration.Models;
@@ -39,7 +40,8 @@ public class SetMoveChoiceAction : IntegrationTestAction
{ {
var user = battle.Sides[Place[0]].Pokemon[Place[1]]; var user = battle.Sides[Place[0]].Pokemon[Place[1]];
await Assert.That(user).IsNotNull(); await Assert.That(user).IsNotNull();
var move = user!.Moves.First(m => m?.MoveData.Name == Move); StringKey moveName = Move;
var move = user!.Moves.First(m => m?.MoveData.Name == moveName);
await Assert.That(move).IsNotNull(); await Assert.That(move).IsNotNull();
var res = battle.TrySetChoice(new MoveChoice(user, move!, Target[0], Target[1])); var res = battle.TrySetChoice(new MoveChoice(user, move!, Target[0], Target[1]));
await Assert.That(res).IsTrue(); await Assert.That(res).IsTrue();

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; var moveLibrary = library.StaticLibrary.Moves;
foreach (var move in moveLibrary) foreach (var move in moveLibrary)
{ {
if (move.SecondaryEffect?.Name != "set_status") if (move.SecondaryEffect?.Name != MoveEffectNames.SetStatus)
continue; continue;
yield return () => new SetStatusTestCaseData(library, move); yield return () => new SetStatusTestCaseData(library, move);
} }

View File

@@ -59,7 +59,7 @@ public class DreamEaterTests
{ {
// Arrange // Arrange
var (script, move, target, _) = CreateTestSetup(100); var (script, move, target, _) = CreateTestSetup(100);
target.HasStatus(new StringKey("asleep")).Returns(false); target.HasStatus(new StringKey("sleep")).Returns(false);
var block = false; var block = false;
// Act // Act
@@ -77,7 +77,7 @@ public class DreamEaterTests
{ {
// Arrange // Arrange
var (script, move, target, _) = CreateTestSetup(100); var (script, move, target, _) = CreateTestSetup(100);
target.HasStatus(new StringKey("asleep")).Returns(true); target.HasStatus(new StringKey("sleep")).Returns(true);
var block = false; var block = false;
// Act // Act
@@ -96,7 +96,7 @@ public class DreamEaterTests
{ {
// Arrange // Arrange
var (script, move, target, _) = CreateTestSetup(100); 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>(); var comatose = Substitute.For<IAbility>();
comatose.Name.Returns(new StringKey("comatose")); comatose.Name.Returns(new StringKey("comatose"));
target.ActiveAbility.Returns(comatose); target.ActiveAbility.Returns(comatose);

View File

@@ -27,7 +27,7 @@ public static class AIDamageFunctions
{ {
score += 10; score += 10;
if ((option.Move.Move.HasFlag(MoveFlags.MultiHit) && target.CurrentHealth == target.MaxHealth && 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; score += 8;
} }

View File

@@ -17,7 +17,7 @@ public static class AIHelperFunctions
if (move.User.BattleData?.SideIndex != target.BattleData?.SideIndex) if (move.User.BattleData?.SideIndex != target.BattleData?.SideIndex)
desireMult = -1; desireMult = -1;
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == "contrary") if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == AbilityNames.Contrary)
{ {
if (desireMult > 0 && wholeEffect) if (desireMult > 0 && wholeEffect)
{ {
@@ -39,10 +39,10 @@ public static class AIHelperFunctions
if (expectedEndOfTurnDamage >= target.CurrentHealth) if (expectedEndOfTurnDamage >= target.CurrentHealth)
return wholeEffect ? ExplicitAI.MoveUselessScore : score; 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") != var foeIsAware =
true; target.BattleData?.BattleSide.Pokemon.Any(x => x?.ActiveAbility?.Name == AbilityNames.Unaware) != true;
if (!foeIsAware) if (!foeIsAware)
{ {
return wholeEffect ? ExplicitAI.MoveUselessScore : score; return wholeEffect ? ExplicitAI.MoveUselessScore : score;
@@ -56,7 +56,7 @@ public static class AIHelperFunctions
{ {
continue; continue;
} }
if (!fixedChange && target.ActiveAbility?.Name == "simple") if (!fixedChange && target.ActiveAbility?.Name == AbilityNames.Simple)
{ {
increment *= 2; increment *= 2;
} }
@@ -88,7 +88,7 @@ public static class AIHelperFunctions
var desireMult = -1; var desireMult = -1;
if (move.User.BattleData?.SideIndex == target.BattleData?.SideIndex) if (move.User.BattleData?.SideIndex == target.BattleData?.SideIndex)
desireMult = 1; desireMult = 1;
if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == "contrary") if (!ignoreContrary && !fixedChange && target.ActiveAbility?.Name == AbilityNames.Contrary)
{ {
if (desireMult > 0 && wholeEffect) if (desireMult > 0 && wholeEffect)
{ {
@@ -109,7 +109,7 @@ public static class AIHelperFunctions
return wholeEffect ? ExplicitAI.MoveUselessScore : score; return wholeEffect ? ExplicitAI.MoveUselessScore : score;
var foeIsAware = false; 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; foeIsAware = true;
} }
@@ -125,7 +125,7 @@ public static class AIHelperFunctions
{ {
continue; continue;
} }
if (!fixedChange && target.ActiveAbility?.Name == "simple") if (!fixedChange && target.ActiveAbility?.Name == AbilityNames.Simple)
{ {
decrement *= 2; decrement *= 2;
} }
@@ -152,7 +152,7 @@ public static class AIHelperFunctions
{ {
if (!fixedChange && pokemon.StatBoost.GetStatistic(stat) == StatBoostStatisticSet.MaxStatBoost) if (!fixedChange && pokemon.StatBoost.GetStatistic(stat) == StatBoostStatisticSet.MaxStatBoost)
return false; return false;
if (!pokemon.HasMoveWithEffect("power_trip", "baton_pass")) if (!pokemon.HasMoveWithEffect(MoveEffectNames.PowerTrip, MoveEffectNames.BatonPass))
return true; return true;
switch (stat) switch (stat)
@@ -160,7 +160,8 @@ public static class AIHelperFunctions
case Statistic.Attack: case Statistic.Attack:
{ {
if (!pokemon.Moves.WhereNotNull().Any(x => x.MoveData.Category == MoveCategory.Physical && 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; return false;
} }
@@ -170,7 +171,8 @@ public static class AIHelperFunctions
{ {
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y => 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: case Statistic.SpecialAttack:
{ {
@@ -184,11 +186,12 @@ public static class AIHelperFunctions
{ {
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y => 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: case Statistic.Speed:
{ {
if (!pokemon.HasMoveWithEffect("electro_ball", "power_trip")) if (!pokemon.HasMoveWithEffect(MoveEffectNames.ElectroBall, MoveEffectNames.PowerTrip))
{ {
var targetSpeed = pokemon.BoostedStats.Speed; var targetSpeed = pokemon.BoostedStats.Speed;
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
@@ -228,13 +231,15 @@ public static class AIHelperFunctions
case Statistic.Attack: case Statistic.Attack:
{ {
return pokemon.Moves.WhereNotNull().Any(x => x.MoveData.Category == MoveCategory.Physical && 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: case Statistic.Defense:
{ {
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y => 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: case Statistic.SpecialAttack:
{ {
@@ -244,11 +249,12 @@ public static class AIHelperFunctions
{ {
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
return opponentSide.Pokemon.WhereNotNull().Any(x => x.Moves.WhereNotNull().Any(y => 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: case Statistic.Speed:
{ {
if (!pokemon.HasMoveWithEffect("electro_ball")) if (!pokemon.HasMoveWithEffect(MoveEffectNames.ElectroBall))
{ {
var targetSpeed = pokemon.BoostedStats.Speed; var targetSpeed = pokemon.BoostedStats.Speed;
var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide); var opponentSide = pokemon.BattleData!.Battle.Sides.First(x => x != pokemon.BattleData.BattleSide);
@@ -266,8 +272,6 @@ public static class AIHelperFunctions
return true; return true;
} }
private static readonly StringKey FoulPlayAbilityName = "foul_play";
private static void GetTargetStatRaiseScoreOne(ref int score, IPokemon target, Statistic stat, sbyte increment, private static void GetTargetStatRaiseScoreOne(ref int score, IPokemon target, Statistic stat, sbyte increment,
AIMoveState move, float desireMult = 1) AIMoveState move, float desireMult = 1)
{ {
@@ -311,7 +315,7 @@ public static class AIHelperFunctions
{ {
var hasPhysicalMoves = target.Moves.WhereNotNull().Any(x => var hasPhysicalMoves = target.Moves.WhereNotNull().Any(x =>
x.MoveData.Category == MoveCategory.Physical && x.MoveData.Category == MoveCategory.Physical &&
x.MoveData.SecondaryEffect?.Name != FoulPlayAbilityName); x.MoveData.SecondaryEffect?.Name != MoveEffectNames.FoulPlay);
var inc = hasPhysicalMoves ? 8 : 12; var inc = hasPhysicalMoves ? 8 : 12;
score += (int)(inc * incMult); score += (int)(inc * incMult);
} }
@@ -340,15 +344,15 @@ public static class AIHelperFunctions
else else
score += (int)(8 * incMult); score += (int)(8 * incMult);
} }
if (target.HasMoveWithEffect("electro_ball", "power_trip")) if (target.HasMoveWithEffect(MoveEffectNames.ElectroBall, MoveEffectNames.PowerTrip))
{ {
score += (int)(5 * incMult); 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); 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)); score -= (int)(15 * (target.Opposes(move.User) ? 1 : desireMult));
} }
@@ -393,11 +397,11 @@ public static class AIHelperFunctions
break; break;
} }
} }
if (target.HasMoveWithEffect("power_trip")) if (target.HasMoveWithEffect(MoveEffectNames.PowerTrip))
{ {
score += (int)(5 * increment * desireMult); 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); score -= (int)(5 * increment * desireMult);
} }
@@ -445,7 +449,7 @@ public static class AIHelperFunctions
{ {
var hasPhysicalMoves = target.Moves.WhereNotNull().Any(x => var hasPhysicalMoves = target.Moves.WhereNotNull().Any(x =>
x.MoveData.Category == MoveCategory.Physical && x.MoveData.Category == MoveCategory.Physical &&
x.MoveData.SecondaryEffect?.Name != FoulPlayAbilityName); x.MoveData.SecondaryEffect?.Name != MoveEffectNames.FoulPlay);
var dec = hasPhysicalMoves ? 8 : 12; var dec = hasPhysicalMoves ? 8 : 12;
score += (int)(dec * decMult); score += (int)(dec * decMult);
} }
@@ -475,11 +479,11 @@ public static class AIHelperFunctions
score += (int)(8 * decMult); score += (int)(8 * decMult);
break; 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); 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)); score -= (int)(15 * (target.Opposes(move.User) ? 1 : desireMult));
} }
@@ -502,11 +506,11 @@ public static class AIHelperFunctions
break; break;
} }
} }
if (target.HasMoveWithEffect("power_trip")) if (target.HasMoveWithEffect(MoveEffectNames.PowerTrip))
{ {
score += (int)(5 * decrement * desireMult); 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); score -= (int)(5 * decrement * desireMult);
} }
@@ -545,13 +549,13 @@ public static class AIHelperFunctions
{ {
if (move.Move.SecondaryEffect is null) if (move.Move.SecondaryEffect is null)
return 0; return 0;
if (move.User.ActiveAbility?.Name == "sheer_force") if (move.User.ActiveAbility?.Name == AbilityNames.SheerForce)
return -999; return -999;
if (target is not null && target.BattleData?.Position != move.User.BattleData?.Position && if (target is not null && target.BattleData?.Position != move.User.BattleData?.Position &&
target.ActiveAbility?.Name == "shield_dust") target.ActiveAbility?.Name == AbilityNames.ShieldDust)
return -999; 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) move.User.BattleData?.BattleSide.VolatileScripts.Contains<RainbowEffect>() == true)
{ {
return 5; return 5;
@@ -574,38 +578,38 @@ public static class AIHelperFunctions
return true; return true;
if (pokemon.ActiveAbility != null) 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>() && status != ScriptUtils.ResolveName<Frozen>() &&
IsStatRaiseWorthwhile(pokemon, Statistic.Attack, 1, true)) IsStatRaiseWorthwhile(pokemon, Statistic.Attack, 1, true))
{ {
return true; return true;
} }
if (pokemon.ActiveAbility.Name == "marvel_scale" && if (pokemon.ActiveAbility.Name == AbilityNames.MarvelScale &&
IsStatRaiseWorthwhile(pokemon, Statistic.Defense, 1, true)) IsStatRaiseWorthwhile(pokemon, Statistic.Defense, 1, true))
{ {
return 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)) status != ScriptUtils.ResolveName<Frozen>() && IsStatRaiseWorthwhile(pokemon, Statistic.Speed, 1, true))
{ {
return 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)) IsStatRaiseWorthwhile(pokemon, Statistic.SpecialAttack, 1, true))
{ {
return true; return true;
} }
if (pokemon.ActiveAbility.Name == "toxic_boost" && if (pokemon.ActiveAbility.Name == AbilityNames.ToxicBoost &&
(status == ScriptUtils.ResolveName<Poisoned>() || status == ScriptUtils.ResolveName<BadlyPoisoned>()) && (status == ScriptUtils.ResolveName<Poisoned>() || status == ScriptUtils.ResolveName<BadlyPoisoned>()) &&
IsStatRaiseWorthwhile(pokemon, Statistic.Attack, 1, true)) IsStatRaiseWorthwhile(pokemon, Statistic.Attack, 1, true))
{ {
return 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; return true;
} }
if (pokemon.ActiveAbility.Name == "magic_guard") if (pokemon.ActiveAbility.Name == AbilityNames.MagicGuard)
{ {
if (status != ScriptUtils.ResolveName<Poisoned>() && if (status != ScriptUtils.ResolveName<Poisoned>() &&
status != ScriptUtils.ResolveName<BadlyPoisoned>() && status != ScriptUtils.ResolveName<Burned>()) status != ScriptUtils.ResolveName<BadlyPoisoned>() && status != ScriptUtils.ResolveName<Burned>())

View File

@@ -26,8 +26,6 @@ public static class AISwitchFunctions
return effect.Turns <= 1; return effect.Turns <= 1;
} }
private static readonly StringKey PoisonHealAbilityName = "poison_heal";
/// <summary> /// <summary>
/// Switch out if the Pokémon is expected to take significant end-of-turn damage. /// Switch out if the Pokémon is expected to take significant end-of-turn damage.
/// </summary> /// </summary>
@@ -50,7 +48,7 @@ public static class AISwitchFunctions
return true; return true;
var statusScript = pokemon.StatusScript.Script; var statusScript = pokemon.StatusScript.Script;
if (statusScript is BadlyPoisoned { Turns: > 0 } badlyPoisoned && if (statusScript is BadlyPoisoned { Turns: > 0 } badlyPoisoned &&
pokemon.ActiveAbility?.Name != PoisonHealAbilityName) pokemon.ActiveAbility?.Name != AbilityNames.PoisonHeal)
{ {
var poisonDamage = pokemon.MaxHealth / 8; var poisonDamage = pokemon.MaxHealth / 8;
var nextToxicDamage = pokemon.MaxHealth * badlyPoisoned.GetPoisonMultiplier(); var nextToxicDamage = pokemon.MaxHealth * badlyPoisoned.GetPoisonMultiplier();
@@ -92,28 +90,18 @@ public static class AISwitchFunctions
return bigThreat; 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() private static readonly Dictionary<StringKey, List<StringKey>> StatusCureAbilities = new()
{ {
{ {
ImmunityAbilityName, AbilityNames.Immunity,
[ScriptUtils.ResolveName<Poisoned>(), ScriptUtils.ResolveName<BadlyPoisoned>()] [ScriptUtils.ResolveName<Poisoned>(), ScriptUtils.ResolveName<BadlyPoisoned>()]
}, },
{ InsomniaAbilityName, [ScriptUtils.ResolveName<Sleep>()] }, { AbilityNames.Insomnia, [ScriptUtils.ResolveName<Sleep>()] },
{ LimberAbilityName, [ScriptUtils.ResolveName<Paralyzed>()] }, { AbilityNames.Limber, [ScriptUtils.ResolveName<Paralyzed>()] },
{ MagmaArmorAbilityName, [ScriptUtils.ResolveName<Frozen>()] }, { AbilityNames.MagmaArmor, [ScriptUtils.ResolveName<Frozen>()] },
{ VitalSpiritAbilityName, [ScriptUtils.ResolveName<Sleep>()] }, { AbilityNames.VitalSpirit, [ScriptUtils.ResolveName<Sleep>()] },
{ WaterBubbleAbilityName, [ScriptUtils.ResolveName<Burned>()] }, { AbilityNames.WaterBubble, [ScriptUtils.ResolveName<Burned>()] },
{ WaterVeilAbilityName, [ScriptUtils.ResolveName<Burned>()] }, { AbilityNames.WaterVeil, [ScriptUtils.ResolveName<Burned>()] },
}; };
/// <summary> /// <summary>
@@ -138,7 +126,7 @@ public static class AISwitchFunctions
// Check abilities that cure specific status conditions // Check abilities that cure specific status conditions
var canCureStatus = false; var canCureStatus = false;
if (abilityName == NaturalCureAbilityName) if (abilityName == AbilityNames.NaturalCure)
{ {
canCureStatus = true; canCureStatus = true;
} }
@@ -161,7 +149,7 @@ public static class AISwitchFunctions
// Don't bother curing a poisoning if Toxic Spikes will just re-poison // Don't bother curing a poisoning if Toxic Spikes will just re-poison
if (pokemon.StatusScript.Script is Poisoned or BadlyPoisoned && 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 _)) if (pokemon.BattleData!.BattleSide.VolatileScripts.TryGet<ToxicSpikesEffect>(out _))
{ {
@@ -177,7 +165,7 @@ public static class AISwitchFunctions
if (ai.Random.GetInt(100) < 70) if (ai.Random.GetInt(100) < 70)
return true; 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 // Not worth healing if battler would lose more HP from switching back in later
if (entryHazardDamage >= pokemon.MaxHealth / 3) 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> <ItemGroup>
<ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj"/> <ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj"/>
<ProjectReference Include="..\..\PkmnLib.Static\PkmnLib.Static.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>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Data\*.*"/> <EmbeddedResource Include="Data\*.*"/>
<AdditionalFiles Include="Data\*.*"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -9,8 +9,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
[Script(ScriptCategory.Ability, "aftermath")] [Script(ScriptCategory.Ability, "aftermath")]
public class Aftermath : Script, IScriptOnIncomingHit, IScriptOnFaint public class Aftermath : Script, IScriptOnIncomingHit, IScriptOnFaint
{ {
private static readonly StringKey DampAbilityName = new("damp");
private IExecutingMove? _lastAttack; private IExecutingMove? _lastAttack;
/// <inheritdoc /> /// <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 // Aftermath does not trigger if a Pokémon with Damp is on the field
var battle = user.BattleData.Battle; var battle = user.BattleData.Battle;
var hasDamp = battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull() var hasDamp = battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull()
.Any(p => p.ActiveAbility?.Name == DampAbilityName); .Any(p => p.ActiveAbility?.Name == AbilityNames.Damp);
if (hasDamp) if (hasDamp)
return; return;

View File

@@ -30,10 +30,10 @@ public class Anticipation : Script, IScriptOnOpponentSwitchIn
// Either the move is super effective against the owner or // Either the move is super effective against the owner or
typeLibrary.GetEffectiveness(move.MoveData.MoveType, _owner.Types) > 1.0f || typeLibrary.GetEffectiveness(move.MoveData.MoveType, _owner.Types) > 1.0f ||
// the move is a OHKO move // 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 // the move is a self-destruct move
move.MoveData.SecondaryEffect?.Name == "self_destruct" || move.MoveData.SecondaryEffect?.Name == MoveEffectNames.SelfDestruct ||
move.MoveData.SecondaryEffect?.Name == "explosion"); move.MoveData.SecondaryEffect?.Name == MoveEffectNames.Explosion);
if (relevantMoves) if (relevantMoves)
{ {

View File

@@ -16,7 +16,7 @@ public class AuraBreak : Script, IScriptCustomTrigger
return; return;
var typeName = auraArgs.Move.UseMove.MoveType.Name; 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% // Reverse the aura effect by reducing power by 25%
auraArgs.AuraEffect *= 0.75f; auraArgs.AuraEffect *= 0.75f;

View File

@@ -12,8 +12,8 @@ public class BattleBond : Script, IScriptChangeNumberOfHits, IScriptOnOpponentFa
/// <inheritdoc /> /// <inheritdoc />
public void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit) public void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit)
{ {
if (move.User.Species.Name == "greninja" && move.User.Form.Name != "ash" && if (move.User.Species.Name == SpeciesNames.Greninja && move.User.Form.Name != FormNames.Ash &&
move.User.Species.TryGetForm("ash", out var ashForm)) move.User.Species.TryGetForm(FormNames.Ash, out var ashForm))
{ {
move.User.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); move.User.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
move.User.ChangeForm(ashForm); move.User.ChangeForm(ashForm);
@@ -23,14 +23,14 @@ public class BattleBond : Script, IScriptChangeNumberOfHits, IScriptOnOpponentFa
/// <inheritdoc /> /// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower) 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; basePower = 20;
} }
/// <inheritdoc /> /// <inheritdoc />
public void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits) 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; numberOfHits = 3;
} }

View File

@@ -13,7 +13,7 @@ public class Damp : Script, IScriptFailIncomingMove
/// <inheritdoc /> /// <inheritdoc />
public void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail) 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; fail = true;
} }

View File

@@ -12,7 +12,7 @@ public class DarkAura : Script, IScriptChangeDamageModifier
/// <inheritdoc /> /// <inheritdoc />
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier) 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 auraModifier = 5448f / 4096f;
var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier); var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier);

View File

@@ -19,17 +19,17 @@ public class Disguise : Script, IScriptChangeIncomingDamage
return; return;
if (source is not DamageSource.MoveDamage and not DamageSource.Confusion) if (source is not DamageSource.MoveDamage and not DamageSource.Confusion)
return; return;
if (pokemon.Form.Name == "busted" || pokemon.Form.Name == "totem-busted") if (pokemon.Form.Name == FormNames.Busted || pokemon.Form.Name == FormNames.TotemBusted)
return; return;
IForm form; 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; 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; return;
} }
else 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) public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
{ {
var hitType = move.GetHitData(target, hit).Type; var hitType = move.GetHitData(target, hit).Type;
if (hitType?.Name == "fire") if (hitType?.Name == TypeNames.Fire)
{ {
modifier *= 1.25f; modifier *= 1.25f;
} }
else if (hitType?.Name == "water") else if (hitType?.Name == TypeNames.Water)
{ {
modifier = 0; modifier = 0;
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));

View File

@@ -13,11 +13,11 @@ public class EffectSpore : Script, IScriptOnIncomingHit
/// <inheritdoc /> /// <inheritdoc />
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) 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; return;
if (move.User.ActiveAbility?.Name == "effect_spore") if (move.User.ActiveAbility?.Name == AbilityNames.EffectSpore)
return; return;
if (move.User.HasHeldItem("safety_goggles")) if (move.User.HasHeldItem(ItemNames.SafetyGoggles))
return; return;
if (!move.GetHitData(target, hit).IsContact) if (!move.GetHitData(target, hit).IsContact)
return; return;

View File

@@ -13,7 +13,7 @@ public class FairyAura : Script, IScriptChangeDamageModifier
/// <inheritdoc /> /// <inheritdoc />
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier) 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 auraModifier = 5448f / 4096f;
var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier); 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, public void ChangeIncomingEffectiveness(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref float effectiveness) ref float effectiveness)
{ {
if (executingMove.GetHitData(target, hitIndex).Type?.Name != "fire") if (executingMove.GetHitData(target, hitIndex).Type?.Name != TypeNames.Fire)
return; return;
effectiveness = 0f; effectiveness = 0f;

View File

@@ -28,10 +28,10 @@ public class FlowerGift : Script, IScriptOnWeatherChange
return; return;
if (weatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>()) if (weatherName != ScriptUtils.ResolveName<Weather.HarshSunlight>())
return; return;
if (_pokemon.Species.Name != "cherrim") if (_pokemon.Species.Name != SpeciesNames.Cherrim)
return; return;
EventBatchId batchId = new(); 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); _pokemon.ChangeForm(form, batchId);
} }
@@ -47,7 +47,7 @@ public class FlowerGift : Script, IScriptOnWeatherChange
script.OnRemoved(_pokemon); script.OnRemoved(_pokemon);
} }
if (_pokemon.Species.Name != "cherrim") if (_pokemon.Species.Name != SpeciesNames.Cherrim)
return; return;
EventBatchId batchId = new(); EventBatchId batchId = new();

View File

@@ -12,7 +12,7 @@ public class Fluffy : Script, IScriptChangeDamageModifier
/// <inheritdoc /> /// <inheritdoc />
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier) 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; modifier *= 2f;
} }

View File

@@ -45,21 +45,21 @@ public class Forecast : Script, IScriptOnSwitchIn, IScriptOnWeatherChange
private static void ChangeForm(IPokemon pokemon, StringKey? weather) private static void ChangeForm(IPokemon pokemon, StringKey? weather)
{ {
if (pokemon.Species.Name != "castform") if (pokemon.Species.Name != SpeciesNames.Castform)
return; return;
if (weather == ScriptUtils.ResolveName<Weather.HarshSunlight>() && 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); pokemon.ChangeForm(sunnyForm);
} }
else if (weather == ScriptUtils.ResolveName<Weather.Rain>() && 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); pokemon.ChangeForm(rainyForm);
} }
else if (weather == ScriptUtils.ResolveName<Weather.Hail>() && 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); 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) private static byte GetBasePower(IMoveData moveData)
{ {
// OHKO moves (handled by secondary effect) // OHKO moves (handled by secondary effect)
if (moveData.SecondaryEffect?.Name == "one_hit_ko") if (moveData.SecondaryEffect?.Name == MoveEffectNames.OneHitKo)
return 150; return 150;
return moveData.Name.ToString() switch if (FixedBasePowers.TryGetValue(moveData.Name, out var fixedBasePower))
{ return fixedBasePower;
// 150 BP: Specific moves
"blast_burn" or "eruption" or "water_spout" or "fissure" or "guillotine" or "horn_drill" return moveData.BasePower == 0 ? (byte)1 : moveData.BasePower;
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,
};
} }
} }

View File

@@ -12,8 +12,8 @@ public class Galvanize : Script, IScriptChangeMoveType, IScriptChangeDamageModif
/// <inheritdoc /> /// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier) public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
{ {
if (typeIdentifier?.Name == "normal" && if (typeIdentifier?.Name == TypeNames.Normal &&
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType)) move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Electric, out var electricType))
{ {
typeIdentifier = electricType; typeIdentifier = electricType;
} }
@@ -22,7 +22,7 @@ public class Galvanize : Script, IScriptChangeMoveType, IScriptChangeDamageModif
/// <inheritdoc /> /// <inheritdoc />
public void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier) 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; modifier *= 1.2f;
} }
} }

View File

@@ -11,7 +11,7 @@ public class Heatproof : Script, IScriptChangeBasePower
/// <inheritdoc /> /// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower) 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); basePower = (ushort)(basePower / 2);
} }

View File

@@ -11,7 +11,7 @@ public class Justified : Script, IScriptOnIncomingHit
/// <inheritdoc /> /// <inheritdoc />
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) 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; return;
EventBatchId batchId = new(); EventBatchId batchId = new();

View File

@@ -11,7 +11,7 @@ public class LightningRod : Script, IScriptChangeIncomingTargets, IScriptChangeE
/// <inheritdoc /> /// <inheritdoc />
public void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets) 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]; targets = [moveChoice.User];
} }
@@ -20,7 +20,7 @@ public class LightningRod : Script, IScriptChangeIncomingTargets, IScriptChangeE
/// <inheritdoc /> /// <inheritdoc />
public void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness) 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; return;
effectiveness = 0f; 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) public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
{ {
if (move.UseMove.HasFlag(MoveFlags.Sound) && 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; typeIdentifier = waterType;
} }

View File

@@ -11,14 +11,14 @@ public class MagnetPull : Script, IScriptPreventOpponentRunAway, IScriptPreventO
/// <inheritdoc /> /// <inheritdoc />
public void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent) 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; prevent = true;
} }
/// <inheritdoc /> /// <inheritdoc />
public void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent) 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; prevent = true;
} }
} }

View File

@@ -15,7 +15,8 @@ public class Minus : Script, IScriptChangeOffensiveStatValue
var battleData = move.User.BattleData; var battleData = move.User.BattleData;
if (battleData is null) if (battleData is null)
return; 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); value = value.MultiplyOrMax(1.5f);
} }

View File

@@ -11,7 +11,7 @@ public class MotorDrive : Script, IScriptIsInvulnerableToMove
/// <inheritdoc /> /// <inheritdoc />
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable) public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
{ {
if (move.UseMove.MoveType.Name != "electric") if (move.UseMove.MoveType.Name != TypeNames.Electric)
return; return;
invulnerable = true; invulnerable = true;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));

View File

@@ -8,72 +8,40 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
[Script(ScriptCategory.Ability, "multitype")] [Script(ScriptCategory.Ability, "multitype")]
public class Multitype : Script, IScriptOnAfterHeldItemChange 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 /> /// <inheritdoc />
public void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item) public void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item)
{ {
if (pokemon.Species.Name != "arceus") if (pokemon.Species.Name != SpeciesNames.Arceus)
return; return;
if (item is null && pokemon.Form.Name != "default") if (item is null && pokemon.Form.Name != FormNames.Default)
{ {
pokemon.ChangeForm(pokemon.Species.GetDefaultForm()); 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); pokemon.ChangeForm(form);
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;
}
} }
} }
} }

View File

@@ -11,8 +11,8 @@ public class Mummy : Script, IScriptOnIncomingHit
/// <inheritdoc /> /// <inheritdoc />
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{ {
if (!move.GetHitData(target, hit).IsContact || move.User.ActiveAbility?.Name == "mummy" || if (!move.GetHitData(target, hit).IsContact || move.User.ActiveAbility?.Name == AbilityNames.Mummy ||
!move.Battle.Library.StaticLibrary.Abilities.TryGet("mummy", out var mummyAbility)) !move.Battle.Library.StaticLibrary.Abilities.TryGet(AbilityNames.Mummy, out var mummyAbility))
return; return;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));

View File

@@ -11,14 +11,14 @@ public class Normalize : Script, IScriptChangeMoveType, IScriptChangeBasePower
/// <inheritdoc /> /// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier) 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; typeIdentifier = normalType;
} }
/// <inheritdoc /> /// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower) 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% basePower = (ushort)(basePower * 1.2f); // Boost Normal-type moves by 30%
} }
} }

View File

@@ -11,15 +11,15 @@ public class Pixilate : Script, IScriptChangeMoveType, IScriptChangeBasePower
/// <inheritdoc /> /// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier) public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
{ {
if (typeIdentifier?.Name == "normal" && if (typeIdentifier?.Name == TypeNames.Normal &&
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("fairy", out var fairyType)) move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Fairy, out var fairyType))
typeIdentifier = fairyType; typeIdentifier = fairyType;
} }
/// <inheritdoc /> /// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower) 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% 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; var battleData = move.User.BattleData;
if (battleData is null) if (battleData is null)
return; 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); value = value.MultiplyOrMax(1.5f);
} }

View File

@@ -23,13 +23,13 @@ public class PowerConstruct : Script, IScriptOnEndTurn
{ {
if (_pokemon?.BattleData?.Battle == null) if (_pokemon?.BattleData?.Battle == null)
return; return;
if (_pokemon.Species.Name != "zygarde") if (_pokemon.Species.Name != SpeciesNames.Zygarde)
return; return;
if (_pokemon.CurrentHealth > _pokemon.BoostedStats.Hp / 2) if (_pokemon.CurrentHealth > _pokemon.BoostedStats.Hp / 2)
return; return;
if (_pokemon.Form.Name != "10" || _pokemon.Form.Name != "50") if (_pokemon.Form.Name != FormNames._10 || _pokemon.Form.Name != FormNames._50)
return; return;
if (!_pokemon.Species.TryGetForm("complete", out var completeForm)) if (!_pokemon.Species.TryGetForm(FormNames.Complete, out var completeForm))
return; return;
_pokemon.ChangeForm(completeForm); _pokemon.ChangeForm(completeForm);

View File

@@ -13,7 +13,7 @@ public class QuickFeet : Script, IScriptChangeSpeed
{ {
if (choice.User.StatusScript.IsEmpty) if (choice.User.StatusScript.IsEmpty)
return; 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(2);
speed = speed.MultiplyOrMax(1.5f); speed = speed.MultiplyOrMax(1.5f);
} }

View File

@@ -11,9 +11,9 @@ public class RksSystem : Script, IScriptOnAfterHeldItemChange
/// <inheritdoc /> /// <inheritdoc />
public void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item) public void OnAfterHeldItemChange(IPokemon pokemon, IItem? previous, IItem? item)
{ {
if (pokemon.Species.Name != "silvally") if (pokemon.Species.Name != SpeciesNames.Silvally)
return; return;
if (item is null && pokemon.Form.Name != "default") if (item is null && pokemon.Form.Name != FormNames.Default)
{ {
pokemon.ChangeForm(pokemon.Species.GetDefaultForm()); pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
} }

View File

@@ -14,7 +14,7 @@ public class Rattled : Script, IScriptOnIncomingHit
var type = move.GetHitData(target, hit).Type; var type = move.GetHitData(target, hit).Type;
if (type is null) if (type is null)
return; 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; return;
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
target.ChangeStatBoost(Statistic.Speed, 1, true, false); target.ChangeStatBoost(Statistic.Speed, 1, true, false);

View File

@@ -11,15 +11,15 @@ public class Refrigerate : Script, IScriptChangeMoveType, IScriptChangeBasePower
/// <inheritdoc /> /// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier) public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? typeIdentifier)
{ {
if (typeIdentifier?.Name == "normal" && if (typeIdentifier?.Name == TypeNames.Normal &&
move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("ice", out var iceType)) move.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Ice, out var iceType))
typeIdentifier = iceType; typeIdentifier = iceType;
} }
/// <inheritdoc /> /// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower) 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% 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>()) if (move.Battle.WeatherName == ScriptUtils.ResolveName<Weather.Sandstorm>())
{ {
var type = move.GetHitData(target, hit).Type; var type = move.GetHitData(target, hit).Type;
if (type != null && if (type != null && (type.Value.Name == TypeNames.Rock || type.Value.Name == TypeNames.Ground ||
(type.Value.Name == "rock" || type.Value.Name == "ground" || type.Value.Name == "steel")) type.Value.Name == TypeNames.Steel))
{ {
basePower = basePower.MultiplyOrMax(1.3f); basePower = basePower.MultiplyOrMax(1.3f);
} }

View File

@@ -11,7 +11,7 @@ public class SapSipper : Script, IScriptIsInvulnerableToMove
/// <inheritdoc /> /// <inheritdoc />
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable) 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; invulnerable = true;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));

View File

@@ -29,15 +29,15 @@ public class Schooling : Script, IScriptOnEndTurn, IScriptOnSwitchIn
if (pokemon is null) if (pokemon is null)
return; return;
if (pokemon.Species.Name != "wishiwashi" || pokemon.BattleData?.Battle == null) if (pokemon.Species.Name != SpeciesNames.Wishiwashi || pokemon.BattleData?.Battle == null)
return; return;
// If Wishiwashi has less than 25% health, change to Solo form // 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()); pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
} }
else if (pokemon.CurrentHealth >= pokemon.MaxHealth / 4 && pokemon.Form.Name != "school" && else if (pokemon.CurrentHealth >= pokemon.MaxHealth / 4 && pokemon.Form.Name != FormNames.School &&
pokemon.Species.TryGetForm("school", out var schoolForm)) pokemon.Species.TryGetForm(FormNames.School, out var schoolForm))
{ {
pokemon.ChangeForm(schoolForm); pokemon.ChangeForm(schoolForm);
} }

View File

@@ -13,9 +13,9 @@ public class Scrappy : Script, IScriptChangeTypesForMove
IList<TypeIdentifier> types) IList<TypeIdentifier> types)
{ {
var hitType = executingMove.GetHitData(target, hitIndex).Type; var hitType = executingMove.GetHitData(target, hitIndex).Type;
if (hitType?.Name != "normal" && hitType?.Name != "fighting") if (hitType?.Name != TypeNames.Normal && hitType?.Name != TypeNames.Fighting)
return; return;
if (types.Any(x => x.Name == "ghost")) if (types.Any(x => x.Name == TypeNames.Ghost))
types.RemoveAll(x => x.Name == "ghost"); types.RemoveAll(x => x.Name == TypeNames.Ghost);
} }
} }

View File

@@ -19,7 +19,7 @@ public class ShieldsDown : Script, IScriptOnEndTurn, IScriptOnSwitchIn
if (pokemon is null) if (pokemon is null)
return; return;
if (pokemon.Species.Name != "minior" || pokemon.BattleData?.Battle == null) if (pokemon.Species.Name != SpeciesNames.Minior || pokemon.BattleData?.Battle == null)
return; return;
if (pokemon.CurrentHealth < pokemon.MaxHealth / 2 && pokemon.Form.Name.ToString().EndsWith("-meteor")) 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) pokemon.Form.Name.ToString().EndsWith("-meteor") == false)
{ {
var baseFormName = pokemon.Form.Name; var baseFormName = pokemon.Form.Name;
if (baseFormName == "default") if (baseFormName == FormNames.Default)
baseFormName = "blue"; baseFormName = "blue";
var meteorFormName = baseFormName + "-meteor"; var meteorFormName = baseFormName + "-meteor";
if (pokemon.Species.TryGetForm(meteorFormName, out var meteorFormData)) if (pokemon.Species.TryGetForm(meteorFormName, out var meteorFormData))

View File

@@ -13,11 +13,11 @@ public class StanceChange : Script, IScriptOnBeforeMove
/// <inheritdoc /> /// <inheritdoc />
public void OnBeforeMove(IExecutingMove move) public void OnBeforeMove(IExecutingMove move)
{ {
if (move.User.Species.Name != "aegislash") if (move.User.Species.Name != SpeciesNames.Aegislash)
return; return;
if (move.UseMove.Category is not (MoveCategory.Physical or MoveCategory.Special) || 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; return;
EventBatchId batchId = new(); EventBatchId batchId = new();
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User) 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, public void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value) 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); value = value.MultiplyOrMax(1.5f);
} }

View File

@@ -11,7 +11,7 @@ public class StormDrain : Script, IScriptChangeIncomingTargets, IScriptChangeEff
/// <inheritdoc /> /// <inheritdoc />
public void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets) 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]; targets = [moveChoice.User];
} }
@@ -20,7 +20,7 @@ public class StormDrain : Script, IScriptChangeIncomingTargets, IScriptChangeEff
/// <inheritdoc /> /// <inheritdoc />
public void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness) 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; return;
effectiveness = 0f; 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) public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{ {
var type = move.GetHitData(target, hit).Type; 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; damage /= 2;
} }

View File

@@ -11,7 +11,7 @@ public class VoltAbsorb : Script, IScriptIsInvulnerableToMove
/// <inheritdoc /> /// <inheritdoc />
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable) 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; return;
invulnerable = true; invulnerable = true;
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));

View File

@@ -11,7 +11,7 @@ public class WaterAbsorb : Script, IScriptIsInvulnerableToMove
/// <inheritdoc /> /// <inheritdoc />
public void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable) 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; return;
invulnerable = true; invulnerable = true;
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));

View File

@@ -18,14 +18,14 @@ public class WaterBubble : Script, IScriptChangeIncomingMoveDamage, IScriptChang
/// <inheritdoc /> /// <inheritdoc />
public void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) 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; damage /= 2;
} }
/// <inheritdoc /> /// <inheritdoc />
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) 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; damage *= 2;
} }
} }

View File

@@ -11,7 +11,7 @@ public class WaterCompaction : Script, IScriptOnIncomingHit
/// <inheritdoc /> /// <inheritdoc />
public void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) 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; return;
target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)); target.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
target.ChangeStatBoost(Statistic.Defense, 2, true, false); target.ChangeStatBoost(Statistic.Defense, 2, true, false);

View File

@@ -19,14 +19,14 @@ public class ZenMode : Script, IScriptOnEndTurn, IScriptOnSwitchIn
if (pokemon is null) if (pokemon is null)
return; return;
if (pokemon.Species.Name != "darmanitan" || pokemon.BattleData?.Battle == null) if (pokemon.Species.Name != SpeciesNames.Darmanitan || pokemon.BattleData?.Battle == null)
return; return;
if (pokemon.CurrentHealth < pokemon.MaxHealth / 2 && pokemon.Form.Name != "zen" && if (pokemon.CurrentHealth < pokemon.MaxHealth / 2 && pokemon.Form.Name != FormNames.Zen &&
pokemon.Species.TryGetForm("zen", out var zenForm)) pokemon.Species.TryGetForm(FormNames.Zen, out var zenForm))
{ {
pokemon.ChangeForm(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()); pokemon.ChangeForm(pokemon.Species.GetDefaultForm());
} }

View File

@@ -8,14 +8,14 @@ public class FairyLockEffect : Script, IScriptOnEndTurn, IScriptPreventSelfRunAw
/// <inheritdoc /> /// <inheritdoc />
public void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) 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; prevent = true;
} }
/// <inheritdoc /> /// <inheritdoc />
public void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) 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; prevent = true;
} }

View File

@@ -12,7 +12,7 @@ public class Gravity : Script, IScriptFailIncomingMove, IScriptOnEndTurn, IScrip
{ {
var typeLibrary = target.Library.StaticLibrary.Types; var typeLibrary = target.Library.StaticLibrary.Types;
if (executingMove.UseMove.MoveType.Name != "ground") if (executingMove.UseMove.MoveType.Name != TypeNames.Ground)
return; return;
// Remove all types that are immune to ground moves // Remove all types that are immune to ground moves
types.RemoveAll(x => typeLibrary.GetSingleEffectiveness(executingMove.UseMove.MoveType, x) == 0); types.RemoveAll(x => typeLibrary.GetSingleEffectiveness(executingMove.UseMove.MoveType, x) == 0);

View File

@@ -6,8 +6,8 @@ public class IonDelugeEffect : Script, IScriptChangeMoveType, IScriptOnEndTurn
/// <inheritdoc /> /// <inheritdoc />
public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType) public void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType)
{ {
if (moveType?.Name == "normal" && if (moveType?.Name == TypeNames.Normal &&
target.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType)) target.Library.StaticLibrary.Types.TryGetTypeIdentifier(TypeNames.Electric, out var electricType))
{ {
moveType = electricType; moveType = electricType;
} }

View File

@@ -8,7 +8,7 @@ public class MudSportEffect : Script, IScriptChangeBasePower, IScriptOnEndTurn
/// <inheritdoc /> /// <inheritdoc />
public void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower) 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); basePower = basePower.MultiplyOrMax(1352 / 4096f);
} }

View File

@@ -32,7 +32,7 @@ public class UproarEffect : Script, IScriptOnBeforeTurnStart, IScriptOnSecondary
/// <inheritdoc /> /// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) 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; _hasUsedUproar = true;
} }

View File

@@ -8,7 +8,7 @@ public class WaterSportEffect : Script, IScriptChangeMoveDamage, IScriptOnSwitch
/// <inheritdoc /> /// <inheritdoc />
public void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) 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; damage = damage / 2;
} }

View File

@@ -17,8 +17,8 @@ public class MoonBall : PokeballScript
{ {
switch (x) switch (x)
{ {
case ItemUseEvolution itemUseEvolution when itemUseEvolution.Item == "moon_ball": case ItemUseEvolution itemUseEvolution when itemUseEvolution.Item == ItemNames.MoonBall:
case ItemGenderEvolution itemGenderEvolution when itemGenderEvolution.Item == "moon_ball": case ItemGenderEvolution itemGenderEvolution when itemGenderEvolution.Item == ItemNames.MoonBall:
return true; return true;
default: default:
return false; 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 /> /// <inheritdoc />
public override void ChangeCatchRate(IPokemon target, ref byte catchRate) 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); catchRate = catchRate.MultiplyOrMax(3.5f);
} }

View File

@@ -5,12 +5,10 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "block")] [Script(ScriptCategory.Move, "block")]
public class Block : Script, IScriptOnSecondaryEffect public class Block : Script, IScriptOnSecondaryEffect
{ {
private static StringKey GhostTypeName => new("ghost");
/// <inheritdoc /> /// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) 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; return;
target.Volatile.Add(new BlockEffect()); target.Volatile.Add(new BlockEffect());
} }

View File

@@ -35,7 +35,7 @@ public class Bounce : Script, IScriptPreventMove, IScriptOnBeforeMove, IScriptOn
var random = battle.Random; var random = battle.Random;
if (random.EffectChance(30, move, target, hit)) 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) if (battleData == null)
return; return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types; var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("fire", out var fireType)) if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Fire, out var fireType))
return; return;
if (!move.User.Types.Contains(fireType)) if (!move.User.Types.Contains(fireType))
{ {
@@ -18,7 +18,7 @@ public class BurnUp : Script, IScriptOnSecondaryEffect
return; return;
} }
if (move.User.HasStatus("frozen")) if (move.User.HasStatus(ScriptUtils.ResolveName<Status.Frozen>()))
move.User.ClearStatus(); move.User.ClearStatus();
move.User.RemoveType(fireType); move.User.RemoveType(fireType);
} }

View File

@@ -20,23 +20,23 @@ public class Camouflage : Script, IScriptOnSecondaryEffect
var environmentCategory = battle.GetEnvironmentCategory(); var environmentCategory = battle.GetEnvironmentCategory();
return environmentCategory switch return environmentCategory switch
{ {
EnvironmentHelper.EnvironmentCategory.Electric when typesLibrary.TryGetTypeIdentifier("electric", EnvironmentHelper.EnvironmentCategory.Electric when typesLibrary.TryGetTypeIdentifier(TypeNames.Electric,
out var electricType) => electricType, out var electricType) => electricType,
EnvironmentHelper.EnvironmentCategory.Fairy when typesLibrary.TryGetTypeIdentifier("fairy", EnvironmentHelper.EnvironmentCategory.Fairy when typesLibrary.TryGetTypeIdentifier(TypeNames.Fairy,
out var fairyType) => fairyType, out var fairyType) => fairyType,
EnvironmentHelper.EnvironmentCategory.Grass when typesLibrary.TryGetTypeIdentifier("grass", EnvironmentHelper.EnvironmentCategory.Grass when typesLibrary.TryGetTypeIdentifier(TypeNames.Grass,
out var grassType) => grassType, out var grassType) => grassType,
EnvironmentHelper.EnvironmentCategory.Psychic when typesLibrary.TryGetTypeIdentifier("psychic", EnvironmentHelper.EnvironmentCategory.Psychic when typesLibrary.TryGetTypeIdentifier(TypeNames.Psychic,
out var psychicType) => psychicType, out var psychicType) => psychicType,
EnvironmentHelper.EnvironmentCategory.Rock when typesLibrary.TryGetTypeIdentifier("rock", out var rockType) EnvironmentHelper.EnvironmentCategory.Rock when typesLibrary.TryGetTypeIdentifier(TypeNames.Rock,
=> rockType, out var rockType) => rockType,
EnvironmentHelper.EnvironmentCategory.Ground when typesLibrary.TryGetTypeIdentifier("ground", EnvironmentHelper.EnvironmentCategory.Ground when typesLibrary.TryGetTypeIdentifier(TypeNames.Ground,
out var groundType) => groundType, out var groundType) => groundType,
EnvironmentHelper.EnvironmentCategory.Ice when typesLibrary.TryGetTypeIdentifier("ice", out var iceType) => EnvironmentHelper.EnvironmentCategory.Ice when typesLibrary.TryGetTypeIdentifier(TypeNames.Ice,
iceType, out var iceType) => iceType,
EnvironmentHelper.EnvironmentCategory.Water when typesLibrary.TryGetTypeIdentifier("water", EnvironmentHelper.EnvironmentCategory.Water when typesLibrary.TryGetTypeIdentifier(TypeNames.Water,
out var waterType) => waterType, out var waterType) => waterType,
EnvironmentHelper.EnvironmentCategory.Normal when typesLibrary.TryGetTypeIdentifier("normal", EnvironmentHelper.EnvironmentCategory.Normal when typesLibrary.TryGetTypeIdentifier(TypeNames.Normal,
out var normalType) => normalType, out var normalType) => normalType,
_ => null, _ => null,
}; };

View File

@@ -14,7 +14,7 @@ public class Captivate : Script, IScriptOnSecondaryEffect
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
return; return;
} }
if (target.ActiveAbility?.Name == "oblivious") if (target.ActiveAbility?.Name == AbilityNames.Oblivious)
{ {
move.GetHitData(target, hit).Fail(); move.GetHitData(target, hit).Fail();
return; return;

View File

@@ -3,11 +3,14 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "conversion_2")] [Script(ScriptCategory.Move, "conversion_2")]
public class Conversion2 : Script, IScriptOnSecondaryEffect 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 /> /// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
var lastMoveByTarget = target.BattleData?.LastMoveChoice; 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(); move.GetHitData(target, hit).Fail();
return; return;

View File

@@ -12,7 +12,7 @@ public class Curse : Script, IScriptOnSecondaryEffect
if (battleData == null) if (battleData == null)
return; return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types; var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("ghost", out var ghostType)) if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Ghost, out var ghostType))
return; return;
if (move.User.Types.Contains(ghostType)) if (move.User.Types.Contains(ghostType))
{ {

View File

@@ -19,7 +19,7 @@ public class Drain : Script, IScriptOnInitialize, IScriptOnSecondaryEffect
var user = move.User; var user = move.User;
var damage = move.GetHitData(target, hit).Damage; var damage = move.GetHitData(target, hit).Damage;
var healed = (uint)(damage * DrainModifier); var healed = (uint)(damage * DrainModifier);
if (move.User.HasHeldItem("big_root")) if (move.User.HasHeldItem(ItemNames.BigRoot))
healed = (uint)(healed * 1.3f); healed = (uint)(healed * 1.3f);
var invert = false; var invert = false;

View File

@@ -3,13 +3,11 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "dream_eater")] [Script(ScriptCategory.Move, "dream_eater")]
public class DreamEater : Script, IScriptOnSecondaryEffect, IScriptBlockOutgoingHit public class DreamEater : Script, IScriptOnSecondaryEffect, IScriptBlockOutgoingHit
{ {
private static readonly StringKey AsleepStatus = new("asleep");
private static readonly StringKey ComatoseAbility = new("comatose");
/// <inheritdoc /> /// <inheritdoc />
public void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) 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; block = true;
} }
@@ -19,7 +17,7 @@ public class DreamEater : Script, IScriptOnSecondaryEffect, IScriptBlockOutgoing
var user = move.User; var user = move.User;
var damage = move.GetHitData(target, hit).Damage; var damage = move.GetHitData(target, hit).Damage;
var healed = (uint)(damage * 0.5f); var healed = (uint)(damage * 0.5f);
if (move.User.HasHeldItem("big_root")) if (move.User.HasHeldItem(ItemNames.BigRoot))
healed = (uint)(healed * 1.3f); healed = (uint)(healed * 1.3f);
var args = new CustomTriggers.ModifyDrainArgs(move, target, hit, damage, healed, false); 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; var random = battleData.Battle.Random;
if (random.EffectChance(10, move, target, hit)) 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. // 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 => var pledgeMove = (IMoveChoice?)move.Battle.ChoiceQueue?.FirstOrDefault(x =>
x is IMoveChoice mc && mc.User.BattleData?.SideIndex == move.User.BattleData?.SideIndex && 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) if (pledgeMove is null)
return; return;
// If a pledge move is already queued, we stop the current move. // If a pledge move is already queued, we stop the current move.
stop = true; stop = true;
if (pledgeMove.ChosenMove.MoveData.Name == "water_pledge") if (pledgeMove.ChosenMove.MoveData.Name == MoveNames.WaterPledge)
{ {
pledgeMove.Volatile.Add(new FireWaterPledgeMove()); 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()); pledgeMove.Volatile.Add(new FireGrassPledgeMove());
} }

View File

@@ -19,14 +19,14 @@ public class FlameWheel : Script, IScriptOnInitialize, IScriptOnSecondaryEffect
/// <inheritdoc /> /// <inheritdoc />
public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) 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(); move.User.ClearStatus();
} }
if (move.Battle.Random.EffectChance(_burnChance, move, target, hit)) 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)) 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); 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; var battleData = target.BattleData;
if (battleData == null) if (battleData == null)
return; 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; return;
var batchId = new EventBatchId(); var batchId = new EventBatchId();

View File

@@ -8,7 +8,7 @@ public class FlyingPress : Script, IScriptChangeTypesForMove
IList<TypeIdentifier> types) IList<TypeIdentifier> types)
{ {
var typeLibrary = executingMove.User.Library.StaticLibrary.Types; var typeLibrary = executingMove.User.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("flying", out var flyingType)) if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Flying, out var flyingType))
return; return;
types.Add(flyingType); types.Add(flyingType);
} }

View File

@@ -12,7 +12,7 @@ public class ForestsCurse : Script, IScriptOnSecondaryEffect
if (battleData == null) if (battleData == null)
return; return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types; 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(); move.GetHitData(target, hit).Fail();
return; return;

View File

@@ -11,7 +11,7 @@ public class FreezeDry : Script, IScriptChangeEffectiveness, IScriptOnSecondaryE
return; return;
var typeLibrary = battleData.Battle.Library.StaticLibrary.Types; var typeLibrary = battleData.Battle.Library.StaticLibrary.Types;
if (!typeLibrary.TryGetTypeIdentifier("water", out var waterType)) if (!typeLibrary.TryGetTypeIdentifier(TypeNames.Water, out var waterType))
return; return;
var hitDataType = move.GetHitData(target, hit).Type; 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)) 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)) 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); .OfType<MoveChoice>().LastOrDefault(x => !x.HasFailed);
// If Fusion Flare was used immediately preceding, Fusion Bolt's power is doubled. // 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; modifier *= 2;
} }

View File

@@ -16,7 +16,7 @@ public class FusionFlare : Script, IScriptChangeDamageModifier
.OfType<MoveChoice>().LastOrDefault(x => !x.HasFailed); .OfType<MoveChoice>().LastOrDefault(x => !x.HasFailed);
// If Fusion Bolt was used immediately preceding, Fusion Flare's power is doubled. // 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; modifier *= 2;
} }

View File

@@ -15,7 +15,7 @@ public class GearUp : Script, IScriptOnSecondaryEffect
foreach (var pokemon in side.Pokemon.WhereNotNull()) foreach (var pokemon in side.Pokemon.WhereNotNull())
{ {
var ability = pokemon.ActiveAbility?.Name; var ability = pokemon.ActiveAbility?.Name;
if (ability != "plus" && ability != "minus") if (ability != AbilityNames.Plus && ability != AbilityNames.Minus)
continue; continue;
pokemon.ChangeStatBoost(Statistic.Attack, 1, pokemon == move.User, false, evtBatchId); pokemon.ChangeStatBoost(Statistic.Attack, 1, pokemon == move.User, false, evtBatchId);
pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, pokemon == move.User, false, evtBatchId); pokemon.ChangeStatBoost(Statistic.SpecialAttack, 1, pokemon == move.User, false, evtBatchId);

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