mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-10 06:34:59 +00:00
AI rewrites of ability-changing function codes, OHKO codes and binding codes
This commit is contained in:
@@ -292,6 +292,16 @@ class Battle::AI
|
|||||||
score += 15 * inc_mult
|
score += 15 * inc_mult
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
# TODO: Prefer if the user is able to cause flinching (moves that flinch,
|
||||||
|
# or has King's Rock/Stench).
|
||||||
|
# Prefer if the user has Electro Ball or Power Trip/Stored Power
|
||||||
|
moves_that_prefer_high_speed = [
|
||||||
|
"PowerHigherWithUserFasterThanTarget",
|
||||||
|
"PowerHigherWithUserPositiveStatStages"
|
||||||
|
]
|
||||||
|
if @user.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) }
|
||||||
|
score += 8 * inc_mult
|
||||||
|
end
|
||||||
# Don't prefer if any foe has Gyro Ball
|
# Don't prefer if any foe has Gyro Ball
|
||||||
each_foe_battler(@user.side) do |b, i|
|
each_foe_battler(@user.side) do |b, i|
|
||||||
next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetFasterThanUser" }
|
next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetFasterThanUser" }
|
||||||
@@ -956,4 +966,295 @@ class Battle::AI
|
|||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#=============================================================================
|
||||||
|
# Returns a value indicating how beneficial the given ability will be to the
|
||||||
|
# given battler if it has it.
|
||||||
|
# Return values are typically between -10 and +10. 0 is indifferent, positive
|
||||||
|
# values mean the battler benefits, negative values mean the battler suffers.
|
||||||
|
#=============================================================================
|
||||||
|
# These values are taken from the Complete-Fire-Red-Upgrade decomp here:
|
||||||
|
# https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/f7f35becbd111c7e936b126f6328fc52d9af68c8/src/ability_battle_effects.c#L41
|
||||||
|
BASE_ABILITY_RATINGS = {
|
||||||
|
:ADAPTABILITY => 8,
|
||||||
|
:AERILATE => 8,
|
||||||
|
:AFTERMATH => 5,
|
||||||
|
:AIRLOCK => 5,
|
||||||
|
:ANALYTIC => 5,
|
||||||
|
:ANGERPOINT => 4,
|
||||||
|
:ANTICIPATION => 2,
|
||||||
|
:ARENATRAP => 9,
|
||||||
|
:AROMAVEIL => 3,
|
||||||
|
# :ASONECHILLINGNEIGH => 0,
|
||||||
|
# :ASONEGRIMNEIGH => 0,
|
||||||
|
:AURABREAK => 3,
|
||||||
|
:BADDREAMS => 4,
|
||||||
|
# :BALLFETCH => 0,
|
||||||
|
# :BATTERY => 0,
|
||||||
|
:BATTLEARMOR => 2,
|
||||||
|
:BATTLEBOND => 6,
|
||||||
|
:BEASTBOOST => 7,
|
||||||
|
:BERSERK => 5,
|
||||||
|
:BIGPECKS => 1,
|
||||||
|
:BLAZE => 5,
|
||||||
|
:BULLETPROOF => 7,
|
||||||
|
:CHEEKPOUCH => 4,
|
||||||
|
# :CHILLINGNEIGH => 0,
|
||||||
|
:CHLOROPHYLL => 6,
|
||||||
|
:CLEARBODY => 4,
|
||||||
|
:CLOUDNINE => 5,
|
||||||
|
:COLORCHANGE => 2,
|
||||||
|
:COMATOSE => 6,
|
||||||
|
:COMPETITIVE => 5,
|
||||||
|
:COMPOUNDEYES => 7,
|
||||||
|
:CONTRARY => 8,
|
||||||
|
:CORROSION => 5,
|
||||||
|
:COTTONDOWN => 3,
|
||||||
|
# :CURIOUSMEDICINE => 0,
|
||||||
|
:CURSEDBODY => 4,
|
||||||
|
:CUTECHARM => 2,
|
||||||
|
:DAMP => 2,
|
||||||
|
:DANCER => 5,
|
||||||
|
:DARKAURA => 6,
|
||||||
|
:DAUNTLESSSHIELD => 3,
|
||||||
|
:DAZZLING => 5,
|
||||||
|
:DEFEATIST => -1,
|
||||||
|
:DEFIANT => 5,
|
||||||
|
:DELTASTREAM => 10,
|
||||||
|
:DESOLATELAND => 10,
|
||||||
|
:DISGUISE => 8,
|
||||||
|
:DOWNLOAD => 7,
|
||||||
|
:DRAGONSMAW => 8,
|
||||||
|
:DRIZZLE => 9,
|
||||||
|
:DROUGHT => 9,
|
||||||
|
:DRYSKIN => 6,
|
||||||
|
:EARLYBIRD => 4,
|
||||||
|
:EFFECTSPORE => 4,
|
||||||
|
:ELECTRICSURGE => 8,
|
||||||
|
:EMERGENCYEXIT => 3,
|
||||||
|
:FAIRYAURA => 6,
|
||||||
|
:FILTER => 6,
|
||||||
|
:FLAMEBODY => 4,
|
||||||
|
:FLAREBOOST => 5,
|
||||||
|
:FLASHFIRE => 6,
|
||||||
|
:FLOWERGIFT => 4,
|
||||||
|
# :FLOWERVEIL => 0,
|
||||||
|
:FLUFFY => 5,
|
||||||
|
:FORECAST => 6,
|
||||||
|
:FOREWARN => 2,
|
||||||
|
# :FRIENDGUARD => 0,
|
||||||
|
:FRISK => 3,
|
||||||
|
:FULLMETALBODY => 4,
|
||||||
|
:FURCOAT => 7,
|
||||||
|
:GALEWINGS => 6,
|
||||||
|
:GALVANIZE => 8,
|
||||||
|
:GLUTTONY => 3,
|
||||||
|
:GOOEY => 5,
|
||||||
|
:GORILLATACTICS => 4,
|
||||||
|
:GRASSPELT => 2,
|
||||||
|
:GRASSYSURGE => 8,
|
||||||
|
# :GRIMNEIGH => 0,
|
||||||
|
:GULPMISSLE => 3,
|
||||||
|
:GUTS => 6,
|
||||||
|
:HARVEST => 5,
|
||||||
|
# :HEALER => 0,
|
||||||
|
:HEATPROOF => 5,
|
||||||
|
:HEAVYMETAL => -1,
|
||||||
|
# :HONEYGATHER => 0,
|
||||||
|
:HUGEPOWER => 10,
|
||||||
|
:HUNGERSWITCH => 2,
|
||||||
|
:HUSTLE => 7,
|
||||||
|
:HYDRATION => 4,
|
||||||
|
:HYPERCUTTER => 3,
|
||||||
|
:ICEBODY => 3,
|
||||||
|
:ICEFACE => 4,
|
||||||
|
:ICESCALES => 7,
|
||||||
|
# :ILLUMINATE => 0,
|
||||||
|
:ILLUSION => 8,
|
||||||
|
:IMMUNITY => 4,
|
||||||
|
:IMPOSTER => 9,
|
||||||
|
:INFILTRATOR => 6,
|
||||||
|
:INNARDSOUT => 5,
|
||||||
|
:INNERFOCUS => 2,
|
||||||
|
:INSOMNIA => 4,
|
||||||
|
:INTIMIDATE => 7,
|
||||||
|
:INTREPIDSWORD => 3,
|
||||||
|
:IRONBARBS => 6,
|
||||||
|
:IRONFIST => 6,
|
||||||
|
:JUSTIFIED => 4,
|
||||||
|
:KEENEYE => 1,
|
||||||
|
:KLUTZ => -1,
|
||||||
|
:LEAFGUARD => 2,
|
||||||
|
:LEVITATE => 7,
|
||||||
|
:LIBERO => 8,
|
||||||
|
:LIGHTMETAL => 2,
|
||||||
|
:LIGHTNINGROD => 7,
|
||||||
|
:LIMBER => 3,
|
||||||
|
:LIQUIDOOZE => 3,
|
||||||
|
:LIQUIDVOICE => 5,
|
||||||
|
:LONGREACH => 3,
|
||||||
|
:MAGICBOUNCE => 9,
|
||||||
|
:MAGICGUARD => 9,
|
||||||
|
:MAGICIAN => 3,
|
||||||
|
:MAGMAARMOR => 1,
|
||||||
|
:MAGNETPULL => 9,
|
||||||
|
:MARVELSCALE => 5,
|
||||||
|
:MEGALAUNCHER => 7,
|
||||||
|
:MERCILESS => 4,
|
||||||
|
:MIMICRY => 2,
|
||||||
|
# :MINUS => 0,
|
||||||
|
:MIRRORARMOR => 6,
|
||||||
|
:MISTYSURGE => 8,
|
||||||
|
:MOLDBREAKER => 7,
|
||||||
|
:MOODY => 10,
|
||||||
|
:MOTORDRIVE => 6,
|
||||||
|
:MOXIE => 7,
|
||||||
|
:MULTISCALE => 8,
|
||||||
|
:MULTITYPE => 8,
|
||||||
|
:MUMMY => 5,
|
||||||
|
:NATURALCURE => 7,
|
||||||
|
:NEUROFORCE => 6,
|
||||||
|
:NEUTRALIZINGGAS => 5,
|
||||||
|
:NOGUARD => 8,
|
||||||
|
:NORMALIZE => -1,
|
||||||
|
:OBLIVIOUS => 2,
|
||||||
|
:OVERCOAT => 5,
|
||||||
|
:OVERGROW => 5,
|
||||||
|
:OWNTEMPO => 3,
|
||||||
|
:PARENTALBOND => 10,
|
||||||
|
:PASTELVEIL => 4,
|
||||||
|
:PERISHBODY => -1,
|
||||||
|
:PICKPOCKET => 3,
|
||||||
|
:PICKUP => 1,
|
||||||
|
:PIXILATE => 8,
|
||||||
|
# :PLUS => 0,
|
||||||
|
:POISONHEAL => 8,
|
||||||
|
:POISONPOINT => 4,
|
||||||
|
:POISONTOUCH => 4,
|
||||||
|
:POWERCONSTRUCT => 10,
|
||||||
|
# :POWEROFALCHEMY => 0,
|
||||||
|
:POWERSPOT => 2,
|
||||||
|
:PRANKSTER => 8,
|
||||||
|
:PRESSURE => 5,
|
||||||
|
:PRIMORDIALSEA => 10,
|
||||||
|
:PRISMARMOR => 6,
|
||||||
|
:PROPELLORTAIL => 2,
|
||||||
|
:PROTEAN => 8,
|
||||||
|
:PSYCHICSURGE => 8,
|
||||||
|
:PUNKROCK => 2,
|
||||||
|
:PUREPOWER => 10,
|
||||||
|
:QUEENLYMAJESTY => 6,
|
||||||
|
# :QUICKDRAW => 0,
|
||||||
|
:QUICKFEET => 5,
|
||||||
|
:RAINDISH => 3,
|
||||||
|
:RATTLED => 3,
|
||||||
|
# :RECEIVER => 0,
|
||||||
|
:RECKLESS => 6,
|
||||||
|
:REFRIGERATE => 8,
|
||||||
|
:REGENERATOR => 8,
|
||||||
|
:RIPEN => 4,
|
||||||
|
:RIVALRY => 1,
|
||||||
|
:RKSSYSTEM => 8,
|
||||||
|
:ROCKHEAD => 5,
|
||||||
|
:ROUGHSKIN => 6,
|
||||||
|
# :RUNAWAY => 0,
|
||||||
|
:SANDFORCE => 4,
|
||||||
|
:SANDRUSH => 6,
|
||||||
|
:SANDSPIT => 5,
|
||||||
|
:SANDSTREAM => 9,
|
||||||
|
:SANDVEIL => 3,
|
||||||
|
:SAPSIPPER => 7,
|
||||||
|
:SCHOOLING => 6,
|
||||||
|
:SCRAPPY => 6,
|
||||||
|
:SCREENCLEANER => 3,
|
||||||
|
:SERENEGRACE => 8,
|
||||||
|
:SHADOWSHIELD => 8,
|
||||||
|
:SHADOWTAG => 10,
|
||||||
|
:SHEDSKIN => 7,
|
||||||
|
:SHEERFORCE => 8,
|
||||||
|
:SHELLARMOR => 2,
|
||||||
|
:SHIELDDUST => 5,
|
||||||
|
:SHIELDSDOWN => 6,
|
||||||
|
:SIMPLE => 8,
|
||||||
|
:SKILLLINK => 7,
|
||||||
|
:SLOWSTART => -2,
|
||||||
|
:SLUSHRUSH => 5,
|
||||||
|
:SNIPER => 3,
|
||||||
|
:SNOWCLOAK => 3,
|
||||||
|
:SNOWWARNING => 8,
|
||||||
|
:SOLARPOWER => 3,
|
||||||
|
:SOLIDROCK => 6,
|
||||||
|
:SOULHEART => 7,
|
||||||
|
:SOUNDPROOF => 4,
|
||||||
|
:SPEEDBOOST => 9,
|
||||||
|
:STAKEOUT => 6,
|
||||||
|
:STALL => -1,
|
||||||
|
:STALWART => 2,
|
||||||
|
:STAMINA => 6,
|
||||||
|
:STANCECHANGE => 10,
|
||||||
|
:STATIC => 4,
|
||||||
|
:STEADFAST => 2,
|
||||||
|
:STEAMENGINE => 3,
|
||||||
|
:STEELWORKER => 6,
|
||||||
|
:STEELYSPIRIT => 2,
|
||||||
|
:STENCH => 1,
|
||||||
|
:STICKYHOLD => 3,
|
||||||
|
:STORMDRAIN => 7,
|
||||||
|
:STRONGJAW => 6,
|
||||||
|
:STURDY => 6,
|
||||||
|
:SUCTIONCUPS => 2,
|
||||||
|
:SUPERLUCK => 3,
|
||||||
|
:SURGESURFER => 4,
|
||||||
|
:SWARM => 5,
|
||||||
|
:SWEETVEIL => 4,
|
||||||
|
:SWIFTSWIM => 6,
|
||||||
|
# :SYMBIOSIS => 0,
|
||||||
|
:SYNCHRONIZE => 4,
|
||||||
|
:TANGLEDFEET => 2,
|
||||||
|
:TANGLINGHAIR => 5,
|
||||||
|
:TECHNICIAN => 8,
|
||||||
|
# :TELEPATHY => 0,
|
||||||
|
:TERAVOLT => 7,
|
||||||
|
:THICKFAT => 7,
|
||||||
|
:TINTEDLENS => 7,
|
||||||
|
:TORRENT => 5,
|
||||||
|
:TOUGHCLAWS => 7,
|
||||||
|
:TOXICBOOST => 6,
|
||||||
|
:TRACE => 6,
|
||||||
|
:TRANSISTOR => 8,
|
||||||
|
:TRIAGE => 7,
|
||||||
|
:TRUANT => -2,
|
||||||
|
:TURBOBLAZE => 7,
|
||||||
|
:UNAWARE => 6,
|
||||||
|
:UNBURDEN => 7,
|
||||||
|
:UNNERVE => 3,
|
||||||
|
# :UNSEENFIST => 0,
|
||||||
|
:VICTORYSTAR => 6,
|
||||||
|
:VITALSPIRIT => 4,
|
||||||
|
:VOLTABSORB => 7,
|
||||||
|
:WANDERINGSPIRIT => 2,
|
||||||
|
:WATERABSORB => 7,
|
||||||
|
:WATERBUBBLE => 8,
|
||||||
|
:WATERCOMPACTION => 4,
|
||||||
|
:WATERVEIL => 4,
|
||||||
|
:WEAKARMOR => 2,
|
||||||
|
:WHITESMOKE => 4,
|
||||||
|
:WIMPOUT => 3,
|
||||||
|
:WONDERGUARD => 10,
|
||||||
|
:WONDERSKIN => 4,
|
||||||
|
:ZENMODE => -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: This method assumes the ability isn't being negated. Should it return
|
||||||
|
# 0 if it is? The calculations that call this method separately check
|
||||||
|
# for it being negated, because they need to do something special in
|
||||||
|
# that case, so I think it's okay for this method to ignore negation.
|
||||||
|
def battler_wants_ability?(battler, ability = :NONE)
|
||||||
|
ability = ability.id if !ability.is_a?(Symbol) && ability.respond_to?("id")
|
||||||
|
# TODO: Ideally replace the above list of ratings with context-sensitive
|
||||||
|
# calculations. Should they all go in this method, or should there be
|
||||||
|
# more handlers for each ability?
|
||||||
|
ret = BASE_ABILITY_RATINGS[ability] || 0
|
||||||
|
return ret
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -916,7 +916,7 @@ Battle::AI::Handlers::MoveFailureCheck.add("UserLosesFireType",
|
|||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToSimple",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToSimple",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
@@ -924,9 +924,23 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToSimpl
|
|||||||
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SetTargetAbilityToSimple",
|
||||||
|
proc { |score, move, user, target, ai, battle|
|
||||||
|
next Battle::AI::MOVE_USELESS_SCORE if !target.ability_active?
|
||||||
|
old_ability_rating = ai.battler_wants_ability?(target, target.ability_id)
|
||||||
|
new_ability_rating = ai.battler_wants_ability?(target, :SIMPLE)
|
||||||
|
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||||
|
if old_ability_rating > new_ability_rating
|
||||||
|
score += 4 * side_mult * [old_ability_rating - new_ability_rating, 3].max
|
||||||
|
elsif old_ability_rating < new_ability_rating
|
||||||
|
score -= 4 * side_mult * [new_ability_rating - old_ability_rating, 3].max
|
||||||
|
end
|
||||||
|
next score
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToInsomnia",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToInsomnia",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
@@ -934,9 +948,23 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToInsom
|
|||||||
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SetTargetAbilityToInsomnia",
|
||||||
|
proc { |score, move, user, target, ai, battle|
|
||||||
|
next Battle::AI::MOVE_USELESS_SCORE if !target.ability_active?
|
||||||
|
old_ability_rating = ai.battler_wants_ability?(target, target.ability_id)
|
||||||
|
new_ability_rating = ai.battler_wants_ability?(target, :INSOMNIA)
|
||||||
|
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||||
|
if old_ability_rating > new_ability_rating
|
||||||
|
score += 4 * side_mult * [old_ability_rating - new_ability_rating, 3].max
|
||||||
|
elsif old_ability_rating < new_ability_rating
|
||||||
|
score -= 4 * side_mult * [new_ability_rating - old_ability_rating, 3].max
|
||||||
|
end
|
||||||
|
next score
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetUserAbilityToTargetAbility",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetUserAbilityToTargetAbility",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
@@ -946,16 +974,20 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetUserAbilityToTargetA
|
|||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SetUserAbilityToTargetAbility",
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SetUserAbilityToTargetAbility",
|
||||||
proc { |score, move, user, target, ai, battle|
|
proc { |score, move, user, target, ai, battle|
|
||||||
score -= 40 # don't prefer this move
|
next Battle::AI::MOVE_USELESS_SCORE if !user.ability_active?
|
||||||
if ai.trainer.medium_skill? && user.opposes?(target)
|
old_ability_rating = ai.battler_wants_ability?(user, user.ability_id)
|
||||||
score -= 50 if [:TRUANT, :SLOWSTART].include?(target.ability_id)
|
new_ability_rating = ai.battler_wants_ability?(user, target.ability_id)
|
||||||
|
if old_ability_rating > new_ability_rating
|
||||||
|
score += 4 * [old_ability_rating - new_ability_rating, 3].max
|
||||||
|
elsif old_ability_rating < new_ability_rating
|
||||||
|
score -= 4 * [new_ability_rating - old_ability_rating, 3].max
|
||||||
end
|
end
|
||||||
next score
|
next score
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToUserAbility",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToUserAbility",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
@@ -967,52 +999,87 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SetTargetAbilityToUserA
|
|||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SetTargetAbilityToUserAbility",
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SetTargetAbilityToUserAbility",
|
||||||
proc { |score, move, user, target, ai, battle|
|
proc { |score, move, user, target, ai, battle|
|
||||||
score -= 40 # don't prefer this move
|
next Battle::AI::MOVE_USELESS_SCORE if !target.ability_active?
|
||||||
if ai.trainer.medium_skill? && user.opposes?(target)
|
old_ability_rating = ai.battler_wants_ability?(target, target.ability_id)
|
||||||
score += 90 if [:TRUANT, :SLOWSTART].include?(user.ability_id)
|
new_ability_rating = ai.battler_wants_ability?(target, user.ability_id)
|
||||||
|
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||||
|
if old_ability_rating > new_ability_rating
|
||||||
|
score += 4 * side_mult * [old_ability_rating - new_ability_rating, 3].max
|
||||||
|
elsif old_ability_rating < new_ability_rating
|
||||||
|
score -= 4 * side_mult * [new_ability_rating - old_ability_rating, 3].max
|
||||||
end
|
end
|
||||||
next score
|
next score
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UserTargetSwapAbilities",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UserTargetSwapAbilities",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
next true if !user.ability || user.battler.unstoppableAbility? ||
|
next true if !user.ability || user.battler.unstoppableAbility? ||
|
||||||
user.ability_id == :WONDERGUARD
|
user.battler.ungainableAbility? || user.ability_id == :WONDERGUARD
|
||||||
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapAbilities",
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapAbilities",
|
||||||
proc { |score, move, user, target, ai, battle|
|
proc { |score, move, user, target, ai, battle|
|
||||||
score -= 40 # don't prefer this move
|
next Battle::AI::MOVE_USELESS_SCORE if !user.ability_active? && !target.ability_active?
|
||||||
if ai.trainer.high_skill? && user.opposes?(target)
|
old_user_ability_rating = ai.battler_wants_ability?(user, user.ability_id)
|
||||||
score -= 90 if [:TRUANT, :SLOWSTART].include?(target.ability_id)
|
new_user_ability_rating = ai.battler_wants_ability?(user, target.ability_id)
|
||||||
|
user_diff = new_user_ability_rating - old_user_ability_rating
|
||||||
|
user_diff = 0 if !user.ability_active?
|
||||||
|
old_target_ability_rating = ai.battler_wants_ability?(target, target.ability_id)
|
||||||
|
new_target_ability_rating = ai.battler_wants_ability?(target, user.ability_id)
|
||||||
|
target_diff = new_target_ability_rating - old_target_ability_rating
|
||||||
|
target_diff = 0 if !target.ability_active?
|
||||||
|
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||||
|
if user_diff > target_diff
|
||||||
|
score += 4 * side_mult * [user_diff - target_diff, 3].max
|
||||||
|
elsif target_diff < user_diff
|
||||||
|
score -= 4 * side_mult * [target_diff - user_diff, 3].max
|
||||||
end
|
end
|
||||||
next score
|
next score
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("NegateTargetAbility",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("NegateTargetAbility",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("NegateTargetAbility",
|
||||||
|
proc { |score, move, user, target, ai, battle|
|
||||||
|
target_ability_rating = ai.battler_wants_ability?(target, target.ability_id)
|
||||||
|
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||||
|
if target_ability_rating > 0
|
||||||
|
score += 4 * side_mult * [target_ability_rating, 3].max
|
||||||
|
elsif target_ability_rating < 0
|
||||||
|
score -= 4 * side_mult * [target_ability_rating.abs, 3].max
|
||||||
|
end
|
||||||
|
next score
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("NegateTargetAbilityIfTargetActed",
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("NegateTargetAbilityIfTargetActed",
|
||||||
proc { |score, move, user, target, ai, battle|
|
proc { |score, move, user, target, ai, battle|
|
||||||
next score if target.effects[PBEffects::Substitute] > 0 || target.effects[PBEffects::GastroAcid]
|
next score if target.effects[PBEffects::Substitute] > 0 || target.effects[PBEffects::GastroAcid]
|
||||||
next score if target.battler.unstoppableAbility?
|
next score if target.battler.unstoppableAbility?
|
||||||
next score if user.faster_than?(target)
|
next score if user.faster_than?(target)
|
||||||
next score + 10
|
target_ability_rating = ai.battler_wants_ability?(target, target.ability_id)
|
||||||
|
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||||
|
if target_ability_rating > 0
|
||||||
|
score += 4 * side_mult * [target_ability_rating, 3].max
|
||||||
|
elsif target_ability_rating < 0
|
||||||
|
score -= 4 * side_mult * [target_ability_rating.abs, 3].max
|
||||||
|
end
|
||||||
|
next score
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1134,7 +1201,9 @@ Battle::AI::Handlers::MoveFailureCheck.add("StartGravity",
|
|||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveEffectScore.add("StartGravity",
|
Battle::AI::Handlers::MoveEffectScore.add("StartGravity",
|
||||||
proc { |score, move, user, ai, battle|
|
proc { |score, move, user, ai, battle|
|
||||||
# TODO: Gravity increases accuracy of all moves. Should this be considered?
|
# TODO: Gravity increases accuracy of all moves. Prefer if user/ally has low
|
||||||
|
# accuracy moves, don't prefer if foes have them. Should "low
|
||||||
|
# accuracy" mean anything below 85%?
|
||||||
ai.each_battler do |b, i|
|
ai.each_battler do |b, i|
|
||||||
# Prefer grounding airborne foes, don't prefer grounding airborne allies
|
# Prefer grounding airborne foes, don't prefer grounding airborne allies
|
||||||
# Prefer making allies affected by terrain, don't prefer making foes
|
# Prefer making allies affected by terrain, don't prefer making foes
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Battle::AI::Handlers::MoveBasePower.add("LowerTargetHPToUserHP",
|
|||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("OHKO",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("OHKO",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
@@ -71,27 +71,44 @@ Battle::AI::Handlers::MoveBasePower.add("OHKO",
|
|||||||
next target.hp
|
next target.hp
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("OHKO",
|
||||||
|
proc { |score, move, user, target, ai, battle|
|
||||||
|
# Don't prefer if the target has less HP and user has a non-OHKO damaging move
|
||||||
|
if user.check_for_move { |m| !m.is_a?(Battle::Move::OHKO) && m.damagingMove? }
|
||||||
|
score -= 10 if target.hp <= target.totalhp / 2
|
||||||
|
score -= 10 if target.hp <= target.totalhp / 4
|
||||||
|
end
|
||||||
|
# TODO: Maybe predict dealt damage of all user's other moves, and score this
|
||||||
|
# move useless if another one can KO the target. Might also need to
|
||||||
|
# take into account whether those moves will fail. Might need to do
|
||||||
|
# this specially after all move scores are determined.
|
||||||
|
next score
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("OHKOIce",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("OHKOIce",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
next true if target.level > user.level
|
|
||||||
next true if !battle.moldBreaker && target.has_active_ability?(:STURDY)
|
|
||||||
next true if target.has_type?(:ICE)
|
next true if target.has_type?(:ICE)
|
||||||
|
next Battle::AI::Handlers.move_will_fail_against_target?("OHKO", move, user, target, ai, battle)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveBasePower.copy("OHKO",
|
Battle::AI::Handlers::MoveBasePower.copy("OHKO",
|
||||||
"OHKOIce")
|
"OHKOIce")
|
||||||
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("OHKO",
|
||||||
|
"OHKOIce")
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.copy("OHKO",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.copy("OHKO",
|
||||||
"OHKOHitsUndergroundTarget")
|
"OHKOHitsUndergroundTarget")
|
||||||
Battle::AI::Handlers::MoveBasePower.copy("OHKO",
|
Battle::AI::Handlers::MoveBasePower.copy("OHKO",
|
||||||
"OHKOHitsUndergroundTarget")
|
"OHKOHitsUndergroundTarget")
|
||||||
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("OHKO",
|
||||||
|
"OHKOHitsUndergroundTarget")
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
#
|
#
|
||||||
@@ -397,6 +414,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DoublePowerIfTargetNotAc
|
|||||||
Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit",
|
Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit",
|
||||||
proc { |score, move, user, ai, battle|
|
proc { |score, move, user, ai, battle|
|
||||||
next Battle::AI::MOVE_USELESS_SCORE if user.effects[PBEffects::LaserFocus] > 0
|
next Battle::AI::MOVE_USELESS_SCORE if user.effects[PBEffects::LaserFocus] > 0
|
||||||
|
# TODO: Useless if user will already always critical hit ("AlwaysCriticalHit"
|
||||||
|
# or Lucky Chant/crit stage is +3/etc.).
|
||||||
next score + 10
|
next score + 10
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -517,6 +536,9 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenPhysicalDamageAgainstUserS
|
|||||||
# Doesn't stack with Aurora Veil
|
# Doesn't stack with Aurora Veil
|
||||||
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
|
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
|
||||||
# Don't prefer the lower the user's HP is
|
# Don't prefer the lower the user's HP is
|
||||||
|
# TODO: Should this HP check exist? The effect can still be set up for
|
||||||
|
# allies. Maybe just don't prefer if there are no replacement mons
|
||||||
|
# left.
|
||||||
if user.hp < user.totalhp / 2
|
if user.hp < user.totalhp / 2
|
||||||
score -= 40 * (0.75 - (user.hp.to_f / user.totalhp)) # -10 to -30
|
score -= 40 * (0.75 - (user.hp.to_f / user.totalhp)) # -10 to -30
|
||||||
end
|
end
|
||||||
@@ -543,6 +565,9 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenSpecialDamageAgainstUserSi
|
|||||||
# Doesn't stack with Aurora Veil
|
# Doesn't stack with Aurora Veil
|
||||||
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
|
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
|
||||||
# Don't prefer the lower the user's HP is
|
# Don't prefer the lower the user's HP is
|
||||||
|
# TODO: Should this HP check exist? The effect can still be set up for
|
||||||
|
# allies. Maybe just don't prefer if there are no replacement mons
|
||||||
|
# left.
|
||||||
if user.hp < user.totalhp / 2
|
if user.hp < user.totalhp / 2
|
||||||
score -= 40 * (0.75 - (user.hp.to_f / user.totalhp)) # -10 to -30
|
score -= 40 * (0.75 - (user.hp.to_f / user.totalhp)) # -10 to -30
|
||||||
end
|
end
|
||||||
@@ -571,6 +596,9 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenDamageAgainstUserSideIfHai
|
|||||||
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::Reflect] > 0 &&
|
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::Reflect] > 0 &&
|
||||||
user.pbOwnSide.effects[PBEffects::LightScreen] > 0
|
user.pbOwnSide.effects[PBEffects::LightScreen] > 0
|
||||||
# Don't prefer the lower the user's HP is
|
# Don't prefer the lower the user's HP is
|
||||||
|
# TODO: Should this HP check exist? The effect can still be set up for
|
||||||
|
# allies. Maybe just don't prefer if there are no replacement mons
|
||||||
|
# left.
|
||||||
if user.hp < user.totalhp / 2
|
if user.hp < user.totalhp / 2
|
||||||
score -= 40 * (0.75 - (user.hp.to_f / user.totalhp)) # -10 to -30
|
score -= 40 * (0.75 - (user.hp.to_f / user.totalhp)) # -10 to -30
|
||||||
end
|
end
|
||||||
@@ -893,14 +921,18 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("EnsureNextMoveAlwaysHits
|
|||||||
next Battle::AI::MOVE_USELESS_SCORE if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD)
|
next Battle::AI::MOVE_USELESS_SCORE if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD)
|
||||||
next Battle::AI::MOVE_USELESS_SCORE if target.effects[PBEffects::Telekinesis] > 0
|
next Battle::AI::MOVE_USELESS_SCORE if target.effects[PBEffects::Telekinesis] > 0
|
||||||
# Prefer if the user knows moves with low accuracy
|
# Prefer if the user knows moves with low accuracy
|
||||||
|
# TODO: This isn't the correct use of check_for_move, since it should just
|
||||||
|
# loop through them instead.
|
||||||
user.check_for_move do |m|
|
user.check_for_move do |m|
|
||||||
next if target.effects[PBEffects::Minimize] && m.tramplesMinimize? && Settings::MECHANICS_GENERATION >= 6
|
next if target.effects[PBEffects::Minimize] && m.tramplesMinimize? && Settings::MECHANICS_GENERATION >= 6
|
||||||
# TODO: There are other effects that make a move certain to hit. Account
|
# TODO: There are other effects that make a move certain to hit. Account
|
||||||
# for those as well, or is that too micro-managey?
|
# for those as well. Score this move useless if no moves would
|
||||||
|
# benefit from locking on.
|
||||||
acc = m.accuracy
|
acc = m.accuracy
|
||||||
acc = m.pbBaseAccuracy(user.battler, target.battler) if ai.trainer.medium_skill?
|
acc = m.pbBaseAccuracy(user.battler, target.battler) if ai.trainer.medium_skill?
|
||||||
score += 4 if acc < 90 && acc != 0
|
score += 4 if acc < 90 && acc != 0
|
||||||
score += 4 if acc <= 50 && acc != 0
|
score += 4 if acc <= 50 && acc != 0
|
||||||
|
# TODO: Prefer more if m is a OHKO move.
|
||||||
end
|
end
|
||||||
# Not worth it if the user or the target is at low HP
|
# Not worth it if the user or the target is at low HP
|
||||||
score -= 10 if user.hp < user.totalhp / 2
|
score -= 10 if user.hp < user.totalhp / 2
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ Battle::AI::Handlers::MoveBasePower.add("TwoTurnAttackOneTurnInSun",
|
|||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackOneTurnInSun",
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackOneTurnInSun",
|
||||||
proc { |score, move, user, target, ai, battle|
|
proc { |score, move, user, target, ai, battle|
|
||||||
# Sunny weather this a 1 turn move, the same as a move with no effect
|
# In sunny weather this a 1 turn move, the same as a move with no effect
|
||||||
next score if [:Sun, :HarshSun].include?(user.battler.effectiveWeather)
|
next score if [:Sun, :HarshSun].include?(user.battler.effectiveWeather)
|
||||||
# Score for being a two turn attack
|
# Score for being a two turn attack
|
||||||
next Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
|
next Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
|
||||||
|
|||||||
@@ -159,27 +159,59 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SwitchOutTargetDamagingM
|
|||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BindTarget",
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BindTarget",
|
||||||
proc { |score, move, user, target, ai, battle|
|
proc { |score, move, user, target, ai, battle|
|
||||||
next score + 40 if target.effects[PBEffects::Trapping] == 0
|
next score if target.effects[PBEffects::Trapping] > 0
|
||||||
|
next score if target.effects[PBEffects::Substitute] > 0
|
||||||
|
# Prefer if the user has a Binding Band or Grip Claw (because why have it if
|
||||||
|
# you don't want to use it?)
|
||||||
|
score += 5 if user.has_active_item?([:BINDINGBAND, :GRIPCLAW])
|
||||||
|
# Target will take damage at the end of each round from the binding
|
||||||
|
score += 8 if target.battler.takesIndirectDamage?
|
||||||
|
# Check whether the target will be trapped in battle by the binding
|
||||||
|
untrappable = Settings::MORE_TYPE_EFFECTS && target.has_type?(:GHOST)
|
||||||
|
if !untrappable && target.ability_active?
|
||||||
|
untrappable = Battle::AbilityEffects.triggerCertainSwitching(target.ability, target.battler, battle)
|
||||||
|
end
|
||||||
|
if !untrappable && target.item_active?
|
||||||
|
untrappable = Battle::ItemEffects.triggerCertainSwitching(target.ability, target.battler, battle)
|
||||||
|
end
|
||||||
|
if !untrappable && !target.battler.trappedInBattle?
|
||||||
|
score += 8 # Prefer if the target will become trapped by this move
|
||||||
|
eor_damage = target.rough_end_of_round_damage
|
||||||
|
if eor_damage > 0
|
||||||
|
# Prefer if the target will take damage at the end of each round on top
|
||||||
|
# of binding damage
|
||||||
|
score += 8
|
||||||
|
elsif eor_damage < 0
|
||||||
|
# Don't prefer if the target will heal itself at the end of each round
|
||||||
|
score -= 8
|
||||||
|
end
|
||||||
|
# Prefer if the target has been Perish Songed
|
||||||
|
score += 10 if target.effects[PBEffects::PerishSong] > 0
|
||||||
|
end
|
||||||
|
# Don't prefer if the target can remove the binding (and the binding has an
|
||||||
|
# effect)
|
||||||
|
if (!untrappable && !target.battler.trappedInBattle?) || target.battler.takesIndirectDamage?
|
||||||
|
if target.check_for_move { |m| m.function == "RemoveUserBindingAndEntryHazards" }
|
||||||
|
score -= 8
|
||||||
|
end
|
||||||
|
end
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::MoveBasePower.add("BindTargetDoublePowerIfTargetUnderwater",
|
Battle::AI::Handlers::MoveBasePower.add("BindTargetDoublePowerIfTargetUnderwater",
|
||||||
proc { |power, move, user, target, ai, battle|
|
proc { |power, move, user, target, ai, battle|
|
||||||
next move.move.pbModifyDamage(power, user.battler, target.battler)
|
next move.move.pbModifyDamage(power, user.battler, target.battler)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BindTargetDoublePowerIfTargetUnderwater",
|
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("BindTarget",
|
||||||
proc { |score, move, user, target, ai, battle|
|
"BindTargetDoublePowerIfTargetUnderwater")
|
||||||
next score + 40 if target.effects[PBEffects::Trapping] == 0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifiers.
|
# TODO: Review score modifiers.
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# TODO: Check all lingering effects to see if the AI needs to adjust itself
|
||||||
|
# because of them.
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# TODO: Review score modifier.
|
# TODO: Review score modifier.
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user