# PwAPI — Perfect World Game Server API

Arsitektur baru untuk komunikasi dengan Perfect World game server daemon (gamedbd, gdeliveryd, gacd), mengadopsi logic dari `src/hrace009/PerfectWorldAPI/API.php` dan `Gamed.php` ke dalam struktur Laravel modern.

---

## 📁 Struktur File

```
app/Services/
├── Api/
│   └── API.php                          # Facade utama (routing ke semua service)
├── PwApi/
│   ├── Core/
│   │   ├── Gamed.php                    # Binary packing/unpacking, socket communication
│   │   └── PwApiService.php             # Core API methods (getRole, putRole, sendMail, dll)
│   ├── Ban/
│   │   └── BanService.php               # forbidAcc, forbidRole, muteAcc, muteRole
│   ├── Chat/
│   │   └── ChatService.php              # sendMail, worldChat, broadcastMessage
│   ├── Clone/
│   │   └── CloneService.php             # copyRole, cloneFullRole, tradeInventory
│   ├── Faction/
│   │   └── FactionService.php           # addFaction, delFaction, getFactionDetail
│   ├── Item/
│   │   └── ItemService.php              # addItem, deleteItem, updateItemCount, updateItemData
│   ├── Money/
│   │   └── MoneyService.php             # putPocketMoney, putStorehouseMoney, getCash
│   ├── Role/
│   │   ├── RoleService.php              # getRole, putRole, getRoleid, renameRole
│   │   └── StatusService.php            # teleportRole, resetSkills, clearStorehousePassword
│   ├── Server/
│   │   └── ServerService.php            # gmControlGame, setExpRate, game attributes
│   └── Territory/
│       └── TerritoryService.php         # getTerritories, getRaw, parseOctet
├── protocols/
│   ├── pw_v07.php                       # Protocol version 0.7
│   ├── pw_v63.php                       # Protocol version 6.3
│   ├── pw_v69.php                       # Protocol version 6.9
│   ├── pw_v70.php                       # Protocol version 7.0
│   ├── pw_v80.php                       # Protocol version 8.0
│   ├── pw_v85.php                       # Protocol version 8.5
│   ├── pw_v88.php                       # Protocol version 8.8
│   ├── pw_v101.php                      # Protocol version 1.0.1
│   ├── pw_v145.php                      # Protocol version 1.4.5
│   ├── pw_v156.php                      # Protocol version 1.5.6 (DEFAULT)
│   └── pw_v172.php                      # Protocol version 1.7.2
└── Analytics/
    └── StatsService.php                 # getOnlineData, getUserStats

config/
└── pw-api.php                           # Konfigurasi koneksi game server
```

---

## 🔧 Konfigurasi

File: `config/pw-api.php`

```php
return [
    'local' => '127.0.0.1',
    'ports' => [
        'gamedbd'    => 29400,
        'gdeliveryd' => 29100,
        'gacd'       => 29300,
        'client'     => 29000,
    ],
    'game_version' => '156',
    'maxbuffer'    => 65536,
    's_block'      => false,
    's_readtype'   => 3,
    'max_online_list_iterations' => 5000,
];
```

### Penjelasan Port
| Port | Service | Fungsi |
|------|---------|--------|
| 29000 | client | Cek status server online/offline |
| 29100 | gdeliveryd | Mail, chat, ban, game attributes |
| 29300 | gacd | World chat, territory war commands |
| 29400 | gamedbd | Get/Put role, user data, faction |

### Penjelasan Socket Settings
| Key | Value | Fungsi |
|-----|-------|--------|
| `s_block` | `false` | Socket blocking mode |
| `s_readtype` | `3` | Cara membaca response (1=buffer, 2=loop 1024, 3=baca length header) |
| `maxbuffer` | `65536` | Max buffer size untuk readtype 1 |

---

## 🏗️ Arsitektur

