How KingofKnight.com Was Built

A behind-the-scenes look at the technology, data sources, and ingenuity that powered one of the most comprehensive Knights Online databases on the web — from 2007 to 2014.

The Story

KingofKnight.com was born out of a simple frustration: Knights Online players in 2007 had almost no reliable web resources. You wanted to know where a monster spawned, what it dropped, or which clan was dominating your server — and there was nowhere to look. In-game knowledge lived in forum posts, personal notes, and word of mouth.

So we built the resource ourselves. Starting from nothing — with raw PHP, a MySQL database, and a lot of time spent inside the game — we constructed a platform that could look up characters, display item stats, map zones with mob locations, and automatically generate weekly clan ranking charts for every server.

Over seven years of operation, the site accumulated over 130,000 image assets, 37 player-authored guides, complete item and skill encyclopedias, detailed zone maps, monster drop tables, and a phpBB forum community. At peak traffic, the site was processing thousands of database queries per day.

Archive available: The original site in its entirety is preserved at /old/ — all 12GB of it. PHP source code, templates, and static assets are intact. The MySQL database is not included in the public archive, but the schema and query patterns are documented on this page.

The Tech Stack

By today’s standards the stack looks antiquated — but in 2007, this was a pragmatic, fast-to-deploy approach for a fan site with no budget.

PHP 4 / PHP 5

All server-side logic was written in PHP using the legacy mysql_* extension. Procedural style, include-based architecture. No frameworks, no ORM — direct SQL everywhere.

MySQL 5

Relational database with roughly 30+ tables covering items, skills, monsters, zones, clans, characters, quests, and user content. Legacy MyISAM storage engine for read-heavy workloads.

Apache + mod_rewrite

Standard LAMP stack hosting. Apache handled serving, mod_rewrite provided clean URLs, and .htaccess files controlled directory access and custom error pages.

jQuery 1.2 – 1.7

JavaScript interactivity powered by early jQuery. AJAX tooltips loaded item and character previews on hover without full page reloads — cutting-edge for 2008.

GD Library (PNG Generation)

PHP’s GD image library was used to dynamically composite clan ranking charts — combining background images, clan symbols, and text overlays into pre-rendered PNG files.

phpBB 3.0

Community forum system powering user discussions, guide submissions, and news posts. Forum posts from board ID 32 were pulled directly into the homepage news feed.

How We Gathered Game Data

The most technically interesting part of the site was how we collected real-time game data. Knights Online’s client communicates with servers using a binary protocol. Rather than intercepting packets (which would have violated terms of service), we used a combination of approaches: in-game surveying, community crowd-sourcing, and parsing publicly available game files.

The Survey / Scanner System

The core data pipeline relied on a survey system. Player volunteers would run a lightweight PHP-based “scanner” that accepted manually entered game data (monster locations, character stats, item data). This data was submitted to our database through a simple HTTP API and aggregated across all contributors.

The common/scanner.php file handled incoming survey data, validated it, and inserted or updated the appropriate database records. The monsterhelper and monsterloc tables stored crowd-sourced monster location data tied to zone IDs.

PHP — scanner.php (simplified)
// Receive and validate mob location data from player scouts
function process_mob_location($data) {
    $zone_id  = intval($data['zone']);
    $mob_id   = intval($data['mob']);
    $coord_x  = intval($data['x']);
    $coord_z  = intval($data['z']);

    // Check if location already known; update confidence weight if so
    $existing = mysql_query(
        "SELECT id, count FROM monsterloc
         WHERE zone_id = $zone_id
           AND mob_id  = $mob_id
           AND ABS(x - $coord_x) < 50
           AND ABS(z - $coord_z) < 50"
    );

    if (mysql_num_rows($existing) > 0) {
        $row = mysql_fetch_assoc($existing);
        mysql_query("UPDATE monsterloc SET count = count + 1 WHERE id = {$row['id']}");
    } else {
        mysql_query(
            "INSERT INTO monsterloc (zone_id, mob_id, x, z, count)
             VALUES ($zone_id, $mob_id, $coord_x, $coord_z, 1)"
        );
    }
}

// Fix inconsistent server name formatting across data sources
function normalize_server_name($raw) {
    $map = [
        'ares'    => 'Ares',
        'xigenon' => 'Xigenon',
        'beramus' => 'Beramus',
        'cypher'  => 'Cypher',
    ];
    return $map[strtolower($raw)] ?? ucfirst($raw);
}

Item Data Pipeline

