Files
odinsea-elixir/lib/odinsea/anticheat.ex
2026-02-14 23:12:33 -07:00

286 lines
8.6 KiB
Elixir

defmodule Odinsea.AntiCheat do
@moduledoc """
Anti-Cheat system for Odinsea.
Ported from Java:
- client.anticheat.CheatTracker
- client.anticheat.CheatingOffense
- client.anticheat.CheatingOffenseEntry
- server.AutobanManager
This module provides:
- Damage validation
- Movement validation
- Item validation
- EXP validation
- Autoban system with threshold-based banning
- Offense tracking with expiration
"""
alias Odinsea.AntiCheat.{CheatTracker, CheatingOffense, CheatingOffenseEntry}
# Re-export main modules
defdelegate start_tracker(character_id, character_pid), to: CheatTracker
defdelegate stop_tracker(character_id), to: CheatTracker
defdelegate register_offense(character_id, offense, param \\ nil), to: CheatTracker
defdelegate get_points(character_id), to: CheatTracker
defdelegate get_summary(character_id), to: CheatTracker
defdelegate check_attack(character_id, skill_id, tick_count), to: CheatTracker
defdelegate check_take_damage(character_id, damage), to: CheatTracker
defdelegate check_same_damage(character_id, damage, expected), to: CheatTracker
defdelegate check_drop(character_id, dc \\ false), to: CheatTracker
defdelegate check_message(character_id), to: CheatTracker
defdelegate can_smega(character_id), to: CheatTracker
defdelegate can_avatar_smega(character_id), to: CheatTracker
defdelegate can_bbs(character_id), to: CheatTracker
defdelegate update_tick(character_id, new_tick), to: CheatTracker
defdelegate check_summon_attack(character_id), to: CheatTracker
defdelegate reset_summon_attack(character_id), to: CheatTracker
defdelegate check_familiar_attack(character_id), to: CheatTracker
defdelegate reset_familiar_attack(character_id), to: CheatTracker
defdelegate set_attacks_without_hit(character_id, increase), to: CheatTracker
defdelegate get_attacks_without_hit(character_id), to: CheatTracker
defdelegate check_move_monsters(character_id, position), to: CheatTracker
defdelegate get_offenses(character_id), to: CheatTracker
# CheatingOffense access
def get_offense_types do
CheatingOffense.all_offenses()
end
def get_offense_points(offense_type) do
CheatingOffense.get_points(offense_type)
end
def should_autoban?(offense_type, count) do
CheatingOffense.should_autoban?(offense_type, count)
end
def get_ban_type(offense_type) do
CheatingOffense.get_ban_type(offense_type)
end
def is_enabled?(offense_type) do
CheatingOffense.is_enabled?(offense_type)
end
def get_validity_duration(offense_type) do
CheatingOffense.get_validity_duration(offense_type)
end
end
defmodule Odinsea.AntiCheat.CheatingOffense do
@moduledoc """
Defines all cheating offenses and their properties.
Ported from: client.anticheat.CheatingOffense.java
Each offense has:
- points: Points added per occurrence
- validity_duration: How long the offense stays active (ms)
- autoban_count: Number of occurrences before autoban (-1 = disabled)
- ban_type: 0 = disabled, 1 = ban, 2 = DC
"""
@type offense_type :: atom()
@type ban_type :: :disabled | :ban | :disconnect
@offenses %{
# Offense type => {points, validity_duration_ms, autoban_count, ban_type}
# ban_type: 0 = disabled, 1 = ban, 2 = disconnect
# Attack speed offenses
:fast_summon_attack => {5, 6_000, 50, :disconnect},
:fast_attack => {5, 6_000, 200, :disconnect},
:fast_attack_2 => {5, 6_000, 500, :disconnect},
# Movement offenses
:move_monsters => {5, 30_000, 500, :disconnect},
:high_jump => {1, 60_000, -1, :disabled},
:using_faraway_portal => {1, 60_000, 100, :disabled},
# Regen offenses
:fast_hp_mp_regen => {5, 20_000, 100, :disconnect},
:regen_high_hp => {10, 30_000, 1000, :disconnect},
:regen_high_mp => {10, 30_000, 1000, :disconnect},
# Damage offenses
:same_damage => {5, 180_000, -1, :disconnect},
:attack_without_getting_hit => {1, 30_000, 1200, :disabled},
:high_damage_magic => {5, 30_000, -1, :disabled},
:high_damage_magic_2 => {10, 180_000, -1, :ban},
:high_damage => {5, 30_000, -1, :disabled},
:high_damage_2 => {10, 180_000, -1, :ban},
:exceed_damage_cap => {5, 60_000, 800, :disabled},
:attack_faraway_monster => {5, 180_000, -1, :disabled},
:attack_faraway_monster_summon => {5, 180_000, 200, :disconnect},
# Item offenses
:itemvac_client => {3, 10_000, 100, :disabled},
:itemvac_server => {2, 10_000, 100, :disconnect},
:pet_itemvac_client => {3, 10_000, 100, :disabled},
:pet_itemvac_server => {2, 10_000, 100, :disconnect},
:using_unavailable_item => {1, 300_000, -1, :ban},
# Combat offenses
:fast_take_damage => {1, 60_000, 100, :disabled},
:high_avoid => {5, 180_000, 100, :disabled},
:mismatching_bulletcount => {1, 300_000, -1, :ban},
:etc_explosion => {1, 300_000, -1, :ban},
:attacking_while_dead => {1, 300_000, -1, :ban},
:exploding_nonexistant => {1, 300_000, -1, :ban},
:summon_hack => {1, 300_000, -1, :ban},
:summon_hack_mobs => {1, 300_000, -1, :ban},
:aran_combo_hack => {1, 600_000, 50, :disconnect},
:heal_attacking_undead => {20, 30_000, 100, :disabled},
# Social offenses
:faming_self => {1, 300_000, -1, :ban},
:faming_under_15 => {1, 300_000, -1, :ban}
}
@doc """
Returns all offense types.
"""
@spec all_offenses() :: list(offense_type())
def all_offenses do
Map.keys(@offenses)
end
@doc """
Get the points for an offense type.
"""
@spec get_points(offense_type()) :: integer()
def get_points(offense_type) do
case Map.get(@offenses, offense_type) do
{points, _, _, _} -> points
nil -> 0
end
end
@doc """
Get the validity duration for an offense type.
"""
@spec get_validity_duration(offense_type()) :: integer()
def get_validity_duration(offense_type) do
case Map.get(@offenses, offense_type) do
{_, duration, _, _} -> duration
nil -> 0
end
end
@doc """
Check if an offense should trigger autoban at the given count.
"""
@spec should_autoban?(offense_type(), integer()) :: boolean()
def should_autoban?(offense_type, count) do
case Map.get(@offenses, offense_type) do
{_, _, autoban_count, _} when autoban_count > 0 -> count >= autoban_count
_ -> false
end
end
@doc """
Get the ban type for an offense.
"""
@spec get_ban_type(offense_type()) :: ban_type()
def get_ban_type(offense_type) do
case Map.get(@offenses, offense_type) do
{_, _, _, ban_type} -> ban_type
nil -> :disabled
end
end
@doc """
Check if an offense type is enabled (ban_type >= 1).
"""
@spec is_enabled?(offense_type()) :: boolean()
def is_enabled?(offense_type) do
case get_ban_type(offense_type) do
:disabled -> false
_ -> true
end
end
end
defmodule Odinsea.AntiCheat.CheatingOffenseEntry do
@moduledoc """
Represents a single cheating offense entry for a character.
Ported from: client.anticheat.CheatingOffenseEntry.java
"""
alias Odinsea.AntiCheat.CheatingOffense
defstruct [
:offense_type,
:character_id,
:count,
:last_offense_time,
:param
]
@type t :: %__MODULE__{
offense_type: CheatingOffense.offense_type(),
character_id: integer(),
count: integer(),
last_offense_time: integer() | nil,
param: String.t() | nil
}
@doc """
Creates a new offense entry.
"""
@spec new(CheatingOffense.offense_type(), integer()) :: t()
def new(offense_type, character_id) do
%__MODULE__{
offense_type: offense_type,
character_id: character_id,
count: 0,
last_offense_time: nil,
param: nil
}
end
@doc """
Increments the offense count and updates timestamp.
"""
@spec increment(t()) :: t()
def increment(entry) do
%__MODULE__{entry |
count: entry.count + 1,
last_offense_time: System.monotonic_time(:millisecond)
}
end
@doc """
Sets a parameter for the offense (additional info).
"""
@spec set_param(t(), String.t()) :: t()
def set_param(entry, param) do
%__MODULE__{entry | param: param}
end
@doc """
Checks if the offense entry has expired.
"""
@spec expired?(t()) :: boolean()
def expired?(entry) do
if entry.last_offense_time == nil do
false
else
validity_duration = CheatingOffense.get_validity_duration(entry.offense_type)
now = System.monotonic_time(:millisecond)
now - entry.last_offense_time > validity_duration
end
end
@doc """
Calculates the total points for this offense entry.
"""
@spec get_points(t()) :: integer()
def get_points(entry) do
entry.count * CheatingOffense.get_points(entry.offense_type)
end
end