kimi gone wild

This commit is contained in:
ra
2026-02-14 23:12:33 -07:00
parent bbd205ecbe
commit 0222be36c5
98 changed files with 39726 additions and 309 deletions

View 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