mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2026-11-03 00:06:01 +00:00
384 lines
12 KiB
Ruby
384 lines
12 KiB
Ruby
class OverworldPokemonEvent < Game_Event
|
|
alias setup_pokemon_detection setup_pokemon
|
|
RETALIATION_DELAY = 20 # frames of "stunned/reacting" before fighting back
|
|
|
|
def setup_pokemon(*args)
|
|
setup_pokemon_detection(*args)
|
|
@currently_seen_events = []
|
|
@previously_seen_events = []
|
|
@target = nil
|
|
@noticed_pokemon_behavior = nil
|
|
@attack_timer = 0
|
|
@retaliation_timer = 0
|
|
@pending_retaliation_target = nil
|
|
end
|
|
|
|
|
|
#Version that only detects when the Pokemon is actually looking at the pokemon
|
|
#
|
|
# alias turn_generic_pokemon_detection turn_generic
|
|
# def turn_generic(*args)
|
|
# turn_generic_pokemon_detection(*args)
|
|
# @currently_seen_events = listEventsInVisionCone
|
|
# @currently_seen_events.each do |event|
|
|
# event_pokemon = event[0]
|
|
# distance = event[1]
|
|
# noticed_pokemon_behaviors = POKEMON_BEHAVIOR_DATA[@species][:behavior_pokemon]
|
|
# if noticed_pokemon_behaviors && noticed_pokemon_behaviors.include?(event_pokemon.species)
|
|
# behavior = noticed_pokemon_behaviors[event_pokemon.species]
|
|
# playDetectAnimation(behavior)
|
|
# update_state(:NOTICED_POKEMON)
|
|
# set_noticed_pokemon_movement(behavior, event_pokemon)
|
|
# end
|
|
# end
|
|
# echoln @currently_seen_events
|
|
# end
|
|
BEHAVIOR_PRIORITY = {
|
|
skittish: 1,
|
|
aggressive: 2,
|
|
shy: 3,
|
|
semi_aggressive: 4,
|
|
curious: 5
|
|
}.freeze
|
|
|
|
alias turn_generic_pokemon_detection turn_generic
|
|
def turn_generic(*args)
|
|
turn_generic_pokemon_detection(*args)
|
|
return unless Settings::EXPERIMENTAL_FEATURES
|
|
@currently_seen_events = listEventsInRadius(@detection_radius)
|
|
|
|
if @current_state == :NOTICED_POKEMON
|
|
unless target_still_valid?
|
|
update_state(:ROAMING)
|
|
back_to_roaming_action
|
|
return
|
|
end
|
|
end
|
|
|
|
behavior_data = POKEMON_BEHAVIOR_DATA[@species]
|
|
noticed_pokemon_behaviors = behavior_data ? behavior_data[:behavior_pokemon] : nil
|
|
sorted_events = @currently_seen_events.sort_by { |_event, distance| distance }
|
|
|
|
if sorted_events.empty?
|
|
if @current_state == :NOTICED_POKEMON
|
|
update_state(:ROAMING)
|
|
back_to_roaming_action
|
|
@target = nil
|
|
end
|
|
return
|
|
end
|
|
|
|
@pack_size = 0
|
|
nearest_distance = nil
|
|
candidates = [] # [event, behavior] pairs, all at nearest_distance
|
|
pokeblock_candidate = nil
|
|
|
|
sorted_events.each do |event_pokemon, distance|
|
|
# Once we've passed the nearest distance that had any qualifying target, stop
|
|
break if nearest_distance && distance > nearest_distance
|
|
|
|
if event_pokemon.is_a?(PokeblockEvent)
|
|
pokeblock_candidate ||= event_pokemon
|
|
nearest_distance ||= distance
|
|
next
|
|
end
|
|
|
|
next if event_pokemon.pokemon.shiny?
|
|
|
|
if noticed_pokemon_behaviors && noticed_pokemon_behaviors.include?(event_pokemon.species)
|
|
candidates << [event_pokemon, noticed_pokemon_behaviors[event_pokemon.species]]
|
|
nearest_distance ||= distance
|
|
elsif event_pokemon.species == @pokemon.species
|
|
@pack_size += 1
|
|
elsif noticed_pokemon_behaviors && noticed_pokemon_behaviors.include?(:everything)
|
|
candidates << [event_pokemon, noticed_pokemon_behaviors[:everything]]
|
|
nearest_distance ||= distance
|
|
end
|
|
end
|
|
|
|
if pokeblock_candidate
|
|
unless @current_state == :NOTICED_POKEMON
|
|
playAnimation(HEART_ANIMATION_SHORT_ID, @x, @y)
|
|
end
|
|
update_state(:NOTICED_POKEMON)
|
|
@target = pokeblock_candidate
|
|
@noticed_pokemon_behavior = :eat_pokeblock
|
|
@move_type = MOVE_TYPE_TOWARDS_TARGET
|
|
self.move_frequency = 6
|
|
return
|
|
end
|
|
|
|
return if candidates.empty?
|
|
|
|
best_event, best_behavior = candidates.min_by { |_event, behavior| BEHAVIOR_PRIORITY[behavior] || 99 }
|
|
|
|
playDetectAnimation(best_behavior, false)
|
|
update_state(:NOTICED_POKEMON)
|
|
set_noticed_pokemon_movement(best_behavior, best_event)
|
|
end
|
|
|
|
def target_still_valid?
|
|
return false unless @target && !@target.erased
|
|
@currently_seen_events.any? { |event| event[0] == @target }
|
|
end
|
|
|
|
def set_noticed_pokemon_movement(behavior, target_event)
|
|
@noticed_pokemon_behavior = behavior if target_event
|
|
@target = target_event
|
|
case behavior
|
|
when :random
|
|
@move_type = MOVE_TYPE_RANDOM
|
|
self.move_frequency = @roaming_frequency
|
|
when :still
|
|
@move_type = MOVE_TYPE_FIXED
|
|
when :curious
|
|
@move_type = MOVE_TYPE_TOWARDS_TARGET
|
|
self.move_frequency = @roaming_frequency
|
|
when :semi_aggressive
|
|
@move_type = MOVE_TYPE_TOWARDS_TARGET
|
|
self.move_frequency = @roaming_frequency
|
|
when :aggressive
|
|
@move_type = MOVE_TYPE_TOWARDS_TARGET
|
|
self.move_frequency = 6
|
|
when :shy
|
|
@move_type = MOVE_TYPE_SHY_FROM_TARGET
|
|
self.move_frequency = @roaming_frequency
|
|
when :skittish
|
|
@move_type = MOVE_TYPE_AWAY_FROM_TARGET
|
|
self.move_frequency = 6
|
|
when :flee, :flee_flying, :teleport_away
|
|
flee(behavior)
|
|
else
|
|
category = @behavior_noticed ? :noticed : :roaming
|
|
set_custom_move_route(OW_BEHAVIOR_MOVE_ROUTES[category][behavior])
|
|
end
|
|
|
|
@step_anime = true unless behavior == :still
|
|
@move_speed = @noticed_move_speed
|
|
end
|
|
|
|
|
|
def update_attack_target
|
|
unless @current_state == :NOTICED_POKEMON &&
|
|
[:aggressive, :semi_aggressive, :eat_pokeblock].include?(@noticed_pokemon_behavior) &&
|
|
adjacent_to?(@target)
|
|
@attack_timer = 0
|
|
return
|
|
end
|
|
|
|
@attack_timer += 1
|
|
if @attack_timer >= 80
|
|
attack_pokemon_target
|
|
@attack_timer = 0
|
|
end
|
|
end
|
|
|
|
def attack_pokemon_target
|
|
hp_chunk = calculate_damage_on_target
|
|
|
|
# Attacks of pokemon's type 1, except if it's type 1. Then type 2 (if it has one) - so that normal/flying uses flying moves.
|
|
attack_type = @pokemon.type1 == :NORMAL && @pokemon.type2 ? @pokemon.type2 : @pokemon.type1
|
|
effectiveness = Effectiveness.calculate(attack_type, @target.pokemon.type1, @target.pokemon.type2) / 8.0
|
|
|
|
hp_chunk = (hp_chunk * effectiveness).floor
|
|
#echoln "#{attack_type} on #{@target.pokemon.type1}, #{@target.pokemon.type2} : x#{effectiveness}"
|
|
|
|
@target.pokemon.hp -= hp_chunk
|
|
flash_white(@target)
|
|
unless @target.is_a?(PokeblockEvent)
|
|
@target.knock_back(self)
|
|
@target.queue_retaliation_against(self) if @target.pokemon.hp > 0
|
|
end
|
|
|
|
if @target.pokemon.hp <= 0
|
|
@target.despawn
|
|
@target = nil
|
|
update_state(:ROAMING)
|
|
end
|
|
end
|
|
|
|
def queue_retaliation_against(attacker)
|
|
return unless attacker && !attacker.erased
|
|
@pending_retaliation_target = attacker
|
|
@retaliation_timer = RETALIATION_DELAY
|
|
end
|
|
|
|
def update_retaliation
|
|
return unless @pending_retaliation_target
|
|
unless @pending_retaliation_target.erased
|
|
@retaliation_timer -= 1
|
|
if @retaliation_timer <= 0
|
|
target = @pending_retaliation_target
|
|
@pending_retaliation_target = nil
|
|
update_state(:NOTICED_POKEMON)
|
|
set_noticed_pokemon_movement(:aggressive, target)
|
|
end
|
|
else
|
|
@pending_retaliation_target = nil
|
|
end
|
|
end
|
|
|
|
|
|
#Equivalent of a neutral base 20 damage attack
|
|
# Simplified version of PokeBattle_Move.pbCalcDamage
|
|
def calculate_damage_on_target()
|
|
baseDmg = 20
|
|
atk = @pokemon.attack
|
|
defense = @target.pokemon.defense
|
|
damage = (((2.0 * @pokemon.level / 5 + 2).floor * baseDmg * atk / defense).floor / 50).floor + 2
|
|
return damage
|
|
end
|
|
def flash_white(target, duration = 20, alpha = 200)
|
|
return unless target && $scene && $scene.respond_to?(:spriteset)
|
|
spriteset = $scene.spriteset
|
|
return unless spriteset
|
|
|
|
sprite = spriteset.character_sprites.find { |s| s.character == target }
|
|
sprite&.flash(Color.new(255, 255, 255, alpha), duration)
|
|
end
|
|
|
|
def knock_back(event_knocking_back)
|
|
turnEventTowardsEvent(self,event_knocking_back)
|
|
original_direction_fix = @direction_fix
|
|
|
|
@direction_fix = true
|
|
self.jumpBackward
|
|
#self.move_backward
|
|
@direction_fix = original_direction_fix
|
|
end
|
|
# Orthogonal adjacency check (no diagonals), same rule used when
|
|
# stopping movement toward a target in moveEventTowardsEvent.
|
|
def adjacent_to?(event)
|
|
return false unless event && !event.erased
|
|
dx = event.x - self.x
|
|
dy = event.y - self.y
|
|
(dx == 0 && dy.abs == 1) || (dy == 0 && dx.abs == 1)
|
|
end
|
|
|
|
# def update
|
|
# super
|
|
# if $game_temp.message_window_showing
|
|
# pause_movement unless @current_state == :PAUSED
|
|
# else
|
|
# update_attack_target
|
|
# update_retaliation
|
|
# @behavior_update_counter = (@behavior_update_counter || 0) + 1
|
|
# if @behavior_update_counter >= UPDATE_TIME
|
|
# @behavior_update_counter = 0
|
|
# update_behavior
|
|
# end
|
|
# end
|
|
# end
|
|
|
|
#-----------------------------------------------------------------------
|
|
# Returns the forward distance (along the facing axis) to (px, py) if it
|
|
# falls inside the vision cone, or nil if it doesn't. Returning the
|
|
# distance instead of a bool lets callers build a hash without
|
|
# recomputing dy/dx a second time.
|
|
#-----------------------------------------------------------------------
|
|
def pbConeDistanceTo(px, py, distance, spread = 1)
|
|
return nil if distance <= 0
|
|
ex = self.x
|
|
ey = self.y
|
|
case self.direction
|
|
when 2 # Facing Down
|
|
dy = py - ey
|
|
return nil if dy <= 0 || dy > distance
|
|
side = (px - ex).abs
|
|
return (side <= dy * spread) ? dy : nil
|
|
when 8 # Facing Up
|
|
dy = ey - py
|
|
return nil if dy <= 0 || dy > distance
|
|
side = (px - ex).abs
|
|
return (side <= dy * spread) ? dy : nil
|
|
when 6 # Facing Right
|
|
dx = px - ex
|
|
return nil if dx <= 0 || dx > distance
|
|
side = (py - ey).abs
|
|
return (side <= dx * spread) ? dx : nil
|
|
when 4 # Facing Left
|
|
dx = ex - px
|
|
return nil if dx <= 0 || dx > distance
|
|
side = (py - ey).abs
|
|
return (side <= dx * spread) ? dx : nil
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
# Boolean wrapper, kept so existing call sites don't need to change.
|
|
def pbPointInVisionCone?(px, py, distance, spread = 1)
|
|
!pbConeDistanceTo(px, py, distance, spread).nil?
|
|
end
|
|
|
|
def pbPlayerInEventCone?(player, distance, spread = 1)
|
|
return false if !player
|
|
pbPointInVisionCone?(player.x, player.y, distance, spread)
|
|
end
|
|
|
|
#-----------------------------------------------------------------------
|
|
# { event => distance }
|
|
#-----------------------------------------------------------------------
|
|
def listEventsInVisionCone(distance = @detection_radius, spread = 1)
|
|
seen = {}
|
|
return seen if !$game_map
|
|
|
|
#TODO: Maybe. Use this for player also and refactor player detection in base OverworldEvent
|
|
# If not too costly to run this every frame like we do for the player detection...
|
|
#
|
|
# player_distance = pbConeDistanceTo($game_player.x, $game_player.y, distance, spread)
|
|
# seen[$game_player] = player_distance if player_distance
|
|
|
|
$game_map.events.each_value do |event|
|
|
next unless event.is_a?(OverworldPokemonEvent)
|
|
next if event == self
|
|
next if event.erased
|
|
d = pbConeDistanceTo(event.x, event.y, distance, spread)
|
|
seen[event] = d if d
|
|
end
|
|
|
|
return seen
|
|
end
|
|
|
|
#-----------------------------------------------------------------------
|
|
# { event => distance }
|
|
#-----------------------------------------------------------------------
|
|
def listEventsInRadius(radius = @detection_radius)
|
|
seen = {}
|
|
return seen if !$game_map
|
|
|
|
$game_map.events.each_value do |event|
|
|
next unless event.is_a?(OverworldPokemonEvent) || event.is_a?(PokeblockEvent)
|
|
next if event == self
|
|
next if event.erased
|
|
d = pbDistanceTo(event.x, event.y, radius)
|
|
seen[event] = d if d
|
|
end
|
|
|
|
return seen
|
|
end
|
|
|
|
def pbDistanceTo(px, py, radius)
|
|
return nil if radius <= 0
|
|
dx = (px - self.x).abs
|
|
dy = (py - self.y).abs
|
|
dist = dx + dy
|
|
return (dist <= radius) ? dist : nil
|
|
end
|
|
|
|
def pbPointInRadius?(px, py, radius)
|
|
!pbDistanceTo(px, py, radius).nil?
|
|
end
|
|
|
|
def pbPlayerInEventRadius?(player, radius)
|
|
return false if !player
|
|
pbPointInRadius?(player.x, player.y, radius)
|
|
end
|
|
|
|
|
|
def check_detect_trainer
|
|
#return unless noticed_state_different_from_roaming()
|
|
return if $game_system.map_interpreter.running? || @starting
|
|
return pbPlayerInEventCone?($game_player, @detection_radius)
|
|
end
|
|
end |