From 4d3c6c4a406f239afa9f54d36aa37e0bfe191c35 Mon Sep 17 00:00:00 2001 From: Mind Rapist Date: Fri, 13 Feb 2026 01:51:51 +0200 Subject: [PATCH] Several bug fixes --- README.md | 316 +-------------------------------------- src/game/battle.cpp | 21 ++- src/game/char.h | 2 + src/game/char_battle.cpp | 13 +- src/game/char_resist.cpp | 22 ++- 5 files changed, 54 insertions(+), 320 deletions(-) diff --git a/README.md b/README.md index e95dd5c..65378a3 100644 --- a/README.md +++ b/README.md @@ -25,317 +25,11 @@ It builds as it is, without external dependencies. ## πŸ“‹ Changelog -### Encryption & Security Overhaul - -The entire legacy encryption system has been replaced with [libsodium](https://doc.libsodium.org/). - -#### Removed Legacy Crypto -* **Crypto++ (cryptopp) vendor library** β€” Completely removed from the project -* **Panama cipher** (`CFilterEncoder`, `CFilterDecoder`) β€” Removed from `NetStream` -* **TEA encryption** (`tea.h`, `tea.cpp`) β€” Removed from both client and server -* **DH2 key exchange** (`cipher.h`, `cipher.cpp`) β€” Removed from `EterBase` -* **Camellia cipher** β€” Removed all references -* **`_IMPROVED_PACKET_ENCRYPTION_`** β€” Entire system removed (XTEA key scheduling, sequence encryption, key agreement) -* **`adwClientKey[4]`** β€” Removed from all packet structs (`TPacketCGLogin2`, `TPacketCGLogin3`, `TPacketGDAuthLogin`, `TPacketGDLoginByKey`, `TPacketLoginOnSetup`) and all associated code on both client and server -* **`LSS_SECURITY_KEY`** β€” Dead code removed (`"testtesttesttest"` hardcoded key, `GetSecurityKey()` function) - -#### New Encryption System (libsodium) -* **X25519 key exchange** β€” `SecureCipher` class handles keypair generation and session key derivation via `crypto_kx_client_session_keys` / `crypto_kx_server_session_keys` -* **XChaCha20-Poly1305 AEAD** β€” Used for authenticated encryption of handshake tokens (key exchange, session tokens) -* **XChaCha20 stream cipher** β€” Used for in-place network buffer encryption via `EncryptInPlace()` / `DecryptInPlace()` (zero overhead, nonce-counter based replay prevention) -* **Challenge-response authentication** β€” HMAC-based (`crypto_auth`) verification during key exchange to prove shared secret derivation -* **New handshake protocol** β€” `HEADER_GC_KEY_CHALLENGE` / `HEADER_CG_KEY_RESPONSE` / `HEADER_GC_KEY_COMPLETE` packet flow for secure session establishment - -#### Network Encryption Pipeline -* **Client send path** β€” Data is encrypted at queue time in `CNetworkStream::Send()` (prevents double-encryption on partial TCP sends) -* **Client receive path** β€” Data is decrypted immediately after `recv()` in `__RecvInternalBuffer()`, before being committed to the buffer -* **Server send path** β€” Data is encrypted in `DESC::Packet()` via `EncryptInPlace()` after encoding to the output buffer -* **Server receive path** β€” Newly received bytes are decrypted in `DESC::ProcessInput()` via `DecryptInPlace()` before buffer commit - -#### Login Security Hardening -* **Removed plaintext login path** β€” `HEADER_CG_LOGIN` (direct password to game server) has been removed. All game server logins now require a login key obtained through the auth server (`HEADER_CG_LOGIN2` / `LoginByKey`) -* **CSPRNG login keys** β€” `CreateLoginKey()` now uses `randombytes_uniform()` (libsodium) instead of the non-cryptographic Xoshiro128PlusPlus PRNG -* **Single-use login keys** β€” Keys are consumed (removed from the map) immediately after successful authentication -* **Shorter key expiry** β€” Expired login keys are cleaned up after 15 seconds (down from 60 seconds). Orphaned keys (descriptor gone, never expired) are also cleaned up -* **Login rate limiting** β€” Per-IP tracking of failed login attempts. After 5 failures within 60 seconds, the IP is blocked with a `BLOCK` status and disconnected. Counter resets after cooldown or successful login -* **Removed Brazil password bypass** β€” The `LC_IsBrazil()` block that unconditionally disabled password verification has been removed - -#### Pack File Encryption -* **libsodium-based pack encryption** β€” `PackLib` now uses XChaCha20-Poly1305 for pack file encryption, replacing the legacy Camellia/XTEA system -* **Secure key derivation** β€” Pack encryption keys are derived using `crypto_pwhash` (Argon2id) - ---- - -### Networking Modernization Roadmap - -A 5-phase modernization of the entire client/server networking stack β€” packet format, buffer management, handshake protocol, connection architecture, and packet dispatching. Every phase is complete and verified on both client and server. - ---- - -#### Phase 1 β€” Packet Format + Buffer System + Memory Safety - -Replaced the legacy 1-byte packet headers and raw C-style buffers with a modern, uniform protocol. - -##### What changed -* **2-byte headers + 2-byte length prefix** β€” All packet types (`CG::`, `GC::`, `GG::`, `GD::`, `DG::`) now use `uint16_t` header + `uint16_t` length. This increases the addressable packet space from 256 to 65,535 unique packet types and enables safe variable-length parsing -* **Namespaced packet headers** β€” All headers moved from flat `HEADER_CG_*` defines to C++ namespaces: `CG::MOVE`, `GC::PING`, `GG::LOGIN`, `GD::PLAYER_SAVE`, `DG::BOOT`. Subheaders similarly namespaced: `GuildSub::GC::LOGIN`, `ShopSub::CG::BUY`, etc. -* **RAII RingBuffer** β€” All raw `buffer_t` / `LPBUF` / `new[]`/`delete[]` patterns replaced with a single `RingBuffer` class featuring lazy compaction at 50% read position, exponential growth, and inlined accessors -* **PacketReader / PacketWriter** β€” Type-safe helpers that wrap buffer access with bounds checking, eliminating raw pointer arithmetic throughout the codebase -* **Sequence system modernized** β€” Packet sequence tracking retained for debugging but fixed to byte offset 4 (after header + length) -* **SecureCipher** β€” XChaCha20-Poly1305 stream cipher for all post-handshake traffic (see Encryption section above) - -##### What was removed -* `buffer.h` / `buffer.cpp` (legacy C buffer library) -* All `LPBUF`, `buffer_new()`, `buffer_delete()`, `buffer_read()`, `buffer_write()` calls -* Raw `new[]`/`delete[]` buffer allocations in DESC classes -* 1-byte header constants (`HEADER_CG_*`, `HEADER_GC_*`, etc.) - -##### Why -The legacy 1-byte header system limited the protocol to 256 packet types (already exhausted), raw C buffers had no bounds checking and were prone to buffer overflows, and the flat namespace caused header collisions between subsystems. - ---- - -#### Phase 2 β€” Modern Buffer System *(merged into Phase 1)* - -All connection types now use `RingBuffer` uniformly. - -##### What changed -* **All DESC types** (`DESC`, `CLIENT_DESC`, `DESC_P2P`) use `RingBuffer` for `m_inputBuffer`, `m_outputBuffer`, and `m_bufferedOutputBuffer` -* **PeerBase** (db layer) ported to `RingBuffer` -* **TEMP_BUFFER** (local utility for building packets) backed by `RingBuffer` - -##### What was removed -* `libthecore/buffer.h` and `libthecore/buffer.cpp` β€” the entire legacy buffer library - -##### Why -The legacy buffer system used separate implementations across different connection types, had no RAII semantics (manual malloc/free), and offered no protection against buffer overflows. - ---- - -#### Phase 3 β€” Simplified Handshake - -Replaced the legacy multi-step handshake (4+ round trips with time synchronization and UDP binding) with a streamlined 1.5 round-trip flow. - -##### What changed -* **1.5 round-trip handshake** β€” Server sends `GC::KEY_CHALLENGE` (with embedded time sync), client responds with `CG::KEY_RESPONSE`, server confirms with `GC::KEY_COMPLETE`. Session is encrypted from that point forward -* **Time sync embedded** β€” Initial time synchronization folded into `GC::KEY_CHALLENGE`; periodic time sync handled by `GC::PING` / `CG::PONG` -* **Handshake timeout** β€” 5-second expiry on handshake phase; stale connections are automatically cleaned up in `DestroyClosed()` - -##### What was removed -* **6 dead packet types**: `CG_HANDSHAKE`, `GC_HANDSHAKE`, `CG_TIME_SYNC`, `GC_TIME_SYNC`, `GC_HANDSHAKE_OK`, `GC_BINDUDP` -* **Server functions**: `StartHandshake()`, `SendHandshake()`, `HandshakeProcess()`, `CreateHandshake()`, `FindByHandshake()`, `m_map_handshake` -* **Client functions**: `RecvHandshakePacket()`, `RecvHandshakeOKPacket()`, `m_HandshakeData`, `SendHandshakePacket()` -* ~12 server files and ~10 client files modified - -##### Why -The original handshake required 4+ round trips, included dead UDP binding steps, had no timeout protection (stale connections could linger indefinitely), and the time sync was a separate multi-step sub-protocol that added latency to every new connection. - ---- - -#### Phase 4 β€” Unified Connection (Client-Side Deduplication) - -Consolidated duplicated connection logic into the base `CNetworkStream` class. - -##### What changed -* **Key exchange** (`RecvKeyChallenge` / `RecvKeyComplete`) moved from 4 separate implementations to `CNetworkStream` base class -* **Ping/pong** (`RecvPingPacket` / `SendPongPacket`) moved from 3 separate implementations to `CNetworkStream` base class -* **CPythonNetworkStream** overrides `RecvKeyChallenge` only for time sync, delegates all crypto to base -* **CGuildMarkDownloader/Uploader** β€” `RecvKeyCompleteAndLogin` wraps base + sends `CG::MARK_LOGIN` -* **CAccountConnector** β€” Fixed raw `crypto_aead` bug (now uses base class `cipher.DecryptToken`) -* **Control-plane structs** extracted to `EterLib/ControlPackets.h` (Phase, Ping, Pong, KeyChallenge, KeyResponse, KeyComplete) -* **CGuildMarkUploader** β€” `m_pbySymbolBuf` migrated from `new[]`/`delete[]` to `std::vector` - -##### What was removed -* ~200 lines of duplicated code across `CAccountConnector`, `CGuildMarkDownloader`, `CGuildMarkUploader`, and `CPythonNetworkStream` - -##### Why -The same key exchange and ping/pong logic was copy-pasted across 3-4 connection subclasses, leading to inconsistent behavior (the `CAccountConnector` had a raw crypto bug), difficult maintenance, and unnecessary code volume. - ---- - -#### Phase 5 β€” Packet Handler Registration / Dispatcher - -Replaced giant `switch` statements with `std::unordered_map` dispatch tables for O(1) packet routing. - -##### What changed - -**Client:** -* `CPythonNetworkStream` β€” Phase-specific handler maps for Game, Loading, Login, Select, and Handshake phases -* Registration pattern: `m_gameHandlers[GC::MOVE] = &CPythonNetworkStream::RecvCharacterMovePacket;` -* Dispatch: `DispatchPacket(m_gameHandlers)` β€” reads header, looks up handler, calls it - -**Server:** -* `CInputMain`, `CInputDead`, `CInputAuth`, `CInputLogin`, `CInputP2P`, `CInputHandshake`, `CInputDB` β€” all converted to dispatch tables -* `CInputDB` uses 3 template adapters (`DataHandler`, `DescHandler`, `TypedHandler`) + 14 custom adapters for the diverse DB callback signatures - -##### What was removed -* All `switch (header)` blocks across 7 server input processors and 5 client phase handlers -* ~3,000 lines of switch/case boilerplate - -##### Why -The original dispatch used switch statements with 50-100+ cases each. Adding a new packet required modifying a massive switch block, which was error-prone and caused merge conflicts. The table-driven approach enables O(1) lookup, self-documenting handler registration, and trivial addition of new packet types. - ---- - -### Post-Phase 5 Cleanup - -Follow-up tasks after the core roadmap was complete. - -#### One-Liner Adapter Reformat -* 24 adapter methods across 4 server files (`input_main.cpp`, `input_p2p.cpp`, `input_auth.cpp`, `input_login.cpp`) reformatted from single-line to multi-line for readability - -#### MAIN_CHARACTER Packet Merge -* **4 mutually exclusive packets** (`GC::MAIN_CHARACTER`, `MAIN_CHARACTER2_EMPIRE`, `MAIN_CHARACTER3_BGM`, `MAIN_CHARACTER4_BGM_VOL`) merged into a single unified `GC::MAIN_CHARACTER` packet -* Single struct always includes BGM fields (zero when unused β€” 29 extra bytes on a one-time-per-load packet) -* 4 nearly identical client handlers merged into 1 -* 3 redundant server send paths merged into 1 - -#### UDP Leftover Removal -* **7 client files deleted**: `NetDatagram.h/.cpp`, `NetDatagramReceiver.h/.cpp`, `NetDatagramSender.h/.cpp`, `PythonNetworkDatagramModule.cpp` -* **8 files edited**: Removed dead stubs (`PushUDPState`, `initudp`, `netSetUDPRecvBufferSize`, `netConnectUDP`), declarations, and Python method table entries -* **Server**: Removed `socket_udp_read()`, `socket_udp_bind()`, `__UDP_BLOCK__` define - -#### Subheader Dispatch -* Extended the Phase 5 table-driven pattern to subheader switches with 8+ cases -* **Client**: Guild (19 sub-handlers), Shop (10), Exchange (8) in `PythonNetworkStreamPhaseGame.cpp` -* **Server**: Guild (15 sub-handlers) in `input_main.cpp` -* Small switches intentionally kept as-is: Messenger (5), Fishing (6), Dungeon (2), Server Shop (4) - ---- - -### Performance Audit & Optimization - -Comprehensive audit of all Phase 1-5 changes to identify and eliminate performance overhead. - -#### Debug Logging Cleanup -* **Removed all hot-path `TraceError`/`Tracef`/`sys_log`** from networking code on both client and server -* Client: `NetStream.cpp`, `SecureCipher.cpp`, `PythonNetworkStream*.cpp` β€” eliminated per-frame and per-packet traces that caused disk I/O every frame -* Server: `desc.cpp`, `input.cpp`, `input_login.cpp`, `input_auth.cpp`, `SecureCipher.cpp` β€” eliminated `[SEND]`, `[RECV]`, `[CIPHER]` logs that fired on every packet - -#### Packet Processing Throughput -* **`MAX_RECV_COUNT` 4 β†’ 32** β€” Game phase now processes up to 32 packets per frame (was 4, severely limiting entity spawning on map entry) -* **Loading phase while-loop** β€” Changed from processing 1 packet per frame to draining all available packets, making phase transitions near-instant - -#### Flood Check Optimization -* **Replaced `get_dword_time()` with `thecore_pulse()`** in `DESC::CheckPacketFlood()` β€” eliminates a `gettimeofday()` syscall on every single packet received. `thecore_pulse()` is cached once per game-loop iteration - -#### Flood Protection -* **Per-IP connection limits** β€” Configurable maximum connections per IP address (`flood_max_connections_per_ip`, default: 10) -* **Global connection limits** β€” Configurable maximum total connections (`flood_max_global_connections`, default: 8192) -* **Per-second packet rate limiting** β€” Connections exceeding `flood_max_packets_per_sec` (default: 300) are automatically disconnected -* **Handshake timeout** β€” 5-second expiry prevents connection slot exhaustion from incomplete handshakes - ---- - -### DDoS / Flood Mitigation - -Two-layer defense against UDP floods and TCP SYN floods, integrated directly into the game server process. - -#### Layer 1 β€” UDP Sink Sockets - -Dummy UDP sockets bound to each game and P2P port with a minimal receive buffer (`SO_RCVBUF = 1`). The sockets are never read from β€” once the tiny kernel buffer fills, incoming UDP packets are silently dropped without generating ICMP port-unreachable replies. This prevents the server from being used as an ICMP reflection amplifier and reduces kernel CPU overhead from UDP floods. - -* **Zero overhead** β€” No threads, no polling, no syscalls. The kernel handles everything. -* **Always active** β€” Created unconditionally at startup alongside the TCP listeners -* **Defense-in-depth** β€” Acts as a fallback if iptables rules cannot be installed (e.g., non-root) - -#### Layer 2 β€” Kernel-Level Firewall (`FirewallManager`) - -The `FirewallManager` singleton programmatically installs firewall rules at server startup, dropping malicious traffic at the kernel level β€” before it reaches the socket/application layer. This is dramatically more efficient than socket-level drops at high packet rates (700k+ pps). - -* **FreeBSD** β€” Uses `ipfw` numbered rules (deterministic rule base per port) -* **Linux** β€” Uses `iptables` with a dedicated chain per process (e.g., `M2_GUARD_11011`) -* **Windows** β€” No-op stubs (compiles but does nothing). Windows is dev-only - -##### What it does -* **Drops all unsolicited inbound UDP** β€” Packets are rejected at the kernel firewall layer with near-zero CPU cost per packet -* **Rate-limits TCP SYN** on game and P2P ports β€” Default: 500 new connections/sec (per-source limit on FreeBSD, rate limit on Linux). Protects against SYN flood attacks while allowing legitimate mass logins (e.g., 1000 players at server launch) -* **Drops ICMP port-unreachable** β€” Prevents reflection/amplification attacks -* **Per-process isolation** β€” Each game process installs its own rules to avoid conflicts in multi-channel deployments -* **Crash recovery** β€” On startup, stale rules from previous crashes are automatically cleaned up before installing fresh rules -* **Graceful cleanup** β€” All rules are removed on normal shutdown - -##### Configuration (`conf/game.txt`) - -| Token | Default | Description | -|-------|---------|-------------| -| `firewall_enable` | `0` | Enable/disable the firewall manager (0=off, 1=on) | -| `firewall_tcp_syn_limit` | `500` | Max new TCP SYN connections/sec per port | -| `firewall_tcp_syn_burst` | `1000` | SYN burst allowance before rate limiting kicks in (Linux only) | - -##### FreeBSD setup (ipfw) - -Before enabling the firewall manager, the `ipfw` kernel module must be loaded with a default-allow policy. Without the allow rule, loading ipfw will block all traffic and lock you out. - -```bash -# Load ipfw module and immediately add a default allow rule -kldload ipfw && /sbin/ipfw -q add 65000 allow ip from any to any -``` - -To persist across reboots, add to `/boot/loader.conf`: -``` -ipfw_load="YES" -``` - -And to `/etc/rc.conf`: -``` -firewall_enable="YES" -firewall_type="open" -``` - -##### Verification - -FreeBSD: -```bash -# Check rules are installed (each process uses rule base 50000 + (port % 1000) * 10) -/sbin/ipfw list | grep 500 - -# Verify rules are cleaned up after shutdown -/sbin/ipfw list | grep 500 # should show no matching rules -``` - -Linux: -```bash -# Check rules are installed -iptables -L M2_GUARD_11011 -n -v - -# Verify chain is cleaned up after shutdown -iptables -L M2_GUARD_11011 # should fail with "No chain/target/match by that name" -``` - -##### Requirements -* **Root access** β€” Required on both FreeBSD and Linux for firewall rule installation. If not root, logs a warning and the server continues without firewall rules (Layer 1 UDP sinks still protect) - ---- - -### Pre-Phase 3 Cleanup - -Preparatory cleanup performed before the handshake simplification. - -* **File consolidation** β€” Merged scattered packet definitions into centralized header files -* **Alias removal** β€” Removed legacy `#define` aliases that mapped old names to new identifiers -* **Monarch system removal** β€” Completely removed the unused Monarch (emperor) system from both client and server, including all related packets, commands, quest functions, and UI code -* **TrafficProfiler removal** β€” Removed the `TrafficProfiler` class and all references (unnecessary runtime overhead) -* **Quest management stub removal** β€” Removed empty `questlua_mgmt.cpp` (monarch-era placeholder with no functions) - ---- - -### Summary of Removed Legacy Systems - -A consolidated reference of all legacy systems, files, and dead code removed across the entire modernization effort. - -| System | What was removed | Replaced by | -|--------|-----------------|-------------| -| **Legacy C buffer** | `buffer.h`, `buffer.cpp`, all `LPBUF`/`buffer_new()`/`buffer_delete()` calls, raw `new[]`/`delete[]` buffer allocations | RAII `RingBuffer` class | -| **1-byte packet headers** | All `HEADER_CG_*`, `HEADER_GC_*`, `HEADER_GG_*`, `HEADER_GD_*`, `HEADER_DG_*` defines | 2-byte namespaced headers (`CG::`, `GC::`, `GG::`, `GD::`, `DG::`) | -| **Old handshake protocol** | 6 packet types (`CG_HANDSHAKE`, `GC_HANDSHAKE`, `CG_TIME_SYNC`, `GC_TIME_SYNC`, `GC_HANDSHAKE_OK`, `GC_BINDUDP`), all handshake functions and state | 1.5 round-trip key exchange (`KEY_CHALLENGE`/`KEY_RESPONSE`/`KEY_COMPLETE`) | -| **UDP networking** | 7 client files (`NetDatagram*.h/.cpp`, `PythonNetworkDatagramModule.cpp`), server `socket_udp_read()`/`socket_udp_bind()`/`__UDP_BLOCK__` | Removed entirely (game is TCP-only) | -| **Old sequence system** | `m_seq`, `SetSequence()`, `GetSequence()`, old sequence variables | Modernized sequence at fixed byte offset 4 | -| **TrafficProfiler** | `TrafficProfiler` class and all references | Removed entirely | -| **Monarch system** | All monarch/emperor packets, commands (`do_monarch_*`), quest functions (`questlua_monarch.cpp`, `questlua_mgmt.cpp`), UI code, GM commands | Removed entirely (unused feature) | -| **Legacy crypto** | Crypto++, Panama cipher, TEA, DH2, Camellia, XTEA, `adwClientKey[4]`, `LSS_SECURITY_KEY` | libsodium (X25519 + XChaCha20-Poly1305) | -| **Switch-based dispatch** | Giant `switch (header)` blocks (50-100+ cases each) across 7 server input processors and 5 client phase handlers | `std::unordered_map` dispatch tables | -| **Duplicated connection code** | Key exchange and ping/pong copy-pasted across 3-4 client subclasses | Consolidated in `CNetworkStream` base class | +### ⬆️ Feature Improvements + - **Poison**: Verified consistency with the official methods and increased chances of infliction to lower level entities + - **Fire**: + - Updated so it cannot kill the target, only lower them to 1 HP (consistency with official). + - The Powerful Ice Witch (`1192`) takes 20% less damage from the fire affect (consistency with official).

