Files
odinsea-elixir/lib/odinsea/channel/handler/player_shop.ex
2026-02-14 23:12:33 -07:00

974 lines
29 KiB
Elixir

defmodule Odinsea.Channel.Handler.PlayerShop do
@moduledoc """
Handles player shop and hired merchant packets.
Ported from:
- src/handling/channel/handler/PlayerInteractionHandler.java
- src/handling/channel/handler/HiredMerchantHandler.java
Handles:
- Creating player shops and mini games
- Visiting shops
- Buying/selling items
- Managing visitors
- Mini game operations (Omok, Match Card)
- Hired merchant operations
"""
require Logger
alias Odinsea.Net.Packet.{In, Out}
alias Odinsea.Net.Opcodes
alias Odinsea.Game.{PlayerShop, HiredMerchant, MiniGame, ShopItem, Item, Equip}
# Interaction action constants (from PlayerInteractionHandler.Interaction enum)
# GMS v342 values
@action_create 0x06
@action_invite_trade 0x11
@action_deny_trade 0x12
@action_visit 0x09
@action_chat 0x14
@action_exit 0x18
@action_open 0x16
@action_set_items 0x00
@action_set_meso 0x01
@action_confirm_trade 0x02
@action_player_shop_add_item 0x28
@action_buy_item_player_shop 0x22
@action_add_item 0x23
@action_buy_item_store 0x24
@action_buy_item_hired_merchant 0x26
@action_remove_item 0x28
@action_maintenance_off 0x29
@action_maintenance_organise 0x30
@action_close_merchant 0x31
@action_admin_store_namechange 0x35
@action_view_merchant_visitor 0x36
@action_view_merchant_blacklist 0x37
@action_merchant_blacklist_add 0x38
@action_merchant_blacklist_remove 0x39
@action_request_tie 0x51
@action_answer_tie 0x52
@action_give_up 0x53
@action_request_redo 0x55
@action_answer_redo 0x56
@action_exit_after_game 0x57
@action_cancel_exit 0x58
@action_ready 0x59
@action_un_ready 0x60
@action_expel 0x61
@action_start 0x62
@action_skip 0x64
@action_move_omok 0x65
@action_select_card 0x68
# Create type constants
@create_type_trade 3
@create_type_omok 1
@create_type_match_card 2
@create_type_player_shop 4
@create_type_hired_merchant 5
@doc """
Main handler for player interaction packets.
"""
def handle_interaction(packet, client_state) do
{action, packet} = In.decode_byte(packet)
case action do
@action_create -> handle_create(packet, client_state)
@action_visit -> handle_visit(packet, client_state)
@action_chat -> handle_chat(packet, client_state)
@action_exit -> handle_exit(packet, client_state)
@action_open -> handle_open(packet, client_state)
@action_player_shop_add_item -> handle_add_item(packet, client_state)
@action_add_item -> handle_add_item(packet, client_state)
@action_buy_item_player_shop -> handle_buy_item(packet, client_state)
@action_buy_item_store -> handle_buy_item(packet, client_state)
@action_buy_item_hired_merchant -> handle_buy_item(packet, client_state)
@action_remove_item -> handle_remove_item(packet, client_state)
@action_maintenance_off -> handle_maintenance_off(packet, client_state)
@action_maintenance_organise -> handle_maintenance_organise(packet, client_state)
@action_close_merchant -> handle_close_merchant(packet, client_state)
@action_view_merchant_visitor -> handle_view_visitors(packet, client_state)
@action_view_merchant_blacklist -> handle_view_blacklist(packet, client_state)
@action_merchant_blacklist_add -> handle_blacklist_add(packet, client_state)
@action_merchant_blacklist_remove -> handle_blacklist_remove(packet, client_state)
@action_ready -> handle_ready(packet, client_state)
@action_un_ready -> handle_ready(packet, client_state)
@action_start -> handle_start_game(packet, client_state)
@action_give_up -> handle_give_up(packet, client_state)
@action_request_tie -> handle_request_tie(packet, client_state)
@action_answer_tie -> handle_answer_tie(packet, client_state)
@action_skip -> handle_skip(packet, client_state)
@action_move_omok -> handle_move_omok(packet, client_state)
@action_select_card -> handle_select_card(packet, client_state)
@action_exit_after_game -> handle_exit_after_game(packet, client_state)
@action_cancel_exit -> handle_exit_after_game(packet, client_state)
_ ->
Logger.debug("Unhandled player interaction action: #{action}")
send_enable_actions(client_state)
{:ok, client_state}
end
end
@doc """
Handles hired merchant specific packets.
"""
def handle_hired_merchant(packet, client_state) do
{operation, packet} = In.decode_byte(packet)
case operation do
# Display Fredrick/Merchant item store
20 -> handle_display_merch(client_state)
# Open merch item store
25 -> handle_open_merch_store(client_state)
# Retrieve items
26 -> handle_retrieve_items(packet, client_state)
# Close dialog
27 ->
send_enable_actions(client_state)
{:ok, client_state}
_ ->
Logger.debug("Unhandled hired merchant operation: #{operation}")
send_enable_actions(client_state)
{:ok, client_state}
end
end
# ============================================================================
# Create Shop/Game Handlers
# ============================================================================
defp handle_create(packet, client_state) do
{create_type, packet} = In.decode_byte(packet)
{description, packet} = In.decode_string(packet)
{has_password, packet} = In.decode_byte(packet)
password =
if has_password > 0 do
{pwd, packet} = In.decode_string(packet)
pwd
else
""
end
case create_type do
@create_type_omok ->
{piece, _packet} = In.decode_byte(packet)
create_mini_game(client_state, description, password, MiniGame.game_type_omok(), piece)
@create_type_match_card ->
{piece, _packet} = In.decode_byte(packet)
create_mini_game(client_state, description, password, MiniGame.game_type_match_card(), piece)
@create_type_player_shop ->
# Skip slot and item ID validation for now
create_player_shop(client_state, description)
@create_type_hired_merchant ->
create_hired_merchant(client_state, description)
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
defp create_mini_game(client_state, description, password, game_type, piece_type) do
with {:ok, character} <- get_character(client_state) do
game_opts = %{
id: generate_id(),
owner_id: character.id,
owner_name: character.name,
description: description,
password: password,
game_type: game_type,
piece_type: piece_type,
map_id: character.map_id,
channel: client_state.channel
}
# Start the mini game GenServer
case DynamicSupervisor.start_child(
Odinsea.MiniGameSupervisor,
{MiniGame, game_opts}
) do
{:ok, _pid} ->
# Send mini game packet
packet = encode_mini_game(game_opts)
send_packet(client_state, packet)
send_enable_actions(client_state)
{:ok, %{client_state | player_shop: game_opts.id}}
{:error, reason} ->
Logger.error("Failed to create mini game: #{inspect(reason)}")
send_enable_actions(client_state)
{:ok, client_state}
end
else
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
defp create_player_shop(client_state, description) do
with {:ok, character} <- get_character(client_state) do
shop_opts = %{
id: generate_id(),
owner_id: character.id,
owner_account_id: character.account_id,
owner_name: character.name,
item_id: 5_040_000,
description: description,
map_id: character.map_id,
channel: client_state.channel
}
case DynamicSupervisor.start_child(
Odinsea.ShopSupervisor,
{PlayerShop, shop_opts}
) do
{:ok, _pid} ->
# Send player shop packet
packet = encode_player_shop(shop_opts, true)
send_packet(client_state, packet)
send_enable_actions(client_state)
{:ok, %{client_state | player_shop: shop_opts.id}}
{:error, reason} ->
Logger.error("Failed to create player shop: #{inspect(reason)}")
send_enable_actions(client_state)
{:ok, client_state}
end
else
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
defp create_hired_merchant(client_state, description) do
with {:ok, character} <- get_character(client_state) do
# Check if already has a merchant
# In full implementation, check world for existing merchant
merchant_opts = %{
id: generate_id(),
owner_id: character.id,
owner_account_id: character.account_id,
owner_name: character.name,
item_id: 5_030_000,
description: description,
map_id: character.map_id,
channel: client_state.channel
}
case DynamicSupervisor.start_child(
Odinsea.MerchantSupervisor,
{HiredMerchant, merchant_opts}
) do
{:ok, _pid} ->
# Send hired merchant packet
packet = encode_hired_merchant(merchant_opts, true)
send_packet(client_state, packet)
send_enable_actions(client_state)
{:ok, %{client_state | player_shop: merchant_opts.id}}
{:error, reason} ->
Logger.error("Failed to create hired merchant: #{inspect(reason)}")
send_enable_actions(client_state)
{:ok, client_state}
end
else
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
# ============================================================================
# Visit/Exit Handlers
# ============================================================================
defp handle_visit(packet, client_state) do
{object_id, packet} = In.decode_int(packet)
# Try to find shop by object ID
# This would need proper map object tracking
# For now, simplified version
Logger.debug("Visit shop: object_id=#{object_id}")
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_exit(_packet, client_state) do
# Close player shop or mini game
if client_state.player_shop do
# Clean up
{:ok, %{client_state | player_shop: nil}}
else
{:ok, client_state}
end
end
# ============================================================================
# Shop Management Handlers
# ============================================================================
defp handle_open(_packet, client_state) do
with {:ok, character} <- get_character(client_state),
shop_id <- client_state.player_shop,
true <- shop_id != nil do
# Try player shop first, then hired merchant
case PlayerShop.set_open(shop_id, true) do
:ok ->
PlayerShop.set_available(shop_id, true)
:ok
{:error, :not_found} ->
HiredMerchant.set_open(shop_id, true)
HiredMerchant.set_available(shop_id, true)
:ok
end
send_enable_actions(client_state)
{:ok, client_state}
else
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
defp handle_add_item(packet, client_state) do
{inv_type, packet} = In.decode_byte(packet)
{slot, packet} = In.decode_short(packet)
{bundles, packet} = In.decode_short(packet)
{per_bundle, packet} = In.decode_short(packet)
{price, _packet} = In.decode_int(packet)
with {:ok, character} <- get_character(client_state),
shop_id <- client_state.player_shop,
true <- shop_id != nil do
# Get item from inventory
# Create shop item
item = %ShopItem{
item: %Item{item_id: 400_0000, quantity: per_bundle},
bundles: bundles,
price: price
}
# Add to shop
case PlayerShop.add_item(shop_id, item) do
:ok ->
# Send item update packet
send_shop_item_update(client_state, shop_id)
_ ->
:ok
end
send_enable_actions(client_state)
{:ok, client_state}
else
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
defp handle_buy_item(packet, client_state) do
{item_slot, packet} = In.decode_byte(packet)
{quantity, _packet} = In.decode_short(packet)
with {:ok, character} <- get_character(client_state),
shop_id <- client_state.player_shop,
true <- shop_id != nil do
# Try player shop buy
case PlayerShop.buy_item(shop_id, item_slot, quantity, character.id, character.name) do
{:ok, item, price, status} ->
# Deduct meso and add item
# Send update packet
send_shop_item_update(client_state, shop_id)
if status == :close do
# Shop closed (all items sold)
send_shop_error_message(client_state, 10, 1)
end
{:error, reason} ->
Logger.debug("Buy item failed: #{reason}")
end
send_enable_actions(client_state)
{:ok, client_state}
else
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
defp handle_remove_item(packet, client_state) do
{slot, _packet} = In.decode_short(packet)
with {:ok, _character} <- get_character(client_state),
shop_id <- client_state.player_shop,
true <- shop_id != nil do
case PlayerShop.remove_item(shop_id, slot) do
{:ok, _item} ->
send_shop_item_update(client_state, shop_id)
_ ->
:ok
end
send_enable_actions(client_state)
{:ok, client_state}
else
_ ->
send_enable_actions(client_state)
{:ok, client_state}
end
end
# ============================================================================
# Hired Merchant Specific Handlers
# ============================================================================
defp handle_maintenance_off(_packet, client_state) do
with shop_id <- client_state.player_shop,
true <- shop_id != nil do
HiredMerchant.set_open(shop_id, true)
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_maintenance_organise(_packet, client_state) do
with shop_id <- client_state.player_shop,
true <- shop_id != nil do
# Clean up sold out items and give meso
# This is a simplified version
:ok
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_close_merchant(_packet, client_state) do
with shop_id <- client_state.player_shop,
true <- shop_id != nil do
HiredMerchant.close_merchant(shop_id, true, true)
# Send Fredrick message
send_drop_message(client_state, 1, "Please visit Fredrick for your items.")
end
send_enable_actions(client_state)
{:ok, %{client_state | player_shop: nil}}
end
defp handle_view_visitors(_packet, client_state) do
with shop_id <- client_state.player_shop,
true <- shop_id != nil,
visitors <- HiredMerchant.get_visitors(shop_id) do
packet = encode_visitor_view(visitors)
send_packet(client_state, packet)
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_view_blacklist(_packet, client_state) do
with shop_id <- client_state.player_shop,
true <- shop_id != nil,
blacklist <- HiredMerchant.get_blacklist(shop_id) do
packet = encode_blacklist_view(blacklist)
send_packet(client_state, packet)
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_blacklist_add(packet, client_state) do
{name, _packet} = In.decode_string(packet)
with shop_id <- client_state.player_shop,
true <- shop_id != nil do
HiredMerchant.add_to_blacklist(shop_id, name)
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_blacklist_remove(packet, client_state) do
{name, _packet} = In.decode_string(packet)
with shop_id <- client_state.player_shop,
true <- shop_id != nil do
HiredMerchant.remove_from_blacklist(shop_id, name)
end
send_enable_actions(client_state)
{:ok, client_state}
end
# ============================================================================
# Mini Game Handlers
# ============================================================================
defp handle_ready(_packet, client_state) do
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
MiniGame.set_ready(game_id, character.id)
# Send ready update
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_start_game(_packet, client_state) do
with game_id <- client_state.player_shop,
true <- game_id != nil do
case MiniGame.start_game(game_id) do
{:ok, loser} ->
# Send game start packet
send_game_start(client_state, loser)
{:error, :not_ready} ->
:ok
end
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_give_up(_packet, client_state) do
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
case MiniGame.give_up(game_id, character.id) do
{:give_up, winner} ->
# Send game result
send_game_result(client_state, 0, winner)
_ ->
:ok
end
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_request_tie(_packet, client_state) do
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
MiniGame.request_tie(game_id, character.id)
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_answer_tie(packet, client_state) do
{accept, _packet} = In.decode_byte(packet)
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
case MiniGame.answer_tie(game_id, character.id, accept > 0) do
{:tie, _} ->
send_game_result(client_state, 1, 0)
{:deny, _} ->
send_deny_tie(client_state)
_ ->
:ok
end
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_skip(_packet, client_state) do
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
MiniGame.skip_turn(game_id, character.id)
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_move_omok(packet, client_state) do
{x, packet} = In.decode_int(packet)
{y, packet} = In.decode_int(packet)
{piece_type, _packet} = In.decode_byte(packet)
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
case MiniGame.make_omok_move(game_id, character.id, x, y, piece_type) do
{:ok, _won} ->
# Broadcast move to all players
:ok
{:win, winner} ->
send_game_result(client_state, 2, winner)
{:error, reason} ->
Logger.debug("Omok move failed: #{reason}")
end
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_select_card(packet, client_state) do
{slot, _packet} = In.decode_byte(packet)
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
case MiniGame.select_card(game_id, character.id, slot) do
{:first_card, _} ->
:ok
{:match, _} ->
:ok
{:no_match, _} ->
:ok
{:game_win, winner} ->
send_game_result(client_state, 2, winner)
_ ->
:ok
end
end
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_exit_after_game(_packet, client_state) do
with game_id <- client_state.player_shop,
true <- game_id != nil,
{:ok, character} <- get_character(client_state) do
MiniGame.set_exit_after(game_id, character.id)
end
send_enable_actions(client_state)
{:ok, client_state}
end
# ============================================================================
# Chat Handler
# ============================================================================
defp handle_chat(packet, client_state) do
{_tick, packet} = In.decode_int(packet)
{message, _packet} = In.decode_string(packet)
with {:ok, character} <- get_character(client_state),
shop_id <- client_state.player_shop,
true <- shop_id != nil do
# Broadcast to all visitors
packet = encode_shop_chat(character.name, message)
PlayerShop.broadcast_to_visitors(shop_id, packet, true)
end
{:ok, client_state}
end
# ============================================================================
# Fredrick/Merch Store Handlers
# ============================================================================
defp handle_display_merch(client_state) do
# Check for stored items
# For now, return empty
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_open_merch_store(client_state) do
# Open the Fredrick item store dialog
packet = encode_merch_item_store()
send_packet(client_state, packet)
send_enable_actions(client_state)
{:ok, client_state}
end
defp handle_retrieve_items(_packet, client_state) do
# Retrieve items from Fredrick
# For now, just acknowledge
send_enable_actions(client_state)
{:ok, client_state}
end
# ============================================================================
# Packet Encoders
# ============================================================================
defp encode_player_shop(shop, is_owner) do
# Player shop packet
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x05) # Shop type
|> Out.encode_byte(PlayerShop.shop_type())
|> Out.encode_int(shop.id)
|> Out.encode_string(shop.owner_name)
|> Out.encode_string(shop.description)
|> Out.encode_byte(0) # Password flag
|> Out.encode_byte(length(shop.items))
|> encode_shop_items(shop.items)
|> Out.encode_byte(if is_owner, do: 0, else: 1)
|> Out.to_data()
end
defp encode_hired_merchant(merchant, is_owner) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x05)
|> Out.encode_byte(HiredMerchant.shop_type())
|> Out.encode_int(merchant.id)
|> Out.encode_string(merchant.owner_name)
|> Out.encode_string(merchant.description)
|> Out.encode_byte(0)
|> Out.encode_int(0) # Time remaining
|> Out.encode_byte(0) # Visitor count
|> Out.encode_byte(0) # Has items
|> Out.encode_byte(if is_owner, do: 0, else: 1)
|> Out.to_data()
end
defp encode_mini_game(game) do
game_type =
case game.game_type do
1 -> MiniGame.shop_type(%{game_type: 1})
2 -> MiniGame.shop_type(%{game_type: 2})
end
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x05)
|> Out.encode_byte(game_type)
|> Out.encode_int(game.id)
|> Out.encode_string(game.owner_name)
|> Out.encode_string(game.description)
|> Out.encode_byte(if game.password != "", do: 1, else: 0)
|> Out.encode_byte(0) # Piece type
|> Out.encode_byte(1) # Is owner
|> Out.encode_byte(0) # Loser
|> Out.encode_byte(0) # Turn
|> Out.to_data()
end
defp encode_shop_items(packet, items) do
Enum.reduce(items, packet, fn item, p ->
p
|> Out.encode_short(item.bundles)
|> Out.encode_short(item.item.quantity)
|> Out.encode_int(item.price)
|> encode_item(item.item)
end)
end
defp encode_item(packet, %Item{} = item) do
packet
|> Out.encode_byte(2) # Item type
|> Out.encode_int(item.item_id)
|> Out.encode_byte(0) # Has cash ID
|> Out.encode_long(0) # Expiration
|> Out.encode_short(item.quantity)
|> Out.encode_string(item.owner)
end
defp encode_item(packet, %Equip{} = equip) do
packet
|> Out.encode_byte(1) # Equip type
|> Out.encode_int(equip.item_id)
|> Out.encode_byte(0) # Has cash ID
|> Out.encode_long(0) # Expiration
# Equipment stats would go here
|> Out.encode_bytes(<<0::size(100)-unit(8)>>)
end
defp encode_shop_chat(name, message) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x06) # Chat
|> Out.encode_byte(0) # Slot
|> Out.encode_string("#{name} : #{message}")
|> Out.to_data()
end
defp encode_shop_item_update(shop) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x07) # Update
|> encode_shop_items(shop.items)
|> Out.to_data()
end
defp encode_visitor_view(visitors) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x0A) # Visitor view
|> Out.encode_byte(length(visitors))
|> encode_visitor_list(visitors)
|> Out.to_data()
end
defp encode_visitor_list(packet, visitors) do
Enum.reduce(visitors, packet, fn name, p ->
p
|> Out.encode_string(name)
|> Out.encode_long(0) # Visit time
end)
end
defp encode_blacklist_view(blacklist) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x0B) # Blacklist view
|> Out.encode_byte(length(blacklist))
|> encode_string_list(blacklist)
|> Out.to_data()
end
defp encode_string_list(packet, strings) do
Enum.reduce(strings, packet, fn str, p ->
Out.encode_string(p, str)
end)
end
defp encode_game_start(loser) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x0C) # Start
|> Out.encode_byte(loser)
|> Out.to_data()
end
defp encode_game_result(result_type, winner) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x0D) # Result
|> Out.encode_byte(result_type) # 0 = give up, 1 = tie, 2 = win
|> Out.encode_byte(winner)
|> Out.to_data()
end
defp encode_deny_tie do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x0E) # Deny tie
|> Out.to_data()
end
defp encode_merch_item_store do
Out.new(Opcodes.lp_merch_item_store())
|> Out.encode_byte(0x24)
|> Out.to_data()
end
defp send_shop_item_update(client_state, shop_id) do
# Get shop state and send update
case PlayerShop.get_state(shop_id) do
{:error, _} ->
case HiredMerchant.get_state(shop_id) do
{:error, _} -> :ok
state -> send_packet(client_state, encode_shop_item_update(state))
end
state ->
send_packet(client_state, encode_shop_item_update(state))
end
end
defp send_game_start(client_state, loser) do
packet = encode_game_start(loser)
send_packet(client_state, packet)
end
defp send_game_result(client_state, result_type, winner) do
packet = encode_game_result(result_type, winner)
send_packet(client_state, packet)
end
defp send_deny_tie(client_state) do
packet = encode_deny_tie()
send_packet(client_state, packet)
end
defp send_shop_error_message(client_state, error, msg_type) do
Out.new(Opcodes.lp_player_interaction())
|> Out.encode_byte(0x0A) # Error
|> Out.encode_byte(error)
|> Out.encode_byte(msg_type)
|> Out.to_data()
|> then(&send_packet(client_state, &1))
end
defp send_drop_message(client_state, msg_type, message) do
Out.new(Opcodes.lp_blow_weather())
|> Out.encode_int(msg_type)
|> Out.encode_string(message)
|> Out.to_data()
|> then(&send_packet(client_state, &1))
end
# ============================================================================
# Private Helper Functions
# ============================================================================
defp get_character(client_state) do
case client_state.character_id do
nil ->
{:error, :no_character}
character_id ->
case Registry.lookup(Odinsea.CharacterRegistry, character_id) do
[{pid, _}] ->
case Odinsea.Game.Character.get_state(pid) do
{:ok, state} -> {:ok, state}
error -> error
end
[] ->
{:error, :character_not_found}
end
end
end
defp send_packet(client_state, data) do
if client_state.client_pid do
send(client_state.client_pid, {:send_packet, data})
end
end
defp send_enable_actions(client_state) do
packet = <<0x0D, 0x00, 0x00>>
send_packet(client_state, packet)
end
defp generate_id do
:erlang.unique_integer([:positive])
end
end