defmodule Odinsea.Game.PetData do @moduledoc """ Pet data definitions and lookup functions. Ported from src/client/inventory/PetDataFactory.java and src/server/MapleItemInformationProvider.java (pet methods) and src/constants/GameConstants.java (closeness array) Provides: - Pet command data (probability and closeness increase for each command) - Hunger rates per pet - Closeness needed for each level - Pet flag definitions (abilities) """ require Logger # ============================================================================ # Pet Commands # ============================================================================ # Command data structure: {probability, closeness_increase} # Probability is 0-100 representing % chance of success # Default commands for pets without specific data @default_commands %{ 0 => {90, 1}, # Default command 0 1 => {90, 1}, # Default command 1 2 => {80, 2}, # Default command 2 3 => {70, 2}, # Default command 3 4 => {60, 3}, # Default command 4 5 => {50, 3} # Default command 5 } # Pet-specific command overrides # Format: pet_item_id => %{command_id => {probability, closeness_increase}} @pet_commands %{ # Brown Kitty (5000000) 5_000_000 => %{ 0 => {95, 1}, 1 => {90, 1}, 2 => {85, 2}, 3 => {80, 2}, 4 => {75, 3} }, # Black Kitty (5000001) 5_000_001 => %{ 0 => {95, 1}, 1 => {90, 1}, 2 => {85, 2}, 3 => {80, 2}, 4 => {75, 3} }, # Panda (5000002) 5_000_002 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Brown Puppy (5000003) 5_000_003 => %{ 0 => {95, 1}, 1 => {90, 1}, 2 => {85, 2}, 3 => {80, 2}, 4 => {75, 3} }, # Beagle (5000004) 5_000_004 => %{ 0 => {95, 1}, 1 => {90, 1}, 2 => {85, 2}, 3 => {80, 2}, 4 => {75, 3} }, # Pink Bunny (5000005) 5_000_005 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Husky (5000006) 5_000_006 => %{ 0 => {95, 1}, 1 => {90, 1}, 2 => {85, 2}, 3 => {80, 2}, 4 => {75, 3} }, # Dalmation (5000007) 5_000_007 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Baby Dragon (5000008 - 5000013) 5_000_008 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3}, 5 => {60, 4} }, 5_000_009 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3}, 5 => {60, 4} }, 5_000_010 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3}, 5 => {60, 4} }, 5_000_011 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3}, 5 => {60, 4} }, 5_000_012 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3}, 5 => {60, 4} }, 5_000_013 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3}, 5 => {60, 4} }, # Jr. Balrog (5000014) 5_000_014 => %{ 0 => {85, 1}, 1 => {80, 1}, 2 => {75, 2}, 3 => {70, 2}, 4 => {65, 3}, 5 => {55, 4} }, # White Tiger (5000015) 5_000_015 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Penguin (5000016) 5_000_016 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Jr. Yeti (5000017) 5_000_017 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Golden Pig (5000018) 5_000_018 => %{ 0 => {85, 1}, 1 => {80, 1}, 2 => {75, 2}, 3 => {70, 2}, 4 => {65, 3} }, # Robot (5000019) 5_000_019 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Elf (5000020) 5_000_020 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Pandas (5000021, 5000022) 5_000_021 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, 5_000_022 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Ghost (5000023) 5_000_023 => %{ 0 => {85, 1}, 1 => {80, 1}, 2 => {75, 2}, 3 => {70, 2}, 4 => {65, 3} }, # Jr. Reaper (5000024) 5_000_024 => %{ 0 => {85, 1}, 1 => {80, 1}, 2 => {75, 2}, 3 => {70, 2}, 4 => {65, 3} }, # Mini Yeti (5000025) 5_000_025 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # Kino (5000026) 5_000_026 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} }, # White Tiger (5000027) 5_000_027 => %{ 0 => {90, 1}, 1 => {85, 1}, 2 => {80, 2}, 3 => {75, 2}, 4 => {70, 3} } } # ============================================================================ # Closeness Needed Per Level # ============================================================================ # Cumulative closeness needed for each level (index 0 = level 1) @closeness_levels [ 0, # Level 1 1, # Level 2 3, # Level 3 6, # Level 4 14, # Level 5 31, # Level 6 60, # Level 7 108, # Level 8 181, # Level 9 287, # Level 10 434, # Level 11 632, # Level 12 891, # Level 13 1224, # Level 14 1642, # Level 15 2161, # Level 16 2793, # Level 17 3557, # Level 18 4467, # Level 19 5542, # Level 20 6801, # Level 21 8263, # Level 22 9950, # Level 23 11882, # Level 24 14084, # Level 25 16578, # Level 26 19391, # Level 27 22547, # Level 28 26074, # Level 29 30000 # Level 30 (Max) ] # ============================================================================ # Hunger Rates # ============================================================================ # Default hunger rate (fullness lost per tick) @default_hunger 10 # Pet-specific hunger rates @hunger_rates %{ # Event/special pets have higher hunger rates 5_000_054 => 5, # Time-limited pets 5_000_067 => 5, # Permanent pet (slower hunger) } # ============================================================================ # Pet Flags (Abilities) # ============================================================================ defmodule PetFlag do @moduledoc """ Pet ability flags (ported from MaplePet.PetFlag enum). These are bitflags that can be combined. """ # Flag values @item_pickup 0x01 @expand_pickup 0x02 @auto_pickup 0x04 @unpickable 0x08 @leftover_pickup 0x10 @hp_charge 0x20 @mp_charge 0x40 @pet_buff 0x80 @pet_draw 0x100 @pet_dialogue 0x200 def item_pickup, do: @item_pickup def expand_pickup, do: @expand_pickup def auto_pickup, do: @auto_pickup def unpickable, do: @unpickable def leftover_pickup, do: @leftover_pickup def hp_charge, do: @hp_charge def mp_charge, do: @mp_charge def pet_buff, do: @pet_buff def pet_draw, do: @pet_draw def pet_dialogue, do: @pet_dialogue # Item IDs that add each flag @item_to_flag %{ 5_190_000 => @item_pickup, 5_190_001 => @hp_charge, 5_190_002 => @expand_pickup, 5_190_003 => @auto_pickup, 5_190_004 => @leftover_pickup, 5_190_005 => @unpickable, 5_190_006 => @mp_charge, 5_190_007 => @pet_draw, 5_190_008 => @pet_dialogue, # 1000-series items also add flags 5_191_000 => @item_pickup, 5_191_001 => @hp_charge, 5_191_002 => @expand_pickup, 5_191_003 => @auto_pickup, 5_191_004 => @leftover_pickup } @doc """ Gets the flag value for an item ID. """ def get_by_item_id(item_id) do Map.get(@item_to_flag, item_id) end @doc """ Gets a human-readable name for a flag. """ def name(flag) do case flag do @item_pickup -> "pickupItem" @expand_pickup -> "longRange" @auto_pickup -> "dropSweep" @unpickable -> "ignorePickup" @leftover_pickup -> "pickupAll" @hp_charge -> "consumeHP" @mp_charge -> "consumeMP" @pet_buff -> "autoBuff" @pet_draw -> "recall" @pet_dialogue -> "autoSpeaking" _ -> "unknown" end end end # ============================================================================ # Public API # ============================================================================ @doc """ Gets the closeness needed for a specific level. Returns the cumulative closeness required to reach that level. """ def closeness_for_level(level) when level >= 1 and level <= 30 do Enum.at(@closeness_levels, level - 1, 30_000) end def closeness_for_level(level) when level > 30, do: 30_000 def closeness_for_level(_level), do: 0 @doc """ Gets pet command data (probability and closeness increase). Returns {probability, closeness_increase} or nil if command doesn't exist. """ def get_pet_command(pet_item_id, command_id) do commands = Map.get(@pet_commands, pet_item_id, @default_commands) Map.get(commands, command_id) end @doc """ Gets a random pet command for the pet. Used when player uses the "Random Pet Command" feature. Returns {command_id, {probability, closeness_increase}} or nil. """ def get_random_pet_command(pet_item_id) do commands = Map.get(@pet_commands, pet_item_id, @default_commands) if Enum.empty?(commands) do nil else {command_id, data} = Enum.random(commands) {command_id, data} end end @doc """ Gets the hunger rate for a pet (how fast fullness decreases). Lower values mean slower hunger. """ def get_hunger(pet_item_id) do Map.get(@hunger_rates, pet_item_id, @default_hunger) end @doc """ Gets the default name for a pet based on its item ID. """ def get_default_pet_name(pet_item_id) do # Map of pet item IDs to their default names names = %{ 5_000_000 => "Brown Kitty", 5_000_001 => "Black Kitty", 5_000_002 => "Panda", 5_000_003 => "Brown Puppy", 5_000_004 => "Beagle", 5_000_005 => "Pink Bunny", 5_000_006 => "Husky", 5_000_007 => "Dalmation", 5_000_008 => "Baby Dragon (Red)", 5_000_009 => "Baby Dragon (Blue)", 5_000_010 => "Baby Dragon (Green)", 5_000_011 => "Baby Dragon (Black)", 5_000_012 => "Baby Dragon (Gold)", 5_000_013 => "Baby Dragon (Purple)", 5_000_014 => "Jr. Balrog", 5_000_015 => "White Tiger", 5_000_016 => "Penguin", 5_000_017 => "Jr. Yeti", 5_000_018 => "Golden Pig", 5_000_019 => "Robo", 5_000_020 => "Fairy", 5_000_021 => "Panda (White)", 5_000_022 => "Panda (Pink)", 5_000_023 => "Ghost", 5_000_024 => "Jr. Reaper", 5_000_025 => "Mini Yeti", 5_000_026 => "Kino", 5_000_027 => "White Tiger (Striped)" } Map.get(names, pet_item_id, "Pet") end @doc """ Checks if an item ID is a pet egg (can be hatched into a pet). """ def pet_egg?(item_id) do # Pet eggs are in range 5000000-5000100 item_id >= 5_000_000 and item_id < 5_000_100 end @doc """ Checks if an item ID is pet food. """ def pet_food?(item_id) do # Pet food items are in range 2120000-2130000 item_id >= 2_120_000 and item_id < 2_130_000 end @doc """ Gets the food value (fullness restored) for a pet food item. """ def get_food_value(item_id) do # Standard pet food restores 30 fullness if pet_food?(item_id) do 30 else 0 end end @doc """ Gets pet equip slot mappings. Pets can equip special items that give them abilities. """ def pet_equip_slots do %{ 0 => :hat, 1 => :saddle, 2 => :decor } end @doc """ Checks if an item can be equipped by a pet. """ def pet_equip?(item_id) do # Pet equipment is in range 1802000-1803000 item_id >= 1_802_000 and item_id < 1_803_000 end @doc """ Gets all available pet commands for a pet. """ def list_pet_commands(pet_item_id) do Map.get(@pet_commands, pet_item_id, @default_commands) end end