kimi gone wild
This commit is contained in:
349
lib/odinsea/game/events/ox_quiz.ex
Normal file
349
lib/odinsea/game/events/ox_quiz.ex
Normal file
@@ -0,0 +1,349 @@
|
||||
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
|
||||
Reference in New Issue
Block a user