#===============================================================================
# Beach + Puddle Bubble System (Final Stable Version)
#===============================================================================
module BeachWaterBubbles
TOGGLE_BUBBLES_FOR_ALL_FOLLOWERS = false
# Unified sound settings
ENABLE_BUBBLE_SOUND = true
BUBBLE_SOUND = "puddle" # Audio/SE/puddle.ogg
BUBBLE_VOLUME = 50 # Default volume (0–100)
# Followers that never show bubbles
NO_BUBBLE_FOLLOWERS = [
:BEEDRILL, :VENOMOTH, :ABRA, :GEODUDE, :MAGNEMITE, :GASTLY, :HAUNTER,
:KOFFING, :WEEZING, :PORYGON, :MEW,
:MISDREAVUS, :UNOWN, :PORYGON2, :CELEBI,
:DUSTOX, :SHEDINJA, :MEDITITE, :VOLBEAT, :ILLUMISE, :FLYGON, :LUNATONE,
:SOLROCK, :BALTOY, :CLAYDOL, :CASTFORM, :SHUPPET, :DUSKULL, :CHIMECHO,
:GLALIE, :BELDUM, :METANG, :LATIAS, :LATIOS, :JIRACHI,
:MISMAGIUS, :BRONZOR, :BRONZONG, :SPIRITOMB, :CARNIVINE, :MAGNEZONE,
:PORYGONZ, :PROBOPASS, :DUSKNOIR, :FROSLASS, :ROTOM, :UXIE, :MESPRIT,
:AZELF, :GIRATINA_1, :CRESSELIA, :DARKRAI,
:MUNNA, :MUSHARNA, :YAMASK, :COFAGRIGUS, :SOLOSIS, :DUOSION, :REUNICLUS,
:VANILLITE, :VANILLISH, :VANILLUXE, :ELGYEM, :BEHEEYEM, :LAMPENT,
:CHANDELURE, :CRYOGONAL, :HYDREIGON, :VOLCARONA, :RESHIRAM, :ZEKROM,
:SPRITZEE, :DRAGALGE, :CARBINK, :KLEFKI, :PHANTUMP, :DIANCIE, :HOOPA,
:VIKAVOLT, :CUTIEFLY, :RIBOMBEE, :COMFEY, :DHELMISE, :TAPUKOKO, :TAPULELE,
:TAPUBULU, :COSMOG, :COSMOEM, :LUNALA, :NIHILEGO, :KARTANA, :NECROZMA,
:MAGEARNA, :POIPOLE, :NAGANADEL,
:ORBEETLE, :FLAPPLE, :SINISTEA, :POLTEAGEIST, :FROSMOTH, :DREEPY,
:DRAKLOAK, :DRAGAPULT, :ETERNATUS, :REGIELEKI, :REGIDRAGO, :CALYREX
]
end
#===============================================================================
# Terrain Tags
#===============================================================================
GameData::TerrainTag.register({
:id => :ShallowBeach,
:id_number => 30
})
#===============================================================================
# Sprite_WaterBubble
#===============================================================================
class Sprite_WaterBubble
FRAME_WIDTH = 64
FRAME_HEIGHT = 32
ANIMATION_TIME = 0.3
FRAMES_COUNT = 3
TERRAIN_TAG = 30
PUDDLE_TERRAIN_TAG = 16
def initialize(sprite, event, viewport = nil)
@rsprite = sprite
@event = event
@viewport = viewport
@disposed = false
@visible = true
@animation_timer = 0.0
@sprite = nil
@played_once = false
@played_sound = false
@was_moving_last_frame = false
@last_step_x = @event.x
@last_step_y = @event.y
@waterbitmap = AnimatedBitmap.new("Graphics/Plugins/Beach Water Bubbles/splash")
update
end
def dispose
return if @disposed
@sprite&.dispose
@sprite = nil
@disposed = true
end
def disposed?
@disposed
end
def stepped?
if @event.x != @last_step_x || @event.y != @last_step_y
@last_step_x = @event.x
@last_step_y = @event.y
return true
end
return false
end
def puddle_tile?
$game_map.terrain_tag(@event.x, @event.y) == PUDDLE_TERRAIN_TAG
end
def should_show_bubble?
return false if @event.character_name.empty? || @event.transparent
terrain = $game_map.terrain_tag(@event.x, @event.y)
return false if terrain != TERRAIN_TAG && terrain != PUDDLE_TERRAIN_TAG
if @event.is_a?(Game_Follower)
return false if BeachWaterBubbles::TOGGLE_BUBBLES_FOR_ALL_FOLLOWERS
pkmn = $player.able_party[0]
return false if BeachWaterBubbles::NO_BUBBLE_FOLLOWERS.include?(pkmn.species)
end
return true
end
def update
return if disposed? || !$scene.is_a?(Scene_Map)
unless should_show_bubble?
@sprite&.dispose
@sprite = nil
return
end
# Reset puddle animation when stepping onto a puddle tile
if puddle_tile? && stepped?
@played_once = false
@animation_timer = 0.0
end
create_sprite if !@sprite
# Only skip sprite updates AFTER animation is finished
unless @played_once && puddle_tile?
update_sprite_properties
end
update_animation
end
def create_sprite
@sprite = Sprite.new(@viewport)
@sprite.bitmap = @waterbitmap.bitmap
end
def update_sprite_properties
@sprite.src_rect.set(0, 0, FRAME_WIDTH, FRAME_HEIGHT)
@sprite.x = @rsprite.x
@sprite.y = @rsprite.y
@sprite.ox = FRAME_WIDTH / 2
@sprite.oy = FRAME_HEIGHT - 2
@sprite.z = @rsprite.z + 1
@sprite.zoom_x = @rsprite.zoom_x
@sprite.zoom_y = @rsprite.zoom_y
@sprite.opacity = @rsprite.opacity
pbDayNightTint(@sprite)
end
#=============================================================================
# FINAL FIXED ANIMATION + SOUND LOGIC
#=============================================================================
def update_animation
return unless @sprite
terrain = $game_map.terrain_tag(@event.x, @event.y)
stepped_now = stepped?
moving_now = @event.moving?
#---------------------------------------------------------------------------
# LOOPING MODE (Terrain Tag 30)
#---------------------------------------------------------------------------
if terrain == TERRAIN_TAG
@played_once = false
@played_sound = false if stepped_now
if BeachWaterBubbles::ENABLE_BUBBLE_SOUND && stepped_now
pbSEPlay(BeachWaterBubbles::BUBBLE_SOUND, BeachWaterBubbles::BUBBLE_VOLUME)
end
delta = Graphics.delta
@animation_timer += delta
@animation_timer = 0.0 if @animation_timer >= ANIMATION_TIME
frame = ((@animation_timer / ANIMATION_TIME) * FRAMES_COUNT).to_i % FRAMES_COUNT
@sprite.src_rect.x = frame * FRAME_WIDTH
@sprite.visible = true
@was_moving_last_frame = moving_now
return
end
#---------------------------------------------------------------------------
# ONE-SHOT MODE (Puddle Terrain Tag 16)
#---------------------------------------------------------------------------
if terrain == PUDDLE_TERRAIN_TAG
# SOUND FIX: play once per movement start OR tile step
if BeachWaterBubbles::ENABLE_BUBBLE_SOUND &&
(stepped_now || (moving_now && !@was_moving_last_frame))
pbSEPlay(BeachWaterBubbles::BUBBLE_SOUND, BeachWaterBubbles::BUBBLE_VOLUME)
end
@was_moving_last_frame = moving_now
# ANIMATION: play once, then disappear
if @played_once
@sprite.visible = false
return
end
delta = Graphics.delta
@animation_timer += delta
frame = ((@animation_timer / ANIMATION_TIME) * FRAMES_COUNT).to_i
frame = FRAMES_COUNT - 1 if frame >= FRAMES_COUNT
@sprite.src_rect.x = frame * FRAME_WIDTH
if frame == FRAMES_COUNT - 1
@played_once = true
@sprite.visible = false
else
@sprite.visible = true
end
return
end
@sprite.visible = false
end
end
#===============================================================================
# Character Sprite Hook
#===============================================================================
class Sprite_Character < RPG::Sprite
alias init_bubbles initialize
def initialize(viewport, character = nil)
init_bubbles(viewport, character)
@waterbubble = Sprite_WaterBubble.new(self, character, viewport)
end
alias dispose_bubbles dispose
def dispose
@waterbubble&.dispose
dispose_bubbles
end
alias update_bubbles update
def update
update_bubbles
@waterbubble&.update
end
end