### 1. `Gamed.php` — Binary Layer
Menangani semua operasi binary:
- **Packing**: `packString()`, `packOctet()`, `packInt()`, `packByte()`, `packShort()`, `packFloat()`, `packLong()`, `packLongOctet()`, `packString2()`, `cuint()`
- **Unpacking**: `unpackString()`, `unpackOctet()`, `unpackCuint()`, `unpackLong()`
- **Marshal/Unmarshal**: Konversi array PHP ↔ binary protocol
- **Socket**: `SendToGamedBD()`, `SendToDelivery()`, `SendToProvider()`, `SendToSocket()`
- **Utility**: `createHeader()`, `deleteHeader()`, `reverseOctet()`, `hex2octet()`, `hex2int()`, `getIp()`, `getTime()`, `putIp()`

### 2. `PwApiService.php` — Core API
Method-method utama yang identik dengan `hrace009/PerfectWorldAPI/API.php`:

| Kategori | Method | Fungsi |
|----------|--------|--------|
| **Server** | `serverOnline()` | Cek status server (real-time, tanpa cache) |
| | `ports()` | Cek semua port |
| | `getOnlineList()` | Daftar player online |
| | `checkRoleOnline($roleid)` | Cek apakah character online |
| **Role Read** | `getRole($roleid)` | Ambil semua data character |
| | `getRoleBase($roleid)` | Base info (name, race, class, userid) |
| | `getRoleStatus($roleid)` | Status (level, exp, hp, mp, position) |
| | `getRoleInventory($roleid)` | Inventory/pocket |
| | `getRoleEquipment($roleid)` | Equipment |
| | `getRoleStorehouse($roleid)` | Gudang |
| | `getRoleTask($roleid)` | Task data |
| | `getRolePetBadge($roleid)` | Pet badge data |
| | `getRoles($userid)` | Daftar character dari user ID |
| | `getUser($userid)` | User account info + login IP/time |
| | `getUserCash($userid)` | Gold/cash total |
| | `getRoleid($rolename)` | Cari role ID dari nama |
| | `getRoleFriends($roleid)` | Friend list |
| | `getRolePvp($var_data)` | PvP data dari octet |
| **Role Write** | `putRole($roleid, $params)` | Simpan semua data character |
| | `renameRole($roleid, $old, $new)` | Ganti nama character |
| **Mail/Chat** | `sendMail($receiver, $title, $context, $money, $item)` | Kirim mail in-game |
| | `worldChat($role, $msg, $channel)` | Chat ke world channel |
| **Ban/Mute** | `forbidAcc($roleid, $time, $reason)` | Ban account |
| | `forbidRole($roleid, $time, $reason)` | Ban character |
| | `muteAcc($roleid, $time, $reason)` | Mute account chat |
| | `muteRole($roleid, $time, $reason)` | Mute character chat |
| **Faction** | `getFactionInfo($fid)` | Info guild |
| | `getFactionDetail($fid)` | Detail guild + members |
| | `addFaction($roleid, $name, $fid)` | Buat guild |
| | `delFaction($fid)` | Hapus guild |
| | `upgradeFaction($roleid, $fid, $level)` | Upgrade guild |
| | `getUserFaction($roleid)` | Guild user |
| **Territory** | `getTerritories()` | Data territory war |
| | `territoryWar($param)` | Start/stop territory war |
| | `getRaw($table, $handler, $key)` | Raw database read |
| **Misc** | `generateSkill($params)` | Generate skill hex |
| | `parseOctet($octet, $name)` | Parse octet data |
| | `kickRole($roleid)` | Kick player |
| | `kickRoleAndWait($roleid, $maxWait)` | Kick + tunggu offline |

### 3. `API.php` — Facade
Facade yang merouting semua method ke service yang tepat via `__call()`.

```php
$api = new API();
$api->getRole(1024);           // Routing ke PwApiService
$api->sendMail(...);           // Routing ke ChatService
$api->teleportRole(...);       // Routing ke StatusService
$api->addItem(...);            // Routing ke ItemService
```

---

## 📦 Protocol Structure (pw_v156.php)

