Files
odinsea-elixir/lib/odinsea/game/events/ox_quiz_questions.ex
2026-02-14 23:12:33 -07:00

284 lines
12 KiB
Elixir

defmodule Odinsea.Game.Events.OxQuizQuestions do
@moduledoc """
OX Quiz Question Database.
Ported from Java `server.events.MapleOxQuizFactory`.
Stores true/false questions loaded from database or fallback data.
Questions are organized into sets and IDs for efficient lookup.
## Question Format
- question: The question text
- display: How to display the answer (O/X)
- answer: :o for true, :x for false
- question_set: Category/set number
- question_id: ID within the set
"""
require Logger
# ============================================================================
# Types
# ============================================================================
@typedoc "OX Quiz question struct"
@type question :: %{
question: String.t(),
display: String.t(),
answer: :o | :x,
ids: {non_neg_integer(), non_neg_integer()} # {question_set, question_id}
}
# ============================================================================
# GenServer State
# ============================================================================
use GenServer
defstruct [
:questions, # Map of {{set, id} => question}
:ets_table
]
# ============================================================================
# Client API
# ============================================================================
@doc """
Starts the OX Quiz question cache.
"""
def start_link(_opts) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@doc """
Gets a random question from the database.
"""
def get_random_question do
GenServer.call(__MODULE__, :get_random_question)
end
@doc """
Gets a specific question by set and ID.
"""
def get_question(question_set, question_id) do
GenServer.call(__MODULE__, {:get_question, question_set, question_id})
end
@doc """
Gets all questions.
"""
def get_all_questions do
GenServer.call(__MODULE__, :get_all_questions)
end
@doc """
Gets the total number of questions.
"""
def question_count do
GenServer.call(__MODULE__, :question_count)
end
@doc """
Reloads questions from database.
"""
def reload do
GenServer.cast(__MODULE__, :reload)
end
# ============================================================================
# Server Callbacks
# ============================================================================
@impl true
def init(_) do
# Create ETS table for fast lookups
ets = :ets.new(:ox_quiz_questions, [:set, :protected, :named_table])
# Load initial questions
questions = load_questions()
# Store in ETS
Enum.each(questions, fn {key, q} ->
:ets.insert(ets, {key, q})
end)
Logger.info("OX Quiz Questions loaded: #{map_size(questions)} questions")
{:ok, %__MODULE__{questions: questions, ets_table: ets}}
end
@impl true
def handle_call(:get_random_question, _from, state) do
question = get_random_question_impl(state)
{:reply, question, state}
end
@impl true
def handle_call({:get_question, set, id}, _from, state) do
question = Map.get(state.questions, {set, id})
{:reply, question, state}
end
@impl true
def handle_call(:get_all_questions, _from, state) do
{:reply, Map.values(state.questions), state}
end
@impl true
def handle_call(:question_count, _from, state) do
{:reply, map_size(state.questions), state}
end
@impl true
def handle_cast(:reload, state) do
# Clear ETS
:ets.delete_all_objects(state.ets_table)
# Reload questions
questions = load_questions()
# Store in ETS
Enum.each(questions, fn {key, q} ->
:ets.insert(state.ets_table, {key, q})
end)
Logger.info("OX Quiz Questions reloaded: #{map_size(questions)} questions")
{:noreply, %{state | questions: questions}}
end
# ============================================================================
# Private Functions
# ============================================================================
defp get_random_question_impl(state) do
questions = Map.values(state.questions)
if length(questions) > 0 do
Enum.random(questions)
else
# Return fallback question if none loaded
fallback_question()
end
end
defp load_questions do
# Try to load from database
# In real implementation:
# - Query wz_oxdata table
# - Parse each row into question struct
# For now, use fallback questions
fallback_questions()
|> Enum.map(fn q -> {{elem(q.ids, 0), elem(q.ids, 1)}, q} end)
|> Map.new()
end
defp parse_answer("o"), do: :o
defp parse_answer("O"), do: :o
defp parse_answer("x"), do: :x
defp parse_answer("X"), do: :x
defp parse_answer(_), do: :o # Default to true
# ============================================================================
# Fallback Questions
# ============================================================================
defp fallback_question do
%{
question: "MapleStory was first released in 2003?",
display: "O",
answer: :o,
ids: {0, 0}
}
end
defp fallback_questions do
[
# Set 1: General MapleStory Knowledge
%{question: "MapleStory was first released in 2003?", display: "O", answer: :o, ids: {1, 1}},
%{question: "The maximum level in MapleStory is 200?", display: "O", answer: :o, ids: {1, 2}},
%{question: "Henesys is the starting town for all beginners?", display: "X", answer: :x, ids: {1, 3}},
%{question: "The Pink Bean is a boss monster?", display: "O", answer: :o, ids: {1, 4}},
%{question: "Magicians use swords as their primary weapon?", display: "X", answer: :x, ids: {1, 5}},
%{question: "The EXP curve gets steeper at higher levels?", display: "O", answer: :o, ids: {1, 6}},
%{question: "Gachapon gives random items for NX?", display: "O", answer: :o, ids: {1, 7}},
%{question: "Warriors have the highest INT growth?", display: "X", answer: :x, ids: {1, 8}},
%{question: "The Cash Shop sells permanent pets?", display: "O", answer: :o, ids: {1, 9}},
%{question: "All monsters in Maple Island are passive?", display: "O", answer: :o, ids: {1, 10}},
# Set 2: Classes and Jobs
%{question: "Beginners can use the Three Snails skill?", display: "O", answer: :o, ids: {2, 1}},
%{question: "Magicians require the most DEX to advance?", display: "X", answer: :x, ids: {2, 2}},
%{question: "Thieves can use claws and daggers?", display: "O", answer: :o, ids: {2, 3}},
%{question: "Pirates are the only class that can use guns?", display: "O", answer: :o, ids: {2, 4}},
%{question: "Archers specialize in close-range combat?", display: "X", answer: :x, ids: {2, 5}},
%{question: "First job advancement happens at level 10?", display: "O", answer: :o, ids: {2, 6}},
%{question: "All classes can use magic attacks?", display: "X", answer: :x, ids: {2, 7}},
%{question: "Bowmen require arrows to attack?", display: "O", answer: :o, ids: {2, 8}},
%{question: "Warriors have the highest HP pool?", display: "O", answer: :o, ids: {2, 9}},
%{question: "Cygnus Knights are available at level 1?", display: "X", answer: :x, ids: {2, 10}},
# Set 3: Monsters and Maps
%{question: "Blue Snails are found on Maple Island?", display: "O", answer: :o, ids: {3, 1}},
%{question: "Zakum is located in the Dead Mine?", display: "O", answer: :o, ids: {3, 2}},
%{question: "Pigs drop pork items?", display: "O", answer: :o, ids: {3, 3}},
%{question: "The highest level map is Victoria Island?", display: "X", answer: :x, ids: {3, 4}},
%{question: "Balrog is a level 100 boss?", display: "O", answer: :o, ids: {3, 5}},
%{question: "Mushmom is a giant mushroom monster?", display: "O", answer: :o, ids: {3, 6}},
%{question: "All monsters respawn immediately after death?", display: "X", answer: :x, ids: {3, 7}},
%{question: "Jr. Balrog spawns in Sleepywood Dungeon?", display: "O", answer: :o, ids: {3, 8}},
%{question: "Orbis Tower connects Orbis to El Nath?", display: "O", answer: :o, ids: {3, 9}},
%{question: "Ludibrium is a town made of toys?", display: "O", answer: :o, ids: {3, 10}},
# Set 4: Items and Equipment
%{question: "Equipment can have potential stats?", display: "O", answer: :o, ids: {4, 1}},
%{question: "Mesos are the currency of MapleStory?", display: "O", answer: :o, ids: {4, 2}},
%{question: "Scrolls always succeed?", display: "X", answer: :x, ids: {4, 3}},
%{question: "Potions restore HP and MP?", display: "O", answer: :o, ids: {4, 4}},
%{question: " NX Cash is required to buy Cash Shop items?", display: "O", answer: :o, ids: {4, 5}},
%{question: "All equipment can be traded?", display: "X", answer: :x, ids: {4, 6}},
%{question: "Stars are thrown by Night Lords?", display: "O", answer: :o, ids: {4, 7}},
%{question: "Beginners can equip level 100 items?", display: "X", answer: :x, ids: {4, 8}},
%{question: "Clean Slate Scrolls remove failed slots?", display: "O", answer: :o, ids: {4, 9}},
%{question: "Chaos Scrolls randomize item stats?", display: "O", answer: :o, ids: {4, 10}},
# Set 5: Quests and NPCs
%{question: "Mai is the first quest NPC beginners meet?", display: "O", answer: :o, ids: {5, 1}},
%{question: "All quests can be repeated?", display: "X", answer: :x, ids: {5, 2}},
%{question: "NPCs with \"!\" above them give quests?", display: "O", answer: :o, ids: {5, 3}},
%{question: "Party quests require exactly 6 players?", display: "X", answer: :x, ids: {5, 4}},
%{question: "Roger sells potions in Henesys?", display: "X", answer: :x, ids: {5, 5}},
%{question: "The Lost City is another name for Kerning City?", display: "X", answer: :x, ids: {5, 6}},
%{question: "Guilds can have up to 200 members?", display: "O", answer: :o, ids: {5, 7}},
%{question: "All NPCs can be attacked?", display: "X", answer: :x, ids: {5, 8}},
%{question: "Big Headward sells hairstyles?", display: "O", answer: :o, ids: {5, 9}},
%{question: "The Storage Keeper stores items for free?", display: "X", answer: :x, ids: {5, 10}},
# Set 6: Game Mechanics
%{question: "Fame can be given or taken once per day?", display: "O", answer: :o, ids: {6, 1}},
%{question: "Party play gives bonus EXP?", display: "O", answer: :o, ids: {6, 2}},
%{question: "Dying causes EXP loss?", display: "O", answer: :o, ids: {6, 3}},
%{question: "All skills have no cooldown?", display: "X", answer: :x, ids: {6, 4}},
%{question: "Trade window allows up to 9 items?", display: "O", answer: :o, ids: {6, 5}},
%{question: "Mounting a pet requires level 70?", display: "X", answer: :x, ids: {6, 6}},
%{question: "Monster Book tracks monster information?", display: "O", answer: :o, ids: {6, 7}},
%{question: "Bosses have purple health bars?", display: "O", answer: :o, ids: {6, 8}},
%{question: "Channel changing is instant?", display: "X", answer: :x, ids: {6, 9}},
%{question: "Expedition mode is for large boss fights?", display: "O", answer: :o, ids: {6, 10}},
# Set 7: Trivia
%{question: "MapleStory is developed by Nexon?", display: "O", answer: :o, ids: {7, 1}},
%{question: "The Black Mage is the main antagonist?", display: "O", answer: :o, ids: {7, 2}},
%{question: "Elvis is a monster in MapleStory?", display: "X", answer: :x, ids: {7, 3}},
%{question: "Golems are made of rock?", display: "O", answer: :o, ids: {7, 4}},
%{question: "Maple Island is shaped like a maple leaf?", display: "O", answer: :o, ids: {7, 5}},
%{question: "All classes can fly?", display: "X", answer: :x, ids: {7, 6}},
%{question: "The Moon Bunny is a boss?", display: "X", answer: :x, ids: {7, 7}},
%{question: "Scissors of Karma make items tradable?", display: "O", answer: :o, ids: {7, 8}},
%{question: "Monster Life is a farming minigame?", display: "O", answer: :o, ids: {7, 9}},
%{question: "FM stands for Free Market?", display: "O", answer: :o, ids: {7, 10}}
]
end
end