Item data presented a unique challenge: Knights Online has thousands of items with complex stat formulas, enchantment tiers, and class requirements. We extracted this data by parsing the game client’s data files, cross-referencing with community wikis, and manual testing.

Item rarity was calculated from the enchantment level (+1 through +9) and item tier. The scanner.php file contained the rarity calculation logic that colored items in the UI — similar to how World of Warcraft colors gear by quality tier.

PHP — Item Rarity Classification
// Determine item rarity display class from enchant level and item grade
function get_item_rarity($enchant, $grade) {
    if ($grade >= 9 && $enchant >= 8) {
        return 'item-legendary';  // Gold text
    } elseif ($grade >= 7 || $enchant >= 7) {
        return 'item-epic';       // Purple text
    } elseif ($grade >= 5 || $enchant >= 5) {
        return 'item-rare';       // Blue text
    } elseif ($grade >= 3) {
        return 'item-uncommon';   // Green text
    }
    return 'item-common';         // White text
}

// Resolve weapon type from item ID range
function get_weapon_type($item_id) {
    if ($item_id >= 100 && $item_id <= 199) return 'Sword';
    if ($item_id >= 200 && $item_id <= 299) return 'Axe';
    if ($item_id >= 300 && $item_id <= 399) return 'Staff';
    if ($item_id >= 400 && $item_id <= 499) return 'Bow';
    return 'Unknown';
}

Interactive Zone Maps

One of the most popular features on the site was the interactive zone map system. Players could navigate to any zone, see the map as a background image, and then see mob and NPC locations overlaid on top — along with clickable tooltips showing creature stats.

The implementation used an HTML canvas element for coordinate-based overlays, with monster location data fetched from the database and projected onto the zone map image. Each zone map image was a PNG extracted and scaled from the game client’s texture files.

PHP — zone.php (map overlay rendering)
// Fetch all mob locations for a zone and output as JS data
$zone_id = intval($_GET['id']);
$result  = mysql_query(
    "SELECT ml.x, ml.z, ml.count,
            m.name, m.level, m.hp, m.exp
     FROM   monsterloc ml
     JOIN   monsters m ON m.id = ml.mob_id
     WHERE  ml.zone_id = $zone_id
       AND  ml.count   >= 3
     ORDER BY ml.count DESC"
);

$mobs = [];
while ($row = mysql_fetch_assoc($result)) {
    // Scale game world coordinates to map image coordinates
    $mobs[] = [
        'x'     => ($row['x'] / 65536) * 512,
        'y'     => ($row['z'] / 65536) * 512,
        'name'  => $row['name'],
        'level' => $row['level'],
        'hp'    => $row['hp'],
        'exp'   => $row['exp'],
    ];
}

// Output JSON for the canvas overlay script
echo '<script>var MOB_DATA = ' . json_encode($mobs) . ';</script>';
Map data accuracy: Mob locations were validated by requiring a minimum of 3 independent player reports before appearing on the map. The count field tracked how many players had confirmed each location, providing a basic confidence metric.

Auto-Generated Clan Ranking Charts

The clan ranking system was one of the most technically ambitious features on the site. Every week, PHP cron jobs would fetch updated clan standings from the database and generate pre-rendered PNG images — one per server — showing the top clans, their symbols, and their ranking scores.

Rather than rendering rankings dynamically on every page load (which would be expensive), we pre-computed the images and cached them on disk. This meant a simple <img> tag could serve the entire ranking chart at negligible CPU cost. The clanranking/ directory still contains 818MB of these pre-rendered charts.

PHP — Clan Ranking PNG Generator
function generate_clan_ranking_image($server, $world) {
    // Fetch top 10 clans for this server/world
    $result = mysql_query(
        "SELECT c.name, c.grade, c.symbol_id, cr.rank, cr.points
         FROM   clanranks cr
         JOIN   clans c ON c.id = cr.clan_id
         WHERE  cr.server = '$server'
           AND  cr.world  = '$world'
         ORDER  BY cr.rank ASC
         LIMIT  10"
    );

    // Create base image (800x600 with transparent background)
    $img = imagecreatetruecolor(800, 600);
    $bg  = imagecolorallocate($img, 15, 20, 35);
    imagefill($img, 0, 0, $bg);

    $gold = imagecolorallocate($img, 201, 168, 76);
    $y    = 60;

    while ($clan = mysql_fetch_assoc($result)) {
        // Overlay clan symbol PNG from symbols/ directory
        $symbol_path = "/images/symbols/{$clan['symbol_id']}.png";
        if (file_exists($symbol_path)) {
            $symbol = imagecreatefrompng($symbol_path);
            imagecopy($img, $symbol, 20, $y, 0, 0, 32, 32);
        }

        // Write clan name and points
        imagestring($img, 4, 65, $y + 8, $clan['name'], $gold);
        imagestring($img, 3, 700, $y + 10, $clan['points'], $gold);
        $y += 54;
    }

    // Save to disk for static serving
    $output = "/clanranking/{$server}_{$world}.png";
    imagepng($img, $output);
    imagedestroy($img);
}