### Item Structure (Inventory/Equipment/Storehouse)
```php
'inv' => [
    'id'          => 'int',     // 4 bytes, big-endian
    'pos'         => 'int',     // 4 bytes
    'count'       => 'int',     // 4 bytes
    'max_count'   => 'int',     // 4 bytes
    'data'        => 'octets',  // cuint(length) + binary data
    'proctype'    => 'int',     // 4 bytes
    'expire_date' => 'int',     // 4 bytes
    'guid1'       => 'int',     // 4 bytes
    'guid2'       => 'int',     // 4 bytes
    'mask'        => 'int',     // 4 bytes
]
```

### Binary Size per Item
- Header (id+pos+count+max_count): 16 bytes
- Data octets: 1 byte (cuint) + N bytes (minimal 1 byte untuk '0')
- Footer (proctype+expire+guid1+guid2+mask): 20 bytes
- **Total minimal: ~41 bytes per item**

---

## 🚀 Cara Penggunaan

### Basic Usage
```php
use App\Services\Api\API;

$api = new API();

// Cek server online
if ($api->online) {
    echo "Server is online\n";
}

// Ambil data character
$role = $api->getRole(1024);
echo "Name: {$role['base']['name']}\n";
echo "Level: {$role['status']['level']}\n";

// Kirim mail dengan item
$api->sendMail(1031, 'Hadiah', 'Selamat!', 0, [
    'id' => 11208,
    'pos' => 0,
    'count' => 10,
    'max_count' => 10,
    'data' => '0',
    'proctype' => 0,
    'expire_date' => 0,
    'guid1' => 0,
    'guid2' => 0,
    'mask' => 0,
]);
```

### Static Call
```php
use App\Services\Api\API;

$online = API::serverOnline();
$role = API::getRole(1024);
```

### Via Controller
```php
class ShopController extends Controller
{
    protected API $api;

    public function __construct()
    {
        $this->api = new API();
    }

    public function purchase(Request $request)
    {
        if (!$this->api->online) {
            return response()->json(['error' => 'Server offline']);
        }

        $this->api->sendMail(
            $request->role_id,
            'Shop Purchase',
            'Thank you for your purchase!',
            0,
            $itemData
        );
    }
}
```

---

## 🧪 Testing

### Test sendMail (Verified)
```
Receiver: 1031
Item ID: 11208
Count: 10
Max Count: 10
Data: 0
Money: 0
Result: ✅ SUCCESS (Response: 9 bytes)
```

### Test File
```bash
php artisan test --filter=PwApiServiceTest
```

---

## ⚠️ Perbedaan dengan Original `hrace009/PerfectWorldAPI`

| Aspek | Original | Baru |
|-------|----------|------|
| Config | `config('pw-config.server_ip')` | `$this->pwConfig` (require langsung) |
| serverOnline | Dipanggil di constructor | Dipanggil di constructor (real-time, tanpa cache) |
| sendMail param | `($receiver, $title, $context, $item, $money)` | `($receiver, $title, $context, $money, $item)` |
| Arsitektur | 1 file monolitik | Facade + Core + Service terpisah |
| unpackCuint safety | Tidak ada | Ada `$remaining` checks |
| StatsService cache | 60 detik untuk serverOnline | Real-time (tanpa cache) |

---

## 📝 Changelog

### 2025-04-26
- ✅ Porting semua method dari `hrace009/PerfectWorldAPI/API.php` ke `PwApiService.php`
- ✅ Porting semua method dari `hrace009/PerfectWorldAPI/Gamed.php` ke `Gamed.php`
- ✅ Config loading: `require` langsung dari `config/pw-api.php`
- ✅ `serverOnline()` real-time tanpa cache
- ✅ `StatsService::getOnlineData()` — cek server status real-time
- ✅ `packString2()` ditambahkan ke `Gamed.php`
- ✅ `sendMail()` parameter disesuaikan dengan codebase
- ✅ Test sendMail ke role 1031 berhasil
- ✅ Semua syntax PHP verified
