port over some more
This commit is contained in:
@@ -96,6 +96,7 @@ defmodule Odinsea.Channel.Client do
|
||||
cp_item_move = Opcodes.cp_item_move()
|
||||
cp_item_sort = Opcodes.cp_item_sort()
|
||||
cp_item_gather = Opcodes.cp_item_gather()
|
||||
cp_item_pickup = Opcodes.cp_item_pickup()
|
||||
cp_use_item = Opcodes.cp_use_item()
|
||||
cp_use_return_scroll = Opcodes.cp_use_return_scroll()
|
||||
cp_use_scroll = Opcodes.cp_use_upgrade_scroll()
|
||||
@@ -273,6 +274,12 @@ defmodule Odinsea.Channel.Client do
|
||||
_ -> state
|
||||
end
|
||||
|
||||
^cp_item_pickup ->
|
||||
case Handler.Pickup.handle_item_pickup(packet, state) do
|
||||
{:ok, new_state} -> new_state
|
||||
_ -> state
|
||||
end
|
||||
|
||||
^cp_use_item ->
|
||||
case Handler.Inventory.handle_use_item(packet, state) do
|
||||
{:ok, new_state} -> new_state
|
||||
|
||||
247
lib/odinsea/channel/handler/pickup.ex
Normal file
247
lib/odinsea/channel/handler/pickup.ex
Normal file
@@ -0,0 +1,247 @@
|
||||
defmodule Odinsea.Channel.Handler.Pickup do
|
||||
@moduledoc """
|
||||
Handles drop pickup from the map (item and meso drops).
|
||||
Ported from src/handling/channel/handler/InventoryHandler.java
|
||||
|
||||
This handler processes CP_ItemPickup (0x10C) packets when a player
|
||||
attempts to pick up a drop from the map.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Net.Packet.{In, Out}
|
||||
alias Odinsea.Net.Opcodes
|
||||
alias Odinsea.Game.{Character, Drop, Map}
|
||||
alias Odinsea.Channel.Packets
|
||||
|
||||
@doc """
|
||||
Handles item pickup from map (CP_ItemPickup).
|
||||
|
||||
Packet structure:
|
||||
- tick (4 bytes): Client tick count
|
||||
- oid (4 bytes): Object ID of the drop on the map
|
||||
|
||||
Ported from InventoryHandler.handlePickup()
|
||||
"""
|
||||
def handle_item_pickup(packet, client_state) do
|
||||
with {:ok, character_pid} <- get_character(client_state),
|
||||
{:ok, character} <- Character.get_state(character_pid) do
|
||||
|
||||
# Decode packet
|
||||
{_tick, packet} = In.decode_int(packet)
|
||||
{drop_oid, _packet} = In.decode_int(packet)
|
||||
|
||||
Logger.debug("Item pickup attempt: character=#{character.name}, drop_oid=#{drop_oid}")
|
||||
|
||||
# Attempt to pick up the drop
|
||||
case attempt_pickup_drop(character, character_pid, drop_oid, client_state.channel_id) do
|
||||
{:ok, :meso, amount} ->
|
||||
Logger.debug("Picked up meso: #{amount}")
|
||||
# Send pickup success response
|
||||
send_pickup_result(client_state, 0, 0)
|
||||
{:ok, client_state}
|
||||
|
||||
{:ok, :item, item} ->
|
||||
Logger.debug("Picked up item: #{item.item_id}")
|
||||
# Send pickup success response
|
||||
send_pickup_result(client_state, 0, 0)
|
||||
{:ok, client_state}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.debug("Pickup failed: #{reason}")
|
||||
# Send failure response (enable actions to unblock client)
|
||||
send_enable_actions(client_state)
|
||||
{:ok, client_state}
|
||||
end
|
||||
else
|
||||
{:error, reason} ->
|
||||
Logger.warning("Item pickup failed: #{inspect(reason)}")
|
||||
send_enable_actions(client_state)
|
||||
{:ok, client_state}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles pet item pickup request (CP_PetDropPickUpRequest).
|
||||
|
||||
Similar to player pickup but initiated by pet movement.
|
||||
"""
|
||||
def handle_pet_item_pickup(packet, client_state) do
|
||||
with {:ok, character_pid} <- get_character(client_state),
|
||||
{:ok, character} <- Character.get_state(character_pid) do
|
||||
|
||||
# Decode packet
|
||||
{_tick, packet} = In.decode_int(packet)
|
||||
{pet_slot, packet} = In.decode_byte(packet)
|
||||
{drop_oid, _packet} = In.decode_int(packet)
|
||||
|
||||
Logger.debug("Pet item pickup attempt: character=#{character.name}, pet_slot=#{pet_slot}, drop_oid=#{drop_oid}")
|
||||
|
||||
# Attempt to pick up the drop (pets have same rules but different animation)
|
||||
case attempt_pickup_drop(character, character_pid, drop_oid, client_state.channel_id, pet_slot) do
|
||||
{:ok, :meso, amount} ->
|
||||
Logger.debug("Pet picked up meso: #{amount}")
|
||||
{:ok, client_state}
|
||||
|
||||
{:ok, :item, item} ->
|
||||
Logger.debug("Pet picked up item: #{item.item_id}")
|
||||
{:ok, client_state}
|
||||
|
||||
{:error, _reason} ->
|
||||
{:ok, client_state}
|
||||
end
|
||||
else
|
||||
{:error, _reason} ->
|
||||
{:ok, client_state}
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Private Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
defp attempt_pickup_drop(character, character_pid, drop_oid, channel_id, pet_slot \\ nil) do
|
||||
# Call Map.pickup_drop to atomically attempt pickup
|
||||
case Map.pickup_drop(character.map_id, channel_id, drop_oid, character.id) do
|
||||
{:ok, drop} ->
|
||||
# Successfully claimed the drop, now process it
|
||||
process_pickup(character, character_pid, drop, channel_id, pet_slot)
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_pickup(character, character_pid, %Drop{} = drop, channel_id, pet_slot) do
|
||||
cond do
|
||||
Drop.meso?(drop) ->
|
||||
process_meso_pickup(character, character_pid, drop, channel_id, pet_slot)
|
||||
|
||||
Drop.item?(drop) ->
|
||||
process_item_pickup(character, character_pid, drop, channel_id, pet_slot)
|
||||
|
||||
true ->
|
||||
{:error, :invalid_drop}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_meso_pickup(character, character_pid, %Drop{meso: amount} = drop, channel_id, pet_slot) do
|
||||
# Add meso to character
|
||||
case Character.gain_meso(character_pid, amount, true) do
|
||||
{:ok, _new_meso} ->
|
||||
# Broadcast pickup animation to all players on map
|
||||
broadcast_pickup(character.map_id, channel_id, drop.oid, character.id, pet_slot)
|
||||
|
||||
# Show meso gain in chat (optional)
|
||||
# send_meso_gain_message(client_state, amount)
|
||||
|
||||
{:ok, :meso, amount}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warning("Failed to add meso: #{reason}")
|
||||
{:error, :gain_meso_failed}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_item_pickup(character, character_pid, %Drop{} = drop, channel_id, pet_slot) do
|
||||
# Check inventory space
|
||||
inventory_type = get_inventory_type(drop.item_id)
|
||||
|
||||
case Character.check_inventory_space(character_pid, inventory_type, drop.quantity) do
|
||||
{:ok, _slot} ->
|
||||
# Add item to inventory
|
||||
item_to_add = drop.item || create_item_from_drop(drop)
|
||||
|
||||
case Character.add_item_from_drop(character_pid, item_to_add) do
|
||||
{:ok, added_item} ->
|
||||
# Broadcast pickup animation
|
||||
broadcast_pickup(character.map_id, channel_id, drop.oid, character.id, pet_slot)
|
||||
|
||||
{:ok, :item, added_item}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warning("Failed to add item to inventory: #{reason}")
|
||||
# Item couldn't be added - drop would normally be returned to map
|
||||
# but for simplicity we just fail
|
||||
{:error, :add_item_failed}
|
||||
end
|
||||
|
||||
{:error, :inventory_full} ->
|
||||
# Send inventory full message to client
|
||||
{:error, :inventory_full}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp create_item_from_drop(%Drop{} = drop) do
|
||||
# Create a basic item struct from drop data
|
||||
%{
|
||||
item_id: drop.item_id,
|
||||
quantity: drop.quantity,
|
||||
position: 0 # Will be assigned by inventory
|
||||
}
|
||||
end
|
||||
|
||||
defp get_inventory_type(item_id) do
|
||||
# Determine inventory type from item ID
|
||||
type_prefix = div(item_id, 1_000_000)
|
||||
|
||||
case type_prefix do
|
||||
1 -> :equip
|
||||
2 -> :use
|
||||
3 -> :setup
|
||||
4 -> :etc
|
||||
5 -> :cash
|
||||
_ -> :etc
|
||||
end
|
||||
end
|
||||
|
||||
defp broadcast_pickup(map_id, channel_id, drop_oid, character_id, nil) do
|
||||
# Player pickup - animation type 2
|
||||
remove_packet = Packets.remove_drop(drop_oid, 2, character_id)
|
||||
Map.broadcast(map_id, channel_id, remove_packet)
|
||||
end
|
||||
|
||||
defp broadcast_pickup(map_id, channel_id, drop_oid, character_id, pet_slot) do
|
||||
# Pet pickup - animation type 5
|
||||
remove_packet = Packets.remove_drop(drop_oid, 5, character_id, pet_slot)
|
||||
Map.broadcast(map_id, channel_id, remove_packet)
|
||||
end
|
||||
|
||||
defp send_pickup_result(client_state, _result, _item_id) do
|
||||
# Send inventory update or status packet
|
||||
# For now, just enable actions
|
||||
send_enable_actions(client_state)
|
||||
end
|
||||
|
||||
defp send_enable_actions(client_state) do
|
||||
# Send enable actions packet to allow further client actions
|
||||
enable_packet = Packets.enable_actions()
|
||||
send_packet(client_state, enable_packet)
|
||||
end
|
||||
|
||||
defp send_packet(client_state, data) when is_pid(client_state) do
|
||||
send(client_state, {:send_packet, data})
|
||||
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 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, _}] -> {:ok, pid}
|
||||
[] -> {:error, :character_not_found}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
257
lib/odinsea/database/redis.ex
Normal file
257
lib/odinsea/database/redis.ex
Normal file
@@ -0,0 +1,257 @@
|
||||
defmodule Odinsea.Database.Redis do
|
||||
@moduledoc """
|
||||
Redis client for Odinsea.
|
||||
Provides key-value storage, pub/sub, and caching functionality.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
# ==================================================================================================
|
||||
# Connection
|
||||
# ==================================================================================================
|
||||
|
||||
defp conn do
|
||||
# Get Redis connection from application environment
|
||||
# In production, this would be a persistent connection pool
|
||||
host = Application.get_env(:odinsea, :redis_host, "localhost")
|
||||
port = Application.get_env(:odinsea, :redis_port, 6379)
|
||||
database = Application.get_env(:odinsea, :redis_database, 0)
|
||||
password = Application.get_env(:odinsea, :redis_password, nil)
|
||||
|
||||
opts = [host: host, port: port, database: database]
|
||||
opts = if password, do: Keyword.put(opts, :password, password), else: opts
|
||||
|
||||
case Redix.start_link(opts) do
|
||||
{:ok, conn} -> conn
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
# ==================================================================================================
|
||||
# Key-Value Operations
|
||||
# ==================================================================================================
|
||||
|
||||
@doc """
|
||||
Gets a value by key.
|
||||
"""
|
||||
@spec get(String.t()) :: {:ok, String.t() | nil} | {:error, term()}
|
||||
def get(key) do
|
||||
with conn when not is_nil(conn) <- conn(),
|
||||
{:ok, value} <- Redix.command(conn, ["GET", key]) do
|
||||
{:ok, value}
|
||||
else
|
||||
nil -> {:error, :no_connection}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
after
|
||||
close_conn()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets a key to a value.
|
||||
"""
|
||||
@spec set(String.t(), String.t()) :: :ok | {:error, term()}
|
||||
def set(key, value) do
|
||||
with conn when not is_nil(conn) <- conn(),
|
||||
{:ok, _} <- Redix.command(conn, ["SET", key, value]) do
|
||||
:ok
|
||||
else
|
||||
nil -> {:error, :no_connection}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
after
|
||||
close_conn()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets a key to a value with expiration (in seconds).
|
||||
"""
|
||||
@spec setex(String.t(), integer(), String.t()) :: :ok | {:error, term()}
|
||||
def setex(key, seconds, value) do
|
||||
with conn when not is_nil(conn) <- conn(),
|
||||
{:ok, _} <- Redix.command(conn, ["SETEX", key, seconds, value]) do
|
||||
:ok
|
||||
else
|
||||
nil -> {:error, :no_connection}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
after
|
||||
close_conn()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a key.
|
||||
"""
|
||||
@spec del(String.t()) :: :ok | {:error, term()}
|
||||
def del(key) do
|
||||
with conn when not is_nil(conn) <- conn(),
|
||||
{:ok, _} <- Redix.command(conn, ["DEL", key]) do
|
||||
:ok
|
||||
else
|
||||
nil -> {:error, :no_connection}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
after
|
||||
close_conn()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a key exists.
|
||||
"""
|
||||
@spec exists?(String.t()) :: boolean()
|
||||
def exists?(key) do
|
||||
with conn when not is_nil(conn) <- conn(),
|
||||
{:ok, count} <- Redix.command(conn, ["EXISTS", key]) do
|
||||
count > 0
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
after
|
||||
close_conn()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets expiration on a key (in seconds).
|
||||
"""
|
||||
@spec expire(String.t(), integer()) :: :ok | {:error, term()}
|
||||
def expire(key, seconds) do
|
||||
with conn when not is_nil(conn) <- conn(),
|
||||
{:ok, _} <- Redix.command(conn, ["EXPIRE", key, seconds]) do
|
||||
:ok
|
||||
else
|
||||
nil -> {:error, :no_connection}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
after
|
||||
close_conn()
|
||||
end
|
||||
|
||||
# ==================================================================================================
|
||||
# Pub/Sub Operations
|
||||
# ==================================================================================================
|
||||
|
||||
@doc """
|
||||
Publishes a message to a channel.
|
||||
The message is automatically JSON-encoded.
|
||||
"""
|
||||
@spec publish(String.t(), map()) :: :ok | {:error, term()}
|
||||
def publish(channel, message) do
|
||||
json_message = Jason.encode!(message)
|
||||
|
||||
with conn when not is_nil(conn) <- conn(),
|
||||
{:ok, _} <- Redix.command(conn, ["PUBLISH", channel, json_message]) do
|
||||
:ok
|
||||
else
|
||||
nil -> {:error, :no_connection}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
after
|
||||
close_conn()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Subscribes to channels and handles messages with a callback function.
|
||||
This is a blocking operation that should be run in a separate process.
|
||||
"""
|
||||
@spec subscribe([String.t()], (String.t(), map() -> any())) :: :ok | {:error, term()}
|
||||
def subscribe(channels, callback) when is_list(channels) and is_function(callback, 2) do
|
||||
# Pub/Sub in Redix requires a separate connection
|
||||
host = Application.get_env(:odinsea, :redis_host, "localhost")
|
||||
port = Application.get_env(:odinsea, :redis_port, 6379)
|
||||
|
||||
opts = [host: host, port: port]
|
||||
|
||||
case Redix.PubSub.start_link(opts) do
|
||||
{:ok, pubsub} ->
|
||||
# Subscribe to channels
|
||||
Enum.each(channels, fn channel ->
|
||||
Redix.PubSub.subscribe(pubsub, channel, self())
|
||||
end)
|
||||
|
||||
# Message loop
|
||||
message_loop(pubsub, callback)
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp message_loop(pubsub, callback) do
|
||||
receive do
|
||||
{:redix_pubsub, ^pubsub, :message, %{channel: channel, payload: payload}} ->
|
||||
# Decode JSON payload
|
||||
case Jason.decode(payload) do
|
||||
{:ok, decoded} -> callback.(channel, decoded)
|
||||
{:error, _} -> callback.(channel, %{"raw" => payload})
|
||||
end
|
||||
message_loop(pubsub, callback)
|
||||
|
||||
{:redix_pubsub, ^pubsub, :subscribed, %{channel: _channel}} ->
|
||||
message_loop(pubsub, callback)
|
||||
|
||||
_ ->
|
||||
message_loop(pubsub, callback)
|
||||
end
|
||||
end
|
||||
|
||||
# ==================================================================================================
|
||||
# Helper Functions
|
||||
# ==================================================================================================
|
||||
|
||||
defp close_conn do
|
||||
# Note: In a production setup with connection pooling,
|
||||
# this would return the connection to the pool instead of closing
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the online count for a world.
|
||||
"""
|
||||
@spec get_world_online_count(integer()) :: integer()
|
||||
def get_world_online_count(world_id) do
|
||||
case get("world:#{world_id}:online_count") do
|
||||
{:ok, nil} -> 0
|
||||
{:ok, count} -> String.to_integer(count)
|
||||
{:error, _} -> 0
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the online count for a world.
|
||||
"""
|
||||
@spec update_world_online_count(integer(), integer()) :: :ok
|
||||
def update_world_online_count(world_id, count) do
|
||||
set("world:#{world_id}:online_count", to_string(count))
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a player as online.
|
||||
"""
|
||||
@spec register_player_online(integer(), integer(), String.t()) :: :ok
|
||||
def register_player_online(character_id, world_id, channel) do
|
||||
setex("player:#{character_id}:online", 60, Jason.encode!(%{
|
||||
world_id: world_id,
|
||||
channel: channel,
|
||||
timestamp: System.system_time(:second)
|
||||
}))
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unregisters a player as online.
|
||||
"""
|
||||
@spec unregister_player_online(integer()) :: :ok
|
||||
def unregister_player_online(character_id) do
|
||||
del("player:#{character_id}:online")
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a player is online.
|
||||
"""
|
||||
@spec player_online?(integer()) :: boolean()
|
||||
def player_online?(character_id) do
|
||||
exists?("player:#{character_id}:online")
|
||||
end
|
||||
end
|
||||
25
lib/odinsea/database/schema/achievement.ex
Normal file
25
lib/odinsea/database/schema/achievement.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule Odinsea.Database.Schema.Achievement do
|
||||
@moduledoc """
|
||||
Ecto schema for the achievements table.
|
||||
Represents character achievements.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
schema "achievements" do
|
||||
field :achievementid, :integer, primary_key: true
|
||||
field :charid, :integer, primary_key: true
|
||||
field :accountid, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an achievement.
|
||||
"""
|
||||
def changeset(achievement, attrs) do
|
||||
achievement
|
||||
|> cast(attrs, [:achievementid, :charid, :accountid])
|
||||
|> validate_required([:achievementid, :charid])
|
||||
end
|
||||
end
|
||||
55
lib/odinsea/database/schema/alliance.ex
Normal file
55
lib/odinsea/database/schema/alliance.ex
Normal file
@@ -0,0 +1,55 @@
|
||||
defmodule Odinsea.Database.Schema.Alliance do
|
||||
@moduledoc """
|
||||
Ecto schema for the alliances table.
|
||||
Represents guild alliances.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "alliances" do
|
||||
field :name, :string
|
||||
field :leaderid, :integer
|
||||
field :guild1, :integer
|
||||
field :guild2, :integer
|
||||
field :guild3, :integer, default: 0
|
||||
field :guild4, :integer, default: 0
|
||||
field :guild5, :integer, default: 0
|
||||
field :rank1, :string, default: "Master"
|
||||
field :rank2, :string, default: "Jr.Master"
|
||||
field :rank3, :string, default: "Member"
|
||||
field :rank4, :string, default: "Member"
|
||||
field :rank5, :string, default: "Member"
|
||||
field :capacity, :integer, default: 2
|
||||
field :notice, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an alliance.
|
||||
"""
|
||||
def creation_changeset(alliance, attrs) do
|
||||
alliance
|
||||
|> cast(attrs, [:name, :leaderid, :guild1, :guild2, :capacity])
|
||||
|> validate_required([:name, :leaderid, :guild1, :guild2])
|
||||
|> validate_length(:name, min: 1, max: 13)
|
||||
|> unique_constraint(:name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for updating alliance guilds.
|
||||
"""
|
||||
def guilds_changeset(alliance, attrs) do
|
||||
alliance
|
||||
|> cast(attrs, [:guild1, :guild2, :guild3, :guild4, :guild5])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for updating alliance ranks.
|
||||
"""
|
||||
def ranks_changeset(alliance, attrs) do
|
||||
alliance
|
||||
|> cast(attrs, [:rank1, :rank2, :rank3, :rank4, :rank5])
|
||||
end
|
||||
end
|
||||
26
lib/odinsea/database/schema/android.ex
Normal file
26
lib/odinsea/database/schema/android.ex
Normal file
@@ -0,0 +1,26 @@
|
||||
defmodule Odinsea.Database.Schema.Android do
|
||||
@moduledoc """
|
||||
Ecto schema for the androids table.
|
||||
Represents android companion data.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:uniqueid, :id, autogenerate: true}
|
||||
|
||||
schema "androids" do
|
||||
field :name, :string, default: "Android"
|
||||
field :hair, :integer, default: 0
|
||||
field :face, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating an android.
|
||||
"""
|
||||
def changeset(android, attrs) do
|
||||
android
|
||||
|> cast(attrs, [:name, :hair, :face])
|
||||
|> validate_required([:name])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/battle_log.ex
Normal file
27
lib/odinsea/database/schema/battle_log.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.BattleLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the battlelog table.
|
||||
Represents PvP battle records between accounts.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:battlelogid, :id, autogenerate: true}
|
||||
@timestamps_opts [inserted_at: :when, updated_at: false]
|
||||
|
||||
schema "battlelog" do
|
||||
field :accid, :integer, default: 0
|
||||
field :accid_to, :integer, default: 0
|
||||
field :when, :naive_datetime
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a battle log entry.
|
||||
"""
|
||||
def changeset(battle_log, attrs) do
|
||||
battle_log
|
||||
|> cast(attrs, [:accid, :accid_to])
|
||||
|> validate_required([:accid, :accid_to])
|
||||
end
|
||||
end
|
||||
33
lib/odinsea/database/schema/bbs_reply.ex
Normal file
33
lib/odinsea/database/schema/bbs_reply.ex
Normal file
@@ -0,0 +1,33 @@
|
||||
defmodule Odinsea.Database.Schema.BbsReply do
|
||||
@moduledoc """
|
||||
Ecto schema for the bbs_replies table.
|
||||
Represents guild BBS thread replies.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:replyid, :id, autogenerate: true}
|
||||
|
||||
schema "bbs_replies" do
|
||||
field :threadid, :integer
|
||||
field :postercid, :integer
|
||||
field :timestamp, :integer
|
||||
field :content, :string, default: ""
|
||||
field :guildid, :integer, default: 0
|
||||
|
||||
belongs_to :bbs_thread, Odinsea.Database.Schema.BbsThread,
|
||||
foreign_key: :threadid,
|
||||
references: :threadid,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a BBS reply.
|
||||
"""
|
||||
def changeset(bbs_reply, attrs) do
|
||||
bbs_reply
|
||||
|> cast(attrs, [:threadid, :postercid, :timestamp, :content, :guildid])
|
||||
|> validate_required([:threadid, :postercid, :timestamp])
|
||||
end
|
||||
end
|
||||
32
lib/odinsea/database/schema/bbs_thread.ex
Normal file
32
lib/odinsea/database/schema/bbs_thread.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule Odinsea.Database.Schema.BbsThread do
|
||||
@moduledoc """
|
||||
Ecto schema for the bbs_threads table.
|
||||
Represents guild BBS threads.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:threadid, :id, autogenerate: true}
|
||||
|
||||
schema "bbs_threads" do
|
||||
field :postercid, :integer
|
||||
field :name, :string, default: ""
|
||||
field :timestamp, :integer
|
||||
field :icon, :integer
|
||||
field :startpost, :string
|
||||
field :guildid, :integer
|
||||
field :localthreadid, :integer
|
||||
|
||||
has_many :bbs_replies, Odinsea.Database.Schema.BbsReply, foreign_key: :threadid
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a BBS thread.
|
||||
"""
|
||||
def creation_changeset(bbs_thread, attrs) do
|
||||
bbs_thread
|
||||
|> cast(attrs, [:postercid, :name, :timestamp, :icon, :startpost, :guildid, :localthreadid])
|
||||
|> validate_required([:postercid, :timestamp, :guildid, :localthreadid])
|
||||
end
|
||||
end
|
||||
32
lib/odinsea/database/schema/buddy.ex
Normal file
32
lib/odinsea/database/schema/buddy.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule Odinsea.Database.Schema.Buddy do
|
||||
@moduledoc """
|
||||
Ecto schema for the buddies table.
|
||||
Represents buddy list entries for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "buddies" do
|
||||
field :characterid, :integer
|
||||
field :buddyid, :integer
|
||||
field :pending, :integer, default: 0
|
||||
field :groupname, :string, default: "ETC"
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a buddy entry.
|
||||
"""
|
||||
def changeset(buddy, attrs) do
|
||||
buddy
|
||||
|> cast(attrs, [:characterid, :buddyid, :pending, :groupname])
|
||||
|> validate_required([:characterid, :buddyid])
|
||||
end
|
||||
end
|
||||
24
lib/odinsea/database/schema/cashshop_limit_sell.ex
Normal file
24
lib/odinsea/database/schema/cashshop_limit_sell.ex
Normal file
@@ -0,0 +1,24 @@
|
||||
defmodule Odinsea.Database.Schema.CashshopLimitSell do
|
||||
@moduledoc """
|
||||
Ecto schema for the cashshop_limit_sell table.
|
||||
Represents limited sale quantities for cash shop items.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:serial, :integer, autogenerate: false}
|
||||
|
||||
schema "cashshop_limit_sell" do
|
||||
field :amount, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for cashshop limit sell.
|
||||
"""
|
||||
def changeset(cashshop_limit_sell, attrs) do
|
||||
cashshop_limit_sell
|
||||
|> cast(attrs, [:serial, :amount])
|
||||
|> validate_required([:serial])
|
||||
end
|
||||
end
|
||||
40
lib/odinsea/database/schema/cashshop_modified_item.ex
Normal file
40
lib/odinsea/database/schema/cashshop_modified_item.ex
Normal file
@@ -0,0 +1,40 @@
|
||||
defmodule Odinsea.Database.Schema.CashshopModifiedItem do
|
||||
@moduledoc """
|
||||
Ecto schema for the cashshop_modified_items table.
|
||||
Represents modified cash shop items (discounts, etc).
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:serial, :integer, autogenerate: false}
|
||||
|
||||
schema "cashshop_modified_items" do
|
||||
field :discount_price, :integer, default: -1
|
||||
field :mark, :integer, default: -1
|
||||
field :showup, :integer, default: 0
|
||||
field :itemid, :integer, default: 0
|
||||
field :priority, :integer, default: 0
|
||||
field :package, :integer, default: 0
|
||||
field :period, :integer, default: 0
|
||||
field :gender, :integer, default: 0
|
||||
field :count, :integer, default: 0
|
||||
field :meso, :integer, default: 0
|
||||
field :unk_1, :integer, default: 0
|
||||
field :unk_2, :integer, default: 0
|
||||
field :unk_3, :integer, default: 0
|
||||
field :extra_flags, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for cashshop modified items.
|
||||
"""
|
||||
def changeset(cashshop_modified_item, attrs) do
|
||||
cashshop_modified_item
|
||||
|> cast(attrs, [
|
||||
:serial, :discount_price, :mark, :showup, :itemid, :priority,
|
||||
:package, :period, :gender, :count, :meso, :unk_1, :unk_2, :unk_3, :extra_flags
|
||||
])
|
||||
|> validate_required([:serial])
|
||||
end
|
||||
end
|
||||
26
lib/odinsea/database/schema/character_slot.ex
Normal file
26
lib/odinsea/database/schema/character_slot.ex
Normal file
@@ -0,0 +1,26 @@
|
||||
defmodule Odinsea.Database.Schema.CharacterSlot do
|
||||
@moduledoc """
|
||||
Ecto schema for the character_slots table.
|
||||
Represents character slot counts per world.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "character_slots" do
|
||||
field :accid, :integer, default: 0
|
||||
field :worldid, :integer, default: 0
|
||||
field :charslots, :integer, default: 6
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating character slots.
|
||||
"""
|
||||
def changeset(character_slot, attrs) do
|
||||
character_slot
|
||||
|> cast(attrs, [:accid, :worldid, :charslots])
|
||||
|> validate_required([:accid, :worldid])
|
||||
end
|
||||
end
|
||||
29
lib/odinsea/database/schema/cheat_log.ex
Normal file
29
lib/odinsea/database/schema/cheat_log.ex
Normal file
@@ -0,0 +1,29 @@
|
||||
defmodule Odinsea.Database.Schema.CheatLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the cheatlog table.
|
||||
Represents cheat/anti-cheat log entries.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
@timestamps_opts [inserted_at: :lastoffensetime, updated_at: false]
|
||||
|
||||
schema "cheatlog" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :offense, :string
|
||||
field :count, :integer, default: 0
|
||||
field :lastoffensetime, :naive_datetime
|
||||
field :param, :string
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a cheat log entry.
|
||||
"""
|
||||
def changeset(cheat_log, attrs) do
|
||||
cheat_log
|
||||
|> cast(attrs, [:characterid, :offense, :count, :param])
|
||||
|> validate_required([:characterid, :offense])
|
||||
end
|
||||
end
|
||||
26
lib/odinsea/database/schema/compensation_log.ex
Normal file
26
lib/odinsea/database/schema/compensation_log.ex
Normal file
@@ -0,0 +1,26 @@
|
||||
defmodule Odinsea.Database.Schema.CompensationLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the compensationlog_confirmed table.
|
||||
Represents compensation records for players.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:chrname, :string, autogenerate: false}
|
||||
|
||||
schema "compensationlog_confirmed" do
|
||||
field :donor, :integer, default: 0
|
||||
field :value, :integer, default: 0
|
||||
field :taken, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for compensation log.
|
||||
"""
|
||||
def changeset(compensation_log, attrs) do
|
||||
compensation_log
|
||||
|> cast(attrs, [:chrname, :donor, :value, :taken])
|
||||
|> validate_required([:chrname])
|
||||
end
|
||||
end
|
||||
41
lib/odinsea/database/schema/cs_item.ex
Normal file
41
lib/odinsea/database/schema/cs_item.ex
Normal file
@@ -0,0 +1,41 @@
|
||||
defmodule Odinsea.Database.Schema.CsItem do
|
||||
@moduledoc """
|
||||
Ecto schema for the csitems table.
|
||||
Represents cash shop inventory items.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:inventoryitemid, :integer, autogenerate: false}
|
||||
|
||||
schema "csitems" do
|
||||
field :characterid, :integer
|
||||
field :accountid, :integer
|
||||
field :packageid, :integer
|
||||
field :itemid, :integer, default: 0
|
||||
field :inventorytype, :integer, default: 0
|
||||
field :position, :integer, default: 0
|
||||
field :quantity, :integer, default: 0
|
||||
field :owner, :string
|
||||
field :gm_log, :string, source: :GM_Log
|
||||
field :uniqueid, :integer, default: -1
|
||||
field :flag, :integer, default: 0
|
||||
field :expiredate, :integer, default: -1
|
||||
field :type, :integer, default: 0
|
||||
field :sender, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a cash shop item.
|
||||
"""
|
||||
def changeset(cs_item, attrs) do
|
||||
cs_item
|
||||
|> cast(attrs, [
|
||||
:inventoryitemid, :characterid, :accountid, :packageid, :itemid,
|
||||
:inventorytype, :position, :quantity, :owner, :gm_log, :uniqueid,
|
||||
:flag, :expiredate, :type, :sender
|
||||
])
|
||||
|> validate_required([:inventoryitemid, :itemid, :inventorytype, :position, :quantity])
|
||||
end
|
||||
end
|
||||
29
lib/odinsea/database/schema/donation.ex
Normal file
29
lib/odinsea/database/schema/donation.ex
Normal file
@@ -0,0 +1,29 @@
|
||||
defmodule Odinsea.Database.Schema.Donation do
|
||||
@moduledoc """
|
||||
Ecto schema for the donation table.
|
||||
Represents donation records.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
@timestamps_opts [inserted_at: :date, updated_at: false]
|
||||
|
||||
schema "donation" do
|
||||
field :date, :naive_datetime
|
||||
field :ip, :string
|
||||
field :username, :string
|
||||
field :quantity, :integer
|
||||
field :status, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for donation records.
|
||||
"""
|
||||
def changeset(donation, attrs) do
|
||||
donation
|
||||
|> cast(attrs, [:ip, :username, :quantity, :status])
|
||||
|> validate_required([:ip, :username])
|
||||
end
|
||||
end
|
||||
34
lib/odinsea/database/schema/donor_log.ex
Normal file
34
lib/odinsea/database/schema/donor_log.ex
Normal file
@@ -0,0 +1,34 @@
|
||||
defmodule Odinsea.Database.Schema.DonorLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the donorlog table.
|
||||
Represents donation transaction logs.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "donorlog" do
|
||||
field :accname, :string, default: ""
|
||||
field :acc_id, :integer, default: 0, source: :accId
|
||||
field :chrname, :string, default: ""
|
||||
field :chr_id, :integer, default: 0, source: :chrId
|
||||
field :log, :string, default: ""
|
||||
field :time, :string, default: ""
|
||||
field :previous_points, :integer, default: 0, source: :previousPoints
|
||||
field :current_points, :integer, default: 0, source: :currentPoints
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a donor log entry.
|
||||
"""
|
||||
def changeset(donor_log, attrs) do
|
||||
donor_log
|
||||
|> cast(attrs, [
|
||||
:accname, :acc_id, :chrname, :chr_id, :log, :time,
|
||||
:previous_points, :current_points
|
||||
])
|
||||
|> validate_required([:acc_id])
|
||||
end
|
||||
end
|
||||
29
lib/odinsea/database/schema/drop_data.ex
Normal file
29
lib/odinsea/database/schema/drop_data.ex
Normal file
@@ -0,0 +1,29 @@
|
||||
defmodule Odinsea.Database.Schema.DropData do
|
||||
@moduledoc """
|
||||
Ecto schema for the drop_data table.
|
||||
Represents monster drop tables.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "drop_data" do
|
||||
field :dropperid, :integer
|
||||
field :itemid, :integer, default: 0
|
||||
field :minimum_quantity, :integer, default: 1
|
||||
field :maximum_quantity, :integer, default: 1
|
||||
field :questid, :integer, default: 0
|
||||
field :chance, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating drop data.
|
||||
"""
|
||||
def changeset(drop_data, attrs) do
|
||||
drop_data
|
||||
|> cast(attrs, [:dropperid, :itemid, :minimum_quantity, :maximum_quantity, :questid, :chance])
|
||||
|> validate_required([:dropperid, :itemid, :chance])
|
||||
end
|
||||
end
|
||||
31
lib/odinsea/database/schema/drop_data_global.ex
Normal file
31
lib/odinsea/database/schema/drop_data_global.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Odinsea.Database.Schema.DropDataGlobal do
|
||||
@moduledoc """
|
||||
Ecto schema for the drop_data_global table.
|
||||
Represents global drops that apply to all monsters in a continent.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "drop_data_global" do
|
||||
field :continent, :integer
|
||||
field :drop_type, :integer, default: 0, source: :dropType
|
||||
field :itemid, :integer, default: 0
|
||||
field :minimum_quantity, :integer, default: 1
|
||||
field :maximum_quantity, :integer, default: 1
|
||||
field :questid, :integer, default: 0
|
||||
field :chance, :integer, default: 0
|
||||
field :comments, :string
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating global drop data.
|
||||
"""
|
||||
def changeset(drop_data_global, attrs) do
|
||||
drop_data_global
|
||||
|> cast(attrs, [:continent, :drop_type, :itemid, :minimum_quantity, :maximum_quantity, :questid, :chance, :comments])
|
||||
|> validate_required([:continent, :itemid, :chance])
|
||||
end
|
||||
end
|
||||
41
lib/odinsea/database/schema/duey_item.ex
Normal file
41
lib/odinsea/database/schema/duey_item.ex
Normal file
@@ -0,0 +1,41 @@
|
||||
defmodule Odinsea.Database.Schema.DueyItem do
|
||||
@moduledoc """
|
||||
Ecto schema for the dueyitems table.
|
||||
Represents Duey package items.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:inventoryitemid, :integer, autogenerate: false}
|
||||
|
||||
schema "dueyitems" do
|
||||
field :characterid, :integer
|
||||
field :accountid, :integer
|
||||
field :packageid, :integer
|
||||
field :itemid, :integer, default: 0
|
||||
field :inventorytype, :integer, default: 0
|
||||
field :position, :integer, default: 0
|
||||
field :quantity, :integer, default: 0
|
||||
field :owner, :string
|
||||
field :gm_log, :string, source: :GM_Log
|
||||
field :uniqueid, :integer, default: -1
|
||||
field :flag, :integer, default: 0
|
||||
field :expiredate, :integer, default: -1
|
||||
field :type, :integer, default: 0
|
||||
field :sender, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a duey item.
|
||||
"""
|
||||
def changeset(duey_item, attrs) do
|
||||
duey_item
|
||||
|> cast(attrs, [
|
||||
:inventoryitemid, :characterid, :accountid, :packageid, :itemid,
|
||||
:inventorytype, :position, :quantity, :owner, :gm_log, :uniqueid,
|
||||
:flag, :expiredate, :type, :sender
|
||||
])
|
||||
|> validate_required([:inventoryitemid, :itemid, :inventorytype, :position, :quantity])
|
||||
end
|
||||
end
|
||||
37
lib/odinsea/database/schema/duey_package.ex
Normal file
37
lib/odinsea/database/schema/duey_package.ex
Normal file
@@ -0,0 +1,37 @@
|
||||
defmodule Odinsea.Database.Schema.DueyPackage do
|
||||
@moduledoc """
|
||||
Ecto schema for the dueypackages table.
|
||||
Represents Duey packages (mail/delivery system).
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:package_id, :id, autogenerate: true, source: :PackageId}
|
||||
|
||||
schema "dueypackages" do
|
||||
field :reciever_id, :integer, source: :RecieverId
|
||||
field :sender_name, :string, source: :SenderName
|
||||
field :mesos, :integer, default: 0, source: :Mesos
|
||||
field :timestamp, :integer, source: :TimeStamp
|
||||
field :checked, :integer, default: 1, source: :Checked
|
||||
field :type, :integer, source: :Type
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a duey package.
|
||||
"""
|
||||
def creation_changeset(duey_package, attrs) do
|
||||
duey_package
|
||||
|> cast(attrs, [:reciever_id, :sender_name, :mesos, :timestamp, :type])
|
||||
|> validate_required([:reciever_id, :sender_name, :type])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for marking package as checked.
|
||||
"""
|
||||
def checked_changeset(duey_package, attrs) do
|
||||
duey_package
|
||||
|> cast(attrs, [:checked])
|
||||
end
|
||||
end
|
||||
25
lib/odinsea/database/schema/extended_slot.ex
Normal file
25
lib/odinsea/database/schema/extended_slot.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule Odinsea.Database.Schema.ExtendedSlot do
|
||||
@moduledoc """
|
||||
Ecto schema for the extendedslots table.
|
||||
Represents extended inventory slots for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "extendedslots" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :item_id, :integer, default: 0, source: :itemId
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an extended slot entry.
|
||||
"""
|
||||
def changeset(extended_slot, attrs) do
|
||||
extended_slot
|
||||
|> cast(attrs, [:characterid, :item_id])
|
||||
|> validate_required([:characterid])
|
||||
end
|
||||
end
|
||||
32
lib/odinsea/database/schema/fame_log.ex
Normal file
32
lib/odinsea/database/schema/fame_log.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule Odinsea.Database.Schema.FameLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the famelog table.
|
||||
Represents fame (reputation) transactions between characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:famelogid, :id, autogenerate: true}
|
||||
@timestamps_opts [inserted_at: :when, updated_at: false]
|
||||
|
||||
schema "famelog" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :characterid_to, :integer, default: 0
|
||||
field :when, :naive_datetime
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a fame log entry.
|
||||
"""
|
||||
def changeset(fame_log, attrs) do
|
||||
fame_log
|
||||
|> cast(attrs, [:characterid, :characterid_to])
|
||||
|> validate_required([:characterid, :characterid_to])
|
||||
end
|
||||
end
|
||||
34
lib/odinsea/database/schema/familiar.ex
Normal file
34
lib/odinsea/database/schema/familiar.ex
Normal file
@@ -0,0 +1,34 @@
|
||||
defmodule Odinsea.Database.Schema.Familiar do
|
||||
@moduledoc """
|
||||
Ecto schema for the familiars table.
|
||||
Represents familiar data for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "familiars" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :familiar, :integer, default: 0
|
||||
field :name, :string, default: ""
|
||||
field :fatigue, :integer, default: 0
|
||||
field :expiry, :integer, default: 0
|
||||
field :vitality, :integer, default: 0
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a familiar.
|
||||
"""
|
||||
def changeset(familiar, attrs) do
|
||||
familiar
|
||||
|> cast(attrs, [:characterid, :familiar, :name, :fatigue, :expiry, :vitality])
|
||||
|> validate_required([:characterid, :familiar])
|
||||
end
|
||||
end
|
||||
33
lib/odinsea/database/schema/family.ex
Normal file
33
lib/odinsea/database/schema/family.ex
Normal file
@@ -0,0 +1,33 @@
|
||||
defmodule Odinsea.Database.Schema.Family do
|
||||
@moduledoc """
|
||||
Ecto schema for the families table.
|
||||
Represents family data in the game.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:familyid, :id, autogenerate: true}
|
||||
|
||||
schema "families" do
|
||||
field :leaderid, :integer, default: 0
|
||||
field :notice, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a family.
|
||||
"""
|
||||
def creation_changeset(family, attrs) do
|
||||
family
|
||||
|> cast(attrs, [:leaderid, :notice])
|
||||
|> validate_required([:leaderid])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for updating family notice.
|
||||
"""
|
||||
def notice_changeset(family, attrs) do
|
||||
family
|
||||
|> cast(attrs, [:notice])
|
||||
end
|
||||
end
|
||||
28
lib/odinsea/database/schema/gift.ex
Normal file
28
lib/odinsea/database/schema/gift.ex
Normal file
@@ -0,0 +1,28 @@
|
||||
defmodule Odinsea.Database.Schema.Gift do
|
||||
@moduledoc """
|
||||
Ecto schema for the gifts table.
|
||||
Represents cash shop gifts sent between characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:giftid, :id, autogenerate: true}
|
||||
|
||||
schema "gifts" do
|
||||
field :recipient, :integer, default: 0
|
||||
field :from, :string, default: ""
|
||||
field :message, :string, default: ""
|
||||
field :sn, :integer, default: 0
|
||||
field :uniqueid, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a gift.
|
||||
"""
|
||||
def changeset(gift, attrs) do
|
||||
gift
|
||||
|> cast(attrs, [:recipient, :from, :message, :sn, :uniqueid])
|
||||
|> validate_required([:recipient, :from])
|
||||
end
|
||||
end
|
||||
28
lib/odinsea/database/schema/gm_log.ex
Normal file
28
lib/odinsea/database/schema/gm_log.ex
Normal file
@@ -0,0 +1,28 @@
|
||||
defmodule Odinsea.Database.Schema.GmLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the gmlog table.
|
||||
Represents GM command usage logs.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:gmlogid, :id, autogenerate: true}
|
||||
@timestamps_opts [inserted_at: :time, updated_at: false]
|
||||
|
||||
schema "gmlog" do
|
||||
field :cid, :integer, default: 0
|
||||
field :command, :string
|
||||
field :mapid, :integer, default: 0
|
||||
field :time, :naive_datetime
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a GM log entry.
|
||||
"""
|
||||
def changeset(gm_log, attrs) do
|
||||
gm_log
|
||||
|> cast(attrs, [:cid, :command, :mapid])
|
||||
|> validate_required([:cid, :command])
|
||||
end
|
||||
end
|
||||
63
lib/odinsea/database/schema/guild.ex
Normal file
63
lib/odinsea/database/schema/guild.ex
Normal file
@@ -0,0 +1,63 @@
|
||||
defmodule Odinsea.Database.Schema.Guild do
|
||||
@moduledoc """
|
||||
Ecto schema for the guilds table.
|
||||
Represents guild data in the game.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:guildid, :id, autogenerate: true}
|
||||
|
||||
schema "guilds" do
|
||||
field :leader, :integer, default: 0
|
||||
field :gp, :integer, default: 0, source: :GP
|
||||
field :logo, :integer
|
||||
field :logo_color, :integer, default: 0, source: :logoColor
|
||||
field :name, :string
|
||||
field :rank1title, :string, default: "Master"
|
||||
field :rank2title, :string, default: "Jr. Master"
|
||||
field :rank3title, :string, default: "Member"
|
||||
field :rank4title, :string, default: "Member"
|
||||
field :rank5title, :string, default: "Member"
|
||||
field :capacity, :integer, default: 10
|
||||
field :logo_bg, :integer, source: :logoBG
|
||||
field :logo_bg_color, :integer, default: 0, source: :logoBGColor
|
||||
field :notice, :string
|
||||
field :signature, :integer, default: 0
|
||||
field :alliance, :integer, default: 0
|
||||
|
||||
has_many :guild_skills, Odinsea.Database.Schema.GuildSkill, foreign_key: :guildid
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a guild.
|
||||
"""
|
||||
def creation_changeset(guild, attrs) do
|
||||
guild
|
||||
|> cast(attrs, [:leader, :name, :capacity, :logo, :logo_color, :logo_bg, :logo_bg_color])
|
||||
|> validate_required([:leader, :name])
|
||||
|> validate_length(:name, min: 1, max: 45)
|
||||
|> unique_constraint(:name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for updating guild settings.
|
||||
"""
|
||||
def settings_changeset(guild, attrs) do
|
||||
guild
|
||||
|> cast(attrs, [
|
||||
:rank1title, :rank2title, :rank3title, :rank4title, :rank5title,
|
||||
:capacity, :notice, :signature, :alliance
|
||||
])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for updating guild leader.
|
||||
"""
|
||||
def leader_changeset(guild, attrs) do
|
||||
guild
|
||||
|> cast(attrs, [:leader])
|
||||
|> validate_required([:leader])
|
||||
end
|
||||
end
|
||||
33
lib/odinsea/database/schema/guild_skill.ex
Normal file
33
lib/odinsea/database/schema/guild_skill.ex
Normal file
@@ -0,0 +1,33 @@
|
||||
defmodule Odinsea.Database.Schema.GuildSkill do
|
||||
@moduledoc """
|
||||
Ecto schema for the guildskills table.
|
||||
Represents purchased guild skills.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "guildskills" do
|
||||
field :guildid, :integer, default: 0
|
||||
field :skillid, :integer, default: 0
|
||||
field :level, :integer, default: 1
|
||||
field :timestamp, :integer, default: 0
|
||||
field :purchaser, :string, default: ""
|
||||
|
||||
belongs_to :guild, Odinsea.Database.Schema.Guild,
|
||||
foreign_key: :guildid,
|
||||
references: :guildid,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a guild skill.
|
||||
"""
|
||||
def changeset(guild_skill, attrs) do
|
||||
guild_skill
|
||||
|> cast(attrs, [:guildid, :skillid, :level, :timestamp, :purchaser])
|
||||
|> validate_required([:guildid, :skillid])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/hired_merch.ex
Normal file
27
lib/odinsea/database/schema/hired_merch.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.HiredMerch do
|
||||
@moduledoc """
|
||||
Ecto schema for the hiredmerch table.
|
||||
Represents hired merchant storage.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:package_id, :id, autogenerate: true, source: :PackageId}
|
||||
|
||||
schema "hiredmerch" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :accountid, :integer
|
||||
field :mesos, :integer, default: 0, source: :Mesos
|
||||
field :time, :integer, source: :time
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating hired merchant data.
|
||||
"""
|
||||
def changeset(hired_merch, attrs) do
|
||||
hired_merch
|
||||
|> cast(attrs, [:characterid, :accountid, :mesos, :time])
|
||||
|> validate_required([:characterid])
|
||||
end
|
||||
end
|
||||
41
lib/odinsea/database/schema/hired_merch_item.ex
Normal file
41
lib/odinsea/database/schema/hired_merch_item.ex
Normal file
@@ -0,0 +1,41 @@
|
||||
defmodule Odinsea.Database.Schema.HiredMerchItem do
|
||||
@moduledoc """
|
||||
Ecto schema for the hiredmerchitems table.
|
||||
Represents hired merchant items.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:inventoryitemid, :integer, autogenerate: false}
|
||||
|
||||
schema "hiredmerchitems" do
|
||||
field :characterid, :integer
|
||||
field :accountid, :integer
|
||||
field :packageid, :integer
|
||||
field :itemid, :integer, default: 0
|
||||
field :inventorytype, :integer, default: 0
|
||||
field :position, :integer, default: 0
|
||||
field :quantity, :integer, default: 0
|
||||
field :owner, :string
|
||||
field :gm_log, :string, source: :GM_Log
|
||||
field :uniqueid, :integer, default: -1
|
||||
field :flag, :integer, default: 0
|
||||
field :expiredate, :integer, default: -1
|
||||
field :type, :integer, default: 0
|
||||
field :sender, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a hired merchant item.
|
||||
"""
|
||||
def changeset(hired_merch_item, attrs) do
|
||||
hired_merch_item
|
||||
|> cast(attrs, [
|
||||
:inventoryitemid, :characterid, :accountid, :packageid, :itemid,
|
||||
:inventorytype, :position, :quantity, :owner, :gm_log, :uniqueid,
|
||||
:flag, :expiredate, :type, :sender
|
||||
])
|
||||
|> validate_required([:inventoryitemid, :itemid, :inventorytype, :position, :quantity])
|
||||
end
|
||||
end
|
||||
30
lib/odinsea/database/schema/hyperrock_location.ex
Normal file
30
lib/odinsea/database/schema/hyperrock_location.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Odinsea.Database.Schema.HyperrockLocation do
|
||||
@moduledoc """
|
||||
Ecto schema for the hyperrocklocations table.
|
||||
Represents hyper teleport rock locations for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:trockid, :id, autogenerate: true}
|
||||
|
||||
schema "hyperrocklocations" do
|
||||
field :characterid, :integer
|
||||
field :mapid, :integer
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a hyper teleport rock location.
|
||||
"""
|
||||
def changeset(hyperrock_location, attrs) do
|
||||
hyperrock_location
|
||||
|> cast(attrs, [:characterid, :mapid])
|
||||
|> validate_required([:characterid, :mapid])
|
||||
end
|
||||
end
|
||||
34
lib/odinsea/database/schema/imp.ex
Normal file
34
lib/odinsea/database/schema/imp.ex
Normal file
@@ -0,0 +1,34 @@
|
||||
defmodule Odinsea.Database.Schema.Imp do
|
||||
@moduledoc """
|
||||
Ecto schema for the imps table.
|
||||
Represents Imp (pocket pet) data for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:impid, :id, autogenerate: true}
|
||||
|
||||
schema "imps" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :itemid, :integer, default: 0
|
||||
field :level, :integer, default: 1
|
||||
field :state, :integer, default: 1
|
||||
field :closeness, :integer, default: 0
|
||||
field :fullness, :integer, default: 0
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating an imp.
|
||||
"""
|
||||
def changeset(imp, attrs) do
|
||||
imp
|
||||
|> cast(attrs, [:characterid, :itemid, :level, :state, :closeness, :fullness])
|
||||
|> validate_required([:characterid, :itemid])
|
||||
end
|
||||
end
|
||||
58
lib/odinsea/database/schema/inventory_equipment.ex
Normal file
58
lib/odinsea/database/schema/inventory_equipment.ex
Normal file
@@ -0,0 +1,58 @@
|
||||
defmodule Odinsea.Database.Schema.InventoryEquipment do
|
||||
@moduledoc """
|
||||
Ecto schema for the inventoryequipment table.
|
||||
Represents equipment stats for inventory items.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:inventoryequipmentid, :id, autogenerate: true}
|
||||
|
||||
schema "inventoryequipment" do
|
||||
field :inventoryitemid, :integer, default: 0
|
||||
field :upgradeslots, :integer, default: 0
|
||||
field :level, :integer, default: 0
|
||||
field :str, :integer, default: 0
|
||||
field :dex, :integer, default: 0
|
||||
field :int, :integer, default: 0
|
||||
field :luk, :integer, default: 0
|
||||
field :hp, :integer, default: 0
|
||||
field :mp, :integer, default: 0
|
||||
field :watk, :integer, default: 0
|
||||
field :matk, :integer, default: 0
|
||||
field :wdef, :integer, default: 0
|
||||
field :mdef, :integer, default: 0
|
||||
field :acc, :integer, default: 0
|
||||
field :avoid, :integer, default: 0
|
||||
field :hands, :integer, default: 0
|
||||
field :speed, :integer, default: 0
|
||||
field :jump, :integer, default: 0
|
||||
field :vicioushammer, :integer, default: 0, source: :ViciousHammer
|
||||
field :itemexp, :integer, default: 0, source: :itemEXP
|
||||
field :durability, :integer, default: -1
|
||||
field :enhance, :integer, default: 0
|
||||
field :potential1, :integer, default: 0
|
||||
field :potential2, :integer, default: 0
|
||||
field :potential3, :integer, default: 0
|
||||
field :hp_r, :integer, default: 0, source: :hpR
|
||||
field :mp_r, :integer, default: 0, source: :mpR
|
||||
field :incskill, :integer, default: -1, source: :incSkill
|
||||
field :charmexp, :integer, default: -1, source: :charmEXP
|
||||
field :pvpdamage, :integer, default: 0, source: :pvpDamage
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating inventory equipment.
|
||||
"""
|
||||
def changeset(inventory_equipment, attrs) do
|
||||
inventory_equipment
|
||||
|> cast(attrs, [
|
||||
:inventoryitemid, :upgradeslots, :level, :str, :dex, :int, :luk,
|
||||
:hp, :mp, :watk, :matk, :wdef, :mdef, :acc, :avoid, :hands, :speed, :jump,
|
||||
:vicioushammer, :itemexp, :durability, :enhance, :potential1, :potential2,
|
||||
:potential3, :hp_r, :mp_r, :incskill, :charmexp, :pvpdamage
|
||||
])
|
||||
|> validate_required([:inventoryitemid])
|
||||
end
|
||||
end
|
||||
25
lib/odinsea/database/schema/inventory_log.ex
Normal file
25
lib/odinsea/database/schema/inventory_log.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule Odinsea.Database.Schema.InventoryLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the inventorylog table.
|
||||
Represents inventory transaction logs.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:inventorylogid, :id, autogenerate: true}
|
||||
|
||||
schema "inventorylog" do
|
||||
field :inventoryitemid, :integer, default: 0
|
||||
field :msg, :string
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an inventory log entry.
|
||||
"""
|
||||
def changeset(inventory_log, attrs) do
|
||||
inventory_log
|
||||
|> cast(attrs, [:inventoryitemid, :msg])
|
||||
|> validate_required([:inventoryitemid, :msg])
|
||||
end
|
||||
end
|
||||
35
lib/odinsea/database/schema/inventory_slot.ex
Normal file
35
lib/odinsea/database/schema/inventory_slot.ex
Normal file
@@ -0,0 +1,35 @@
|
||||
defmodule Odinsea.Database.Schema.InventorySlot do
|
||||
@moduledoc """
|
||||
Ecto schema for the inventoryslot table.
|
||||
Represents inventory slot counts for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "inventoryslot" do
|
||||
field :characterid, :integer
|
||||
field :equip, :integer
|
||||
field :use, :integer
|
||||
field :setup, :integer
|
||||
field :etc, :integer
|
||||
field :cash, :integer
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating inventory slots.
|
||||
"""
|
||||
def changeset(inventory_slot, attrs) do
|
||||
inventory_slot
|
||||
|> cast(attrs, [:characterid, :equip, :use, :setup, :etc, :cash])
|
||||
|> validate_required([:characterid])
|
||||
|> unique_constraint(:characterid)
|
||||
end
|
||||
end
|
||||
24
lib/odinsea/database/schema/ip_ban.ex
Normal file
24
lib/odinsea/database/schema/ip_ban.ex
Normal file
@@ -0,0 +1,24 @@
|
||||
defmodule Odinsea.Database.Schema.IpBan do
|
||||
@moduledoc """
|
||||
Ecto schema for the ipbans table.
|
||||
Represents IP address bans.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:ipbanid, :id, autogenerate: true}
|
||||
|
||||
schema "ipbans" do
|
||||
field :ip, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an IP ban.
|
||||
"""
|
||||
def changeset(ip_ban, attrs) do
|
||||
ip_ban
|
||||
|> cast(attrs, [:ip])
|
||||
|> validate_required([:ip])
|
||||
end
|
||||
end
|
||||
26
lib/odinsea/database/schema/ip_log.ex
Normal file
26
lib/odinsea/database/schema/ip_log.ex
Normal file
@@ -0,0 +1,26 @@
|
||||
defmodule Odinsea.Database.Schema.IpLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the iplog table.
|
||||
Represents IP address login logs.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "iplog" do
|
||||
field :accid, :integer
|
||||
field :ip, :string
|
||||
field :time, :string
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an IP log entry.
|
||||
"""
|
||||
def changeset(ip_log, attrs) do
|
||||
ip_log
|
||||
|> cast(attrs, [:accid, :ip, :time])
|
||||
|> validate_required([:accid, :ip])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/ipvote_log.ex
Normal file
27
lib/odinsea/database/schema/ipvote_log.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.IpvoteLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the ipvotelog table.
|
||||
Represents IP-based voting records.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:vid, :id, autogenerate: true}
|
||||
|
||||
schema "ipvotelog" do
|
||||
field :accid, :integer, default: 0
|
||||
field :ipaddress, :string, default: "127.0.0.1"
|
||||
field :votetime, :integer, default: 0
|
||||
field :votetype, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for IP vote log.
|
||||
"""
|
||||
def changeset(ipvote_log, attrs) do
|
||||
ipvote_log
|
||||
|> cast(attrs, [:accid, :ipaddress, :votetime, :votetype])
|
||||
|> validate_required([:accid])
|
||||
end
|
||||
end
|
||||
32
lib/odinsea/database/schema/keymap.ex
Normal file
32
lib/odinsea/database/schema/keymap.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule Odinsea.Database.Schema.Keymap do
|
||||
@moduledoc """
|
||||
Ecto schema for the keymap table.
|
||||
Represents key bindings for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "keymap" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :key, :integer, default: 0
|
||||
field :type, :integer, default: 0
|
||||
field :action, :integer, default: 0
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a keymap entry.
|
||||
"""
|
||||
def changeset(keymap, attrs) do
|
||||
keymap
|
||||
|> cast(attrs, [:characterid, :key, :type, :action])
|
||||
|> validate_required([:characterid, :key, :type, :action])
|
||||
end
|
||||
end
|
||||
25
lib/odinsea/database/schema/mac_ban.ex
Normal file
25
lib/odinsea/database/schema/mac_ban.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule Odinsea.Database.Schema.MacBan do
|
||||
@moduledoc """
|
||||
Ecto schema for the macbans table.
|
||||
Represents MAC address bans.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:macbanid, :id, autogenerate: true}
|
||||
|
||||
schema "macbans" do
|
||||
field :mac, :string
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a MAC ban.
|
||||
"""
|
||||
def changeset(mac_ban, attrs) do
|
||||
mac_ban
|
||||
|> cast(attrs, [:mac])
|
||||
|> validate_required([:mac])
|
||||
|> unique_constraint(:mac)
|
||||
end
|
||||
end
|
||||
24
lib/odinsea/database/schema/mac_filter.ex
Normal file
24
lib/odinsea/database/schema/mac_filter.ex
Normal file
@@ -0,0 +1,24 @@
|
||||
defmodule Odinsea.Database.Schema.MacFilter do
|
||||
@moduledoc """
|
||||
Ecto schema for the macfilters table.
|
||||
Represents MAC address filters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:macfilterid, :id, autogenerate: true}
|
||||
|
||||
schema "macfilters" do
|
||||
field :filter, :string
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a MAC filter.
|
||||
"""
|
||||
def changeset(mac_filter, attrs) do
|
||||
mac_filter
|
||||
|> cast(attrs, [:filter])
|
||||
|> validate_required([:filter])
|
||||
end
|
||||
end
|
||||
31
lib/odinsea/database/schema/monsterbook.ex
Normal file
31
lib/odinsea/database/schema/monsterbook.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Odinsea.Database.Schema.Monsterbook do
|
||||
@moduledoc """
|
||||
Ecto schema for the monsterbook table.
|
||||
Represents monster book cards collected by characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "monsterbook" do
|
||||
field :charid, :integer, default: 0
|
||||
field :cardid, :integer, default: 0
|
||||
field :level, :integer, default: 1
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :charid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a monsterbook entry.
|
||||
"""
|
||||
def changeset(monsterbook, attrs) do
|
||||
monsterbook
|
||||
|> cast(attrs, [:charid, :cardid, :level])
|
||||
|> validate_required([:charid, :cardid])
|
||||
end
|
||||
end
|
||||
33
lib/odinsea/database/schema/mount_data.ex
Normal file
33
lib/odinsea/database/schema/mount_data.ex
Normal file
@@ -0,0 +1,33 @@
|
||||
defmodule Odinsea.Database.Schema.MountData do
|
||||
@moduledoc """
|
||||
Ecto schema for the mountdata table.
|
||||
Represents mount levels and experience for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "mountdata" do
|
||||
field :characterid, :integer
|
||||
field :level, :integer, default: 0, source: :Level
|
||||
field :exp, :integer, default: 0, source: :Exp
|
||||
field :fatigue, :integer, default: 0, source: :Fatigue
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating mount data.
|
||||
"""
|
||||
def changeset(mount_data, attrs) do
|
||||
mount_data
|
||||
|> cast(attrs, [:characterid, :level, :exp, :fatigue])
|
||||
|> validate_required([:characterid])
|
||||
|> unique_constraint(:characterid)
|
||||
end
|
||||
end
|
||||
30
lib/odinsea/database/schema/mts_cart.ex
Normal file
30
lib/odinsea/database/schema/mts_cart.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Odinsea.Database.Schema.MtsCart do
|
||||
@moduledoc """
|
||||
Ecto schema for the mts_cart table.
|
||||
Represents MTS cart items for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "mts_cart" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :itemid, :integer, default: 0
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an MTS cart entry.
|
||||
"""
|
||||
def changeset(mts_cart, attrs) do
|
||||
mts_cart
|
||||
|> cast(attrs, [:characterid, :itemid])
|
||||
|> validate_required([:characterid, :itemid])
|
||||
end
|
||||
end
|
||||
28
lib/odinsea/database/schema/mts_item.ex
Normal file
28
lib/odinsea/database/schema/mts_item.ex
Normal file
@@ -0,0 +1,28 @@
|
||||
defmodule Odinsea.Database.Schema.MtsItem do
|
||||
@moduledoc """
|
||||
Ecto schema for the mts_items table.
|
||||
Represents MTS (Maple Trading System) listings.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
|
||||
schema "mts_items" do
|
||||
field :tab, :integer, default: 1
|
||||
field :price, :integer, default: 0
|
||||
field :characterid, :integer, default: 0
|
||||
field :seller, :string, default: ""
|
||||
field :expiration, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating an MTS item listing.
|
||||
"""
|
||||
def changeset(mts_item, attrs) do
|
||||
mts_item
|
||||
|> cast(attrs, [:id, :tab, :price, :characterid, :seller, :expiration])
|
||||
|> validate_required([:id, :characterid])
|
||||
end
|
||||
end
|
||||
41
lib/odinsea/database/schema/mts_transfer.ex
Normal file
41
lib/odinsea/database/schema/mts_transfer.ex
Normal file
@@ -0,0 +1,41 @@
|
||||
defmodule Odinsea.Database.Schema.MtsTransfer do
|
||||
@moduledoc """
|
||||
Ecto schema for the mtstransfer table.
|
||||
Represents MTS transfer items.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:inventoryitemid, :integer, autogenerate: false}
|
||||
|
||||
schema "mtstransfer" do
|
||||
field :characterid, :integer
|
||||
field :accountid, :integer
|
||||
field :packageid, :integer
|
||||
field :itemid, :integer, default: 0
|
||||
field :inventorytype, :integer, default: 0
|
||||
field :position, :integer, default: 0
|
||||
field :quantity, :integer, default: 0
|
||||
field :owner, :string
|
||||
field :gm_log, :string, source: :GM_Log
|
||||
field :uniqueid, :integer, default: -1
|
||||
field :flag, :integer, default: 0
|
||||
field :expiredate, :integer, default: -1
|
||||
field :type, :integer, default: 0
|
||||
field :sender, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for MTS transfer items.
|
||||
"""
|
||||
def changeset(mts_transfer, attrs) do
|
||||
mts_transfer
|
||||
|> cast(attrs, [
|
||||
:inventoryitemid, :characterid, :accountid, :packageid, :itemid,
|
||||
:inventorytype, :position, :quantity, :owner, :gm_log, :uniqueid,
|
||||
:flag, :expiredate, :type, :sender
|
||||
])
|
||||
|> validate_required([:inventoryitemid, :itemid])
|
||||
end
|
||||
end
|
||||
28
lib/odinsea/database/schema/note.ex
Normal file
28
lib/odinsea/database/schema/note.ex
Normal file
@@ -0,0 +1,28 @@
|
||||
defmodule Odinsea.Database.Schema.Note do
|
||||
@moduledoc """
|
||||
Ecto schema for the notes table.
|
||||
Represents in-game notes/messages between characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "notes" do
|
||||
field :to, :string, default: ""
|
||||
field :from, :string, default: ""
|
||||
field :message, :string
|
||||
field :timestamp, :integer
|
||||
field :gift, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a note.
|
||||
"""
|
||||
def changeset(note, attrs) do
|
||||
note
|
||||
|> cast(attrs, [:to, :from, :message, :timestamp, :gift])
|
||||
|> validate_required([:to, :from, :message, :timestamp])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/nx_code.ex
Normal file
27
lib/odinsea/database/schema/nx_code.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.NxCode do
|
||||
@moduledoc """
|
||||
Ecto schema for the nxcode table.
|
||||
Represents NX (cash) redemption codes.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:code, :string, autogenerate: false}
|
||||
|
||||
schema "nxcode" do
|
||||
field :valid, :integer, default: 1
|
||||
field :user, :string
|
||||
field :type, :integer, default: 0
|
||||
field :item, :integer, default: 10000
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating an NX code.
|
||||
"""
|
||||
def changeset(nx_code, attrs) do
|
||||
nx_code
|
||||
|> cast(attrs, [:code, :valid, :user, :type, :item])
|
||||
|> validate_required([:code])
|
||||
end
|
||||
end
|
||||
38
lib/odinsea/database/schema/pet.ex
Normal file
38
lib/odinsea/database/schema/pet.ex
Normal file
@@ -0,0 +1,38 @@
|
||||
defmodule Odinsea.Database.Schema.Pet do
|
||||
@moduledoc """
|
||||
Ecto schema for the pets table.
|
||||
Represents pet data in the game.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:petid, :id, autogenerate: true}
|
||||
|
||||
schema "pets" do
|
||||
field :name, :string
|
||||
field :level, :integer, default: 1
|
||||
field :closeness, :integer, default: 0
|
||||
field :fullness, :integer, default: 0
|
||||
field :seconds, :integer, default: 0
|
||||
field :flags, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a pet.
|
||||
"""
|
||||
def creation_changeset(pet, attrs) do
|
||||
pet
|
||||
|> cast(attrs, [:name, :level, :closeness, :fullness])
|
||||
|> validate_required([:name])
|
||||
|> validate_length(:name, min: 1, max: 13)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for updating pet stats.
|
||||
"""
|
||||
def stats_changeset(pet, attrs) do
|
||||
pet
|
||||
|> cast(attrs, [:level, :closeness, :fullness, :seconds, :flags])
|
||||
end
|
||||
end
|
||||
41
lib/odinsea/database/schema/playernpc.ex
Normal file
41
lib/odinsea/database/schema/playernpc.ex
Normal file
@@ -0,0 +1,41 @@
|
||||
defmodule Odinsea.Database.Schema.Playernpc do
|
||||
@moduledoc """
|
||||
Ecto schema for the playernpcs table.
|
||||
Represents player-created NPCs.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "playernpcs" do
|
||||
field :name, :string
|
||||
field :hair, :integer
|
||||
field :face, :integer
|
||||
field :skin, :integer
|
||||
field :x, :integer, default: 0
|
||||
field :y, :integer, default: 0
|
||||
field :map, :integer
|
||||
field :charid, :integer
|
||||
field :scriptid, :integer
|
||||
field :foothold, :integer
|
||||
field :dir, :integer, default: 0
|
||||
field :gender, :integer, default: 0
|
||||
field :pets, :string, default: "0,0,0"
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :charid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a player NPC.
|
||||
"""
|
||||
def changeset(playernpc, attrs) do
|
||||
playernpc
|
||||
|> cast(attrs, [:name, :hair, :face, :skin, :x, :y, :map, :charid, :scriptid, :foothold, :dir, :gender, :pets])
|
||||
|> validate_required([:name, :map, :charid, :scriptid])
|
||||
end
|
||||
end
|
||||
32
lib/odinsea/database/schema/playernpc_equip.ex
Normal file
32
lib/odinsea/database/schema/playernpc_equip.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule Odinsea.Database.Schema.PlayernpcEquip do
|
||||
@moduledoc """
|
||||
Ecto schema for the playernpcs_equip table.
|
||||
Represents equipment for player NPCs.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "playernpcs_equip" do
|
||||
field :npcid, :integer
|
||||
field :equipid, :integer
|
||||
field :equippos, :integer
|
||||
field :charid, :integer
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :charid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for player NPC equipment.
|
||||
"""
|
||||
def changeset(playernpc_equip, attrs) do
|
||||
playernpc_equip
|
||||
|> cast(attrs, [:npcid, :equipid, :equippos, :charid])
|
||||
|> validate_required([:npcid, :equipid, :charid])
|
||||
end
|
||||
end
|
||||
51
lib/odinsea/database/schema/pokemon.ex
Normal file
51
lib/odinsea/database/schema/pokemon.ex
Normal file
@@ -0,0 +1,51 @@
|
||||
defmodule Odinsea.Database.Schema.Pokemon do
|
||||
@moduledoc """
|
||||
Ecto schema for the pokemon table.
|
||||
Represents Pokemon-like pet data for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "pokemon" do
|
||||
field :monsterid, :integer, default: 0
|
||||
field :characterid, :integer, default: 0
|
||||
field :level, :integer, default: 1
|
||||
field :exp, :integer, default: 0
|
||||
field :name, :string, default: ""
|
||||
field :nature, :integer, default: 0
|
||||
field :active, :integer, default: 0
|
||||
field :accountid, :integer, default: 0
|
||||
field :itemid, :integer, default: 0
|
||||
field :gender, :integer, default: -1
|
||||
field :hpiv, :integer, default: -1
|
||||
field :atkiv, :integer, default: -1
|
||||
field :defiv, :integer, default: -1
|
||||
field :spatkiv, :integer, default: -1
|
||||
field :spdefiv, :integer, default: -1
|
||||
field :speediv, :integer, default: -1
|
||||
field :evaiv, :integer, default: -1
|
||||
field :acciv, :integer, default: -1
|
||||
field :ability, :integer, default: -1
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a pokemon.
|
||||
"""
|
||||
def changeset(pokemon, attrs) do
|
||||
pokemon
|
||||
|> cast(attrs, [
|
||||
:monsterid, :characterid, :level, :exp, :name, :nature, :active,
|
||||
:accountid, :itemid, :gender, :hpiv, :atkiv, :defiv, :spatkiv,
|
||||
:spdefiv, :speediv, :evaiv, :acciv, :ability
|
||||
])
|
||||
|> validate_required([:monsterid])
|
||||
end
|
||||
end
|
||||
31
lib/odinsea/database/schema/quest_info.ex
Normal file
31
lib/odinsea/database/schema/quest_info.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Odinsea.Database.Schema.QuestInfo do
|
||||
@moduledoc """
|
||||
Ecto schema for the questinfo table.
|
||||
Represents custom quest info data for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:questinfoid, :id, autogenerate: true}
|
||||
|
||||
schema "questinfo" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :quest, :integer, default: 0
|
||||
field :custom_data, :string, source: :customData
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating quest info.
|
||||
"""
|
||||
def changeset(quest_info, attrs) do
|
||||
quest_info
|
||||
|> cast(attrs, [:characterid, :quest, :custom_data])
|
||||
|> validate_required([:characterid, :quest])
|
||||
end
|
||||
end
|
||||
36
lib/odinsea/database/schema/quest_status.ex
Normal file
36
lib/odinsea/database/schema/quest_status.ex
Normal file
@@ -0,0 +1,36 @@
|
||||
defmodule Odinsea.Database.Schema.QuestStatus do
|
||||
@moduledoc """
|
||||
Ecto schema for the queststatus table.
|
||||
Represents quest progress/status for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:queststatusid, :id, autogenerate: true}
|
||||
|
||||
schema "queststatus" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :quest, :integer, default: 0
|
||||
field :status, :integer, default: 0
|
||||
field :time, :integer, default: 0
|
||||
field :forfeited, :integer, default: 0
|
||||
field :custom_data, :string, source: :customData
|
||||
|
||||
has_many :quest_status_mobs, Odinsea.Database.Schema.QuestStatusMob, foreign_key: :queststatusid
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating quest status.
|
||||
"""
|
||||
def changeset(quest_status, attrs) do
|
||||
quest_status
|
||||
|> cast(attrs, [:characterid, :quest, :status, :time, :forfeited, :custom_data])
|
||||
|> validate_required([:characterid, :quest])
|
||||
end
|
||||
end
|
||||
31
lib/odinsea/database/schema/quest_status_mob.ex
Normal file
31
lib/odinsea/database/schema/quest_status_mob.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Odinsea.Database.Schema.QuestStatusMob do
|
||||
@moduledoc """
|
||||
Ecto schema for the queststatusmobs table.
|
||||
Represents mob kill counts for active quests.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:queststatusmobid, :id, autogenerate: true}
|
||||
|
||||
schema "queststatusmobs" do
|
||||
field :queststatusid, :integer, default: 0
|
||||
field :mob, :integer, default: 0
|
||||
field :count, :integer, default: 0
|
||||
|
||||
belongs_to :quest_status, Odinsea.Database.Schema.QuestStatus,
|
||||
foreign_key: :queststatusid,
|
||||
references: :queststatusid,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating quest status mob.
|
||||
"""
|
||||
def changeset(quest_status_mob, attrs) do
|
||||
quest_status_mob
|
||||
|> cast(attrs, [:queststatusid, :mob, :count])
|
||||
|> validate_required([:queststatusid, :mob])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/reactor_drop.ex
Normal file
27
lib/odinsea/database/schema/reactor_drop.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.ReactorDrop do
|
||||
@moduledoc """
|
||||
Ecto schema for the reactordrops table.
|
||||
Represents reactor (map object) drop tables.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:reactordropid, :id, autogenerate: true}
|
||||
|
||||
schema "reactordrops" do
|
||||
field :reactorid, :integer
|
||||
field :itemid, :integer
|
||||
field :chance, :integer
|
||||
field :questid, :integer, default: -1
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a reactor drop.
|
||||
"""
|
||||
def changeset(reactor_drop, attrs) do
|
||||
reactor_drop
|
||||
|> cast(attrs, [:reactorid, :itemid, :chance, :questid])
|
||||
|> validate_required([:reactorid, :itemid, :chance])
|
||||
end
|
||||
end
|
||||
30
lib/odinsea/database/schema/regrock_location.ex
Normal file
30
lib/odinsea/database/schema/regrock_location.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Odinsea.Database.Schema.RegrockLocation do
|
||||
@moduledoc """
|
||||
Ecto schema for the regrocklocations table.
|
||||
Represents regular teleport rock locations for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:trockid, :id, autogenerate: true}
|
||||
|
||||
schema "regrocklocations" do
|
||||
field :characterid, :integer
|
||||
field :mapid, :integer
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a regular teleport rock location.
|
||||
"""
|
||||
def changeset(regrock_location, attrs) do
|
||||
regrock_location
|
||||
|> cast(attrs, [:characterid, :mapid])
|
||||
|> validate_required([:characterid, :mapid])
|
||||
end
|
||||
end
|
||||
26
lib/odinsea/database/schema/report.ex
Normal file
26
lib/odinsea/database/schema/report.ex
Normal file
@@ -0,0 +1,26 @@
|
||||
defmodule Odinsea.Database.Schema.Report do
|
||||
@moduledoc """
|
||||
Ecto schema for the reports table.
|
||||
Represents player reports.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:reportid, :id, autogenerate: true}
|
||||
|
||||
schema "reports" do
|
||||
field :characterid, :integer, default: 0, primary_key: true
|
||||
field :type, :integer, default: 0
|
||||
field :count, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a report.
|
||||
"""
|
||||
def changeset(report, attrs) do
|
||||
report
|
||||
|> cast(attrs, [:characterid, :type, :count])
|
||||
|> validate_required([:characterid])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/ring.ex
Normal file
27
lib/odinsea/database/schema/ring.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.Ring do
|
||||
@moduledoc """
|
||||
Ecto schema for the rings table.
|
||||
Represents ring (friendship/marriage) data.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:ringid, :id, autogenerate: true}
|
||||
|
||||
schema "rings" do
|
||||
field :partner_ring_id, :integer, default: 0, source: :partnerRingId
|
||||
field :partner_chr_id, :integer, default: 0, source: :partnerChrId
|
||||
field :itemid, :integer, default: 0
|
||||
field :partnername, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a ring.
|
||||
"""
|
||||
def changeset(ring, attrs) do
|
||||
ring
|
||||
|> cast(attrs, [:partner_ring_id, :partner_chr_id, :itemid, :partnername])
|
||||
|> validate_required([:itemid])
|
||||
end
|
||||
end
|
||||
31
lib/odinsea/database/schema/saved_location.ex
Normal file
31
lib/odinsea/database/schema/saved_location.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Odinsea.Database.Schema.SavedLocation do
|
||||
@moduledoc """
|
||||
Ecto schema for the savedlocations table.
|
||||
Represents saved locations for characters (teleport rocks, etc).
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "savedlocations" do
|
||||
field :characterid, :integer
|
||||
field :locationtype, :integer, default: 0
|
||||
field :map, :integer
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a saved location.
|
||||
"""
|
||||
def changeset(saved_location, attrs) do
|
||||
saved_location
|
||||
|> cast(attrs, [:characterid, :locationtype, :map])
|
||||
|> validate_required([:characterid, :map])
|
||||
end
|
||||
end
|
||||
37
lib/odinsea/database/schema/scroll_log.ex
Normal file
37
lib/odinsea/database/schema/scroll_log.ex
Normal file
@@ -0,0 +1,37 @@
|
||||
defmodule Odinsea.Database.Schema.ScrollLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the scroll_log table.
|
||||
Represents scroll usage logs.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "scroll_log" do
|
||||
field :acc_id, :integer, default: 0, source: :accId
|
||||
field :chr_id, :integer, default: 0, source: :chrId
|
||||
field :scroll_id, :integer, default: 0, source: :scrollId
|
||||
field :item_id, :integer, default: 0, source: :itemId
|
||||
field :old_slots, :integer, default: 0, source: :oldSlots
|
||||
field :new_slots, :integer, default: 0, source: :newSlots
|
||||
field :hammer, :integer, default: 0
|
||||
field :result, :string, default: ""
|
||||
field :white_scroll, :integer, default: 0, source: :whiteScroll
|
||||
field :legendary_spirit, :integer, default: 0, source: :legendarySpirit
|
||||
field :vega_id, :integer, default: 0, source: :vegaId
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a scroll log entry.
|
||||
"""
|
||||
def changeset(scroll_log, attrs) do
|
||||
scroll_log
|
||||
|> cast(attrs, [
|
||||
:acc_id, :chr_id, :scroll_id, :item_id, :old_slots, :new_slots,
|
||||
:hammer, :result, :white_scroll, :legendary_spirit, :vega_id
|
||||
])
|
||||
|> validate_required([:acc_id, :chr_id, :scroll_id, :item_id])
|
||||
end
|
||||
end
|
||||
23
lib/odinsea/database/schema/shop.ex
Normal file
23
lib/odinsea/database/schema/shop.ex
Normal file
@@ -0,0 +1,23 @@
|
||||
defmodule Odinsea.Database.Schema.Shop do
|
||||
@moduledoc """
|
||||
Ecto schema for the shops table.
|
||||
Represents NPC shop definitions.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:shopid, :id, autogenerate: true}
|
||||
|
||||
schema "shops" do
|
||||
field :npcid, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a shop.
|
||||
"""
|
||||
def changeset(shop, attrs) do
|
||||
shop
|
||||
|> cast(attrs, [:npcid])
|
||||
end
|
||||
end
|
||||
30
lib/odinsea/database/schema/shop_item.ex
Normal file
30
lib/odinsea/database/schema/shop_item.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Odinsea.Database.Schema.ShopItem do
|
||||
@moduledoc """
|
||||
Ecto schema for the shopitems table.
|
||||
Represents items available in NPC shops.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:shopitemid, :id, autogenerate: true}
|
||||
|
||||
schema "shopitems" do
|
||||
field :shopid, :integer, default: 0
|
||||
field :itemid, :integer, default: 0
|
||||
field :price, :integer, default: 0
|
||||
field :position, :integer, default: 0
|
||||
field :reqitem, :integer, default: 0
|
||||
field :reqitemq, :integer, default: 0
|
||||
field :rank, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a shop item.
|
||||
"""
|
||||
def changeset(shop_item, attrs) do
|
||||
shop_item
|
||||
|> cast(attrs, [:shopid, :itemid, :price, :position, :reqitem, :reqitemq, :rank])
|
||||
|> validate_required([:shopid, :itemid])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/shop_rank.ex
Normal file
27
lib/odinsea/database/schema/shop_rank.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.ShopRank do
|
||||
@moduledoc """
|
||||
Ecto schema for the shopranks table.
|
||||
Represents shop rank definitions.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "shopranks" do
|
||||
field :shopid, :integer, default: 0
|
||||
field :rank, :integer, default: 0
|
||||
field :name, :string, default: ""
|
||||
field :itemid, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a shop rank.
|
||||
"""
|
||||
def changeset(shop_rank, attrs) do
|
||||
shop_rank
|
||||
|> cast(attrs, [:shopid, :rank, :name, :itemid])
|
||||
|> validate_required([:shopid])
|
||||
end
|
||||
end
|
||||
25
lib/odinsea/database/schema/sidekick.ex
Normal file
25
lib/odinsea/database/schema/sidekick.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule Odinsea.Database.Schema.Sidekick do
|
||||
@moduledoc """
|
||||
Ecto schema for the sidekicks table.
|
||||
Represents sidekick (partner) relationships between characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "sidekicks" do
|
||||
field :firstid, :integer, default: 0
|
||||
field :secondid, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a sidekick relationship.
|
||||
"""
|
||||
def changeset(sidekick, attrs) do
|
||||
sidekick
|
||||
|> cast(attrs, [:firstid, :secondid])
|
||||
|> validate_required([:firstid, :secondid])
|
||||
end
|
||||
end
|
||||
33
lib/odinsea/database/schema/skill.ex
Normal file
33
lib/odinsea/database/schema/skill.ex
Normal file
@@ -0,0 +1,33 @@
|
||||
defmodule Odinsea.Database.Schema.Skill do
|
||||
@moduledoc """
|
||||
Ecto schema for the skills table.
|
||||
Represents character skill levels.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "skills" do
|
||||
field :skillid, :integer, default: 0
|
||||
field :characterid, :integer, default: 0
|
||||
field :skilllevel, :integer, default: 0
|
||||
field :masterlevel, :integer, default: 0
|
||||
field :expiration, :integer, default: -1
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a skill.
|
||||
"""
|
||||
def changeset(skill, attrs) do
|
||||
skill
|
||||
|> cast(attrs, [:skillid, :characterid, :skilllevel, :masterlevel, :expiration])
|
||||
|> validate_required([:skillid, :characterid])
|
||||
end
|
||||
end
|
||||
32
lib/odinsea/database/schema/skill_cooldown.ex
Normal file
32
lib/odinsea/database/schema/skill_cooldown.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule Odinsea.Database.Schema.SkillCooldown do
|
||||
@moduledoc """
|
||||
Ecto schema for the skills_cooldowns table.
|
||||
Represents active skill cooldowns for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "skills_cooldowns" do
|
||||
field :charid, :integer
|
||||
field :skill_id, :integer, source: :SkillID
|
||||
field :length, :integer
|
||||
field :start_time, :integer, source: :StartTime
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :charid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a skill cooldown.
|
||||
"""
|
||||
def changeset(skill_cooldown, attrs) do
|
||||
skill_cooldown
|
||||
|> cast(attrs, [:charid, :skill_id, :length, :start_time])
|
||||
|> validate_required([:charid, :skill_id, :length, :start_time])
|
||||
end
|
||||
end
|
||||
35
lib/odinsea/database/schema/skill_macro.ex
Normal file
35
lib/odinsea/database/schema/skill_macro.ex
Normal file
@@ -0,0 +1,35 @@
|
||||
defmodule Odinsea.Database.Schema.SkillMacro do
|
||||
@moduledoc """
|
||||
Ecto schema for the skillmacros table.
|
||||
Represents skill macros (combo skills) for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "skillmacros" do
|
||||
field :characterid, :integer, default: 0
|
||||
field :position, :integer, default: 0
|
||||
field :skill1, :integer, default: 0
|
||||
field :skill2, :integer, default: 0
|
||||
field :skill3, :integer, default: 0
|
||||
field :name, :string
|
||||
field :shout, :integer, default: 0
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a skill macro.
|
||||
"""
|
||||
def changeset(skill_macro, attrs) do
|
||||
skill_macro
|
||||
|> cast(attrs, [:characterid, :position, :skill1, :skill2, :skill3, :name, :shout])
|
||||
|> validate_required([:characterid, :position])
|
||||
end
|
||||
end
|
||||
28
lib/odinsea/database/schema/speedrun.ex
Normal file
28
lib/odinsea/database/schema/speedrun.ex
Normal file
@@ -0,0 +1,28 @@
|
||||
defmodule Odinsea.Database.Schema.Speedrun do
|
||||
@moduledoc """
|
||||
Ecto schema for the speedruns table.
|
||||
Represents dungeon speedrun records.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "speedruns" do
|
||||
field :type, :string
|
||||
field :leader, :string
|
||||
field :timestring, :string
|
||||
field :time, :integer, default: 0
|
||||
field :members, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a speedrun record.
|
||||
"""
|
||||
def changeset(speedrun, attrs) do
|
||||
speedrun
|
||||
|> cast(attrs, [:type, :leader, :timestring, :time, :members])
|
||||
|> validate_required([:type, :leader, :timestring])
|
||||
end
|
||||
end
|
||||
31
lib/odinsea/database/schema/storage.ex
Normal file
31
lib/odinsea/database/schema/storage.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Odinsea.Database.Schema.Storage do
|
||||
@moduledoc """
|
||||
Ecto schema for the storages table.
|
||||
Represents account storage (bank) data.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:storageid, :id, autogenerate: true}
|
||||
|
||||
schema "storages" do
|
||||
field :accountid, :integer, default: 0
|
||||
field :slots, :integer, default: 0
|
||||
field :meso, :integer, default: 0
|
||||
|
||||
belongs_to :account, Odinsea.Database.Schema.Account,
|
||||
foreign_key: :accountid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating storage.
|
||||
"""
|
||||
def changeset(storage, attrs) do
|
||||
storage
|
||||
|> cast(attrs, [:accountid, :slots, :meso])
|
||||
|> validate_required([:accountid])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/tournament_log.ex
Normal file
27
lib/odinsea/database/schema/tournament_log.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.TournamentLog do
|
||||
@moduledoc """
|
||||
Ecto schema for the tournamentlog table.
|
||||
Represents tournament records.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:logid, :id, autogenerate: true}
|
||||
@timestamps_opts [inserted_at: :when, updated_at: false]
|
||||
|
||||
schema "tournamentlog" do
|
||||
field :winnerid, :integer, default: 0
|
||||
field :num_contestants, :integer, default: 0, source: :numContestants
|
||||
field :when, :naive_datetime
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a tournament log entry.
|
||||
"""
|
||||
def changeset(tournament_log, attrs) do
|
||||
tournament_log
|
||||
|> cast(attrs, [:winnerid, :num_contestants])
|
||||
|> validate_required([:winnerid])
|
||||
end
|
||||
end
|
||||
30
lib/odinsea/database/schema/trock_location.ex
Normal file
30
lib/odinsea/database/schema/trock_location.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Odinsea.Database.Schema.TrockLocation do
|
||||
@moduledoc """
|
||||
Ecto schema for the trocklocations table.
|
||||
Represents teleport rock locations for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:trockid, :id, autogenerate: true}
|
||||
|
||||
schema "trocklocations" do
|
||||
field :characterid, :integer
|
||||
field :mapid, :integer
|
||||
|
||||
belongs_to :character, Odinsea.Database.Schema.Character,
|
||||
foreign_key: :characterid,
|
||||
references: :id,
|
||||
define_field: false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating/updating a teleport rock location.
|
||||
"""
|
||||
def changeset(trock_location, attrs) do
|
||||
trock_location
|
||||
|> cast(attrs, [:characterid, :mapid])
|
||||
|> validate_required([:characterid, :mapid])
|
||||
end
|
||||
end
|
||||
24
lib/odinsea/database/schema/wishlist.ex
Normal file
24
lib/odinsea/database/schema/wishlist.ex
Normal file
@@ -0,0 +1,24 @@
|
||||
defmodule Odinsea.Database.Schema.Wishlist do
|
||||
@moduledoc """
|
||||
Ecto schema for the wishlist table.
|
||||
Represents cash shop wishlist items for characters.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
schema "wishlist" do
|
||||
field :characterid, :integer, primary_key: true
|
||||
field :sn, :integer, primary_key: true
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for creating a wishlist entry.
|
||||
"""
|
||||
def changeset(wishlist, attrs) do
|
||||
wishlist
|
||||
|> cast(attrs, [:characterid, :sn])
|
||||
|> validate_required([:characterid, :sn])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/wz_item_add_data.ex
Normal file
27
lib/odinsea/database/schema/wz_item_add_data.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.WzItemAddData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_itemadddata table.
|
||||
Represents additional static item data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "wz_itemadddata" do
|
||||
field :itemid, :integer
|
||||
field :key, :string
|
||||
field :value1, :integer, default: 0
|
||||
field :value2, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ item add data.
|
||||
"""
|
||||
def changeset(wz_item_add_data, attrs) do
|
||||
wz_item_add_data
|
||||
|> cast(attrs, [:itemid, :key, :value1, :value2])
|
||||
|> validate_required([:itemid, :key])
|
||||
end
|
||||
end
|
||||
49
lib/odinsea/database/schema/wz_item_data.ex
Normal file
49
lib/odinsea/database/schema/wz_item_data.ex
Normal file
@@ -0,0 +1,49 @@
|
||||
defmodule Odinsea.Database.Schema.WzItemData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_itemdata table.
|
||||
Represents static item data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:itemid, :integer, autogenerate: false}
|
||||
|
||||
schema "wz_itemdata" do
|
||||
field :name, :string
|
||||
field :msg, :string
|
||||
field :desc, :string
|
||||
field :slot_max, :integer, default: 1, source: :slotMax
|
||||
field :price, :string, default: "-1.0"
|
||||
field :whole_price, :integer, default: -1, source: :wholePrice
|
||||
field :state_change, :integer, default: 0, source: :stateChange
|
||||
field :flags, :integer, default: 0
|
||||
field :karma, :integer, default: 0
|
||||
field :meso, :integer, default: 0
|
||||
field :monster_book, :integer, default: 0, source: :monsterBook
|
||||
field :item_make_level, :integer, default: 0, source: :itemMakeLevel
|
||||
field :quest_id, :integer, default: 0, source: :questId
|
||||
field :scroll_reqs, :string, source: :scrollReqs
|
||||
field :consume_item, :string, source: :consumeItem
|
||||
field :totalprob, :integer, default: 0
|
||||
field :inc_skill, :string, default: "", source: :incSkill
|
||||
field :replaceid, :integer, default: 0
|
||||
field :replacemsg, :string, default: ""
|
||||
field :create, :integer, default: 0
|
||||
field :after_image, :string, default: "", source: :afterImage
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ item data.
|
||||
"""
|
||||
def changeset(wz_item_data, attrs) do
|
||||
wz_item_data
|
||||
|> cast(attrs, [
|
||||
:itemid, :name, :msg, :desc, :slot_max, :price, :whole_price,
|
||||
:state_change, :flags, :karma, :meso, :monster_book, :item_make_level,
|
||||
:quest_id, :scroll_reqs, :consume_item, :totalprob, :inc_skill,
|
||||
:replaceid, :replacemsg, :create, :after_image
|
||||
])
|
||||
|> validate_required([:itemid])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/wz_item_equip_data.ex
Normal file
27
lib/odinsea/database/schema/wz_item_equip_data.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.WzItemEquipData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_itemequipdata table.
|
||||
Represents static equipment data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "wz_itemequipdata" do
|
||||
field :itemid, :integer
|
||||
field :item_level, :integer, default: -1, source: :itemLevel
|
||||
field :key, :string
|
||||
field :value, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ equipment data.
|
||||
"""
|
||||
def changeset(wz_item_equip_data, attrs) do
|
||||
wz_item_equip_data
|
||||
|> cast(attrs, [:itemid, :item_level, :key, :value])
|
||||
|> validate_required([:itemid, :key])
|
||||
end
|
||||
end
|
||||
30
lib/odinsea/database/schema/wz_item_reward_data.ex
Normal file
30
lib/odinsea/database/schema/wz_item_reward_data.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Odinsea.Database.Schema.WzItemRewardData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_itemrewarddata table.
|
||||
Represents item reward data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "wz_itemrewarddata" do
|
||||
field :itemid, :integer
|
||||
field :item, :integer
|
||||
field :prob, :integer, default: 0
|
||||
field :quantity, :integer, default: 0
|
||||
field :period, :integer, default: -1
|
||||
field :world_msg, :string, default: "", source: :worldMsg
|
||||
field :effect, :string, default: ""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ item reward data.
|
||||
"""
|
||||
def changeset(wz_item_reward_data, attrs) do
|
||||
wz_item_reward_data
|
||||
|> cast(attrs, [:itemid, :item, :prob, :quantity, :period, :world_msg, :effect])
|
||||
|> validate_required([:itemid, :item])
|
||||
end
|
||||
end
|
||||
43
lib/odinsea/database/schema/wz_mob_skill_data.ex
Normal file
43
lib/odinsea/database/schema/wz_mob_skill_data.ex
Normal file
@@ -0,0 +1,43 @@
|
||||
defmodule Odinsea.Database.Schema.WzMobSkillData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_mobskilldata table.
|
||||
Represents monster skill data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "wz_mobskilldata" do
|
||||
field :skillid, :integer
|
||||
field :level, :integer
|
||||
field :hp, :integer, default: 100
|
||||
field :mpcon, :integer, default: 0
|
||||
field :x, :integer, default: 1
|
||||
field :y, :integer, default: 1
|
||||
field :time, :integer, default: 0
|
||||
field :prop, :integer, default: 100
|
||||
field :limit, :integer, default: 0
|
||||
field :spawneffect, :integer, default: 0
|
||||
field :interval, :integer, default: 0
|
||||
field :summons, :string, default: ""
|
||||
field :ltx, :integer, default: 0
|
||||
field :lty, :integer, default: 0
|
||||
field :rbx, :integer, default: 0
|
||||
field :rby, :integer, default: 0
|
||||
field :once, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ mob skill data.
|
||||
"""
|
||||
def changeset(wz_mob_skill_data, attrs) do
|
||||
wz_mob_skill_data
|
||||
|> cast(attrs, [
|
||||
:skillid, :level, :hp, :mpcon, :x, :y, :time, :prop, :limit,
|
||||
:spawneffect, :interval, :summons, :ltx, :lty, :rbx, :rby, :once
|
||||
])
|
||||
|> validate_required([:skillid, :level])
|
||||
end
|
||||
end
|
||||
27
lib/odinsea/database/schema/wz_ox_data.ex
Normal file
27
lib/odinsea/database/schema/wz_ox_data.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule Odinsea.Database.Schema.WzOxData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_oxdata table.
|
||||
Represents OX quiz questions from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
schema "wz_oxdata" do
|
||||
field :questionset, :integer, default: 0, primary_key: true
|
||||
field :questionid, :integer, default: 0, primary_key: true
|
||||
field :question, :string, default: ""
|
||||
field :display, :string, default: ""
|
||||
field :answer, :string
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ OX data.
|
||||
"""
|
||||
def changeset(wz_ox_data, attrs) do
|
||||
wz_ox_data
|
||||
|> cast(attrs, [:questionset, :questionid, :question, :display, :answer])
|
||||
|> validate_required([:questionset, :questionid, :answer])
|
||||
end
|
||||
end
|
||||
29
lib/odinsea/database/schema/wz_quest_act_data.ex
Normal file
29
lib/odinsea/database/schema/wz_quest_act_data.ex
Normal file
@@ -0,0 +1,29 @@
|
||||
defmodule Odinsea.Database.Schema.WzQuestActData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_questactdata table.
|
||||
Represents quest action data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "wz_questactdata" do
|
||||
field :questid, :integer, default: 0
|
||||
field :name, :string, default: ""
|
||||
field :type, :integer, default: 0
|
||||
field :int_store, :integer, default: 0, source: :intStore
|
||||
field :applicable_jobs, :string, default: "", source: :applicableJobs
|
||||
field :uniqueid, :integer, default: 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ quest action data.
|
||||
"""
|
||||
def changeset(wz_quest_act_data, attrs) do
|
||||
wz_quest_act_data
|
||||
|> cast(attrs, [:questid, :name, :type, :int_store, :applicable_jobs, :uniqueid])
|
||||
|> validate_required([:questid])
|
||||
end
|
||||
end
|
||||
34
lib/odinsea/database/schema/wz_quest_data.ex
Normal file
34
lib/odinsea/database/schema/wz_quest_data.ex
Normal file
@@ -0,0 +1,34 @@
|
||||
defmodule Odinsea.Database.Schema.WzQuestData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_questdata table.
|
||||
Represents static quest data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:questid, :integer, autogenerate: false}
|
||||
|
||||
schema "wz_questdata" do
|
||||
field :name, :string, default: ""
|
||||
field :auto_start, :integer, default: 0, source: :autoStart
|
||||
field :auto_pre_complete, :integer, default: 0, source: :autoPreComplete
|
||||
field :view_medal_item, :integer, default: 0, source: :viewMedalItem
|
||||
field :selected_skill_id, :integer, default: 0, source: :selectedSkillID
|
||||
field :blocked, :integer, default: 0
|
||||
field :auto_accept, :integer, default: 0, source: :autoAccept
|
||||
field :auto_complete, :integer, default: 0, source: :autoComplete
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ quest data.
|
||||
"""
|
||||
def changeset(wz_quest_data, attrs) do
|
||||
wz_quest_data
|
||||
|> cast(attrs, [
|
||||
:questid, :name, :auto_start, :auto_pre_complete, :view_medal_item,
|
||||
:selected_skill_id, :blocked, :auto_accept, :auto_complete
|
||||
])
|
||||
|> validate_required([:questid])
|
||||
end
|
||||
end
|
||||
29
lib/odinsea/database/schema/wz_quest_req_data.ex
Normal file
29
lib/odinsea/database/schema/wz_quest_req_data.ex
Normal file
@@ -0,0 +1,29 @@
|
||||
defmodule Odinsea.Database.Schema.WzQuestReqData do
|
||||
@moduledoc """
|
||||
Ecto schema for the wz_questreqdata table.
|
||||
Represents quest requirement data from WZ files.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
|
||||
schema "wz_questreqdata" do
|
||||
field :questid, :integer, default: 0
|
||||
field :name, :string, default: ""
|
||||
field :type, :integer, default: 0
|
||||
field :string_store, :string, default: "", source: :stringStore
|
||||
field :int_stores_first, :string, default: "", source: :intStoresFirst
|
||||
field :int_stores_second, :string, default: "", source: :intStoresSecond
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for WZ quest requirement data.
|
||||
"""
|
||||
def changeset(wz_quest_req_data, attrs) do
|
||||
wz_quest_req_data
|
||||
|> cast(attrs, [:questid, :name, :type, :string_store, :int_stores_first, :int_stores_second])
|
||||
|> validate_required([:questid])
|
||||
end
|
||||
end
|
||||
@@ -94,6 +94,10 @@ defmodule Odinsea.Game.Character do
|
||||
:face,
|
||||
# GM Level (0 = normal player, >0 = GM)
|
||||
:gm,
|
||||
# Guild
|
||||
:guild_id,
|
||||
:guild_rank,
|
||||
:alliance_rank,
|
||||
# Stats
|
||||
:stats,
|
||||
# Position & Map
|
||||
@@ -134,6 +138,9 @@ defmodule Odinsea.Game.Character do
|
||||
hair: non_neg_integer(),
|
||||
face: non_neg_integer(),
|
||||
gm: non_neg_integer(),
|
||||
guild_id: non_neg_integer(),
|
||||
guild_rank: non_neg_integer(),
|
||||
alliance_rank: non_neg_integer(),
|
||||
stats: Stats.t(),
|
||||
map_id: non_neg_integer(),
|
||||
position: Position.t(),
|
||||
@@ -281,6 +288,30 @@ defmodule Odinsea.Game.Character do
|
||||
GenServer.call(via_tuple(character_id), {:drop_item, inv_type, position, quantity})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds meso to the character.
|
||||
Returns {:ok, new_meso} on success, {:error, reason} on failure.
|
||||
"""
|
||||
def gain_meso(character_id, amount, show_in_chat \\ false) do
|
||||
GenServer.call(via_tuple(character_id), {:gain_meso, amount, show_in_chat})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character has inventory space for an item.
|
||||
Returns {:ok, slot} with the next free slot, or {:error, :inventory_full}.
|
||||
"""
|
||||
def check_inventory_space(character_id, inv_type, quantity \\ 1) do
|
||||
GenServer.call(via_tuple(character_id), {:check_inventory_space, inv_type, quantity})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an item to the character's inventory (from a drop).
|
||||
Returns {:ok, item} on success, {:error, reason} on failure.
|
||||
"""
|
||||
def add_item_from_drop(character_id, item) do
|
||||
GenServer.call(via_tuple(character_id), {:add_item_from_drop, item})
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# GenServer Callbacks
|
||||
# ============================================================================
|
||||
@@ -423,6 +454,45 @@ defmodule Odinsea.Game.Character do
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:gain_meso, amount, show_in_chat}, _from, state) do
|
||||
# Cap meso at 9,999,999,999 (MapleStory max)
|
||||
max_meso = 9_999_999_999
|
||||
new_meso = min(state.meso + amount, max_meso)
|
||||
|
||||
new_state = %{state | meso: new_meso, updated_at: DateTime.utc_now()}
|
||||
|
||||
# TODO: Send meso gain packet to client if show_in_chat is true
|
||||
|
||||
{:reply, {:ok, new_meso}, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:check_inventory_space, inv_type, _quantity}, _from, state) do
|
||||
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
|
||||
|
||||
case Inventory.get_next_free_slot(inventory) do
|
||||
nil -> {:reply, {:error, :inventory_full}, state}
|
||||
slot -> {:reply, {:ok, slot}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:add_item_from_drop, item}, _from, state) do
|
||||
inv_type = get_inventory_type_from_item_id(item.item_id)
|
||||
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
|
||||
|
||||
case Inventory.add_item(inventory, item) do
|
||||
{:ok, new_inventory, assigned_item} ->
|
||||
new_inventories = Map.put(state.inventories, inv_type, new_inventory)
|
||||
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
|
||||
{:reply, {:ok, assigned_item}, new_state}
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:update_position, position}, state) do
|
||||
new_state = %{
|
||||
@@ -459,6 +529,19 @@ defmodule Odinsea.Game.Character do
|
||||
{:via, Registry, {Odinsea.CharacterRegistry, character_id}}
|
||||
end
|
||||
|
||||
defp get_inventory_type_from_item_id(item_id) do
|
||||
type_prefix = div(item_id, 1_000_000)
|
||||
|
||||
case type_prefix do
|
||||
1 -> :equip
|
||||
2 -> :use
|
||||
3 -> :setup
|
||||
4 -> :etc
|
||||
5 -> :cash
|
||||
_ -> :etc
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts database character to in-game state.
|
||||
"""
|
||||
@@ -517,6 +600,9 @@ defmodule Odinsea.Game.Character do
|
||||
hair: db_char.hair,
|
||||
face: db_char.face,
|
||||
gm: db_char.gm,
|
||||
guild_id: db_char.guild_id || 0,
|
||||
guild_rank: db_char.guild_rank || 0,
|
||||
alliance_rank: db_char.alliance_rank || 0,
|
||||
stats: stats,
|
||||
map_id: db_char.map_id,
|
||||
position: position,
|
||||
@@ -850,4 +936,112 @@ defmodule Odinsea.Game.Character do
|
||||
# TODO: Use actual MapleStory EXP table
|
||||
level * level * level + 100 * level
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Scripting API Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Gets the character's channel ID.
|
||||
"""
|
||||
def get_channel(character_id) do
|
||||
case get_state(character_id) do
|
||||
nil -> {:error, :character_not_found}
|
||||
%State{channel_id: channel_id} -> {:ok, channel_id}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the character's meso.
|
||||
"""
|
||||
def update_meso(character_id, new_meso) do
|
||||
GenServer.cast(via_tuple(character_id), {:update_meso, new_meso})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the character's job.
|
||||
"""
|
||||
def update_job(character_id, new_job) do
|
||||
GenServer.cast(via_tuple(character_id), {:update_job, new_job})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a skill level.
|
||||
"""
|
||||
def update_skill(character_id, skill_id, level, master_level) do
|
||||
GenServer.cast(via_tuple(character_id), {:update_skill, skill_id, level, master_level})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an item to inventory.
|
||||
"""
|
||||
def add_item(character_id, inventory_type, item) do
|
||||
GenServer.call(via_tuple(character_id), {:add_item, inventory_type, item})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes items by item ID.
|
||||
"""
|
||||
def remove_item_by_id(character_id, item_id, quantity) do
|
||||
GenServer.call(via_tuple(character_id), {:remove_item_by_id, item_id, quantity})
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# GenServer Callbacks - Scripting Operations
|
||||
# ============================================================================
|
||||
|
||||
@impl true
|
||||
def handle_cast({:update_meso, new_meso}, state) do
|
||||
new_state = %{state | meso: new_meso, updated_at: DateTime.utc_now()}
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:update_job, new_job}, state) do
|
||||
new_state = %{state | job: new_job, updated_at: DateTime.utc_now()}
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:update_skill, skill_id, level, master_level}, state) do
|
||||
skill_entry = %{
|
||||
level: level,
|
||||
master_level: master_level,
|
||||
expiration: -1
|
||||
}
|
||||
new_skills = Map.put(state.skills, skill_id, skill_entry)
|
||||
new_state = %{state | skills: new_skills, updated_at: DateTime.utc_now()}
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:add_item, inventory_type, item}, _from, state) do
|
||||
inventory = Map.get(state.inventories, inventory_type, Inventory.new(inventory_type))
|
||||
|
||||
case Inventory.add_item(inventory, item) do
|
||||
{:ok, new_inventory} ->
|
||||
new_inventories = Map.put(state.inventories, inventory_type, new_inventory)
|
||||
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
|
||||
{:reply, :ok, new_state}
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:remove_item_by_id, item_id, quantity}, _from, state) do
|
||||
inventory_type = Inventory.get_type_by_item_id(item_id)
|
||||
inventory = Map.get(state.inventories, inventory_type, Inventory.new(inventory_type))
|
||||
|
||||
case Inventory.remove_by_id(inventory, item_id, quantity) do
|
||||
{:ok, new_inventory, _removed} ->
|
||||
new_inventories = Map.put(state.inventories, inventory_type, new_inventory)
|
||||
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
|
||||
{:reply, :ok, new_state}
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,6 +14,8 @@ defmodule Odinsea.Game.Drop do
|
||||
- Type 3: Explosive/FFA (instant FFA)
|
||||
"""
|
||||
|
||||
alias Odinsea.Game.Item
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
oid: integer(),
|
||||
item_id: integer(),
|
||||
@@ -30,7 +32,9 @@ defmodule Odinsea.Game.Drop do
|
||||
created_at: integer(),
|
||||
expire_time: integer() | nil,
|
||||
public_time: integer() | nil,
|
||||
dropper_oid: integer() | nil
|
||||
dropper_oid: integer() | nil,
|
||||
# Item struct for item drops (nil for meso drops)
|
||||
item: Item.t() | nil
|
||||
}
|
||||
|
||||
defstruct [
|
||||
@@ -49,7 +53,8 @@ defmodule Odinsea.Game.Drop do
|
||||
:created_at,
|
||||
:expire_time,
|
||||
:public_time,
|
||||
:dropper_oid
|
||||
:dropper_oid,
|
||||
:item
|
||||
]
|
||||
|
||||
# Default drop expiration times (milliseconds)
|
||||
@@ -65,6 +70,7 @@ defmodule Odinsea.Game.Drop do
|
||||
individual_reward = Keyword.get(opts, :individual_reward, false)
|
||||
dropper_oid = Keyword.get(opts, :dropper_oid, nil)
|
||||
source_position = Keyword.get(opts, :source_position, nil)
|
||||
item = Keyword.get(opts, :item, nil)
|
||||
now = System.system_time(:millisecond)
|
||||
|
||||
%__MODULE__{
|
||||
@@ -83,7 +89,8 @@ defmodule Odinsea.Game.Drop do
|
||||
created_at: now,
|
||||
expire_time: now + @default_expire_time,
|
||||
public_time: if(drop_type < 2, do: now + @default_public_time, else: 0),
|
||||
dropper_oid: dropper_oid
|
||||
dropper_oid: dropper_oid,
|
||||
item: item
|
||||
}
|
||||
end
|
||||
|
||||
@@ -113,7 +120,8 @@ defmodule Odinsea.Game.Drop do
|
||||
created_at: now,
|
||||
expire_time: now + @default_expire_time,
|
||||
public_time: if(drop_type < 2, do: now + @default_public_time, else: 0),
|
||||
dropper_oid: dropper_oid
|
||||
dropper_oid: dropper_oid,
|
||||
item: nil
|
||||
}
|
||||
end
|
||||
|
||||
@@ -141,13 +149,22 @@ defmodule Odinsea.Game.Drop do
|
||||
@doc """
|
||||
Checks if a drop is visible to a specific character.
|
||||
Considers quest requirements and individual rewards.
|
||||
|
||||
For quest items, the character must have the quest started.
|
||||
For individual rewards, only the owner can see the drop.
|
||||
"""
|
||||
def visible_to?(%__MODULE__{} = drop, character_id, _quest_status) do
|
||||
def visible_to?(%__MODULE__{} = drop, character_id, quest_status) do
|
||||
# Individual rewards only visible to owner
|
||||
if drop.individual_reward do
|
||||
drop.owner_id == character_id
|
||||
else
|
||||
true
|
||||
# Check quest requirement
|
||||
if drop.quest_id > 0 do
|
||||
# Only visible if character has quest started (status 1)
|
||||
Map.get(quest_status, drop.quest_id, 0) == 1
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -158,6 +175,13 @@ defmodule Odinsea.Game.Drop do
|
||||
drop.meso > 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if this is an item drop.
|
||||
"""
|
||||
def item?(%__MODULE__{} = drop) do
|
||||
drop.meso == 0 and drop.item_id > 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the display ID (item_id for items, meso amount for meso).
|
||||
"""
|
||||
@@ -170,7 +194,13 @@ defmodule Odinsea.Game.Drop do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a character can loot this drop.
|
||||
Checks if a character can loot this drop based on ownership rules.
|
||||
|
||||
Drop types:
|
||||
- 0: Owner only (until timeout)
|
||||
- 1: Owner's party (until timeout)
|
||||
- 2: Free-for-all (FFA)
|
||||
- 3: Explosive (instant FFA)
|
||||
"""
|
||||
def can_loot?(%__MODULE__{} = drop, character_id, now) do
|
||||
# If already picked up, can't loot
|
||||
@@ -197,4 +227,35 @@ defmodule Odinsea.Game.Drop do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a character can loot this drop, including party check.
|
||||
Requires party information to validate party drops.
|
||||
"""
|
||||
def can_loot_with_party?(%__MODULE__{} = drop, character_id, party_id, party_members, now) do
|
||||
if drop.picked_up do
|
||||
false
|
||||
else
|
||||
case drop.drop_type do
|
||||
0 ->
|
||||
# Timeout for non-owner only
|
||||
drop.owner_id == character_id or is_public_time?(drop, now)
|
||||
1 ->
|
||||
# Timeout for non-owner's party
|
||||
owner_in_same_party = drop.owner_id in party_members
|
||||
(owner_in_same_party and party_id != nil) or
|
||||
drop.owner_id == character_id or
|
||||
is_public_time?(drop, now)
|
||||
2 ->
|
||||
# FFA
|
||||
true
|
||||
3 ->
|
||||
# Explosive/FFA (instant FFA)
|
||||
true
|
||||
_ ->
|
||||
# Default to owner-only
|
||||
drop.owner_id == character_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,6 +167,17 @@ defmodule Odinsea.Game.Inventory do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the next available slot number.
|
||||
Returns nil if the inventory is full (Elixir-style).
|
||||
"""
|
||||
def get_next_free_slot(%__MODULE__{} = inv) do
|
||||
case next_free_slot(inv) do
|
||||
-1 -> nil
|
||||
slot -> slot
|
||||
end
|
||||
end
|
||||
|
||||
defp find_next_slot(items, limit, slot) when slot > limit, do: -1
|
||||
|
||||
defp find_next_slot(items, limit, slot) do
|
||||
@@ -205,6 +216,23 @@ defmodule Odinsea.Game.Inventory do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a plain map item to the inventory (used for drops).
|
||||
Returns {:ok, new_inventory, assigned_item} or {:error, :inventory_full}.
|
||||
"""
|
||||
def add_item(%__MODULE__{} = inv, %{} = item_map) when not is_struct(item_map) do
|
||||
slot = next_free_slot(inv)
|
||||
|
||||
if slot < 0 do
|
||||
{:error, :inventory_full}
|
||||
else
|
||||
# Convert map to item with assigned position
|
||||
assigned_item = Map.put(item_map, :position, slot)
|
||||
new_items = Map.put(inv.items, slot, assigned_item)
|
||||
{:ok, %{inv | items: new_items}, assigned_item}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an item from the database (preserves position).
|
||||
"""
|
||||
@@ -391,4 +419,103 @@ defmodule Odinsea.Game.Inventory do
|
||||
end
|
||||
|
||||
def equipped_items(%__MODULE__{}), do: []
|
||||
|
||||
@doc """
|
||||
Gets the inventory type based on item ID.
|
||||
"""
|
||||
def get_type_by_item_id(item_id) do
|
||||
InventoryType.from_item_id(item_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if inventory has at least the specified quantity of an item.
|
||||
"""
|
||||
def has_item_count(%__MODULE__{} = inv, item_id, quantity) do
|
||||
count_by_id(inv, item_id) >= quantity
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if there's at least one free slot in the inventory.
|
||||
"""
|
||||
def has_free_slot(%__MODULE__{} = inv) do
|
||||
next_free_slot(inv) >= 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if inventory can hold the specified quantity of an item.
|
||||
For stackable items, checks if there's room to stack or a free slot.
|
||||
"""
|
||||
def can_hold_quantity(%__MODULE__{} = inv, item_id, quantity) do
|
||||
# Find existing item to check stack space
|
||||
existing = find_by_id(inv, item_id)
|
||||
slot_max = InventoryType.slot_limit(inv.type)
|
||||
|
||||
if existing do
|
||||
# Check if we can stack
|
||||
space_in_stack = slot_max - existing.quantity
|
||||
remaining = quantity - space_in_stack
|
||||
|
||||
if remaining <= 0 do
|
||||
true
|
||||
else
|
||||
# Need additional slots
|
||||
free_slots = count_free_slots(inv)
|
||||
slots_needed = div(remaining, slot_max) + if rem(remaining, slot_max) > 0, do: 1, else: 0
|
||||
free_slots >= slots_needed
|
||||
end
|
||||
else
|
||||
# Need new slot(s)
|
||||
free_slots = count_free_slots(inv)
|
||||
slots_needed = div(quantity, slot_max) + if rem(quantity, slot_max) > 0, do: 1, else: 0
|
||||
free_slots >= slots_needed
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes items by item ID.
|
||||
Returns {:ok, new_inventory, removed_count} or {:error, reason}.
|
||||
"""
|
||||
def remove_by_id(%__MODULE__{} = inv, item_id, quantity) do
|
||||
items_with_id =
|
||||
inv.items
|
||||
|> Map.values()
|
||||
|> Enum.filter(fn item -> item.item_id == item_id end)
|
||||
|> Enum.sort_by(fn item -> item.position end)
|
||||
|
||||
total_available = Enum.map(items_with_id, fn i -> i.quantity end) |> Enum.sum()
|
||||
|
||||
if total_available < quantity do
|
||||
{:error, :insufficient_quantity}
|
||||
else
|
||||
{new_items, removed} = do_remove_by_id(inv.items, items_with_id, quantity, 0)
|
||||
{:ok, %{inv | items: new_items}, removed}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_remove_by_id(items, _items_to_remove, 0, removed), do: {items, removed}
|
||||
defp do_remove_by_id(items, [], _quantity, removed), do: {items, removed}
|
||||
defp do_remove_by_id(items, [item | rest], quantity, removed) do
|
||||
if quantity <= 0 do
|
||||
{items, removed}
|
||||
else
|
||||
to_remove = min(item.quantity, quantity)
|
||||
new_quantity = item.quantity - to_remove
|
||||
|
||||
new_items = if new_quantity <= 0 do
|
||||
Map.delete(items, item.position)
|
||||
else
|
||||
Map.put(items, item.position, %{item | quantity: new_quantity})
|
||||
end
|
||||
|
||||
do_remove_by_id(new_items, rest, quantity - to_remove, removed + to_remove)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Counts free slots in the inventory.
|
||||
"""
|
||||
def count_free_slots(%__MODULE__{} = inv) do
|
||||
used_slots = map_size(inv.items)
|
||||
inv.slot_limit - used_slots
|
||||
end
|
||||
end
|
||||
|
||||
120
lib/odinsea/game/inventory_manipulator.ex
Normal file
120
lib/odinsea/game/inventory_manipulator.ex
Normal file
@@ -0,0 +1,120 @@
|
||||
defmodule Odinsea.Game.InventoryManipulator do
|
||||
@moduledoc """
|
||||
High-level inventory operations for adding/removing items.
|
||||
Ported from Java server.MapleInventoryManipulator
|
||||
|
||||
This module provides convenient functions for:
|
||||
- Adding items from drops
|
||||
- Adding items by ID
|
||||
- Removing items
|
||||
- Checking inventory space
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Game.Character
|
||||
|
||||
@doc """
|
||||
Adds an item to the character's inventory from a drop.
|
||||
Returns {:ok, item} on success, {:error, reason} on failure.
|
||||
|
||||
Ported from MapleInventoryManipulator.addFromDrop()
|
||||
"""
|
||||
def add_from_drop(character_pid, item) when is_pid(character_pid) do
|
||||
Character.add_item_from_drop(character_pid, item)
|
||||
end
|
||||
|
||||
def add_from_drop(character_id, item) when is_integer(character_id) do
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, character_id) do
|
||||
[{pid, _}] -> add_from_drop(pid, item)
|
||||
[] -> {:error, :character_not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an item to the character's inventory by item ID and quantity.
|
||||
Creates a new item instance with default properties.
|
||||
|
||||
Ported from MapleInventoryManipulator.addById()
|
||||
"""
|
||||
def add_by_id(character_pid, item_id, quantity \\ 1, gm_log \\ "") do
|
||||
# Create a basic item
|
||||
item = %{
|
||||
item_id: item_id,
|
||||
quantity: quantity,
|
||||
owner: "",
|
||||
flag: 0,
|
||||
gm_log: gm_log
|
||||
}
|
||||
|
||||
add_from_drop(character_pid, item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an item to the character's inventory with full item details.
|
||||
|
||||
Ported from MapleInventoryManipulator.addbyItem()
|
||||
"""
|
||||
def add_by_item(character_pid, item) do
|
||||
add_from_drop(character_pid, item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes an item from a specific inventory slot.
|
||||
|
||||
Ported from MapleInventoryManipulator.removeFromSlot()
|
||||
"""
|
||||
def remove_from_slot(character_pid, inv_type, slot, quantity \\ 1, _from_drop \\ false, _wedding \\ false) do
|
||||
Character.drop_item(character_pid, inv_type, slot, quantity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes items by item ID from the inventory.
|
||||
|
||||
Ported from MapleInventoryManipulator.removeById()
|
||||
"""
|
||||
def remove_by_id(_character_pid, _inv_type, _item_id, _quantity, _delete \\ false, _wedding \\ false) do
|
||||
# TODO: Implement remove by ID (search for item, then remove)
|
||||
{:ok, 0}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character has space for an item.
|
||||
|
||||
Ported from MapleInventoryManipulator.checkSpace()
|
||||
"""
|
||||
def check_space(character_pid, item_id, quantity \\ 1, _owner \\ "") do
|
||||
inv_type = get_inventory_type(item_id)
|
||||
|
||||
case Character.check_inventory_space(character_pid, inv_type, quantity) do
|
||||
{:ok, _slot} -> true
|
||||
{:error, _} -> false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character's inventory is full.
|
||||
"""
|
||||
def inventory_full?(character_pid, inv_type) do
|
||||
case Character.check_inventory_space(character_pid, inv_type, 1) do
|
||||
{:ok, _} -> false
|
||||
{:error, :inventory_full} -> true
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the inventory type from an item ID.
|
||||
"""
|
||||
def get_inventory_type(item_id) do
|
||||
type_prefix = div(item_id, 1_000_000)
|
||||
|
||||
case type_prefix do
|
||||
1 -> :equip
|
||||
2 -> :use
|
||||
3 -> :setup
|
||||
4 -> :etc
|
||||
5 -> :cash
|
||||
_ -> :etc
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -95,4 +95,33 @@ defmodule Odinsea.Game.InventoryType do
|
||||
Lists all inventory types including equipped.
|
||||
"""
|
||||
def all_types, do: [:equip, :use, :setup, :etc, :cash, :equipped]
|
||||
|
||||
@doc """
|
||||
Gets the inventory type from an item ID.
|
||||
Based on MapleStory item ID ranges:
|
||||
- 1000000-1999999: Equip
|
||||
- 2000000-2999999: Use (consumables)
|
||||
- 3000000-3999999: Setup
|
||||
- 4000000-4999999: Etc
|
||||
- 5000000-5999999: Cash
|
||||
"""
|
||||
def from_item_id(item_id) when is_integer(item_id) do
|
||||
cond do
|
||||
item_id >= 1_000_000 and item_id < 2_000_000 -> :equip
|
||||
item_id >= 2_000_000 and item_id < 3_000_000 -> :use
|
||||
item_id >= 3_000_000 and item_id < 4_000_000 -> :setup
|
||||
item_id >= 4_000_000 and item_id < 5_000_000 -> :etc
|
||||
item_id >= 5_000_000 and item_id < 6_000_000 -> :cash
|
||||
true -> :etc
|
||||
end
|
||||
end
|
||||
|
||||
def from_item_id(_), do: :etc
|
||||
|
||||
@doc """
|
||||
Gets slot limit for an inventory type.
|
||||
"""
|
||||
def slot_limit(type) do
|
||||
default_slot_limit(type)
|
||||
end
|
||||
end
|
||||
|
||||
110
lib/odinsea/game/job_type.ex
Normal file
110
lib/odinsea/game/job_type.ex
Normal file
@@ -0,0 +1,110 @@
|
||||
defmodule Odinsea.Game.JobType do
|
||||
@moduledoc """
|
||||
Job type definitions for character creation.
|
||||
Ported from Java LoginInformationProvider.JobType
|
||||
|
||||
Job types:
|
||||
- 0 = Resistance
|
||||
- 1 = Adventurer
|
||||
- 2 = Cygnus
|
||||
- 3 = Aran
|
||||
- 4 = Evan
|
||||
"""
|
||||
|
||||
@type t :: :resistance | :adventurer | :cygnus | :aran | :evan | :ultimate_adventurer
|
||||
|
||||
@doc """
|
||||
Converts an integer job type to atom.
|
||||
"""
|
||||
@spec from_int(integer()) :: t()
|
||||
def from_int(0), do: :resistance
|
||||
def from_int(1), do: :adventurer
|
||||
def from_int(2), do: :cygnus
|
||||
def from_int(3), do: :aran
|
||||
def from_int(4), do: :evan
|
||||
def from_int(_), do: :adventurer
|
||||
|
||||
@doc """
|
||||
Converts a job type atom to integer.
|
||||
"""
|
||||
@spec to_int(t()) :: integer()
|
||||
def to_int(:resistance), do: 0
|
||||
def to_int(:adventurer), do: 1
|
||||
def to_int(:cygnus), do: 2
|
||||
def to_int(:aran), do: 3
|
||||
def to_int(:evan), do: 4
|
||||
def to_int(:ultimate_adventurer), do: 5
|
||||
def to_int(_), do: 1
|
||||
|
||||
@doc """
|
||||
Gets the base job ID for a job type.
|
||||
"""
|
||||
@spec get_job_id(t()) :: integer()
|
||||
def get_job_id(:resistance), do: 3000
|
||||
def get_job_id(:adventurer), do: 0
|
||||
def get_job_id(:cygnus), do: 1000
|
||||
def get_job_id(:aran), do: 2000
|
||||
def get_job_id(:evan), do: 2001
|
||||
def get_job_id(:ultimate_adventurer), do: 0
|
||||
def get_job_id(_), do: 0
|
||||
|
||||
@doc """
|
||||
Checks if a job type is valid for character creation.
|
||||
"""
|
||||
@spec valid?(integer() | t()) :: boolean()
|
||||
def valid?(type) when is_integer(type), do: type >= 0 and type <= 4
|
||||
def valid?(type) when is_atom(type), do: type in [:resistance, :adventurer, :cygnus, :aran, :evan]
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc """
|
||||
Gets the tutorial map ID for a job type.
|
||||
"""
|
||||
@spec get_tutorial_map(t() | integer()) :: integer()
|
||||
def get_tutorial_map(:resistance), do: 931000000
|
||||
def get_tutorial_map(:adventurer), do: 0 # Maple Island (special handling)
|
||||
def get_tutorial_map(:cygnus), do: 130030000
|
||||
def get_tutorial_map(:aran), do: 914000000
|
||||
def get_tutorial_map(:evan), do: 900010000
|
||||
def get_tutorial_map(type) when is_integer(type) do
|
||||
type |> from_int() |> get_tutorial_map()
|
||||
end
|
||||
def get_tutorial_map(_), do: 100000000 # Default to Henesys
|
||||
|
||||
@doc """
|
||||
Gets the beginner guide book item ID for a job type.
|
||||
"""
|
||||
@spec get_guide_book(t() | integer()) :: integer() | nil
|
||||
def get_guide_book(:resistance), do: 4161001
|
||||
def get_guide_book(:adventurer), do: 4161001
|
||||
def get_guide_book(:cygnus), do: 4161047
|
||||
def get_guide_book(:aran), do: 4161048
|
||||
def get_guide_book(:evan), do: 4161052
|
||||
def get_guide_book(type) when is_integer(type) do
|
||||
type |> from_int() |> get_guide_book()
|
||||
end
|
||||
def get_guide_book(_), do: nil
|
||||
|
||||
@doc """
|
||||
Gets the initial quests for a job type.
|
||||
Returns a list of {quest_id, status, custom_data} tuples.
|
||||
"""
|
||||
@spec get_initial_quests(t() | integer()) :: list()
|
||||
def get_initial_quests(:cygnus) do
|
||||
[
|
||||
{20022, 1, "1"},
|
||||
{20010, 1, nil}
|
||||
]
|
||||
end
|
||||
def get_initial_quests(:ultimate_adventurer) do
|
||||
# Complete all explorer quests (2490-2507)
|
||||
base_quests = Enum.map(2490..2507, fn qid -> {qid, 2, nil} end)
|
||||
[
|
||||
{29947, 2, nil}
|
||||
| base_quests
|
||||
]
|
||||
end
|
||||
def get_initial_quests(type) when is_integer(type) do
|
||||
type |> from_int() |> get_initial_quests()
|
||||
end
|
||||
def get_initial_quests(_), do: []
|
||||
end
|
||||
@@ -16,7 +16,7 @@ defmodule Odinsea.Game.Map do
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Game.Character
|
||||
alias Odinsea.Game.{MapFactory, LifeFactory, Monster, Reactor, ReactorFactory}
|
||||
alias Odinsea.Game.{Drop, MapFactory, LifeFactory, Monster, Reactor, ReactorFactory}
|
||||
alias Odinsea.Channel.Packets, as: ChannelPackets
|
||||
|
||||
# ============================================================================
|
||||
@@ -1073,11 +1073,19 @@ defmodule Odinsea.Game.Map do
|
||||
|
||||
@doc """
|
||||
Attempts to pick up a drop.
|
||||
Returns {:ok, drop} if successful, {:error, reason} if not.
|
||||
"""
|
||||
def pickup_drop(map_id, channel_id, drop_oid, character_id) do
|
||||
GenServer.call(via_tuple(map_id, channel_id), {:pickup_drop, drop_oid, character_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a drop is visible to a character (for quest items, individual rewards).
|
||||
"""
|
||||
def drop_visible_to?(map_id, channel_id, drop_oid, character_id, quest_status \\ %{}) do
|
||||
GenServer.call(via_tuple(map_id, channel_id), {:drop_visible_to, drop_oid, character_id, quest_status})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:get_drops, _from, state) do
|
||||
{:reply, state.items, state}
|
||||
@@ -1092,24 +1100,41 @@ defmodule Odinsea.Game.Map do
|
||||
drop ->
|
||||
now = System.system_time(:millisecond)
|
||||
|
||||
case DropSystem.pickup_drop(drop, character_id, now) do
|
||||
{:ok, updated_drop} ->
|
||||
# Broadcast pickup animation
|
||||
remove_packet = ChannelPackets.remove_drop(drop_oid, 2, character_id)
|
||||
broadcast_to_players(state.players, remove_packet)
|
||||
|
||||
# Remove from map
|
||||
new_items = Map.delete(state.items, drop_oid)
|
||||
|
||||
# Return drop info for inventory addition
|
||||
{:reply, {:ok, updated_drop}, %{state | items: new_items}}
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
# Validate ownership using Drop.can_loot?
|
||||
if not Drop.can_loot?(drop, character_id, now) do
|
||||
{:reply, {:error, :not_owner}, state}
|
||||
else
|
||||
case DropSystem.pickup_drop(drop, character_id, now) do
|
||||
{:ok, updated_drop} ->
|
||||
# Broadcast pickup animation to all players
|
||||
remove_packet = ChannelPackets.remove_drop(drop_oid, 2, character_id)
|
||||
broadcast_to_players(state.players, remove_packet)
|
||||
|
||||
# Remove from map
|
||||
new_items = Map.delete(state.items, drop_oid)
|
||||
|
||||
# Return drop info for inventory addition
|
||||
{:reply, {:ok, updated_drop}, %{state | items: new_items}}
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:drop_visible_to, drop_oid, character_id, quest_status}, _from, state) do
|
||||
case Map.get(state.items, drop_oid) do
|
||||
nil ->
|
||||
{:reply, false, state}
|
||||
|
||||
drop ->
|
||||
visible = Drop.visible_to?(drop, character_id, quest_status)
|
||||
{:reply, visible, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:check_drop_expiration, state) do
|
||||
now = System.system_time(:millisecond)
|
||||
|
||||
@@ -18,6 +18,7 @@ defmodule Odinsea.Login.Handler do
|
||||
alias Odinsea.Login.Packets
|
||||
alias Odinsea.Constants.Server
|
||||
alias Odinsea.Database.Context
|
||||
alias Odinsea.Game.{JobType, InventoryType}
|
||||
|
||||
# ==================================================================================================
|
||||
# Permission Request (Client Hello / Version Check)
|
||||
@@ -78,70 +79,111 @@ defmodule Odinsea.Login.Handler do
|
||||
Logger.info("Login attempt: username=#{username} from #{state.ip}")
|
||||
|
||||
# Check if IP/MAC is banned
|
||||
# TODO: Implement IP/MAC ban checking
|
||||
is_banned = false
|
||||
ip_banned = Context.ip_banned?(state.ip)
|
||||
mac_banned = Context.mac_banned?(state.mac)
|
||||
|
||||
# Authenticate with database
|
||||
case Context.authenticate_user(username, password, state.ip) do
|
||||
{:ok, account_info} ->
|
||||
# TODO: Check if account is banned or temp banned
|
||||
# TODO: Check if already logged in (kick other session)
|
||||
if (ip_banned || mac_banned) do
|
||||
Logger.warning("Banned IP/MAC attempted login: ip=#{state.ip}, mac=#{state.mac}")
|
||||
|
||||
# If MAC banned, also ban the IP for enforcement
|
||||
if mac_banned do
|
||||
Context.ban_ip_address(state.ip, "Enforcing account ban, account #{username}", false, 4)
|
||||
end
|
||||
|
||||
response = Packets.get_login_failed(3)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
else
|
||||
# Authenticate with database
|
||||
case Context.authenticate_user(username, password, state.ip) do
|
||||
{:ok, account_info} ->
|
||||
# Check if account is banned (perm or temp)
|
||||
temp_ban_info = Context.get_temp_ban_info(account_info.account_id)
|
||||
|
||||
if temp_ban_info do
|
||||
Logger.warning("Temp banned account attempted login: #{username}")
|
||||
response = Packets.get_temp_ban(
|
||||
format_timestamp(temp_ban_info.expires),
|
||||
temp_ban_info.reason || ""
|
||||
)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
else
|
||||
# Check if already logged in - kick other session
|
||||
login_state = Context.get_login_state(account_info.account_id)
|
||||
|
||||
if login_state > 0 do
|
||||
Logger.warning("Account already logged in, kicking other session: #{username}")
|
||||
# Kick the existing session
|
||||
kick_existing_session(account_info.account_id, username)
|
||||
# Small delay to allow kick to process
|
||||
Process.sleep(100)
|
||||
end
|
||||
|
||||
# Update login state to logged in
|
||||
Context.update_login_state(account_info.account_id, 2, state.ip)
|
||||
|
||||
# Send success response
|
||||
response = Packets.get_auth_success(
|
||||
account_info.account_id,
|
||||
account_info.username,
|
||||
account_info.gender,
|
||||
account_info.is_gm,
|
||||
account_info.second_password
|
||||
)
|
||||
# Send success response
|
||||
response = Packets.get_auth_success(
|
||||
account_info.account_id,
|
||||
account_info.username,
|
||||
account_info.gender,
|
||||
account_info.is_gm,
|
||||
account_info.second_password
|
||||
)
|
||||
|
||||
new_state =
|
||||
state
|
||||
|> Map.put(:logged_in, true)
|
||||
|> Map.put(:account_id, account_info.account_id)
|
||||
|> Map.put(:account_name, account_info.username)
|
||||
|> Map.put(:gender, account_info.gender)
|
||||
|> Map.put(:is_gm, account_info.is_gm)
|
||||
|> Map.put(:second_password, account_info.second_password)
|
||||
|> Map.put(:login_attempts, 0)
|
||||
new_state =
|
||||
state
|
||||
|> Map.put(:logged_in, true)
|
||||
|> Map.put(:account_id, account_info.account_id)
|
||||
|> Map.put(:account_name, account_info.username)
|
||||
|> Map.put(:gender, account_info.gender)
|
||||
|> Map.put(:is_gm, account_info.is_gm)
|
||||
|> Map.put(:second_password, account_info.second_password)
|
||||
|> Map.put(:login_attempts, 0)
|
||||
|
||||
send_packet(state, response)
|
||||
{:ok, new_state}
|
||||
send_packet(state, response)
|
||||
{:ok, new_state}
|
||||
end
|
||||
|
||||
{:error, :invalid_credentials} ->
|
||||
# Increment login attempts
|
||||
login_attempts = Map.get(state, :login_attempts, 0) + 1
|
||||
{:error, :invalid_credentials} ->
|
||||
# Increment login attempts
|
||||
login_attempts = Map.get(state, :login_attempts, 0) + 1
|
||||
|
||||
if login_attempts > 5 do
|
||||
Logger.warning("Too many login attempts from #{state.ip}")
|
||||
{:disconnect, :too_many_attempts}
|
||||
else
|
||||
# Send login failed (reason 4 = incorrect password)
|
||||
response = Packets.get_login_failed(4)
|
||||
if login_attempts > 5 do
|
||||
Logger.warning("Too many login attempts from #{state.ip}")
|
||||
{:disconnect, :too_many_attempts}
|
||||
else
|
||||
# Send login failed (reason 4 = incorrect password)
|
||||
response = Packets.get_login_failed(4)
|
||||
send_packet(state, response)
|
||||
|
||||
new_state = Map.put(state, :login_attempts, login_attempts)
|
||||
{:ok, new_state}
|
||||
end
|
||||
|
||||
{:error, :account_not_found} ->
|
||||
# Send login failed (reason 5 = not registered ID)
|
||||
response = Packets.get_login_failed(5)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
|
||||
new_state = Map.put(state, :login_attempts, login_attempts)
|
||||
{:ok, new_state}
|
||||
end
|
||||
{:error, :already_logged_in} ->
|
||||
# Try to kick the existing session and allow retry
|
||||
Logger.warning("Already logged in, attempting to kick session for: #{username}")
|
||||
kick_existing_session_by_name(username)
|
||||
Process.sleep(100)
|
||||
|
||||
# Send login failed (reason 7 = already logged in) but client can retry
|
||||
response = Packets.get_login_failed(7)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
|
||||
{:error, :account_not_found} ->
|
||||
# Send login failed (reason 5 = not registered ID)
|
||||
response = Packets.get_login_failed(5)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
|
||||
{:error, :already_logged_in} ->
|
||||
# Send login failed (reason 7 = already logged in)
|
||||
response = Packets.get_login_failed(7)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
|
||||
{:error, :banned} ->
|
||||
# TODO: Check temp ban vs perm ban
|
||||
response = Packets.get_perm_ban(0)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
{:error, :banned} ->
|
||||
response = Packets.get_perm_ban(0)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -245,8 +287,11 @@ defmodule Odinsea.Login.Handler do
|
||||
# {:ok, state}
|
||||
# else
|
||||
|
||||
# TODO: Load character list from database
|
||||
# Load character list from database
|
||||
characters = load_characters(state.account_id, world_id)
|
||||
|
||||
# Store character IDs in state for later validation
|
||||
char_ids = Enum.map(characters, & &1.id)
|
||||
|
||||
response = Packets.get_char_list(
|
||||
characters,
|
||||
@@ -260,6 +305,7 @@ defmodule Odinsea.Login.Handler do
|
||||
state
|
||||
|> Map.put(:world, world_id)
|
||||
|> Map.put(:channel, actual_channel)
|
||||
|> Map.put(:character_ids, char_ids)
|
||||
|
||||
{:ok, new_state}
|
||||
end
|
||||
@@ -282,7 +328,7 @@ defmodule Odinsea.Login.Handler do
|
||||
else
|
||||
{char_name, _packet} = In.decode_string(packet)
|
||||
|
||||
# TODO: Check if name is forbidden or already exists
|
||||
# Check if name is forbidden or already exists
|
||||
name_used = check_name_used(char_name, state)
|
||||
|
||||
response = Packets.get_char_name_response(char_name, name_used)
|
||||
@@ -318,8 +364,8 @@ defmodule Odinsea.Login.Handler do
|
||||
{:disconnect, :not_logged_in}
|
||||
else
|
||||
{name, packet} = In.decode_string(packet)
|
||||
{job_type, packet} = In.decode_int(packet)
|
||||
{_dual_blade, packet} = In.decode_short(packet)
|
||||
{job_type_int, packet} = In.decode_int(packet)
|
||||
{dual_blade, packet} = In.decode_short(packet)
|
||||
{gender, packet} = In.decode_byte(packet)
|
||||
{face, packet} = In.decode_int(packet)
|
||||
{hair, packet} = In.decode_int(packet)
|
||||
@@ -330,17 +376,64 @@ defmodule Odinsea.Login.Handler do
|
||||
{shoes, packet} = In.decode_int(packet)
|
||||
{weapon, _packet} = In.decode_int(packet)
|
||||
|
||||
Logger.info("Create character: name=#{name}, job_type=#{job_type}")
|
||||
|
||||
# TODO: Validate appearance items
|
||||
# TODO: Create character in database
|
||||
# TODO: Add default items and quests
|
||||
|
||||
# For now, send success stub
|
||||
response = Packets.get_add_new_char_entry(%{}, false) # TODO: Pass actual character
|
||||
send_packet(state, response)
|
||||
|
||||
{:ok, state}
|
||||
Logger.info("Create character: name=#{name}, job_type=#{job_type_int}")
|
||||
|
||||
# Validate name is not forbidden and doesn't exist
|
||||
if check_name_used(name, state) do
|
||||
response = Packets.get_add_new_char_entry(nil, false)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
else
|
||||
# TODO: Validate appearance items are eligible for gender/job type
|
||||
# For now, accept the items as provided
|
||||
|
||||
# Create character with default stats
|
||||
job_type = JobType.from_int(job_type_int)
|
||||
default_stats = Context.get_default_stats_for_job(job_type_int, dual_blade)
|
||||
default_map = Context.get_default_map_for_job(job_type_int)
|
||||
|
||||
# Combine hair with hair color
|
||||
final_hair = hair + hair_color
|
||||
|
||||
# Build character attributes
|
||||
attrs = Map.merge(default_stats, %{
|
||||
name: name,
|
||||
accountid: state.account_id,
|
||||
world: state.world,
|
||||
face: face,
|
||||
hair: final_hair,
|
||||
gender: gender,
|
||||
skin: skin_color,
|
||||
map: default_map
|
||||
})
|
||||
|
||||
case Context.create_character(attrs) do
|
||||
{:ok, character} ->
|
||||
# Add default items to character inventory
|
||||
:ok = add_default_items(character.id, top, bottom, shoes, weapon, job_type_int)
|
||||
|
||||
# Add job-specific starter items and quests
|
||||
:ok = add_job_specific_starters(character.id, job_type_int)
|
||||
|
||||
Logger.info("Character created successfully: id=#{character.id}, name=#{name}")
|
||||
|
||||
# Reload character with full data
|
||||
char_data = Context.load_character(character.id)
|
||||
response = Packets.get_add_new_char_entry(char_data, true)
|
||||
send_packet(state, response)
|
||||
|
||||
# Add character ID to state's character list
|
||||
new_char_ids = [character.id | Map.get(state, :character_ids, [])]
|
||||
new_state = Map.put(state, :character_ids, new_char_ids)
|
||||
{:ok, new_state}
|
||||
|
||||
{:error, changeset} ->
|
||||
Logger.error("Failed to create character: #{inspect(changeset.errors)}")
|
||||
response = Packets.get_add_new_char_entry(nil, false)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -366,28 +459,70 @@ defmodule Odinsea.Login.Handler do
|
||||
Handles character deletion.
|
||||
|
||||
Packet structure:
|
||||
- byte: has_spw (1 if second password provided)
|
||||
- string: second_password (if enabled)
|
||||
- string: asia_password (legacy, usually empty)
|
||||
- int: character_id
|
||||
"""
|
||||
def on_delete_character(packet, state) do
|
||||
if not Map.get(state, :logged_in, false) do
|
||||
{:disconnect, :not_logged_in}
|
||||
else
|
||||
# TODO: Read second password if enabled
|
||||
{_spw, packet} = In.decode_string(packet)
|
||||
{has_spw, packet} = In.decode_byte(packet)
|
||||
|
||||
# Read second password if enabled
|
||||
{spw, packet} =
|
||||
if has_spw > 0 do
|
||||
In.decode_string(packet)
|
||||
else
|
||||
{"", packet}
|
||||
end
|
||||
|
||||
{_asia_pw, packet} = In.decode_string(packet)
|
||||
{character_id, _packet} = In.decode_int(packet)
|
||||
|
||||
Logger.info("Delete character: character_id=#{character_id}")
|
||||
Logger.info("Delete character: character_id=#{character_id}, account=#{state.account_name}")
|
||||
|
||||
# Validate second password if account has one
|
||||
spw_valid = validate_second_password(state, spw)
|
||||
|
||||
result =
|
||||
cond do
|
||||
not spw_valid ->
|
||||
Logger.warning("Delete character: invalid second password")
|
||||
12 # Wrong Password
|
||||
|
||||
not character_belongs_to_account?(character_id, state) ->
|
||||
Logger.warning("Delete character: character does not belong to account")
|
||||
1 # General error
|
||||
|
||||
true ->
|
||||
# Attempt to delete character
|
||||
case Context.delete_character(character_id) do
|
||||
:ok ->
|
||||
Logger.info("Character deleted successfully: id=#{character_id}")
|
||||
# Remove from state's character list
|
||||
0 # Success
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to delete character: #{inspect(reason)}")
|
||||
1 # General error
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Validate second password
|
||||
# TODO: Check if character belongs to account
|
||||
# TODO: Delete character from database
|
||||
|
||||
# For now, send success stub
|
||||
response = Packets.get_delete_char_response(character_id, 0)
|
||||
response = Packets.get_delete_char_response(character_id, result)
|
||||
send_packet(state, response)
|
||||
|
||||
# Update state if successful
|
||||
new_state =
|
||||
if result == 0 do
|
||||
new_char_ids = Enum.reject(Map.get(state, :character_ids, []), &(&1 == character_id))
|
||||
Map.put(state, :character_ids, new_char_ids)
|
||||
else
|
||||
state
|
||||
end
|
||||
|
||||
{:ok, state}
|
||||
{:ok, new_state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -400,30 +535,84 @@ defmodule Odinsea.Login.Handler do
|
||||
Initiates migration to the selected channel.
|
||||
|
||||
Packet structure:
|
||||
- byte: set_spw (1 if setting second password)
|
||||
- int: character_id
|
||||
"""
|
||||
def on_select_character(packet, state) do
|
||||
if not Map.get(state, :logged_in, false) do
|
||||
{:disconnect, :not_logged_in}
|
||||
else
|
||||
{set_spw, packet} = In.decode_byte(packet)
|
||||
{character_id, _packet} = In.decode_int(packet)
|
||||
|
||||
Logger.info("Select character: character_id=#{character_id}, channel=#{state.channel}")
|
||||
|
||||
# TODO: Validate character belongs to account
|
||||
# TODO: Load character data
|
||||
# TODO: Register migration token with channel server
|
||||
|
||||
# Send migration command to connect to channel
|
||||
# TODO: Get actual channel IP/port
|
||||
channel_ip = "127.0.0.1"
|
||||
channel_port = 8585 + (state.channel - 1)
|
||||
|
||||
response = Packets.get_server_ip(false, channel_ip, channel_port, character_id)
|
||||
send_packet(state, response)
|
||||
|
||||
new_state = Map.put(state, :character_id, character_id)
|
||||
{:ok, new_state}
|
||||
|
||||
# Validate character belongs to account
|
||||
unless character_belongs_to_account?(character_id, state) do
|
||||
Logger.warning("Select character: character does not belong to account")
|
||||
{:disconnect, :invalid_character}
|
||||
else
|
||||
# Handle setting second password if requested
|
||||
if set_spw > 0 do
|
||||
{new_spw, _} = In.decode_string(packet)
|
||||
|
||||
# Validate second password length
|
||||
if String.length(new_spw) < 6 || String.length(new_spw) > 16 do
|
||||
response = Packets.get_second_pw_error(0x14)
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
else
|
||||
# Update second password
|
||||
Context.update_second_password(state.account_id, new_spw)
|
||||
Logger.info("Second password set for account: #{state.account_name}")
|
||||
|
||||
# Continue with character selection
|
||||
do_character_migration(character_id, state)
|
||||
end
|
||||
else
|
||||
do_character_migration(character_id, state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_character_migration(character_id, state) do
|
||||
# Load character data
|
||||
case Context.load_character(character_id) do
|
||||
nil ->
|
||||
Logger.error("Failed to load character: id=#{character_id}")
|
||||
{:disconnect, :character_not_found}
|
||||
|
||||
character ->
|
||||
# Register migration token with channel server
|
||||
migration_token = generate_migration_token()
|
||||
|
||||
# Store migration info in Redis/ETS for channel server
|
||||
:ok = register_migration_token(
|
||||
migration_token,
|
||||
character_id,
|
||||
state.account_id,
|
||||
state.channel
|
||||
)
|
||||
|
||||
# Update login state to server transition
|
||||
Context.update_login_state(state.account_id, 1, state.ip)
|
||||
|
||||
# Get channel IP and port
|
||||
{channel_ip, channel_port} = get_channel_endpoint(state.channel)
|
||||
|
||||
Logger.info("Character migration: char=#{character.name} to channel #{state.channel} (#{channel_ip}:#{channel_port})")
|
||||
|
||||
# Send migration command
|
||||
response = Packets.get_server_ip(false, channel_ip, channel_port, character_id)
|
||||
send_packet(state, response)
|
||||
|
||||
new_state =
|
||||
state
|
||||
|> Map.put(:character_id, character_id)
|
||||
|> Map.put(:migration_token, migration_token)
|
||||
|
||||
{:ok, new_state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -436,21 +625,27 @@ defmodule Odinsea.Login.Handler do
|
||||
|
||||
Packet structure:
|
||||
- string: second_password
|
||||
- int: character_id
|
||||
"""
|
||||
def on_check_spw_request(packet, state) do
|
||||
{spw, _packet} = In.decode_string(packet)
|
||||
|
||||
# TODO: Validate second password
|
||||
stored_spw = Map.get(state, :second_password)
|
||||
|
||||
if stored_spw == nil or stored_spw == spw do
|
||||
# Success - continue with operation
|
||||
{:ok, state}
|
||||
{spw, packet} = In.decode_string(packet)
|
||||
{character_id, _packet} = In.decode_int(packet)
|
||||
|
||||
# Validate character belongs to account
|
||||
unless character_belongs_to_account?(character_id, state) do
|
||||
{:disconnect, :invalid_character}
|
||||
else
|
||||
# Failure - send error
|
||||
response = Packets.get_second_pw_error(15) # Incorrect SPW
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
stored_spw = Map.get(state, :second_password)
|
||||
|
||||
if stored_spw == nil or stored_spw == spw do
|
||||
# Success - migrate to channel
|
||||
do_character_migration(character_id, state)
|
||||
else
|
||||
# Failure - send error
|
||||
response = Packets.get_second_pw_error(15) # Incorrect SPW
|
||||
send_packet(state, response)
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -535,4 +730,163 @@ defmodule Odinsea.Login.Handler do
|
||||
Context.forbidden_name?(char_name) or
|
||||
Context.character_name_exists?(char_name)
|
||||
end
|
||||
|
||||
defp character_belongs_to_account?(character_id, state) do
|
||||
char_ids = Map.get(state, :character_ids, [])
|
||||
character_id in char_ids
|
||||
end
|
||||
|
||||
defp validate_second_password(state, provided_spw) do
|
||||
stored_spw = Map.get(state, :second_password)
|
||||
|
||||
# If no second password set, accept any
|
||||
if stored_spw == nil || stored_spw == "" do
|
||||
true
|
||||
else
|
||||
stored_spw == provided_spw
|
||||
end
|
||||
end
|
||||
|
||||
defp kick_existing_session(account_id, username) do
|
||||
# TODO: Implement session kicking via World server or Redis pub/sub
|
||||
# For now, just update login state to force disconnect on next tick
|
||||
Context.update_login_state(account_id, 0)
|
||||
|
||||
# Publish kick message to Redis for other servers
|
||||
Odinsea.Database.Redis.publish("kick_session", %{account_id: account_id, username: username})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp kick_existing_session_by_name(username) do
|
||||
# Find account by name and kick
|
||||
case Context.get_account_by_name(username) do
|
||||
nil -> :ok
|
||||
account -> kick_existing_session(account.id, username)
|
||||
end
|
||||
end
|
||||
|
||||
defp add_default_items(character_id, top, bottom, shoes, weapon, _job_type) do
|
||||
# Add equipped items
|
||||
Context.create_inventory_item(character_id, :equipped, %{
|
||||
item_id: top,
|
||||
position: -5,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
if bottom > 0 do
|
||||
Context.create_inventory_item(character_id, :equipped, %{
|
||||
item_id: bottom,
|
||||
position: -6,
|
||||
quantity: 1
|
||||
})
|
||||
end
|
||||
|
||||
Context.create_inventory_item(character_id, :equipped, %{
|
||||
item_id: shoes,
|
||||
position: -7,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
Context.create_inventory_item(character_id, :equipped, %{
|
||||
item_id: weapon,
|
||||
position: -11,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
# Add starter potions
|
||||
Context.create_inventory_item(character_id, :use, %{
|
||||
item_id: 2000013,
|
||||
position: 0,
|
||||
quantity: 100
|
||||
})
|
||||
|
||||
Context.create_inventory_item(character_id, :use, %{
|
||||
item_id: 2000014,
|
||||
position: 0,
|
||||
quantity: 100
|
||||
})
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp add_job_specific_starters(character_id, job_type) do
|
||||
case job_type do
|
||||
0 -> # Resistance
|
||||
Context.create_inventory_item(character_id, :etc, %{
|
||||
item_id: 4161001,
|
||||
position: 0,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
1 -> # Adventurer
|
||||
Context.create_inventory_item(character_id, :etc, %{
|
||||
item_id: 4161001,
|
||||
position: 0,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
2 -> # Cygnus
|
||||
# Add starter quests
|
||||
Context.set_quest_progress(character_id, 20022, 1, "1")
|
||||
Context.set_quest_progress(character_id, 20010, 1, nil)
|
||||
|
||||
Context.create_inventory_item(character_id, :etc, %{
|
||||
item_id: 4161047,
|
||||
position: 0,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
3 -> # Aran
|
||||
Context.create_inventory_item(character_id, :etc, %{
|
||||
item_id: 4161048,
|
||||
position: 0,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
4 -> # Evan
|
||||
Context.create_inventory_item(character_id, :etc, %{
|
||||
item_id: 4161052,
|
||||
position: 0,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp generate_migration_token do
|
||||
# Generate a unique migration token
|
||||
:crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower)
|
||||
end
|
||||
|
||||
defp register_migration_token(token, character_id, account_id, channel) do
|
||||
# Store in Redis with TTL for channel server to pick up
|
||||
Odinsea.Database.Redis.setex(
|
||||
"migration:#{token}",
|
||||
30, # 30 second TTL
|
||||
Jason.encode!(%{
|
||||
character_id: character_id,
|
||||
account_id: account_id,
|
||||
channel: channel,
|
||||
timestamp: System.system_time(:second)
|
||||
})
|
||||
)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp get_channel_endpoint(channel) do
|
||||
# TODO: Get actual channel IP from World server config
|
||||
# For now, return localhost with calculated port
|
||||
ip = Application.get_env(:odinsea, :channel_ip, "127.0.0.1")
|
||||
base_port = Application.get_env(:odinsea, :channel_base_port, 8585)
|
||||
port = base_port + (channel - 1)
|
||||
{ip, port}
|
||||
end
|
||||
|
||||
defp format_timestamp(naive_datetime) do
|
||||
NaiveDateTime.to_string(naive_datetime)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -222,6 +222,21 @@ defmodule Odinsea.Net.Cipher.LoginCrypto do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Hashes a second password (PIC) using SHA-1.
|
||||
Used for second password storage.
|
||||
|
||||
## Parameters
|
||||
- password: Plain text second password
|
||||
|
||||
## Returns
|
||||
- Hex-encoded SHA-1 hash (lowercase)
|
||||
"""
|
||||
@spec hash_second_password(String.t()) :: String.t()
|
||||
def hash_second_password(password) when is_binary(password) do
|
||||
hex_sha1(password)
|
||||
end
|
||||
|
||||
# Private helper: hash a string with a given digest algorithm
|
||||
@spec hash_with_digest(String.t(), atom()) :: String.t()
|
||||
defp hash_with_digest(input, digest) do
|
||||
|
||||
@@ -279,6 +279,10 @@ defmodule Odinsea.Net.Opcodes do
|
||||
|
||||
# General
|
||||
def lp_alive_req(), do: 0x0D
|
||||
def lp_enable_action(), do: 0x0C
|
||||
def lp_set_field(), do: 0x14
|
||||
def lp_set_cash_shop_opened(), do: 0x15
|
||||
def lp_migrate_command(), do: 0x16
|
||||
|
||||
# Login
|
||||
def lp_login_status(), do: 0x01
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user