kimi gone wild
This commit is contained in:
238
lib/odinsea/game/damage_calc.ex
Normal file
238
lib/odinsea/game/damage_calc.ex
Normal file
@@ -0,0 +1,238 @@
|
||||
defmodule Odinsea.Game.DamageCalc do
|
||||
use Bitwise
|
||||
@moduledoc """
|
||||
Damage calculation and application module.
|
||||
Ported from src/handling/channel/handler/DamageParse.java
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Game.{AttackInfo, Character, Map, Monster}
|
||||
alias Odinsea.Net.Packet.Out
|
||||
alias Odinsea.Net.Opcodes
|
||||
alias Odinsea.Channel.Packets
|
||||
|
||||
@doc """
|
||||
Apply attack to monsters on the map.
|
||||
Ported from DamageParse.applyAttack()
|
||||
|
||||
Returns {:ok, total_damage} or {:error, reason}
|
||||
"""
|
||||
def apply_attack(attack_info, character_pid, map_pid, channel_id) do
|
||||
with {:ok, character} <- Character.get_state(character_pid),
|
||||
{:ok, map_data} <- Map.get_monsters(map_pid) do
|
||||
# Check if character is alive
|
||||
if not character.alive? do
|
||||
Logger.warning("Character #{character.name} attacking while dead")
|
||||
{:error, :attacking_while_dead}
|
||||
else
|
||||
# Calculate max damage per monster
|
||||
max_damage_per_monster = calculate_max_damage(character, attack_info)
|
||||
|
||||
# Apply damage to each targeted monster
|
||||
total_damage =
|
||||
Enum.reduce(attack_info.all_damage, 0, fn damage_entry, acc_total ->
|
||||
apply_damage_to_monster(
|
||||
damage_entry,
|
||||
attack_info,
|
||||
character,
|
||||
map_pid,
|
||||
channel_id,
|
||||
max_damage_per_monster
|
||||
) + acc_total
|
||||
end)
|
||||
|
||||
Logger.debug("Attack applied: #{total_damage} total damage to #{length(attack_info.all_damage)} monsters")
|
||||
|
||||
# Broadcast attack packet to other players
|
||||
broadcast_attack(attack_info, character, map_pid, channel_id)
|
||||
|
||||
{:ok, total_damage}
|
||||
end
|
||||
else
|
||||
error ->
|
||||
Logger.error("Failed to apply attack: #{inspect(error)}")
|
||||
{:error, :apply_failed}
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Private Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
defp apply_damage_to_monster(damage_entry, attack_info, character, map_pid, channel_id, max_damage) do
|
||||
# Calculate total damage to this monster
|
||||
total_damage =
|
||||
Enum.reduce(damage_entry.damages, 0, fn {damage, _crit}, acc ->
|
||||
# Cap damage at max allowed
|
||||
capped_damage = min(damage, trunc(max_damage))
|
||||
acc + capped_damage
|
||||
end)
|
||||
|
||||
if total_damage > 0 do
|
||||
# Apply damage via Map module
|
||||
case Map.damage_monster(map_pid, damage_entry.mob_oid, character.id, total_damage) do
|
||||
{:ok, :killed} ->
|
||||
Logger.debug("Monster #{damage_entry.mob_oid} killed by #{character.name}")
|
||||
total_damage
|
||||
|
||||
{:ok, :damaged} ->
|
||||
total_damage
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warning("Failed to damage monster #{damage_entry.mob_oid}: #{inspect(reason)}")
|
||||
0
|
||||
end
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
defp calculate_max_damage(character, attack_info) do
|
||||
# Base damage calculation
|
||||
# TODO: Implement full damage formula with stats, weapon attack, skill damage, etc.
|
||||
# For now, use a simple formula based on level and stats
|
||||
|
||||
base_damage =
|
||||
cond do
|
||||
# Magic attack
|
||||
attack_info.attack_type == :magic ->
|
||||
character.stats.int * 5 + character.stats.luk + character.level * 10
|
||||
|
||||
# Ranged attack
|
||||
attack_info.attack_type == :ranged or
|
||||
attack_info.attack_type == :ranged_with_shadowpartner ->
|
||||
character.stats.dex * 5 + character.stats.str + character.level * 10
|
||||
|
||||
# Melee attack
|
||||
true ->
|
||||
character.stats.str * 5 + character.stats.dex + character.level * 10
|
||||
end
|
||||
|
||||
# Apply skill multiplier if skill is used
|
||||
skill_multiplier =
|
||||
if attack_info.skill > 0 do
|
||||
# TODO: Get actual skill damage multiplier from SkillFactory
|
||||
2.0
|
||||
else
|
||||
1.0
|
||||
end
|
||||
|
||||
# Calculate max damage per hit
|
||||
max_damage_per_hit = base_damage * skill_multiplier
|
||||
|
||||
# For now, return a reasonable cap
|
||||
# TODO: Implement actual damage cap from config
|
||||
min(max_damage_per_hit, 999_999)
|
||||
end
|
||||
|
||||
defp broadcast_attack(attack_info, character, map_pid, channel_id) do
|
||||
# Build attack packet based on attack type
|
||||
attack_packet =
|
||||
case attack_info.attack_type do
|
||||
:melee ->
|
||||
build_melee_attack_packet(attack_info, character)
|
||||
|
||||
:ranged ->
|
||||
build_ranged_attack_packet(attack_info, character)
|
||||
|
||||
:magic ->
|
||||
build_magic_attack_packet(attack_info, character)
|
||||
|
||||
_ ->
|
||||
build_melee_attack_packet(attack_info, character)
|
||||
end
|
||||
|
||||
# Broadcast to all players on map except attacker
|
||||
Map.broadcast_except(
|
||||
character.map_id,
|
||||
channel_id,
|
||||
character.id,
|
||||
attack_packet
|
||||
)
|
||||
end
|
||||
|
||||
defp build_melee_attack_packet(attack_info, character) do
|
||||
packet =
|
||||
Out.new(Opcodes.lp_close_range_attack())
|
||||
|> Out.encode_int(character.id)
|
||||
|> Out.encode_byte(attack_info.tbyte)
|
||||
|> Out.encode_byte(character.stats.skill_level)
|
||||
|> Out.encode_int(attack_info.skill)
|
||||
|> Out.encode_byte(attack_info.display &&& 0xFF)
|
||||
|> Out.encode_byte((attack_info.display >>> 8) &&& 0xFF)
|
||||
|> Out.encode_byte(attack_info.speed)
|
||||
|> Out.encode_int(attack_info.last_attack_tick)
|
||||
|
||||
# Encode damage for each target
|
||||
packet =
|
||||
Enum.reduce(attack_info.all_damage, packet, fn damage_entry, acc ->
|
||||
acc
|
||||
|> Out.encode_int(damage_entry.mob_oid)
|
||||
|> encode_damage_hits(damage_entry.damages)
|
||||
end)
|
||||
|
||||
packet
|
||||
|> Out.encode_short(attack_info.position.x)
|
||||
|> Out.encode_short(attack_info.position.y)
|
||||
|> Out.to_data()
|
||||
end
|
||||
|
||||
defp build_ranged_attack_packet(attack_info, character) do
|
||||
packet =
|
||||
Out.new(Opcodes.lp_ranged_attack())
|
||||
|> Out.encode_int(character.id)
|
||||
|> Out.encode_byte(attack_info.tbyte)
|
||||
|> Out.encode_byte(character.stats.skill_level)
|
||||
|> Out.encode_int(attack_info.skill)
|
||||
|> Out.encode_byte(attack_info.display &&& 0xFF)
|
||||
|> Out.encode_byte((attack_info.display >>> 8) &&& 0xFF)
|
||||
|> Out.encode_byte(attack_info.speed)
|
||||
|> Out.encode_int(attack_info.last_attack_tick)
|
||||
|
||||
# Encode damage for each target
|
||||
packet =
|
||||
Enum.reduce(attack_info.all_damage, packet, fn damage_entry, acc ->
|
||||
acc
|
||||
|> Out.encode_int(damage_entry.mob_oid)
|
||||
|> encode_damage_hits(damage_entry.damages)
|
||||
end)
|
||||
|
||||
packet
|
||||
|> Out.encode_short(attack_info.position.x)
|
||||
|> Out.encode_short(attack_info.position.y)
|
||||
|> Out.to_data()
|
||||
end
|
||||
|
||||
defp build_magic_attack_packet(attack_info, character) do
|
||||
packet =
|
||||
Out.new(Opcodes.lp_magic_attack())
|
||||
|> Out.encode_int(character.id)
|
||||
|> Out.encode_byte(attack_info.tbyte)
|
||||
|> Out.encode_byte(character.stats.skill_level)
|
||||
|> Out.encode_int(attack_info.skill)
|
||||
|> Out.encode_byte(attack_info.display &&& 0xFF)
|
||||
|> Out.encode_byte((attack_info.display >>> 8) &&& 0xFF)
|
||||
|> Out.encode_byte(attack_info.speed)
|
||||
|> Out.encode_int(attack_info.last_attack_tick)
|
||||
|
||||
# Encode damage for each target
|
||||
packet =
|
||||
Enum.reduce(attack_info.all_damage, packet, fn damage_entry, acc ->
|
||||
acc
|
||||
|> Out.encode_int(damage_entry.mob_oid)
|
||||
|> encode_damage_hits(damage_entry.damages)
|
||||
end)
|
||||
|
||||
packet
|
||||
|> Out.encode_short(attack_info.position.x)
|
||||
|> Out.encode_short(attack_info.position.y)
|
||||
|> Out.to_data()
|
||||
end
|
||||
|
||||
defp encode_damage_hits(packet, damages) do
|
||||
Enum.reduce(damages, packet, fn {damage, _crit}, acc ->
|
||||
acc |> Out.encode_int(damage)
|
||||
end)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user