Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands

### Building the project
```bash
# Standard release build
make

# Debug build
make debug

# Clean build artifacts
make clean
```

The project uses CMake. The Makefile wraps CMake commands for convenience:
- `make` - Builds with CMake in Release mode (`-DCMAKE_BUILD_TYPE=Release`)
- `make debug` - Builds with CMake in Debug mode (`-DCMAKE_BUILD_TYPE=Debug`)
- Build output binaries go to `bin/` directory
- Build output libraries go to `lib/` directory

For development, always choose debug build!

### Code formatting
```bash
# Format all C++ code
make fmt

# Check format for modified files only (fast)
make fmt-check

# Check format for all files (slow)
make fmt-check-all
```

The project uses clang-format with a `.clang-format` configuration file.
- Format checking is enforced via GitHub Actions on PRs
- Always run `make fmt` before committing changes

## Project Architecture

This is the **DarkEden** game server - an MMORPG server written in C++11.

### Server Architecture

The server is split into multiple coordinated processes:

1. **loginserver** - Handles authentication and character selection
2. **sharedserver** - Manages shared data (e.g., guild info) across game servers
3. **gameserver** - The main game logic server (one per world/zone group)

### Build System Structure

- **CMake** is the primary build system (CMakeLists.txt files throughout)
- **Legacy Makefiles** exist in subdirectories but are superseded by CMake
- Source files are organized by module under `src/`

### Key Directory Structure

```
src/
├── Core/ # Core library - shared utilities, no server-type dependencies
│ ├── Packets/ # Protocol packet definitions (GC, CG, CL, LC, GL, LG, GS, SG, GG)
│ └── [core utilities] # Socket, datagram, player info, items, skills, etc.
├── server/
│ ├── database/ # Database abstraction layer and connection management
│ ├── gameserver/ # Main game server executable
│ │ ├── skill/ # Skill system module
│ │ ├── item/ # Item system module
│ │ ├── billing/ # Billing/payment module
│ │ ├── war/ # War system module
│ │ ├── couple/ # Couple/party system module
│ │ ├── mission/ # Mission system module
│ │ ├── ctf/ # Capture the flag module
│ │ ├── quest/ # Quest system (with Lua scripting)
│ │ ├── mofus/ # Game events module
│ │ └── exchange/ # Player exchange/auction system
│ ├── loginserver/ # Login server executable
│ └── sharedserver/ # Shared server executable
└── Core/CMakeLists.txt # Defines packet libraries and Core library
```

### Packet System

Packets are the primary communication mechanism between servers and clients. They are organized by direction:

- **GC** (Game → Client): Server sends to client
- **CG** (Client → Game): Client sends to game server
- **LC** (Login → Client): Login server sends to client
- **CL** (Client → Login): Client sends to login server
- **GL** (Game → Login): Game server communicates with login server
- **LG** (Login → Game): Login server communicates with game server
- **GS** (Game → Shared): Game server communicates with shared server
- **SG** (Shared → Game): Shared server responds to game server
- **GG** (Game → Game): Inter-game-server communication

Each packet type typically has two files:
- `PacketName.cpp` - Packet class definition
- `PacketNameHandler.cpp` - Handler that processes the packet

### Preprocessor Macros

Key compile definitions that control behavior:
- `__GAME_SERVER__` - Compiled for gameserver
- `__LOGIN_SERVER__` - Compiled for loginserver
- `__SHARED_SERVER__` - Compiled for sharedserver
- `__COMBAT__` - Enables combat-related code

### Configuration

Server configurations are in `conf/`:
- `gameserver.conf` - Game server configuration
- `loginserver.conf` - Login server configuration
- `sharedserver.conf` - Shared server configuration

Important settings:
- `HomePath` - Repository directory path (must be set correctly)
- `DB_HOST` - Database IP address
- `LoginServerIP` - Login server IP

**Note**: Database `WorldDBInfo` and `GameServerInfo` tables must match config file settings.

## Database Setup

The project requires MySQL 5.7 or 8 with specific SQL mode settings:

```sql
-- Remove NO_ZERO_DATE and STRICT_TRANS_TABLES from sql_mode
set @@global.sql_mode = 'ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
```

Databases:
- `DARKEDEN` - Main game database
- `USERINFO` - User account database

