974 lines
29 KiB
Elixir
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
|