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,438 @@
defmodule Odinsea.AntiCheat.Validator do
@moduledoc """
Validation functions for anti-cheat detection.
Ported from: handling.channel.handler.DamageParse.java
This module provides validation for:
- Damage validation (checking against calculated max damage)
- Movement validation (speed hacking detection)
- Item validation (dupe detection, unavailable items)
- EXP validation (leveling too fast)
- Attack validation (skill timing, bullet count)
"""
require Logger
alias Odinsea.AntiCheat.CheatTracker
alias Odinsea.Game.Character
alias Odinsea.Constants.Game
# Maximum damage cap (from Plugin.java DamageCap)
@damage_cap 9_999_999
# Maximum distance for attacking (squared, for distance check)
@max_attack_distance_sq 500_000
# Maximum movement speed
@max_movement_speed 400
# Maximum jump height
@max_jump_height 200
# =============================================================================
# Damage Validation
# =============================================================================
@doc """
Validates damage dealt to a monster.
Returns {:ok, validated_damage} or {:error, reason}
"""
def validate_damage(character_id, damage, expected_max, monster_id, skill_id) do
# Check if damage exceeds expected max
state = %{character_id: character_id}
# Check for high damage
state = if damage > expected_max do
param = build_damage_param(damage, expected_max, monster_id, skill_id, character_id)
CheatTracker.register_offense(character_id, :high_damage, param)
# Check for same damage (potential damage hack)
CheatTracker.check_same_damage(character_id, damage, expected_max)
state
else
state
end
# Check for damage exceeding 2x expected (HIGH_DAMAGE_2)
state = if damage > expected_max * 2 do
param = build_damage_param(damage, expected_max, monster_id, skill_id, character_id)
CheatTracker.register_offense(character_id, :high_damage_2, param)
# Cap the damage
capped_damage = trunc(expected_max * 2)
{:ok, capped_damage}
else
{:ok, damage}
end
# Check against global damage cap
state = if damage > @damage_cap do
CheatTracker.register_offense(character_id, :exceed_damage_cap,
"Damage: #{damage}, Cap: #{@damage_cap}")
{:ok, @damage_cap}
else
state
end
state
end
@doc """
Validates magic damage dealt to a monster.
"""
def validate_magic_damage(character_id, damage, expected_max, monster_id, skill_id) do
# Check for high magic damage
if damage > expected_max do
param = build_damage_param(damage, expected_max, monster_id, skill_id, character_id)
CheatTracker.register_offense(character_id, :high_damage_magic, param)
# Check for same damage
CheatTracker.check_same_damage(character_id, damage, expected_max)
end
# Check for damage exceeding 2x expected (HIGH_DAMAGE_MAGIC_2)
if damage > expected_max * 2 do
param = build_damage_param(damage, expected_max, monster_id, skill_id, character_id)
CheatTracker.register_offense(character_id, :high_damage_magic_2, param)
# Cap the damage
{:ok, trunc(expected_max * 2)}
else
{:ok, damage}
end
end
@doc """
Calculates maximum weapon damage per hit for validation.
Ported from: DamageParse.CalculateMaxWeaponDamagePerHit()
"""
def calculate_max_weapon_damage(character, monster, attack_skill) do
# Base damage calculation
base_damage = Character.get_stat(character, :max_base_damage) || 100
# Apply skill multipliers
damage = if attack_skill && attack_skill > 0 do
skill_damage = Game.get_skill_damage(attack_skill)
base_damage * (skill_damage / 100.0)
else
base_damage
end
# Apply monster defense
# pdr_rate = Map.get(monster, :pdr_rate, 0)
# damage = damage * (1 - pdr_rate / 100.0)
# Apply boss damage modifier if monster is boss
# damage = if Map.get(monster, :is_boss, false) do
# boss_dam_r = Character.get_stat(character, :bossdam_r) || 0
# damage * (1 + boss_dam_r / 100.0)
# else
# damage
# end
# Apply damage rate
# dam_r = Character.get_stat(character, :dam_r) || 100
# damage = damage * (dam_r / 100.0)
trunc(max(damage, 1))
end
@doc """
Calculates maximum magic damage per hit for validation.
Ported from: DamageParse.CalculateMaxMagicDamagePerHit()
"""
def calculate_max_magic_damage(character, monster, attack_skill) do
# Base magic damage calculation
base_damage = Character.get_stat(character, :max_base_damage) || 100
# Magic has different multipliers
damage = if attack_skill && attack_skill > 0 do
skill_damage = Game.get_skill_damage(attack_skill)
base_damage * (skill_damage / 100.0) * 1.5
else
base_damage * 1.5
end
# Apply monster magic defense
# mdr_rate = Map.get(monster, :mdr_rate, 0)
# damage = damage * (1 - mdr_rate / 100.0)
trunc(max(damage, 1))
end
@doc """
Checks if attack is at valid range.
"""
def validate_attack_range(character_id, attacker_pos, target_pos, skill_id) do
# Calculate distance
distance_sq = calculate_distance_sq(attacker_pos, target_pos)
# Get expected range for skill
expected_range = Game.get_attack_range(skill_id)
if distance_sq > expected_range * expected_range do
param = "Distance: #{distance_sq}, Expected: #{expected_range * expected_range}, Skill: #{skill_id}"
CheatTracker.register_offense(character_id, :attack_faraway_monster, param)
{:error, :out_of_range}
else
:ok
end
end
# =============================================================================
# Attack Validation
# =============================================================================
@doc """
Validates attack count matches skill expectations.
"""
def validate_attack_count(character_id, skill_id, hits, targets, expected_hits, expected_targets) do
# Skip certain skills that have special handling
if skill_id in [4211006, 3221007, 23121003, 1311001] do
:ok
else
# Check hits
if hits > expected_hits do
CheatTracker.register_offense(character_id, :mismatching_bulletcount,
"Hits: #{hits}, Expected: #{expected_hits}")
{:error, :invalid_hits}
else
# Check targets
if targets > expected_targets do
CheatTracker.register_offense(character_id, :mismatching_bulletcount,
"Targets: #{targets}, Expected: #{expected_targets}")
{:error, :invalid_targets}
else
:ok
end
end
end
end
@doc """
Validates the character is alive before attacking.
"""
def validate_alive(character_id, is_alive) do
if not is_alive do
CheatTracker.register_offense(character_id, :attacking_while_dead, nil)
{:error, :dead}
else
:ok
end
end
@doc """
Validates skill usage in specific maps (e.g., Mu Lung, Pyramid).
"""
def validate_skill_map(character_id, skill_id, map_id) do
# Check Mu Lung skills
if Game.is_mulung_skill?(skill_id) do
if div(map_id, 10000) != 92502 do
# Using Mu Lung skill outside dojo
{:error, :wrong_map}
else
:ok
end
else
# Check Pyramid skills
if Game.is_pyramid_skill?(skill_id) do
if div(map_id, 1000000) != 926 do
# Using Pyramid skill outside pyramid
{:error, :wrong_map}
else
:ok
end
else
:ok
end
end
end
# =============================================================================
# Movement Validation
# =============================================================================
@doc """
Validates player movement for speed hacking.
Returns :ok if valid, or {:error, reason} if suspicious.
"""
def validate_movement(character_id, old_pos, new_pos, time_diff_ms) do
# Calculate distance
distance = calculate_distance(old_pos, new_pos)
# Calculate speed
if time_diff_ms > 0 do
speed = distance / (time_diff_ms / 1000.0)
# Check if speed exceeds maximum
if speed > @max_movement_speed do
# Could be speed hacking or lag
# Only flag if significantly over
if speed > @max_movement_speed * 1.5 do
Logger.warning("[AntiCheat] Speed hack suspected for char #{character_id}: #{speed} px/s")
# TODO: Add to offense tracking when FAST_MOVE offense is enabled
{:error, :speed_exceeded}
else
:ok
end
else
:ok
end
else
# Instant movement - check distance
if distance > @max_movement_speed do
{:error, :teleport_detected}
else
:ok
end
end
end
@doc """
Validates jump height for high jump detection.
"""
def validate_jump(character_id, y_delta) do
# Check if jump exceeds maximum
if y_delta < -@max_jump_height do
CheatTracker.register_offense(character_id, :high_jump,
"Jump: #{y_delta}, Max: #{@max_jump_height}")
{:error, :high_jump}
else
:ok
end
end
@doc """
Validates portal usage distance.
"""
def validate_portal_distance(character_id, player_pos, portal_pos) do
distance_sq = calculate_distance_sq(player_pos, portal_pos)
max_portal_distance_sq = 200 * 200 # 200 pixels
if distance_sq > max_portal_distance_sq do
CheatTracker.register_offense(character_id, :using_faraway_portal,
"Distance: #{:math.sqrt(distance_sq)}")
{:error, :too_far}
else
:ok
end
end
# =============================================================================
# Item Validation
# =============================================================================
@doc """
Validates item usage (checks if item is available to character).
"""
def validate_item_usage(character_id, item_id, inventory) do
# Check if item exists in inventory
has_item = Enum.any?(inventory, fn item ->
Map.get(item, :item_id) == item_id
end)
if not has_item do
CheatTracker.register_offense(character_id, :using_unavailable_item,
"ItemID: #{item_id}")
{:error, :item_not_found}
else
:ok
end
end
@doc """
Validates item quantity (dupe detection).
"""
def validate_item_quantity(character_id, item_id, quantity, expected_max) do
if quantity > expected_max do
# Potential dupe
Logger.warning("[AntiCheat] Suspicious item quantity for char #{character_id}: #{item_id} x#{quantity}")
{:error, :quantity_exceeded}
else
:ok
end
end
@doc """
Validates meso explosion (checks if meso exists on map).
"""
def validate_meso_explosion(character_id, map_item) do
if map_item == nil do
CheatTracker.register_offense(character_id, :exploding_nonexistant, nil)
{:error, :no_meso}
else
meso = Map.get(map_item, :meso, 0)
if meso <= 0 do
CheatTracker.register_offense(character_id, :etc_explosion, nil)
{:error, :invalid_meso}
else
:ok
end
end
end
# =============================================================================
# EXP Validation
# =============================================================================
@doc """
Validates EXP gain rate.
"""
def validate_exp_gain(character_id, exp_gained, time_since_last_gain_ms) do
# Calculate EXP per minute
if time_since_last_gain_ms > 0 do
exp_per_minute = exp_gained / (time_since_last_gain_ms / 60000.0)
# Maximum reasonable EXP per minute (varies by level, this is a rough check)
max_exp_per_minute = 10_000_000
if exp_per_minute > max_exp_per_minute do
Logger.warning("[AntiCheat] High EXP rate for char #{character_id}: #{exp_per_minute}/min")
# TODO: Add to offense tracking
{:warning, :high_exp_rate}
else
:ok
end
else
:ok
end
end
@doc """
Validates level progression (checks for impossible jumps).
"""
def validate_level_progression(old_level, new_level) do
max_level_jump = 5
if new_level - old_level > max_level_jump do
{:error, :impossible_level_jump}
else
:ok
end
end
# =============================================================================
# Helper Functions
# =============================================================================
defp build_damage_param(damage, expected, monster_id, skill_id, character_id) do
"[Damage: #{damage}, Expected: #{expected}, Mob: #{monster_id}] [Skill: #{skill_id}]"
end
defp calculate_distance_sq(pos1, pos2) do
dx = Map.get(pos1, :x, 0) - Map.get(pos2, :x, 0)
dy = Map.get(pos1, :y, 0) - Map.get(pos2, :y, 0)
dx * dx + dy * dy
end
defp calculate_distance(pos1, pos2) do
:math.sqrt(calculate_distance_sq(pos1, pos2))
end
end