Load schema with:
```bash
mysql -h 127.0.0.1 -u elcastle -D 'DARKEDEN' -p < initdb/DARKEDEN.sql
mysql -h 127.0.0.1 -u elcastle -D 'USERINFO' -p < initdb/USERINFO.sql
```

## Dependencies

Required libraries:
- **libmysqlclient-dev** (5.7) - MySQL client library
- **lua5.1-dev** or **luajit** - Lua scripting (used by quest system)
- **xerces-c** (3.2.3) - XML parsing (used by SXml in Core)

Install on Ubuntu/Debian:
```bash
sudo apt install libxerces-c-dev libmysqlclient-dev liblua5.1-dev
```

## Key Game Concepts

### Races
- **Slayer** - Human vampire hunters
- **Vampire** - Vampire race
- **Ousters** - Another playable race

### Core Game Systems
- **Zone/ZoneGroup** - Geographic areas where players exist
- **Creature** - Base class for all entities (players, monsters, NPCs)
- **PlayerCreature** - Player-controlled creatures (Slayer, Vampire, Ousters)
- **Effect** - Time-based effects applied to creatures
- **Skill** - Combat and utility abilities
- **Guild/Party** - Social grouping systems
- **DynamicZone** - Instanced content (e.g., dungeons)

## Running the Servers

Start servers in this order:
```bash
./bin/loginserver -f ./conf1/loginserver.conf
./bin/sharedserver -f ./conf1/sharedserver.conf
./bin/gameserver -f ./conf1/gameserver.conf
```

## Development Notes

- Source file encoding is **UTF-8** (project was migrated from legacy encodings)
- Use **English** as code comment, there are some legacy Korean or maybe garbled encoding, translate them to English whenever possible
- C++11 standard is used
- Threaded architecture with `ZoneGroupThread` for parallel zone processing
- Extensive use of inheritance (Creature → PlayerCreature → Slayer/Vampire/Ousters)
- Lua scripting is integrated for quest systems (see `quest/luaScript/`)
- Exchange system in `gameserver/exchange/` handles player trading
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
.PHONY: all fmt fmt fmt-check fmt-check-all clean help debug

# Default target
all:
all: debug

release:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(shell sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)

