350 lines
8.9 KiB
Elixir
350 lines
8.9 KiB
Elixir
defmodule Odinsea.Game.Events.OxQuiz do
|
|
@moduledoc """
|
|
OX Quiz Event - True/False quiz game with position-based answers.
|
|
Ported from Java `server.events.MapleOxQuiz`.
|
|
|
|
## Gameplay
|
|
- 10 questions are asked
|
|
- Players stand on O (true/right side) or X (false/left side) side
|
|
- Wrong answer = eliminated (HP set to 0)
|
|
- Correct answer = gain EXP
|
|
- Last players standing win
|
|
|
|
## Maps
|
|
- Single map: 109020001
|
|
- X side: x < -234, y > -26
|
|
- O side: x > -234, y > -26
|
|
|
|
## Win Condition
|
|
- Answer correctly to survive all 10 questions
|
|
- Remaining players at end get prize
|
|
"""
|
|
|
|
alias Odinsea.Game.Event
|
|
alias Odinsea.Game.Timer.EventTimer
|
|
alias Odinsea.Game.Events.OxQuizQuestions
|
|
|
|
require Logger
|
|
|
|
# ============================================================================
|
|
# Types
|
|
# ============================================================================
|
|
|
|
@typedoc "OX Quiz event state"
|
|
@type t :: %__MODULE__{
|
|
base: Event.t(),
|
|
times_asked: non_neg_integer(),
|
|
current_question: OxQuizQuestions.question() | nil,
|
|
finished: boolean(),
|
|
question_delay: non_neg_integer(), # ms before showing question
|
|
answer_delay: non_neg_integer(), # ms before revealing answer
|
|
schedules: [reference()]
|
|
}
|
|
|
|
defstruct [
|
|
:base,
|
|
times_asked: 0,
|
|
current_question: nil,
|
|
finished: false,
|
|
question_delay: 10_000,
|
|
answer_delay: 10_000,
|
|
schedules: []
|
|
]
|
|
|
|
# ============================================================================
|
|
# Constants
|
|
# ============================================================================
|
|
|
|
@map_ids [109020001]
|
|
@max_questions 10
|
|
|
|
# Position boundaries for O (true) vs X (false)
|
|
@o_side_bounds %{x_min: -234, x_max: 9999, y_min: -26, y_max: 9999}
|
|
@x_side_bounds %{x_min: -9999, x_max: -234, y_min: -26, y_max: 9999}
|
|
|
|
# ============================================================================
|
|
# Event Implementation
|
|
# ============================================================================
|
|
|
|
@doc """
|
|
Creates a new OX Quiz event for the given channel.
|
|
"""
|
|
def new(channel_id) do
|
|
base = Event.new(:ox_quiz, channel_id, @map_ids)
|
|
|
|
%__MODULE__{
|
|
base: base,
|
|
times_asked: 0,
|
|
current_question: nil,
|
|
finished: false,
|
|
question_delay: 10_000,
|
|
answer_delay: 10_000,
|
|
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)
|
|
|
|
# Close entry portal
|
|
set_portal_state(event, "join00", false)
|
|
|
|
base = %{event.base | is_running: true, player_count: 0}
|
|
|
|
%__MODULE__{
|
|
event |
|
|
base: base,
|
|
times_asked: 0,
|
|
current_question: nil,
|
|
finished: false,
|
|
schedules: []
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Cleans up the event after it ends.
|
|
"""
|
|
def unreset(%__MODULE__{} = event) do
|
|
cancel_schedules(event)
|
|
|
|
# Open entry portal
|
|
set_portal_state(event, "join00", true)
|
|
|
|
base = %{event.base | is_running: false, player_count: 0}
|
|
|
|
%__MODULE__{
|
|
event |
|
|
base: base,
|
|
times_asked: 0,
|
|
current_question: nil,
|
|
finished: false,
|
|
schedules: []
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Called when a player finishes.
|
|
OX Quiz doesn't use this - winners determined by survival.
|
|
"""
|
|
def finished(_event, _character) do
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Called when a player loads into the event map.
|
|
Unmutes player (allows chat during quiz).
|
|
"""
|
|
def on_map_load(%__MODULE__{} = _event, character) do
|
|
# Unmute player (allow chat)
|
|
# In real implementation: Character.set_temp_mute(character, false)
|
|
Logger.debug("Player #{character.name} loaded OX Quiz map, unmuting")
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Starts the OX Quiz event gameplay.
|
|
Begins asking questions.
|
|
"""
|
|
def start_event(%__MODULE__{} = event) do
|
|
Logger.info("Starting OX Quiz event on channel #{event.base.channel_id}")
|
|
|
|
# Close entry portal
|
|
set_portal_state(event, "join00", false)
|
|
|
|
# Start asking questions
|
|
send_question(%{event | finished: false})
|
|
end
|
|
|
|
@doc """
|
|
Sends the next question to all players.
|
|
"""
|
|
def send_question(%__MODULE__{finished: true} = event), do: event
|
|
|
|
def send_question(%__MODULE__{} = event) do
|
|
# Grab random question
|
|
question = OxQuizQuestions.get_random_question()
|
|
|
|
# Schedule question display
|
|
question_ref = EventTimer.schedule(
|
|
fn -> display_question(event, question) end,
|
|
event.question_delay
|
|
)
|
|
|
|
# Schedule answer reveal
|
|
answer_ref = EventTimer.schedule(
|
|
fn -> reveal_answer(event, question) end,
|
|
event.question_delay + event.answer_delay
|
|
)
|
|
|
|
%__MODULE__{
|
|
event |
|
|
current_question: question,
|
|
schedules: [question_ref, answer_ref | event.schedules]
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Displays the question to all players.
|
|
"""
|
|
def display_question(%__MODULE__{finished: true}, _question), do: :ok
|
|
|
|
def display_question(%__MODULE__{} = event, question) do
|
|
# Check end conditions
|
|
if should_end_event?(event) do
|
|
end_event(event)
|
|
else
|
|
# Broadcast question
|
|
{question_set, question_id} = question.ids
|
|
Event.broadcast_to_event(event.base, {:ox_quiz_show, question_set, question_id, true})
|
|
Event.broadcast_to_event(event.base, {:clock, 10}) # 10 seconds to answer
|
|
|
|
Logger.debug("OX Quiz: Displaying question #{question_id} from set #{question_set}")
|
|
end
|
|
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Reveals the answer and processes results.
|
|
"""
|
|
def reveal_answer(%__MODULE__{finished: true}, _question), do: :ok
|
|
|
|
def reveal_answer(%__MODULE__{} = event, question) do
|
|
if event.finished do
|
|
:ok
|
|
else
|
|
# Broadcast answer reveal
|
|
{question_set, question_id} = question.ids
|
|
Event.broadcast_to_event(event.base, {:ox_quiz_hide, question_set, question_id})
|
|
|
|
# Process each player
|
|
# In real implementation:
|
|
# - Get all players on map
|
|
# - Check their position vs answer
|
|
# - Wrong position: set HP to 0
|
|
# - Correct position: give EXP
|
|
|
|
Logger.debug("OX Quiz: Revealing answer for question #{question_id}: #{question.answer}")
|
|
|
|
# Increment question count
|
|
event = %{event | times_asked: event.times_asked + 1}
|
|
|
|
# Continue to next question
|
|
send_question(event)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Checks if a player's position corresponds to the correct answer.
|
|
|
|
## Parameters
|
|
- answer: :o (true) or :x (false)
|
|
- x: Player X position
|
|
- y: Player Y position
|
|
|
|
## Returns
|
|
- true if position matches answer
|
|
- false if wrong position
|
|
"""
|
|
def correct_position?(:o, x, y) do
|
|
x > -234 and y > -26
|
|
end
|
|
|
|
def correct_position?(:x, x, y) do
|
|
x < -234 and y > -26
|
|
end
|
|
|
|
@doc """
|
|
Processes a player's answer based on their position.
|
|
Returns {:correct, exp} or {:wrong, 0}
|
|
"""
|
|
def check_player_answer(question_answer, player_x, player_y) do
|
|
player_answer = if player_x > -234, do: :o, else: :x
|
|
|
|
if player_answer == question_answer do
|
|
{:correct, 3000} # 3000 EXP for correct answer
|
|
else
|
|
{:wrong, 0}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the current question number.
|
|
"""
|
|
def current_question_number(%__MODULE__{times_asked: asked}), do: asked + 1
|
|
|
|
@doc """
|
|
Gets the maximum number of questions.
|
|
"""
|
|
def max_questions, do: @max_questions
|
|
|
|
@doc """
|
|
Mutes a player (after event ends).
|
|
"""
|
|
def mute_player(character) do
|
|
# In real implementation: Character.set_temp_mute(character, true)
|
|
Logger.debug("Muting player #{character.name}")
|
|
:ok
|
|
end
|
|
|
|
# ============================================================================
|
|
# Private Functions
|
|
# ============================================================================
|
|
|
|
defp should_end_event?(%__MODULE__{} = event) do
|
|
# End if 10 questions asked or only 1 player left
|
|
event.times_asked >= @max_questions or count_alive_players(event) <= 1
|
|
end
|
|
|
|
defp count_alive_players(%__MODULE__{} = _event) do
|
|
# In real implementation:
|
|
# - Get all players on map
|
|
# - Count non-GM, alive players
|
|
# For now, return placeholder
|
|
10
|
|
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("OX Quiz event ended on channel #{event.base.channel_id}")
|
|
|
|
# Mark as finished
|
|
event = %{event | finished: true}
|
|
|
|
# Broadcast end
|
|
Event.broadcast_to_event(event.base, {:server_notice, "The event has ended"})
|
|
|
|
# Process winners
|
|
# In real implementation:
|
|
# - Get all alive, non-GM players
|
|
# - Give prize to each
|
|
# - Give achievement (ID 19)
|
|
# - Mute players
|
|
# - Warp back
|
|
|
|
# Unreset event
|
|
unreset(event)
|
|
end
|
|
end
|