diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs
index 51ed2e6..39e797d 100644
--- a/PkmnLib.Dynamic/Models/Pokemon.cs
+++ b/PkmnLib.Dynamic/Models/Pokemon.cs
@@ -106,7 +106,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
///
/// The happiness of the Pokemon. Also known as friendship.
///
- byte Happiness { get; }
+ byte Happiness { get; set; }
///
/// The stats of the Pokemon when disregarding any stat boosts.
@@ -708,7 +708,7 @@ public class PokemonImpl : ScriptSource, IPokemon
public float HeightInMeters { get; set; }
///
- public byte Happiness { get; }
+ public byte Happiness { get; set; }
///
public StatisticSet FlatStats { get; } = new();
diff --git a/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs b/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
index c8e00e5..5cdb289 100644
--- a/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/PokeballScript.cs
@@ -17,13 +17,23 @@ public abstract class PokeballScript : ItemScript
///
/// Returns the catch rate of the Pokéball against the given target Pokémon.
///
- public abstract byte GetCatchRate(IPokemon target);
+ public abstract void ChangeCatchRate(IPokemon target, ref byte catchRate);
+
+ public virtual void OnAfterSuccessfulCapture(IPokemon target)
+ {
+ // Default implementation does nothing.
+ // Override this method in derived classes to add custom behavior after a successful capture.
+ }
///
public override void OnUseWithTarget(IPokemon target, EventHook eventHook)
{
var battleData = target.BattleData;
- battleData?.Battle.AttempCapture(battleData.SideIndex, battleData.Position, Item);
+ var result = battleData?.Battle.AttempCapture(battleData.SideIndex, battleData.Position, Item);
+ if (result is { IsCaught: true })
+ {
+ OnAfterSuccessfulCapture(target);
+ }
}
}
\ No newline at end of file
diff --git a/PkmnLib.Static/Utils/NumericHelpers.cs b/PkmnLib.Static/Utils/NumericHelpers.cs
index bb7edf3..1dd107c 100644
--- a/PkmnLib.Static/Utils/NumericHelpers.cs
+++ b/PkmnLib.Static/Utils/NumericHelpers.cs
@@ -20,6 +20,18 @@ public static class NumericHelpers
return result > byte.MaxValue ? byte.MaxValue : (byte)result;
}
+ public static byte AddOrMax(this byte value, byte addend)
+ {
+ var result = value + addend;
+ return result > byte.MaxValue ? byte.MaxValue : (byte)result;
+ }
+
+ public static byte SubtractOrMin(this byte value, byte subtrahend)
+ {
+ var result = value - subtrahend;
+ return result < 0 ? (byte)0 : (byte)result;
+ }
+
///
/// Multiplies two values. If this overflows, returns .
///
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json b/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json
index 517f439..0f58c9a 100755
--- a/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json
+++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Items.json
@@ -869,6 +869,12 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 1
+ }
}
},
{
@@ -1280,6 +1286,7 @@
"additionalData": {
"flingPower": 0
}
+ // TODO: implement dive_ball (How do we know when the battle is triggered while surfing, fishing, or diving?)
},
{
"name": "dna_splicers",
@@ -1448,6 +1455,7 @@
"additionalData": {
"flingPower": 0
}
+ // TODO: implement dusk_ball (How do we know when the battle is triggered at night or in a cave?)
},
{
"name": "dusk_stone",
@@ -1744,6 +1752,9 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "fast_ball"
}
},
{
@@ -1974,6 +1985,9 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "friend_ball"
}
},
{
@@ -2267,6 +2281,12 @@
"price": 600,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 1.5
+ }
}
},
{
@@ -2493,6 +2513,9 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "heavy_ball"
}
},
{
@@ -3088,6 +3111,9 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "level_ball"
}
},
{
@@ -3219,6 +3245,9 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "love_ball"
}
},
{
@@ -3306,6 +3335,7 @@
"additionalData": {
"flingPower": 0
}
+ // TODO: implement lure ball effect (how to know if the pokemon was encountered while fishing)
},
{
"name": "lustrous_orb",
@@ -3324,6 +3354,12 @@
"price": 1000,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 1
+ }
}
},
{
@@ -3472,6 +3508,12 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 255
+ }
}
},
{
@@ -3819,6 +3861,9 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "love_ball"
}
},
{
@@ -3914,6 +3959,9 @@
"price": 1000,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "nest_ball"
}
},
{
@@ -3924,6 +3972,9 @@
"price": 1000,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "net_ball"
}
},
{
@@ -4454,7 +4505,7 @@
"flingPower": 0
},
"battleEffect": {
- "name": "pokeball",
+ "name": "pokeball_flat_modifier",
"parameters": {
"catchRate": 1
}
@@ -4649,6 +4700,12 @@
"price": 20,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 1
+ }
}
},
{
@@ -4816,6 +4873,9 @@
"price": 1000,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "quick_ball"
}
},
{
@@ -5099,6 +5159,9 @@
"price": 1000,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "repeat_ball"
}
},
{
@@ -5418,6 +5481,12 @@
"price": -1,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 1
+ }
}
},
{
@@ -5903,6 +5972,12 @@
"price": 300,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 1
+ }
}
},
{
@@ -6267,6 +6342,9 @@
"price": 1000,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "timer_ball"
}
},
{
@@ -7285,6 +7363,12 @@
"price": 800,
"additionalData": {
"flingPower": 0
+ },
+ "battleEffect": {
+ "name": "pokeball_flat_modifier",
+ "parameters": {
+ "catchRate": 2
+ }
}
},
{
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs
index 513dc00..ba60cf8 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7CaptureLibrary.cs
@@ -1,4 +1,5 @@
using PkmnLib.Dynamic.Libraries;
+using PkmnLib.Static.Species;
namespace PkmnLib.Plugin.Gen7.Libraries.Battling;
@@ -11,6 +12,8 @@ public class Gen7CaptureLibrary : ICaptureLibrary
_configuration = configuration;
}
+ public bool HasPokemonBeenCaughtBefore(ISpecies species) => _configuration.TimesSpeciesCaught(species) > 0;
+
///
public CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random)
{
@@ -18,18 +21,17 @@ public class Gen7CaptureLibrary : ICaptureLibrary
var currentHealth = target.CurrentHealth;
var catchRate = target.Species.CaptureRate;
- byte bonusBall = 1;
if (target.Library.ScriptResolver.TryResolveBattleItemScript(captureItem, out var script) &&
script is PokeballScript pokeballScript)
{
- bonusBall = pokeballScript.GetCatchRate(target);
+ pokeballScript.ChangeCatchRate(target, ref catchRate);
}
byte bonusStatus = 1;
target.RunScriptHook(x =>
x.ChangeCatchRateBonus(target, captureItem, ref bonusStatus));
- var modifiedCatchRate = (3.0f * maxHealth - 2.0f * currentHealth) * catchRate * bonusBall / (3.0f * maxHealth);
+ var modifiedCatchRate = (3.0f * maxHealth - 2.0f * currentHealth) * catchRate / (3.0f * maxHealth);
modifiedCatchRate *= bonusStatus;
var shakeProbability = 65536 / Math.Pow(255 / modifiedCatchRate, 0.1875f);
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/FastBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/FastBall.cs
new file mode 100644
index 0000000..c8e13c4
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/FastBall.cs
@@ -0,0 +1,17 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("fast_ball")]
+public class FastBall : PokeballScript
+{
+ ///
+ public FastBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ var modifier = target.Form.BaseStats.Speed >= 100 ? 4f : 1f;
+ catchRate = catchRate.MultiplyOrMax(modifier);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/FriendBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/FriendBall.cs
new file mode 100644
index 0000000..96d6775
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/FriendBall.cs
@@ -0,0 +1,21 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("friend_ball")]
+public class FriendBall : PokeballScript
+{
+ ///
+ public FriendBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ }
+
+ ///
+ public override void OnAfterSuccessfulCapture(IPokemon target)
+ {
+ target.Happiness = 200;
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/HealBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/HealBall.cs
new file mode 100644
index 0000000..8ad4236
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/HealBall.cs
@@ -0,0 +1,23 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("heal_ball")]
+public class HealBall : PokeballScript
+{
+ ///
+ public HealBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ }
+
+ ///
+ public override void OnAfterSuccessfulCapture(IPokemon target)
+ {
+ target.Heal(target.MaxHealth, true, forceHeal: true);
+ target.ClearStatus();
+ target.RestoreAllPP();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/HeavyBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/HeavyBall.cs
new file mode 100644
index 0000000..6b6a580
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/HeavyBall.cs
@@ -0,0 +1,38 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("heavy_ball")]
+public class HeavyBall : PokeballScript
+{
+ ///
+ public HeavyBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ var weight = target.WeightInKg;
+ switch (weight)
+ {
+ case < 100:
+ {
+ catchRate.SubtractOrMin(20);
+ break;
+ }
+ case < 200:
+ {
+ break;
+ }
+ case < 300:
+ {
+ catchRate.AddOrMax(20);
+ break;
+ }
+ default:
+ {
+ catchRate.AddOrMax(30);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/LevelBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/LevelBall.cs
new file mode 100644
index 0000000..9d3e472
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/LevelBall.cs
@@ -0,0 +1,31 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("level_ball")]
+public class LevelBall : PokeballScript
+{
+ ///
+ public LevelBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ if (target.BattleData is null)
+ return;
+ var opponentSide = target.BattleData.SideIndex == 0 ? 1 : 0;
+ var opponent = target.BattleData.Battle.Sides[opponentSide].Pokemon.FirstOrDefault(x => x is not null);
+ if (opponent is null)
+ return;
+
+ var levelDifferenceModifier = (float)target.Level / opponent.Level;
+ var catchModifier = levelDifferenceModifier switch
+ {
+ >= 1f => 1f,
+ >= 0.5f => 2f,
+ >= 0.25f => 4f,
+ _ => 8f,
+ };
+ catchRate = catchRate.MultiplyOrMax(catchModifier);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/LoveBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/LoveBall.cs
new file mode 100644
index 0000000..834ceee
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/LoveBall.cs
@@ -0,0 +1,26 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("love_ball")]
+public class LoveBall : PokeballScript
+{
+ ///
+ public LoveBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ if (target.BattleData is null)
+ return;
+ var opponentSide = target.BattleData.SideIndex == 0 ? 1 : 0;
+ var opponent = target.BattleData.Battle.Sides[opponentSide].Pokemon.FirstOrDefault(x => x is not null);
+ if (opponent is null)
+ return;
+
+ if (opponent.Species == target.Species && opponent.Gender != target.Gender)
+ {
+ catchRate = catchRate.MultiplyOrMax(8f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/MoonBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/MoonBall.cs
new file mode 100644
index 0000000..6b5d399
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/MoonBall.cs
@@ -0,0 +1,32 @@
+using PkmnLib.Static.Species;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("moon_ball")]
+public class MoonBall : PokeballScript
+{
+ ///
+ public MoonBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ if (target.Species.EvolutionData.Any(x =>
+ {
+ switch (x)
+ {
+ case ItemUseEvolution itemUseEvolution when itemUseEvolution.Item == "moon_ball":
+ case ItemGenderEvolution itemGenderEvolution when itemGenderEvolution.Item == "moon_ball":
+ return true;
+ default:
+ return false;
+ }
+ }))
+ {
+ // If the target can evolve with a Moon Ball, it has a 4x catch rate.
+ catchRate = catchRate.MultiplyOrMax(4f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/NestBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/NestBall.cs
new file mode 100644
index 0000000..e91f7d3
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/NestBall.cs
@@ -0,0 +1,19 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("nest_ball")]
+public class NestBall : PokeballScript
+{
+ ///
+ public NestBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ if (target.Level >= 30)
+ return;
+ var modifier = (41 - target.Level) / 10f;
+ catchRate = catchRate.MultiplyOrMax(modifier);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/NetBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/NetBall.cs
new file mode 100644
index 0000000..6c6e740
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/NetBall.cs
@@ -0,0 +1,22 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("net_ball")]
+public class NetBall : PokeballScript
+{
+ ///
+ public NetBall(IItem item) : base(item)
+ {
+ }
+
+ private static readonly StringKey WaterType = "water";
+ private static readonly StringKey BugType = "bug";
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ if (target.Types.Any(x => x.Name == WaterType || x.Name == BugType))
+ {
+ catchRate = catchRate.MultiplyOrMax(3.5f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/PokeballFlatModifier.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/PokeballFlatModifier.cs
new file mode 100644
index 0000000..d4a4dca
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/PokeballFlatModifier.cs
@@ -0,0 +1,38 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+///
+/// An implementation of a pokeball script that just has a flat catch rate bonus.
+///
+[ItemScript("pokeball_flat_modifier")]
+public class PokeballFlatModifier : PokeballScript
+{
+ private float _catchModifier;
+
+ ///
+ public PokeballFlatModifier(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void OnInitialize(IReadOnlyDictionary? parameters)
+ {
+ var catchModifier = 1f;
+ if (parameters != null && parameters.TryGetValue("catchRate", out var catchRateObj))
+ {
+ catchModifier = catchRateObj switch
+ {
+ float catchModifierFloat => catchModifierFloat,
+ int catchModifierInt => catchModifierInt,
+ _ => catchModifier,
+ };
+ }
+
+ _catchModifier = catchModifier;
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ catchRate = catchRate.MultiplyOrMax(_catchModifier);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/QuickBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/QuickBall.cs
new file mode 100644
index 0000000..28b747d
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/QuickBall.cs
@@ -0,0 +1,23 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("quick_ball")]
+public class QuickBall : PokeballScript
+{
+ ///
+ public QuickBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ var battleData = target.BattleData;
+ if (battleData is null)
+ return;
+
+ if (battleData.Battle.CurrentTurnNumber == 1)
+ {
+ catchRate = catchRate.MultiplyOrMax(5f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/RepeatBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/RepeatBall.cs
new file mode 100644
index 0000000..a8f9dc2
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/RepeatBall.cs
@@ -0,0 +1,22 @@
+using PkmnLib.Plugin.Gen7.Libraries.Battling;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("repeat_ball")]
+public class RepeatBall : PokeballScript
+{
+ ///
+ public RepeatBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ if (target.Library.CaptureLibrary is Gen7CaptureLibrary captureLibrary &&
+ captureLibrary.HasPokemonBeenCaughtBefore(target.Species))
+ {
+ catchRate = catchRate.MultiplyOrMax(3.5f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/TimerBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/TimerBall.cs
new file mode 100644
index 0000000..c078355
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/Pokeballs/TimerBall.cs
@@ -0,0 +1,24 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Items.Pokeballs;
+
+[ItemScript("timer_ball")]
+public class TimerBall : PokeballScript
+{
+ ///
+ public TimerBall(IItem item) : base(item)
+ {
+ }
+
+ ///
+ public override void ChangeCatchRate(IPokemon target, ref byte catchRate)
+ {
+ var battleData = target.BattleData;
+ if (battleData is null)
+ return;
+
+ var turns = battleData.Battle.CurrentTurnNumber;
+ var modifier = 1 + turns * (1229 / 4096f);
+ if (modifier > 4f)
+ modifier = 4f;
+ catchRate = catchRate.MultiplyOrMax(modifier);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/StaticPokeball.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/StaticPokeball.cs
deleted file mode 100644
index d3fa250..0000000
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Items/StaticPokeball.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-namespace PkmnLib.Plugin.Gen7.Scripts.Items;
-
-///
-/// An implementation of a pokeball script that just has a flat catch rate bonus.
-///
-[ItemScript("pokeball")]
-public class StaticPokeball : PokeballScript
-{
- private byte _catchRate;
-
- ///
- public StaticPokeball(IItem item) : base(item)
- {
- }
-
- ///
- public override void OnInitialize(IReadOnlyDictionary? parameters)
- {
- if (parameters == null || !parameters.TryGetValue("catchRate", out var catchRateObj) ||
- catchRateObj is not byte catchRate)
- {
- catchRate = 1;
- }
-
- _catchRate = catchRate;
- }
-
- ///
- public override byte GetCatchRate(IPokemon target) => _catchRate;
-}
\ No newline at end of file