Character & Clan Lookup

The character lookup system (char.php) allowed players to search for any character by name and server, displaying their class, level, equipment, skills, and clan membership. Data came from player submissions via the survey system and community contributions.

The clan information pages (claninfo.php) showed full member lists, clan history, and weekly ranking movements. These pages generated some of the highest traffic on the site, as players checked in on rival clans and scouted enemy rosters before PvP events.

PHP — Character Lookup (char.php pattern)
$name   = mysql_real_escape_string($_GET['name']);
$server = mysql_real_escape_string($_GET['server']);

$char = mysql_fetch_assoc(mysql_query(
    "SELECT c.*, cl.name AS clan_name, cl.grade AS clan_grade
     FROM   survey_chars c
     LEFT   JOIN clans cl ON cl.id = c.clan_id
     WHERE  c.name   = '$name'
       AND  c.server = '$server'
     LIMIT  1"
));

if (!$char) {
    ko_error("Character not found. Has a scout submitted this player's data?");
}

// Resolve class icon from class ID
$class_icons = [
    100 => 'warrior_el',  200 => 'mage_el',
    300 => 'priest_el',   400 => 'archer_el',
    500 => 'rogue_el',   600 => 'warrior_ku',
    /* ... Karus classes ... */
];
$icon = $class_icons[$char['class']] ?? 'unknown';

Database Schema Overview

The MySQL database kingofknight contained around 30+ tables split into distinct domains:

Game Content Tables (ko_*)

  • ko_items — Item definitions (name, type, stats, class req)
  • ko_skills — Skill data (mana cost, effects, requirements)
  • ko_monsters — Monster stats (HP, EXP, level, drops)
  • ko_zones — Zone metadata (name, map image, dimensions)
  • ko_npcs — NPC data (name, zone, type, inventory)
  • ko_quests — Quest objectives and rewards

Community / Survey Tables

  • survey_chars — Player-submitted character data
  • monsterloc — Crowd-sourced monster locations
  • monsterhelper — Aggregated monster tips
  • clanranks — Weekly clan ranking snapshots
  • survey_clangrades — Historical clan grade data
  • guides — Player-authored guide content
SQL — Core item table schema
CREATE TABLE ko_items (
  id           INT UNSIGNED     NOT NULL AUTO_INCREMENT,
  name         VARCHAR(128)     NOT NULL,
  type         TINYINT UNSIGNED NOT NULL DEFAULT 0,
  grade        TINYINT UNSIGNED NOT NULL DEFAULT 0,
  class_req    SMALLINT         NOT NULL DEFAULT 0,
  level_req    TINYINT UNSIGNED NOT NULL DEFAULT 0,
  attack_min   SMALLINT         NOT NULL DEFAULT 0,
  attack_max   SMALLINT         NOT NULL DEFAULT 0,
  defense      SMALLINT         NOT NULL DEFAULT 0,
  hp_bonus     SMALLINT         NOT NULL DEFAULT 0,
  mp_bonus     SMALLINT         NOT NULL DEFAULT 0,
  icon_path    VARCHAR(255)     DEFAULT NULL,
  PRIMARY KEY (id),
  KEY idx_type (type),
  KEY idx_grade (grade)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

What We Learned

What Worked

  • Pre-computed PNG charts dramatically reduced server load compared to on-the-fly rendering
  • Crowd-sourced location data with confidence weighting produced accurate mob maps
  • Separating game data tables (ko_*) from user content tables made queries clean
  • Caching AJAX tooltip responses with short TTLs reduced database hits significantly
  • phpBB forum integration (news from board ID 32) gave the site a living front page

What We’d Do Differently

  • Use PDO with prepared statements instead of mysql_* with manual escaping
  • Build an API layer for data access rather than inline SQL in every PHP file
  • Store coordinates as floats with proper geographic indexing (spatial index)
  • Implement a proper task queue for chart generation instead of blocking cron jobs
  • Use a CDN for 130k+ image assets instead of serving directly from the web host