kimi gone wild
This commit is contained in:
332
lib/odinsea/game/events/ola_ola.ex
Normal file
332
lib/odinsea/game/events/ola_ola.ex
Normal file
@@ -0,0 +1,332 @@
|
||||
defmodule Odinsea.Game.Events.OlaOla do
|
||||
@moduledoc """
|
||||
Ola Ola Event - Portal guessing game (similar to Survival but with portals).
|
||||
Ported from Java `server.events.MapleOla`.
|
||||
|
||||
## Gameplay
|
||||
- 3 stages with random correct portals
|
||||
- Players must guess which portal leads forward
|
||||
- Wrong portals send players back or eliminate them
|
||||
- Fastest to finish wins
|
||||
|
||||
## Maps
|
||||
- Stage 1: 109030001 (5 portals: ch00-ch04)
|
||||
- Stage 2: 109030002 (8 portals: ch00-ch07)
|
||||
- Stage 3: 109030003 (16 portals: ch00-ch15)
|
||||
|
||||
## Win Condition
|
||||
- Reach the finish map by choosing correct portals
|
||||
- First to finish gets best prize, all finishers get prize
|
||||
"""
|
||||
|
||||
alias Odinsea.Game.Event
|
||||
alias Odinsea.Game.Timer.EventTimer
|
||||
alias Odinsea.Game.Character
|
||||
|
||||
require Logger
|
||||
|
||||
# ============================================================================
|
||||
# Types
|
||||
# ============================================================================
|
||||
|
||||
@typedoc "OlaOla event state"
|
||||
@type t :: %__MODULE__{
|
||||
base: Event.t(),
|
||||
stages: [non_neg_integer()], # Correct portal indices for each stage
|
||||
time_started: integer() | nil,
|
||||
event_duration: non_neg_integer(),
|
||||
schedules: [reference()]
|
||||
}
|
||||
|
||||
defstruct [
|
||||
:base,
|
||||
stages: [0, 0, 0], # Will be randomized on start
|
||||
time_started: nil,
|
||||
event_duration: 360_000, # 6 minutes
|
||||
schedules: []
|
||||
]
|
||||
|
||||
# ============================================================================
|
||||
# Constants
|
||||
# ============================================================================
|
||||
|
||||
@map_ids [109030001, 109030002, 109030003]
|
||||
@event_duration 360_000 # 6 minutes in ms
|
||||
|
||||
# Stage configurations
|
||||
@stage_config [
|
||||
%{map: 109030001, portals: 5, prefix: "ch"}, # Stage 1: 5 portals
|
||||
%{map: 109030002, portals: 8, prefix: "ch"}, # Stage 2: 8 portals
|
||||
%{map: 109030003, portals: 16, prefix: "ch"} # Stage 3: 16 portals
|
||||
]
|
||||
|
||||
# ============================================================================
|
||||
# Event Implementation
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Creates a new OlaOla event for the given channel.
|
||||
"""
|
||||
def new(channel_id) do
|
||||
base = Event.new(:ola_ola, channel_id, @map_ids)
|
||||
|
||||
%__MODULE__{
|
||||
base: base,
|
||||
stages: [0, 0, 0],
|
||||
time_started: nil,
|
||||
event_duration: @event_duration,
|
||||
schedules: []
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the map IDs for this event type.
|
||||
"""
|
||||
def map_ids, do: @map_ids
|
||||
|
||||
@doc """
|
||||
Resets the event state for a new game.
|
||||
"""
|
||||
def reset(%__MODULE__{} = event) do
|
||||
# Cancel existing schedules
|
||||
cancel_schedules(event)
|
||||
|
||||
base = %{event.base | is_running: true, player_count: 0}
|
||||
|
||||
%__MODULE__{
|
||||
event |
|
||||
base: base,
|
||||
stages: [0, 0, 0],
|
||||
time_started: nil,
|
||||
schedules: []
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cleans up the event after it ends.
|
||||
Randomizes correct portals for next game.
|
||||
"""
|
||||
def unreset(%__MODULE__{} = event) do
|
||||
cancel_schedules(event)
|
||||
|
||||
# Randomize correct portals for each stage
|
||||
stages = [
|
||||
random_stage_portal(0), # Stage 1: 0-4
|
||||
random_stage_portal(1), # Stage 2: 0-7
|
||||
random_stage_portal(2) # Stage 3: 0-15
|
||||
]
|
||||
|
||||
# Hack check: stage 1 portal 2 is inaccessible
|
||||
stages = if Enum.at(stages, 0) == 2 do
|
||||
List.replace_at(stages, 0, 3)
|
||||
else
|
||||
stages
|
||||
end
|
||||
|
||||
# Open entry portal
|
||||
set_portal_state(event, "join00", true)
|
||||
|
||||
base = %{event.base | is_running: false, player_count: 0}
|
||||
|
||||
%__MODULE__{
|
||||
event |
|
||||
base: base,
|
||||
stages: stages,
|
||||
time_started: nil,
|
||||
schedules: []
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Called when a player finishes (reaches end map).
|
||||
Gives prize and achievement.
|
||||
"""
|
||||
def finished(%__MODULE__{} = event, character) do
|
||||
Logger.info("Player #{character.name} finished Ola Ola event!")
|
||||
|
||||
# Give prize
|
||||
Event.give_prize(character)
|
||||
|
||||
# Give achievement (ID 21)
|
||||
Character.finish_achievement(character, 21)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Called when a player loads into an event map.
|
||||
Sends clock if timer is running.
|
||||
"""
|
||||
def on_map_load(%__MODULE__{} = event, character) do
|
||||
if is_timer_started(event) do
|
||||
time_left = get_time_left(event)
|
||||
Logger.debug("Sending Ola Ola clock to #{character.name}: #{div(time_left, 1000)}s remaining")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts the Ola Ola event gameplay.
|
||||
"""
|
||||
def start_event(%__MODULE__{} = event) do
|
||||
Logger.info("Starting Ola Ola event on channel #{event.base.channel_id}")
|
||||
|
||||
now = System.system_time(:millisecond)
|
||||
|
||||
# Close entry portal
|
||||
set_portal_state(event, "join00", false)
|
||||
|
||||
# Broadcast start
|
||||
Event.broadcast_to_event(event.base, :event_started)
|
||||
Event.broadcast_to_event(event.base, {:clock, div(@event_duration, 1000)})
|
||||
Event.broadcast_to_event(event.base, {:server_notice, "The portal has now opened. Press the up arrow key at the portal to enter."})
|
||||
Event.broadcast_to_event(event.base, {:server_notice, "Fall down once, and never get back up again! Get to the top without falling down!"})
|
||||
|
||||
# Schedule event end
|
||||
end_ref = EventTimer.schedule(
|
||||
fn -> end_event(event) end,
|
||||
@event_duration
|
||||
)
|
||||
|
||||
%__MODULE__{
|
||||
event |
|
||||
time_started: now,
|
||||
schedules: [end_ref]
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a character chose the correct portal for their current stage.
|
||||
|
||||
## Parameters
|
||||
- event: The OlaOla event state
|
||||
- portal_name: The portal name (e.g., "ch00", "ch05")
|
||||
- map_id: Current map ID
|
||||
|
||||
## Returns
|
||||
- true if correct portal
|
||||
- false if wrong portal
|
||||
"""
|
||||
def correct_portal?(%__MODULE__{stages: stages}, portal_name, map_id) do
|
||||
# Get stage index from map ID
|
||||
stage_index = get_stage_index(map_id)
|
||||
|
||||
if stage_index == nil do
|
||||
false
|
||||
else
|
||||
# Get correct portal for this stage
|
||||
correct = Enum.at(stages, stage_index)
|
||||
|
||||
# Format correct portal name
|
||||
correct_name = format_portal_name(correct)
|
||||
|
||||
portal_name == correct_name
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the correct portal name for a stage.
|
||||
"""
|
||||
def get_correct_portal(%__MODULE__{stages: stages}, stage_index) when stage_index in 0..2 do
|
||||
correct = Enum.at(stages, stage_index)
|
||||
format_portal_name(correct)
|
||||
end
|
||||
|
||||
def get_correct_portal(_, _), do: nil
|
||||
|
||||
@doc """
|
||||
Checks if the timer has started.
|
||||
"""
|
||||
def is_timer_started(%__MODULE__{time_started: nil}), do: false
|
||||
def is_timer_started(%__MODULE__{}), do: true
|
||||
|
||||
@doc """
|
||||
Gets the total event duration in milliseconds.
|
||||
"""
|
||||
def get_time(%__MODULE__{event_duration: duration}), do: duration
|
||||
|
||||
@doc """
|
||||
Gets the time remaining in milliseconds.
|
||||
"""
|
||||
def get_time_left(%__MODULE__{time_started: nil}), do: @event_duration
|
||||
def get_time_left(%__MODULE__{time_started: started, event_duration: duration}) do
|
||||
elapsed = System.system_time(:millisecond) - started
|
||||
max(0, duration - elapsed)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the current stage (0-2) for a map ID.
|
||||
"""
|
||||
def get_stage_index(map_id) do
|
||||
Enum.find_index(@map_ids, &(&1 == map_id))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the stage configuration.
|
||||
"""
|
||||
def stage_config, do: @stage_config
|
||||
|
||||
@doc """
|
||||
Handles a player attempting to use a portal.
|
||||
Returns {:ok, destination_map} for correct portal, :error for wrong portal.
|
||||
"""
|
||||
def attempt_portal(%__MODULE__{} = event, portal_name, current_map_id) do
|
||||
if correct_portal?(event, portal_name, current_map_id) do
|
||||
# Correct portal - advance to next stage
|
||||
stage = get_stage_index(current_map_id)
|
||||
|
||||
if stage < 2 do
|
||||
next_map = Enum.at(@map_ids, stage + 1)
|
||||
{:ok, next_map}
|
||||
else
|
||||
# Finished all stages
|
||||
{:finished, 109050000} # Finish map
|
||||
end
|
||||
else
|
||||
# Wrong portal - fail
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Private Functions
|
||||
# ============================================================================
|
||||
|
||||
defp random_stage_portal(stage_index) do
|
||||
portal_count = Enum.at(@stage_config, stage_index).portals
|
||||
:rand.uniform(portal_count) - 1 # 0-based
|
||||
end
|
||||
|
||||
defp format_portal_name(portal_num) do
|
||||
# Format as ch00, ch01, etc.
|
||||
if portal_num < 10 do
|
||||
"ch0#{portal_num}"
|
||||
else
|
||||
"ch#{portal_num}"
|
||||
end
|
||||
end
|
||||
|
||||
defp cancel_schedules(%__MODULE__{schedules: schedules} = event) do
|
||||
Enum.each(schedules, fn ref ->
|
||||
EventTimer.cancel(ref)
|
||||
end)
|
||||
|
||||
%{event | schedules: []}
|
||||
end
|
||||
|
||||
defp set_portal_state(%__MODULE__{}, _portal_name, _state) do
|
||||
# In real implementation, update map portal state
|
||||
:ok
|
||||
end
|
||||
|
||||
defp end_event(%__MODULE__{} = event) do
|
||||
Logger.info("Ola Ola event ended on channel #{event.base.channel_id}")
|
||||
|
||||
# Warp out all remaining players
|
||||
# In real implementation, get all players on event maps and warp them
|
||||
|
||||
# Unreset event
|
||||
unreset(event)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user