mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2026-11-03 16:25:48 +00:00
Release 6.9.0
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
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
|
||||
Reference in New Issue
Block a user