444 lines
11 KiB
Elixir
444 lines
11 KiB
Elixir
defmodule Odinsea.Game.Movement.Path do
|
|
@moduledoc """
|
|
MovePath for mob movement (newer movement system).
|
|
Ported from Java MovePath.java
|
|
|
|
This is an alternative movement system used by mobs in newer
|
|
versions of MapleStory. It uses a more compact encoding.
|
|
|
|
Structure:
|
|
- Initial position (x, y, vx, vy)
|
|
- List of movement elements
|
|
- Optional passive data (keypad states, movement rect)
|
|
"""
|
|
|
|
import Bitwise
|
|
alias Odinsea.Net.Packet.In
|
|
|
|
defstruct [
|
|
:x, # Initial X position
|
|
:y, # Initial Y position
|
|
:vx, # Initial X velocity
|
|
:vy, # Initial Y velocity
|
|
elements: [], # List of MoveElem
|
|
key_pad_states: [], # Keypad states (passive mode)
|
|
move_rect: nil # Movement rectangle (passive mode)
|
|
]
|
|
|
|
@type t :: %__MODULE__{
|
|
x: integer() | nil,
|
|
y: integer() | nil,
|
|
vx: integer() | nil,
|
|
vy: integer() | nil,
|
|
elements: list(MoveElem.t()),
|
|
key_pad_states: list(integer()),
|
|
move_rect: map() | nil
|
|
}
|
|
|
|
defmodule MoveElem do
|
|
@moduledoc """
|
|
Individual movement element within a MovePath.
|
|
"""
|
|
|
|
@type t :: %__MODULE__{
|
|
attribute: integer(), # Movement type/attribute
|
|
x: integer(), # X position
|
|
y: integer(), # Y position
|
|
vx: integer(), # X velocity
|
|
vy: integer(), # Y velocity
|
|
fh: integer(), # Foothold
|
|
fall_start: integer(), # Fall start position
|
|
offset_x: integer(), # X offset
|
|
offset_y: integer(), # Y offset
|
|
sn: integer(), # Skill/stat number
|
|
move_action: integer(), # Move action/stance
|
|
elapse: integer() # Elapsed time
|
|
}
|
|
|
|
defstruct [
|
|
:attribute,
|
|
:x,
|
|
:y,
|
|
:vx,
|
|
:vy,
|
|
:fh,
|
|
:fall_start,
|
|
:offset_x,
|
|
:offset_y,
|
|
:sn,
|
|
:move_action,
|
|
:elapse
|
|
]
|
|
end
|
|
|
|
@doc """
|
|
Decodes a MovePath from a packet.
|
|
|
|
## Parameters
|
|
- packet: The incoming packet
|
|
- passive: Whether to decode passive data (keypad, rect)
|
|
|
|
## Returns
|
|
%MovePath{} struct with decoded data
|
|
"""
|
|
def decode(packet, passive \\ false) do
|
|
old_x = In.decode_short(packet)
|
|
old_y = In.decode_short(packet)
|
|
old_vx = In.decode_short(packet)
|
|
old_vy = In.decode_short(packet)
|
|
|
|
count = In.decode_byte(packet)
|
|
|
|
{elements, final_x, final_y, final_vx, final_vy, _fh_last} =
|
|
decode_elements(packet, count, old_x, old_y, old_vx, old_vy, [])
|
|
|
|
path = %__MODULE__{
|
|
x: old_x,
|
|
y: old_y,
|
|
vx: old_vx,
|
|
vy: old_vy,
|
|
elements: Enum.reverse(elements)
|
|
}
|
|
|
|
if passive do
|
|
{key_pad_states, move_rect} = decode_passive_data(packet)
|
|
%{path |
|
|
x: final_x,
|
|
y: final_y,
|
|
vx: final_vx,
|
|
vy: final_vy,
|
|
key_pad_states: key_pad_states,
|
|
move_rect: move_rect
|
|
}
|
|
else
|
|
%{path |
|
|
x: final_x,
|
|
y: final_y,
|
|
vx: final_vx,
|
|
vy: final_vy
|
|
}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Encodes a MovePath to binary for packet output.
|
|
"""
|
|
def encode(%__MODULE__{} = path, _passive \\ false) do
|
|
elements_data = Enum.map_join(path.elements, &encode_element/1)
|
|
|
|
<<path.x::16-little, path.y::16-little,
|
|
path.vx::16-little, path.vy::16-little,
|
|
length(path.elements)::8,
|
|
elements_data::binary>>
|
|
end
|
|
|
|
@doc """
|
|
Gets the final position from the MovePath.
|
|
"""
|
|
def get_final_position(%__MODULE__{} = path) do
|
|
case List.last(path.elements) do
|
|
nil -> %{x: path.x, y: path.y}
|
|
elem -> %{x: elem.x, y: elem.y}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the final move action/stance from the MovePath.
|
|
"""
|
|
def get_final_action(%__MODULE__{} = path) do
|
|
case List.last(path.elements) do
|
|
nil -> 0
|
|
elem -> elem.move_action
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the final foothold from the MovePath.
|
|
"""
|
|
def get_final_foothold(%__MODULE__{} = path) do
|
|
case List.last(path.elements) do
|
|
nil -> 0
|
|
elem -> elem.fh
|
|
end
|
|
end
|
|
|
|
# ============================================================================
|
|
# Private Functions
|
|
# ============================================================================
|
|
|
|
defp decode_elements(_packet, 0, old_x, old_y, old_vx, old_vy, acc),
|
|
do: {acc, old_x, old_y, old_vx, old_vy, 0}
|
|
|
|
defp decode_elements(packet, count, old_x, old_y, old_vx, old_vy, acc) do
|
|
attr = In.decode_byte(packet)
|
|
|
|
{elem, new_x, new_y, new_vx, new_vy, _fh_last} =
|
|
case attr do
|
|
# Absolute with foothold
|
|
a when a in [0, 6, 13, 15, 37, 38] ->
|
|
x = In.decode_short(packet)
|
|
y = In.decode_short(packet)
|
|
vx = In.decode_short(packet)
|
|
vy = In.decode_short(packet)
|
|
fh = In.decode_short(packet)
|
|
|
|
fall_start = if attr == 13, do: In.decode_short(packet), else: 0
|
|
offset_x = In.decode_short(packet)
|
|
offset_y = In.decode_short(packet)
|
|
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: x,
|
|
y: y,
|
|
vx: vx,
|
|
vy: vy,
|
|
fh: fh,
|
|
fall_start: fall_start,
|
|
offset_x: offset_x,
|
|
offset_y: offset_y
|
|
}
|
|
{elem, x, y, vx, vy, fh}
|
|
|
|
# Velocity only
|
|
a when a in [1, 2, 14, 17, 19, 32, 33, 34, 35] ->
|
|
vx = In.decode_short(packet)
|
|
vy = In.decode_short(packet)
|
|
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: old_x,
|
|
y: old_y,
|
|
vx: vx,
|
|
vy: vy,
|
|
fh: 0
|
|
}
|
|
{elem, old_x, old_y, vx, vy, 0}
|
|
|
|
# Position with foothold
|
|
a when a in [3, 4, 5, 7, 8, 9, 11] ->
|
|
x = In.decode_short(packet)
|
|
y = In.decode_short(packet)
|
|
fh = In.decode_short(packet)
|
|
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: x,
|
|
y: y,
|
|
vx: 0,
|
|
vy: 0,
|
|
fh: fh
|
|
}
|
|
{elem, x, y, 0, 0, fh}
|
|
|
|
# Stat change
|
|
10 ->
|
|
sn = In.decode_byte(packet)
|
|
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
sn: sn,
|
|
x: old_x,
|
|
y: old_y,
|
|
vx: 0,
|
|
vy: 0,
|
|
fh: 0,
|
|
elapse: 0,
|
|
move_action: 0
|
|
}
|
|
{elem, old_x, old_y, 0, 0, 0}
|
|
|
|
# Start fall down
|
|
12 ->
|
|
vx = In.decode_short(packet)
|
|
vy = In.decode_short(packet)
|
|
fall_start = In.decode_short(packet)
|
|
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: old_x,
|
|
y: old_y,
|
|
vx: vx,
|
|
vy: vy,
|
|
fh: 0,
|
|
fall_start: fall_start
|
|
}
|
|
{elem, old_x, old_y, vx, vy, 0}
|
|
|
|
# Flying block
|
|
18 ->
|
|
x = In.decode_short(packet)
|
|
y = In.decode_short(packet)
|
|
vx = In.decode_short(packet)
|
|
vy = In.decode_short(packet)
|
|
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: x,
|
|
y: y,
|
|
vx: vx,
|
|
vy: vy,
|
|
fh: 0
|
|
}
|
|
{elem, x, y, vx, vy, 0}
|
|
|
|
# No change (21-31)
|
|
a when a in [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] ->
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: old_x,
|
|
y: old_y,
|
|
vx: old_vx,
|
|
vy: old_vy,
|
|
fh: 0
|
|
}
|
|
{elem, old_x, old_y, old_vx, old_vy, 0}
|
|
|
|
# Special case 36
|
|
36 ->
|
|
x = In.decode_short(packet)
|
|
y = In.decode_short(packet)
|
|
vx = In.decode_short(packet)
|
|
vy = In.decode_short(packet)
|
|
fh = In.decode_short(packet)
|
|
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: x,
|
|
y: y,
|
|
vx: vx,
|
|
vy: vy,
|
|
fh: fh
|
|
}
|
|
{elem, x, y, vx, vy, fh}
|
|
|
|
# Unknown attribute - skip gracefully
|
|
_unknown ->
|
|
elem = %MoveElem{
|
|
attribute: attr,
|
|
x: old_x,
|
|
y: old_y,
|
|
vx: old_vx,
|
|
vy: old_vy,
|
|
fh: 0
|
|
}
|
|
{elem, old_x, old_y, old_vx, old_vy, 0}
|
|
end
|
|
|
|
# Read move action and elapse (except for stat change)
|
|
{elem, new_x, new_y, new_vx, new_vy} =
|
|
if attr != 10 do
|
|
move_action = In.decode_byte(packet)
|
|
elapse = In.decode_short(packet)
|
|
|
|
{%{elem |
|
|
move_action: move_action,
|
|
elapse: elapse
|
|
}, elem.x, elem.y, elem.vx, elem.vy}
|
|
else
|
|
{elem, new_x, new_y, new_vx, new_vy}
|
|
end
|
|
|
|
decode_elements(
|
|
packet,
|
|
count - 1,
|
|
new_x,
|
|
new_y,
|
|
new_vx,
|
|
new_vy,
|
|
[elem | acc]
|
|
)
|
|
end
|
|
|
|
defp decode_passive_data(packet) do
|
|
keys = In.decode_byte(packet)
|
|
|
|
key_pad_states =
|
|
if keys > 0 do
|
|
decode_keypad_states(packet, keys, 0, [])
|
|
else
|
|
[]
|
|
end
|
|
|
|
move_rect = %{
|
|
left: In.decode_short(packet),
|
|
top: In.decode_short(packet),
|
|
right: In.decode_short(packet),
|
|
bottom: In.decode_short(packet)
|
|
}
|
|
|
|
{Enum.reverse(key_pad_states), move_rect}
|
|
end
|
|
|
|
defp decode_keypad_states(_packet, 0, _value, acc), do: acc
|
|
|
|
defp decode_keypad_states(packet, remaining, value, acc) do
|
|
{new_value, decoded} =
|
|
if rem(length(acc), 2) != 0 do
|
|
{bsr(value, 4), band(value, 0x0F)}
|
|
else
|
|
v = In.decode_byte(packet)
|
|
{v, band(v, 0x0F)}
|
|
end
|
|
|
|
decode_keypad_states(packet, remaining - 1, new_value, [decoded | acc])
|
|
end
|
|
|
|
defp encode_element(%MoveElem{} = elem) do
|
|
attr = elem.attribute
|
|
|
|
base = <<attr::8>>
|
|
|
|
data =
|
|
case attr do
|
|
a when a in [0, 6, 13, 15, 37, 38] ->
|
|
<<elem.x::16-little, elem.y::16-little,
|
|
elem.vx::16-little, elem.vy::16-little,
|
|
elem.fh::16-little>> <>
|
|
if attr == 13 do
|
|
<<elem.fall_start::16-little>>
|
|
else
|
|
<<>>
|
|
end <>
|
|
<<elem.offset_x::16-little, elem.offset_y::16-little>>
|
|
|
|
a when a in [1, 2, 14, 17, 19, 32, 33, 34, 35] ->
|
|
<<elem.vx::16-little, elem.vy::16-little>>
|
|
|
|
a when a in [3, 4, 5, 7, 8, 9, 11] ->
|
|
<<elem.x::16-little, elem.y::16-little,
|
|
elem.fh::16-little>>
|
|
|
|
10 ->
|
|
<<elem.sn::8>>
|
|
|
|
12 ->
|
|
<<elem.vx::16-little, elem.vy::16-little,
|
|
elem.fall_start::16-little>>
|
|
|
|
18 ->
|
|
<<elem.x::16-little, elem.y::16-little,
|
|
elem.vx::16-little, elem.vy::16-little>>
|
|
|
|
a when a in [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] ->
|
|
<<>>
|
|
|
|
36 ->
|
|
<<elem.x::16-little, elem.y::16-little,
|
|
elem.vx::16-little, elem.vy::16-little,
|
|
elem.fh::16-little>>
|
|
|
|
_ ->
|
|
<<>>
|
|
end
|
|
|
|
footer =
|
|
if attr != 10 do
|
|
<<elem.move_action::8, elem.elapse::16-little>>
|
|
else
|
|
<<>>
|
|
end
|
|
|
|
base <> data <> footer
|
|
end
|
|
end
|