diff --git a/src/game/battle.cpp b/src/game/battle.cpp index db5e2a1..7d2db7a 100644 --- a/src/game/battle.cpp +++ b/src/game/battle.cpp @@ -632,17 +632,34 @@ int CalcArrowDamage(LPCHARACTER pkAttacker, LPCHARACTER pkVictim, LPITEM pkBow, //return iDam; } - void NormalAttackAffect(LPCHARACTER pkAttacker, LPCHARACTER pkVictim) { // 독 곡격은 νŠΉμ΄ν•˜λ―€λ‘œ 특수 처리 if (pkAttacker->GetPoint(POINT_POISON_PCT) && !pkVictim->IsAffectFlag(AFF_POISON)) { - if (number(1, 100) <= pkAttacker->GetPoint(POINT_POISON_PCT)) + // MR-11: DPS Debuff Fixes + int delta = pkAttacker->GetLevel() - pkVictim->GetLevel(); + int absDelta = abs(delta); + + if (absDelta > 8) + absDelta = 8; + + int levelPct = 100; + + if (delta < 0) + levelPct = poison_level_adjust[absDelta]; + else if (delta > 0) + levelPct = 100 + (100 - poison_level_adjust[absDelta]) / 2; + + int additChance = pkAttacker->GetPoint(POINT_POISON_PCT) * (levelPct - 100) / 100; + + if (number(1, 100) <= pkAttacker->GetPoint(POINT_POISON_PCT) + additChance) pkVictim->AttackedByPoison(pkAttacker); + // MR-11: -- END OF -- DPS Debuff Fixes } int iStunDuration = 2; + if (pkAttacker->IsPC() && !pkVictim->IsPC()) iStunDuration = 4; diff --git a/src/game/char.h b/src/game/char.h index 304079e..10b07b4 100644 --- a/src/game/char.h +++ b/src/game/char.h @@ -57,6 +57,8 @@ enum SUMMON_MONSTER_COUNT = 3, }; +extern const int poison_level_adjust[9]; + enum { FLY_NONE, diff --git a/src/game/char_battle.cpp b/src/game/char_battle.cpp index 08f3fd1..25885b9 100644 --- a/src/game/char_battle.cpp +++ b/src/game/char_battle.cpp @@ -2173,6 +2173,13 @@ bool CHARACTER::Damage(LPCHARACTER pAttacker, int dam, EDamageType type) // retu dam -= dec_dam; } + // MR-11: DPS Debuff Fixes + if (type == DAMAGE_TYPE_FIRE && IsNPC() && GetRaceNum() == 1192) + { + dam = dam * 80 / 100; + } + // MR-11: -- END OF -- DPS Debuff Fixes + if (pAttacker) { // @@ -2275,14 +2282,16 @@ bool CHARACTER::Damage(LPCHARACTER pAttacker, int dam, EDamageType type) // retu if (IsDead()) return true; - // 독 곡격으둜 μ£½μ§€ μ•Šλ„λ‘ 함. - if (type == DAMAGE_TYPE_POISON) + // MR-11: DPS Debuff Fixes + // 독/뢈 λ°λ―Έμ§€λ‘œ μ£½μ§€ μ•Šλ„λ‘ 함. + if (type == DAMAGE_TYPE_POISON || type == DAMAGE_TYPE_FIRE) { if (GetHP() - dam <= 0) { dam = GetHP() - 1; } } + // MR-11: -- END OF -- DPS Debuff Fixes // ------------------------ // 독일 프리미엄 λͺ¨λ“œ diff --git a/src/game/char_resist.cpp b/src/game/char_resist.cpp index f8ba26e..799bb3f 100644 --- a/src/game/char_resist.cpp +++ b/src/game/char_resist.cpp @@ -46,7 +46,7 @@ EVENTINFO(TPoisonEventInfo) EVENTFUNC(poison_event) { - TPoisonEventInfo * info = dynamic_cast( event->info ); + TPoisonEventInfo * info = dynamic_cast(event->info); if ( info == NULL ) { @@ -59,9 +59,11 @@ EVENTFUNC(poison_event) if (ch == NULL) { // return 0; } + LPCHARACTER pkAttacker = CHARACTER_MANAGER::instance().FindByPID(info->attacker_pid); int dam = ch->GetMaxHP() * GetPoisonDamageRate(ch) / 1000; + if (test_server) ch->ChatPacket(CHAT_TYPE_NOTICE, "Poison Damage %d", dam); if (ch->Damage(pkAttacker, dam, DAMAGE_TYPE_POISON)) @@ -99,7 +101,7 @@ EVENTINFO(TFireEventInfo) EVENTFUNC(fire_event) { - TFireEventInfo * info = dynamic_cast( event->info ); + TFireEventInfo * info = dynamic_cast(event->info); if ( info == NULL ) { @@ -108,12 +110,15 @@ EVENTFUNC(fire_event) } LPCHARACTER ch = info->ch; + if (ch == NULL) { // return 0; } + LPCHARACTER pkAttacker = CHARACTER_MANAGER::instance().FindByPID(info->attacker_pid); int dam = info->amount; + if (test_server) ch->ChatPacket(CHAT_TYPE_NOTICE, "Fire Damage %d", dam); if (ch->Damage(pkAttacker, dam, DAMAGE_TYPE_FIRE)) @@ -157,10 +162,12 @@ EVENTFUNC(fire_event) */ -static int poison_level_adjust[9] = +// MR-11: DPS Debuff Fixes +const int poison_level_adjust[9] = { 100, 90, 80, 70, 50, 30, 10, 5, 0 }; +// MR-11: -- END OF -- DPS Debuff Fixes void CHARACTER::AttackedByFire(LPCHARACTER pkAttacker, int amount, int count) { @@ -197,8 +204,13 @@ void CHARACTER::AttackedByPoison(LPCHARACTER pkAttacker) if (m_bHasPoisoned && !IsPC()) // λͺ¬μŠ€ν„°λŠ” 독이 ν•œλ²ˆλ§Œ κ±Έλ¦°λ‹€. return; + // MR-11: DPS Debuff Fixes + if (IsStone() || IsDoor()) + return; + // MR-11: -- END OF -- DPS Debuff Fixes + // MR-8: Check damage immunity system - prevent poison application if conditions not met - if (m_bDamageImmune && (IsMonster() || IsStone() || IsDoor())) + if (m_bDamageImmune && IsMonster()) { if (!CheckDamageImmunityConditions(pkAttacker)) { @@ -230,7 +242,7 @@ void CHARACTER::AttackedByPoison(LPCHARACTER pkAttacker) info->ch = this; info->count = 10; - info->attacker_pid = pkAttacker?pkAttacker->GetPlayerID():0; + info->attacker_pid = pkAttacker ? pkAttacker->GetPlayerID() : 0; m_pkPoisonEvent = event_create(poison_event, info, 1);