Files
infinitefusion-e18/Data/Scripts/053_PIF_Hoenn/OverworldPokemon/OverworldPokemonEvent.rb
T
2026-07-10 15:42:06 -04:00

626 lines
19 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class OverworldPokemonEvent < Game_Event
attr_accessor :species
attr_accessor :level
attr_accessor :behavior_roaming
attr_accessor :behavior_noticed
attr_accessor :detection_radius
attr_accessor :pokemon
attr_accessor :manual_ow_pokemon
attr_accessor :current_state
attr_reader :part_of_pokeradar_chain
attr_reader :noticed_player_once
attr_reader :last_facing_direction
DISTANCE_FOR_DESPAWN = 16
FLEEING_BEHAVIORS = [:flee, :flee_flying, :teleport_away]
DISGUISED_POKEMON = [:DITTO, :MEW, :ZORUA, :ZOROARK]
CASTFORM_FORMS = [:CASTFORM, :CASTFORM_SUNNY, :CASTFORM_RAINY, :CASTFORM_SNOWY]
UPDATE_TIME = 4 # Nb. of frames for the update_behavior loop
def setup_pokemon(species, level, terrain, behavior_roaming = nil, behavior_noticed = nil)
# return unless @map_id == $game_map.map_id
@species = species
@level = level
roll_for_special_encounters
@behavior_roaming = behavior_roaming if behavior_roaming
@behavior_noticed = behavior_noticed if behavior_noticed
@terrain = terrain
@disguised = false
species_data = GameData::Species.get(@species)
@pokemon = Pokemon.new(@species, @level)
@behavior_species = getBehaviorSpecies(species_data)
unless behavior_roaming
@behavior_roaming = get_behavior_for_species(@behavior_species, :behavior_roaming)
@behavior_roaming = :random unless @behavior_roaming
end
unless behavior_noticed
@behavior_noticed = get_behavior_for_species(@behavior_species, :behavior_noticed)
@behavior_noticed = nil unless @behavior_noticed
end
default_move_speed = calculate_value_from_stat(species_data, :SPEED, 1, 4)
@roaming_move_speed = POKEMON_BEHAVIOR_DATA[@behavior_species][:roaming_move_speed] || default_move_speed
@noticed_move_speed = POKEMON_BEHAVIOR_DATA[@behavior_species][:noticed_move_speed] || default_move_speed
default_frequency = calculate_value_from_stat(species_data, :ATTACK, 1, 5)
@roaming_frequency = POKEMON_BEHAVIOR_DATA[@behavior_species][:roaming_frequency] || default_frequency
@noticed_frequency = POKEMON_BEHAVIOR_DATA[@behavior_species][:noticed_frequency] || default_frequency
@detection_radius = calculate_ow_pokemon_sight_radius(species_data)
# When the player is next to a Pokemon but not facing it, there is a delay before it battles.
# The battle will start when the timer reaches @nearby_notice_limit
# @nearby_notice_limit depends on the size of the pokemon. (usually around 3-5 ticks)
@nearby_notice_timer = 0
@nearby_notice_limit = 4 # calculate_value_from_ref_value(species_data.weight.to_f/10,2,12, 8)# weight is multiplied by 10 in the pokemon data for some reason
@nearby_notice_limit += 1 * @weather_level_at_spawn if @weather_type_at_spawn == :Storm
@current_state = :ROAMING # Possible values: :ROAMING, :NOTICED_PLAYER, :FLEEING
@noticed_player_once = false
@manual_ow_pokemon = false
#@event.name = "OW/#{species.to_s}/#{level.to_s}"
weather = $game_weather.get_current_map_weather if $game_weather
if weather
@weather_type_at_spawn = weather[0]
@weather_level_at_spawn = weather[1]
end
if DISGUISED_POKEMON.include?(@species) && !@manual_ow_pokemon
species_data = getRandomPokemonFromRoute(@species, @terrain)
@disguised = true
end
if CASTFORM_FORMS.include?(@species)
species_data = setCastformToCurrentWeather
end
initialize_sprite(@terrain, species_data)
@disguised_sprite = @roaming_sprite if @disguised
@is_flying = @character_name == @flying_sprite
@is_swimming = false
@step_anime = @is_flying
@forced_z = 300 if @is_flying && $PokemonGlobal.boat #@always_on_top = @is_flying
@part_of_pokeradar_chain = is_pokeradar_chain
if @terrain == :Water
set_swimming
end
apply_shiny_rerolls(@pokemon)
if @pokemon.shiny?
pbSEPlay("shiny", 60)
playAnimation(Settings::SPARKLE_SHORT_ANIMATION_ID, @x, @y)
end
set_roaming_movement
@last_facing_direction = @direction
@setup_complete = true
end
def roll_for_special_encounters
# Mew encounter
if rand(10000) < Settings::MEW_OW_ENCOUNTER_CHANCE
@species = :MEW
@level = 7
end
end
def make_shiny
@pokemon.shiny = true
@pokemon.radar_shiny = true
species_data = GameData::Species.get(@species)
initialize_sprite(@terrain, species_data)
end
def is_pokeradar_chain
if $PokemonTemp.pokeradar
pokeradar_species = $PokemonTemp.pokeradar[0]
return @species == pokeradar_species
end
return false
end
def setCastformToCurrentWeather
case @weather_type_at_spawn
when :Rain, :Storm, :HeavyRain
@species = :CASTFORM_RAINY
when :Sunny, :HarshSun, :Sandstorm
@species = :CASTFORM_SUNNY
when :Wind, :StrongWinds, :Snow, :Blizzard
@species = :CASTFORM_SNOWY
else
@species = :CASTFORM
end
@pokemon = Pokemon.new(@species, @level)
return GameData::Species.get(@species)
end
def get_behavior_for_species(species, behavior_type)
behavior = POKEMON_BEHAVIOR_DATA[species][behavior_type]
if @terrain == :Water
behavior = :random_dive if behavior == :random_burrow
else
behavior = :random if behavior == :random_dive
behavior = :random if behavior == :water_skip
end
if species == :WHISMUR && isWearingHat(HAT_TRUMPET)
behavior = :uproar
end
return behavior
end
# Crops land sprite to make it look underwater
def set_swimming
# return if @species == :SURSKIT || @species == :SUICUNE
unless @is_flying
self.set_animation_speed(2)
@step_anime = true
unless @swimming_sprite
self.forced_bush_depth = 20
self.calculate_bush_depth
else
self.forced_bush_depth = 8
self.calculate_bush_depth
end
@is_swimming = true
end
end
def set_shiny
@pokemon.shiny = true
@pokemon.natural_shiny = true
species_data = GameData::Species.get(@species)
initialize_sprite(@terrain, species_data)
end
# Used for special static pokemon - if need other actions after the pokemon was battled
def set_post_battle_switch(switch_nb)
@post_battle_switch = switch_nb if switch_nb.is_a?(Integer)
end
def getBehaviorSpecies(species_data)
if isSpeciesFusion(@species)
return species_data.get_head_species_symbol
end
return @species
end
def initialize_sprite(terrain, species_data)
@land_sprite = getOverworldLandPath(species_data, @pokemon.shiny?)
@flying_sprite = getOverworldFlyingPath(species_data, @pokemon.shiny?)
@swimming_sprite = getOverworldSwimmingPath(species_data, @pokemon.shiny?)
@noticed_sprite = getOverworldNoticedPath(species_data, @pokemon.shiny?)
@noticed_sprite = @flying_sprite if !@noticed_sprite && @flying_sprite
@roaming_sprite = @land_sprite
@roaming_sprite = @disguised_sprite if @disguised_sprite && @disguised
if terrain == :Water
initialize_water_sprite
else
initialize_land_sprite
end
end
def initialize_water_sprite
if @flying_sprite
@character_name = @flying_sprite
elsif @swimming_sprite
@character_name = @swimming_sprite
else
@character_name = @land_sprite
end
end
def initialize_land_sprite
if @land_sprite
@character_name = @land_sprite
elsif @flying_sprite
@character_name = @flying_sprite
end
end
def get_current_state
return @current_state
end
####
# ACTIONS
# ###
def overworldPokemonBattle()
return if lock?
return if $PokemonTemp.prevent_ow_battles
return if instance_variable_get(:@_triggered)
return if $PokemonTemp.overworld_wild_battle_triggered
instance_variable_set(:@_triggered, true)
playAnimation(Settings::EXCLAMATION_ANIMATION_ID, @x, @y) # unless @current_state == :NOTICED_PLAYER #notice animation already plays instead if the state is roaming
turn_toward_player
playCry(@species)
@pokemon.ow_coordinates = [@x, @y]
$PokemonTemp.overworld_wild_battle_participants = [] if !$PokemonTemp.overworld_wild_battle_participants
$PokemonTemp.overworld_wild_battle_participants << self
pbWait(8)
trigger_overworld_wild_battle
if @post_battle_switch && @post_battle_switch.is_a?(Integer) && @post_battle_switch >= 1
$game_switches[@post_battle_switch] = true
end
return
end
def flee(behavior)
return if @pokemon.shiny?
playCry(@species)
pbSEPlay(SE_FLEE)
if FLEEING_BEHAVIORS.include?(@behavior_noticed)
flee_behavior = OW_BEHAVIOR_MOVE_ROUTES[:noticed][@behavior_noticed]
else
flee_behavior = OW_BEHAVIOR_MOVE_ROUTES[:noticed][:flee]
end
set_custom_move_route(flee_behavior, false)
check_pokeradar_chain_break
@through = true
@detection_radius = 10
force_move_route(@move_route)
@always_on_top = true if behavior == :flee_flying
@current_state = :FLEEING
end
def check_pokeradar_chain_break
return unless $PokemonTemp.pokeradar && @part_of_pokeradar_chain
remaining_pokeradar_species = false
$game_map.events.each do |event|
if event.is_a?(OverworldPokemonEvent)
remaining_pokeradar_species = true if event.part_of_pokeradar_chain
break if remaining_pokeradar_species
end
end
unless remaining_pokeradar_species
pbPokeRadarCancel
end
end
#####
# Behaviors
#####
def noticed_state_different_from_roaming
return false unless @behavior_noticed
return false if @behavior_noticed == :still && @behavior_roaming == :still
return true
end
def breakDisguise
species_data = GameData::Species.get(@species)
playAnimation(TELEPORT_ANIMATION_ID, @x, @y)
initialize_sprite(@terrain, species_data)
end
def playDetectPlayerAnimation
return unless @current_state == :ROAMING
return unless noticed_state_different_from_roaming()
if @behavior_noticed == :curious
playAnimation(Settings::QUESTION_MARK_ANIMATION_ID, @x, @y)
elsif @behavior_noticed == :aggressive
playAnimation(Settings::ANGRY_ANIMATION_ID, @x, @y)
elsif @behavior_noticed == :semi_aggressive
playAnimation(Settings::ANGRY_SHORT_ANIMATION_ID, @x, @y)
else
playAnimation(Settings::EXCLAMATION_ANIMATION_ID, @x, @y)
end
end
def update_behavior()
return unless @setup_complete
return if @opacity == 0
return if @current_state == :FLEEING
return if $game_temp.message_window_showing
@last_facing_direction = @direction
distance = distance_from_player()
is_near_player = distance <= @detection_radius
if distance >= DISTANCE_FOR_DESPAWN
despawn unless @manual_ow_pokemon
end
if is_near_player
if should_start_battle? # Battle
if isRepelActive && pokemon_can_be_repelled
playAnimation(Settings::EXCLAMATION_ANIMATION_ID, @x, @y)
flee(@behavior_noticed)
else
overworldPokemonBattle
end
else
# check for noticed
if @current_state == :ROAMING
if check_detect_trainer
playDetectPlayerAnimation
breakDisguise if @disguised
@noticed_player_once = true
update_state(:NOTICED_PLAYER)
end
end
end
else
if @current_state != :ROAMING
update_state(:ROAMING)
back_to_roaming_action
end
end
end
# Automatically starts a battle if the player is 1 tile away from the Pokemon.
# If the player is behind or to the side of the pokemon, there is a slight delay
def should_start_battle?
return false unless $PokemonTemp.overworld_wild_battle_participants
should_start = false
if player_near_event?(1)
return true if $PokemonTemp.overworld_wild_battle_participants.length >= 1 # Notice immediately if a pokemon is already attacking so that double battles are more likely
position = playerPositionRelativeToEvent
if position[:front]
should_start = true
elsif position[:back]
@nearby_notice_timer += 1
@nearby_notice_timer += 1 if @current_state == :NOTICED_PLAYER
elsif position[:side]
@nearby_notice_timer += 2
should_start = true if @current_state == :NOTICED_PLAYER
end
if @nearby_notice_timer > @nearby_notice_limit
should_start = true
end
else
@nearby_notice_timer = 0
end
@nearby_notice_timer = 0 if should_start
return should_start
end
def pokemon_can_be_repelled
return false if @part_of_pokeradar_chain
return $Trainer.party[0].level > @pokemon.level && !@pokemon.shiny?
end
def turn_generic(*args)
super(*args)
end
# called when a pokemon that has noticed the player goes back to roaming
def back_to_roaming_action
case @behavior_noticed
when :skittish, :shy
turn_toward_player
end
end
def update_state(new_state)
@current_state = new_state
update_movement_type
set_sprite_to_current_state
end
def update_movement_type
case @current_state
when :ROAMING
set_roaming_movement
when :NOTICED_PLAYER
set_noticed_movement
end
set_sprite_to_current_state
end
def check_detect_trainer
return unless noticed_state_different_from_roaming()
return if $game_system.map_interpreter.running? || @starting
# return pbEventCanReachPlayer?(self, $game_player, @detection_radius)
return pbPlayerInEventCone?(self, $game_player, @detection_radius)
end
def pbCheckEventTriggerAfterTurning
return if $game_system.map_interpreter.running? || @starting
if @event.name[/trainer\((\d+)\)/i]
distance = $~[1].to_i
if @trigger == 2 && pbEventCanReachPlayer?(self, $game_player, distance)
start if !jumping? && !over_trigger?
end
end
end
# The rarer the Pokemon, the more skittish it is (larger sight radius)
def calculate_ow_pokemon_sight_radius(species_data)
#--- Fog level ---
fog_level = 0
fog_level = @weather_level_at_spawn if @weather_level_at_spawn == :Fog
#--- Base radius limits (no fog) ---
min_base_radius = 2
max_base_radius = 6
#--- Base radius from Speed (1255) ---
speed = species_data.base_stats[:SPEED]
base_radius = min_base_radius + ((speed - 1) / 254.0) * (max_base_radius - min_base_radius)
#--- Fog effect (quadratic: light fog mild, heavy fog strong) ---
fog_factor = (fog_level / 10.0) ** 2
radius = base_radius * (1.0 - fog_factor)
#--- Absolute minimum visibility ---
return [radius.round, 1].max
end
def calculate_value_from_stat(species_data, stat, min_value, max_value)
stat_value = species_data.base_stats[stat]
average_stat = 70.0
half_range = (max_value - min_value) / 2.0
# Center on average stat and scale up/down
normalized = (stat_value - average_stat) / average_stat
scaled = (min_value + half_range) + normalized * half_range
return scaled.clamp(min_value, max_value).round
end
# Generic version of calculate_value_from_stat. You provide the value.
# There's an optional curve_factor if it shouldn't be linear
# > 1 : steeper towards the end
# < 1 steeper towards the beginning
def calculate_value_from_ref_value(ref_value, min_value, max_value, curve_factor = 1)
ref_value = [ref_value, 1].max
# Logarithmic scaling 0 → 1
scaled_value = Math.log(ref_value) / curve_factor
normalized = scaled_value / (1 + scaled_value)
# Scale to range
value = min_value + normalized * (max_value - min_value)
value.round
end
def set_sprite_to_current_state
case @current_state
when :NOTICED_PLAYER, :FLEEING
set_sprite(@noticed_sprite) if @noticed_sprite
when :ROAMING
playAnimation(TELEPORT_ANIMATION_ID, @x, @y) if @disguised
set_sprite(@roaming_sprite) if @roaming_sprite
end
end
def set_roaming_sprite
set_sprite(@roaming_sprite) if @roaming_sprite
end
def set_noticed_sprite
set_sprite(@noticed_sprite) if @noticed_sprite
end
def set_sprite(sprite_path)
@character_name = sprite_path
@need_refresh = true
end
# Static
def set_noticed_movement
if isRepelActive
@move_type = MOVE_TYPE_AWAY_PLAYER
self.move_frequency = 6
return
end
effective_behavior = @behavior_noticed || @behavior_roaming # fallback on @behavior_roaming if no @behavior_noticed
case effective_behavior
when :random
@move_type = MOVE_TYPE_RANDOM
when :still
@move_type = MOVE_TYPE_FIXED
when :curious
@move_type = MOVE_TYPE_CURIOUS
when :semi_aggressive
@move_type = MOVE_TYPE_TOWARDS_PLAYER
when :aggressive
@move_type = MOVE_TYPE_TOWARDS_PLAYER
self.move_frequency = 6
when :skittish
@move_type = MOVE_TYPE_AWAY_PLAYER
self.move_frequency = 6
when :flee, :flee_flying, :teleport_away
flee(effective_behavior)
else
category = @behavior_noticed ? :noticed : :roaming
set_custom_move_route(OW_BEHAVIOR_MOVE_ROUTES[category][effective_behavior])
end
@step_anime = true unless effective_behavior == :still
@move_speed = @noticed_move_speed
end
def set_roaming_movement
if isRepelActive
@move_type = MOVE_TYPE_AWAY_PLAYER
self.move_frequency = 3
return
end
case @behavior_roaming
when :random
@move_type = MOVE_TYPE_RANDOM
when :still
@move_type = MOVE_TYPE_FIXED
else
set_custom_move_route(OW_BEHAVIOR_MOVE_ROUTES[:roaming][@behavior_roaming])
end
self.move_frequency = 3
check_weather_roaming_behavior
@move_speed = @roaming_move_speed
@step_anime = false unless @is_flying || @is_swimming
end
def check_weather_roaming_behavior
if @weather_type_at_spawn == :Wind || @weather_type_at_spawn == :Storm
wind_behavior = POKEMON_BEHAVIOR_DATA[@species][:behavior_wind_roaming]
if wind_behavior
set_custom_move_route(OW_BEHAVIOR_MOVE_ROUTES[:roaming][wind_behavior])
end
elsif @weather_type_at_spawn == :Rain || @weather_type_at_spawn == :Storm
rain_behavior = POKEMON_BEHAVIOR_DATA[@species][:behavior_rain_roaming]
if rain_behavior
set_custom_move_route(OW_BEHAVIOR_MOVE_ROUTES[:roaming][rain_behavior])
end
elsif @weather_type_at_spawn == :Sunny
sun_behavior = POKEMON_BEHAVIOR_DATA[@species][:behavior_sunny_roaming]
if sun_behavior
set_custom_move_route(OW_BEHAVIOR_MOVE_ROUTES[:roaming][sun_behavior])
end
end
end
def set_custom_move_route(move_list, repeating = true)
@move_type = MOVE_TYPE_CUSTOM
@move_route = RPG::MoveRoute.new
@move_route.repeat = repeating
@move_route.skippable = true
@move_route.list = move_list
end
def despawn
erase
$game_map.events.delete(@id)
$PokemonTemp.overworld_pokemon_on_map&.delete(@id)
$PokemonTemp.tempEvents&.each { |_, events| events.delete(self) }
$PokemonTemp.tempEvents&.delete_if { |_, v| v.empty? }
end
# Additional move types for OW pokemon
def update_command_new
super
ready_for_next_movement = @stop_count >= self.move_frequency_real
case @move_type
when MOVE_TYPE_CURIOUS
move_type_curious(ready_for_next_movement)
end
end
def pause_movement
@move_type = MOVE_TYPE_FIXED
@current_state = :PAUSED
end
def update
super
if $game_temp.message_window_showing
pause_movement unless @current_state == :PAUSED
else
@behavior_update_counter = (@behavior_update_counter || 0) + 1
if @behavior_update_counter >= UPDATE_TIME
@behavior_update_counter = 0
update_behavior
end
end
end
end