class Funfriend::BuddyContext < Funfriend::WindowContext CHATTER_TIMER = 3.0 getter buddy : Buddy getter renderer : BuddyRenderer property chatter_timer : Float64 = 1.0 property chatter_index : Int32 = 0 property chatter_array : Array(String)? property held : Bool = false property held_at : Vec2 = Vec2.zero property started_holding_at : Vec2 = Vec2.zero STAY_STILL_AFTER_HELD = 1.0 property held_timer : Float64 = 0.0 property waiting_for_stable_pos : Bool = false property static_pos : Vec2 property easing_from : Vec2 = Vec2.zero property easing_to : Vec2 = Vec2.zero property easing_dur : Float64 = 0.0 property easing_t : Float64 = 0.0 WANDER_TIMER = 4.0 property wander_timer : Float64 = WANDER_TIMER def initialize(@buddy : Buddy) super( title: "??_#{buddy.name}_??", width: window_size.x_i, height: window_size.y_i, transparent: true ) # just for initialization, let OpenGL know this is the current context window.make_context_current @renderer = BuddyRenderer.new(buddy) window.on_mouse_button do |event| if event.mouse_button.one? if event.action.press? buddy.talk_sound @easing_dur = 0.0 @held = true @held_at = Vec2.new({ x: window.cursor.position[:x].to_i, y: window.cursor.position[:y].to_i, }) if @held_timer <= 0 @started_holding_at = Vec2.new(window.position) LOG.info { "starting holding at #{started_holding_at}" } end @held_timer = STAY_STILL_AFTER_HELD window.cursor Window::Cursor::Shape::Hand elsif event.action.release? @held = false window.cursor Window::Cursor::Shape::Arrow end end end window.on_key do |event| if event.action.press? && event.key.escape? event.window.should_close end end # pick a random pos monitor = Monitor.primary random_pos = { x: monitor.position[:x] + (monitor.video_mode.size[:width] * rand((0.0..1.0))).to_i, y: monitor.position[:y] + (monitor.video_mode.size[:height] * rand((0.0..1.0))).to_i } window.position = random_pos @static_pos = Vec2.new(random_pos) @chatter_array = buddy.dialog(DialogType::Chatter).sample end def window_size funfriend_size = ConfigMan.config["window"]["funfriend_size"].as(Int32) Vec2.new((funfriend_size * 1.3).floor) end def render(dt : Float64) # let OpenGL draw to it window.make_context_current # draw funfriend renderer.render(dt, window_size.x_i, window_size.y_i) end def goto(pos : Vec2, dur : Float64, set_as_static : Bool = true) @easing_t = 0.0 @easing_dur = dur @easing_from = Vec2.new(window.position) @easing_to = pos if set_as_static @static_pos = @easing_to end LOG.info { "going from #{easing_from} to #{easing_to}" } end enum Behavior Wander Follow Stay end FOLLOW_DIST = 120 def behavior : Behavior speaking ? Behavior::Follow : Behavior::Wander end def moving @easing_dur != 0.0 && @easing_t <= @easing_dur end def update_wander(dt : Float64) if moving @easing_t = @easing_t + dt a = Ease.inOutSine(@easing_t / @easing_dur) window.position = (@easing_from * (1.0 - a) + @easing_to * a).xy_i @wander_timer = WANDER_TIMER else case behavior when .wander? @wander_timer = @wander_timer - dt if @wander_timer <= 0 goto(@static_pos + Vec2.rand((0 .. 40)), 4.0, set_as_static: false) end when .follow? if !moving x_dist = window.cursor.position[:x] y_dist = window.cursor.position[:y] x_target = window.position[:x].to_f y_target = window.position[:y].to_f if x_dist.abs > FOLLOW_DIST x_target = window.position[:x] + x_dist - FOLLOW_DIST * x_dist.sign end if y_dist.abs > FOLLOW_DIST y_target = window.position[:y] + y_dist - FOLLOW_DIST * y_dist.sign end goto(Vec2.new(x_target, y_target), 1.0) end end end end def update_pos(dt : Float64) if held @static_pos = Vec2.new(window.position) - held_at + Vec2.new(window.cursor.position) window.position = @static_pos.xy_i else @held_timer = @held_timer - dt if @held_timer <= 0 update_wander(dt) if @waiting_for_stable_pos @waiting_for_stable_pos = false stable_pos_dist = @static_pos.dist(@started_holding_at) LOG.info { "travelled #{stable_pos_dist}" } if !speaking if stable_pos_dist > 50 # moved quite a bit from initial point say buddy.dialog(DialogType::Moved) else # just touched say buddy.dialog(DialogType::Touched) end end end else @waiting_for_stable_pos = true end end end def say(text : String) Funfriend.contexts.each do |context| if context.is_a?(ChatterContext) && context.parent == self context.bump end end Funfriend.add_context(ChatterContext.new(text, Vec2.new( window.position[:x] + window_size.x / 2, window.position[:y] - 20 ), parent: self)) buddy.talk_sound end def say(text : Array(String)) @chatter_array = text @chatter_timer = 0.0 @chatter_index = 0 end def say(text : Array(Array(String))) if text.size > 0 say text.sample end end def speaking @chatter_array && @chatter_index < @chatter_array.not_nil!.size end def update(dt : Float64) @chatter_timer = @chatter_timer - dt if @chatter_timer <= 0.0 @chatter_timer = @chatter_timer + CHATTER_TIMER if @chatter_array.try &.[(@chatter_index)]? say(@chatter_array.not_nil![@chatter_index]) end @chatter_index = @chatter_index + 1 end update_pos(dt) render(dt) window.swap_buffers end def clean_up renderer.clean_up end end