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