Expand Down
93 changes: 93 additions & 0 deletions initdb/DARKEDEN.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11920,6 +11920,99 @@ LOCK TABLES `loginerror` WRITE;
/*!40000 ALTER TABLE `loginerror` DISABLE KEYS */;
/*!40000 ALTER TABLE `loginerror` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `ExchangeListing`
--

DROP TABLE IF EXISTS `ExchangeListing`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ExchangeListing` (
`ListingID` BIGINT(20) NOT NULL AUTO_INCREMENT,
`ServerID` SMALLINT(5) UNSIGNED NOT NULL,
`SellerAccount` VARCHAR(50) NOT NULL,
`SellerPlayer` VARCHAR(50) NOT NULL,
`SellerRace` TINYINT(3) UNSIGNED NOT NULL,
`ItemClass` TINYINT(3) UNSIGNED NOT NULL,
`ItemType` SMALLINT(5) UNSIGNED NOT NULL,
`ItemID` BIGINT(20) NOT NULL,
`ObjectID` INT(10) UNSIGNED NOT NULL,
`PricePoint` INT(10) UNSIGNED NOT NULL,
`Currency` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=Point Coupon',
`Status` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=ACTIVE,1=SOLD,2=CANCELLED,3=EXPIRED',
`BuyerAccount` VARCHAR(50) NULL,
`BuyerPlayer` VARCHAR(50) NULL,
`TaxRate` TINYINT(3) UNSIGNED NOT NULL DEFAULT '8',
`TaxAmount` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`CreatedAt` DATETIME NOT NULL,
`ExpireAt` DATETIME NOT NULL,
`SoldAt` DATETIME NULL,
`CancelledAt` DATETIME NULL,
`UpdatedAt` DATETIME NOT NULL,
`Version` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`ItemName` VARCHAR(100) NULL,
`EnchantLevel` TINYINT(3) UNSIGNED NULL,
`Grade` SMALLINT(5) UNSIGNED NULL,
`Durability` INT(10) UNSIGNED NULL,
`Silver` SMALLINT(5) UNSIGNED NULL,
`OptionType1` TINYINT(3) UNSIGNED NULL,
`OptionType2` TINYINT(3) UNSIGNED NULL,
`OptionType3` TINYINT(3) UNSIGNED NULL,
`OptionValue1` SMALLINT(5) UNSIGNED NULL,
`OptionValue2` SMALLINT(5) UNSIGNED NULL,
`OptionValue3` SMALLINT(5) UNSIGNED NULL,
`StackCount` INT(10) UNSIGNED NULL,
PRIMARY KEY (`ListingID`),
UNIQUE KEY `UNQ_Listings_Item` (`ItemClass`,`ItemID`,`ObjectID`),
KEY `IDX_Listings_Server_Status_CreatedAt` (`ServerID`,`Status`,`CreatedAt`),
KEY `IDX_Listings_Server_ItemClass_ItemType` (`ServerID`,`ItemClass`,`ItemType`),
KEY `IDX_Listings_SellerAccount_Status` (`SellerAccount`,`Status`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `ExchangeListing`
--

LOCK TABLES `ExchangeListing` WRITE;
/*!40000 ALTER TABLE `ExchangeListing` DISABLE KEYS */;
/*!40000 ALTER TABLE `ExchangeListing` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `ExchangeOrder`
--

DROP TABLE IF EXISTS `ExchangeOrder`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ExchangeOrder` (
`OrderID` BIGINT(20) NOT NULL AUTO_INCREMENT,
`ListingID` BIGINT(20) UNIQUE NOT NULL,
`ServerID` SMALLINT(5) UNSIGNED NOT NULL,
`BuyerAccount` VARCHAR(50) NOT NULL,
`BuyerPlayer` VARCHAR(50) NOT NULL,
`PricePoint` INT(10) UNSIGNED NOT NULL,
`TaxAmount` INT(10) UNSIGNED NOT NULL,
`Status` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=PAID,1=DELIVERED,2=CANCELLED',
`CreatedAt` DATETIME NOT NULL,
`DeliveredAt` DATETIME NULL,
`CancelledAt` DATETIME NULL,
PRIMARY KEY (`OrderID`),
KEY `IDX_Orders_BuyerPlayer_Status` (`BuyerPlayer`,`Status`),
KEY `IDX_Orders_ListingID` (`ListingID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `ExchangeOrder`
--

LOCK TABLES `ExchangeOrder` WRITE;
/*!40000 ALTER TABLE `ExchangeOrder` DISABLE KEYS */;
/*!40000 ALTER TABLE `ExchangeOrder` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
Expand Down
56 changes: 56 additions & 0 deletions initdb/USERINFO.sql

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions src/Core/CGExchangeBuy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//////////////////////////////////////////////////////////////////////////////
// Filename : CGExchangeBuy.cpp
// Written By : Exchange System
// Description : Client requests to buy an item
//////////////////////////////////////////////////////////////////////////////

#include "CGExchangeBuy.h"
#include "GCExchangeBuy.h"

#include "PlayerCreature.h"
#include "../server/gameserver/exchange/ExchangeService.h"

void CGExchangeBuy::read(SocketInputStream& iStream) {
__BEGIN_TRY

uint64_t listingID;
iStream.read(listingID);
m_ListingID = (int64_t)listingID;

iStream.read(m_IdempotencyKey);

__END_CATCH
}

void CGExchangeBuy::write(SocketOutputStream& oStream) const {
__BEGIN_TRY

oStream.write((uint64_t)m_ListingID);
oStream.write(m_IdempotencyKey);

__END_CATCH
}

PacketSize_t CGExchangeBuy::getPacketSize() const {
PacketSize_t size = 0;
size += sizeof(m_ListingID);
size += m_IdempotencyKey.size();
return size;
}

string CGExchangeBuy::toString() const {
StringStream msg;
msg << "CGExchangeBuy("
<< "ListingID:" << (int)m_ListingID
<< ",IdempotencyKey:" << m_IdempotencyKey
<< ")";
return msg.toString();
}

void CGExchangeBuy::execute(Player* pPlayer) {
__BEGIN_TRY

CGExchangeBuyHandler::execute(this, pPlayer);

__END_CATCH
}
Loading
Loading