Overview
The Clans is a multi-user fantasy RPG door game for BBS systems. It features clan management, empire warfare, turn-based combat, and a unique Inter-BBS (IBBS) multiplayer system that links multiple BBSes into a shared game world. The game was written in C (originally Turbo C++ for DOS), and has since been modernized to compile with GCC and C17, targeting FreeBSD, Linux, and Windows.
Each BBS running the game acts as a "village" in a shared league. Players build clans, join empires, and fight for control of the village. Through the IBBS system, clans can travel to -- and attack -- other villages over a Fidonet-style packet network.
This document is the developer and contributor reference. For sysop installation and game setup, see release/clans.txt. For quest and NPC development, see devkit/clandev.txt.
License and History
The Clans was written by Allen Ussher from 1997 to 2002. The game originally ran on DOS-based BBS systems, compiled with Turbo C++ and using the OpenDoors door library by Brian Pirie. It was released as open source under the GPL v2 in 2002.
Modernization work includes:
- Compiler: GCC with C17 standard, replacing Turbo C++
- Platforms: FreeBSD, Linux, and Windows (was DOS only)
- Serialization: Explicit pack/unpack macros replacing raw struct I/O
- Platform abstraction:
unix_wrappers.c/win_wrappers.c/platform.c - Build system: GNUmakefile (Unix) and Visual Studio solution (Windows)
The OpenDoors library is still used for BBS door integration and is linked statically as libODoors.a. For the GPL v2 license text, see docs/gpl.txt.
Prerequisites
Unix (FreeBSD, Linux)
- GCC with C17 support
- GNU Make (invoke as
gmakeon BSD systems) libODoors.a-- the OpenDoors library, compiled for your target platform and placed where the linker can find it
On FreeBSD, GCC and GNU Make can be installed via ports or pkg. The OpenDoors library must be compiled from source. Once libODoors.a is available, the build system will link it automatically.
Windows
- Visual Studio 2022 (version 17) or later
- The solution file
src/clans.slncontains a project for every binary. Open it in Visual Studio and build the desired projects or the entire solution.libODoors.amust also be compiled and available for the linker. Alternatively, MinGW-w64 with GCC and GNU Make can be used with the GNUmakefile path described below.
Building from Source
All source files are in src/. Data source files are in data/.
Unix / MinGW -- GNUmakefile
| Command | Description |
|---|---|
gmake -j8 | Build game binaries and data (PAK) |
gmake -j8 game | Build game binaries only |
gmake -j8 devkit | Build devkit tools only |
gmake clean | Remove object files and local copies |
gmake deepclean | Full clean, including bin/ directories |
gmake DEBUG=1 game | Debug build with full warnings and -g |
Build output goes to bin/<os>.<arch>.<flavour>/ -- for example:
bin/freebsd.amd64.opt/ Release build on FreeBSD/amd64 bin/freebsd.amd64.debug/ Debug build
The game target also copies binaries and clans.pak to the project root directory, which is where the test/ instance expects to find them. The release build uses -Os -flto. The debug build enables -Wall -Wextra -Wconversion -pedantic -g -O0.
Windows -- Visual Studio 2022
Open src/clans.sln. The solution contains one project per binary. Build individual projects or the full solution. Output goes to the standard VS output directories (Debug/ or Release/ under each project). Data files must still be built using the devkit tools.
Running and Testing
There are no automated tests. The test/ directory contains a pre-configured game instance for manual verification:
cd test/ ./clans Run the main game ./reset Run the maintenance reset ./config Run the configuration utility ./pcedit Edit player characters
After gmake game, binaries and clans.pak are copied to the project root alongside test/. The shell scripts in test/ expect the binaries in the parent directory (../clans, etc.).
The devkit tool qtest allows testing a single quest event script outside of the full game -- useful when developing new quests.
Module Summary
All 61 source files live in src/. They are organized as follows:
| Category | Files |
|---|---|
| Core game engine | clans.c, game.c, empire.c, user.c, fight.c, village.c |
| Inter-BBS networking | ibbs.c, myibbs.c, packet.h, interbbs.h |
| Gameplay systems | alliance.c, alliancem.c, quests.c, spells.c, items.c, npc.c, class.c, trades.c, pawn.c, voting.c, news.c, mail.c, maint.c, help.c, scores.c, event.c, menus.c, menus2.c |
| Data serialization | serialize.c, deserialize.c, myopen.c |
| Platform abstraction | unix_wrappers.c, win_wrappers.c, platform.c, door.c |
| Content and strings | language.c, mstrings.h |
| Utilities | input.c, video.c, random.c, parsing.c, misc.c, tools.c, readcfg.c, clansini.c, semfile.c, system.c, console.c, reg.c |
| Game utilities | config.c, reset.c, pcedit.c |
| DevKit tools | ecomp.c, mcomp.c, mclass.c, mitems.c, mspells.c, makenpc.c, makepak.c, langcomp.c, chew.c, gum.c, qtest.c, install.c |
Compiled Binaries
| Target | Binaries |
|---|---|
| DOOR_BINARIES | clans, config, pcedit, reset |
| DEVKIT_BINARIES | chew, ecomp, langcomp, makenpc, makepak, mcomp, mclass, mitems, mspells, qtest |
Core Game Engine
| File | Description |
|---|---|
clans.c | Entry point. Command-line parsing, pre-game menu, and daily maintenance gate. main() lives here. |
game.c | Game-state management. Loads and saves GAME.DAT, which records the active game instance: start date, IBBS flag, next clan ID, and per-game settings. |
empire.c | (~100KB) Empire and domain management: buildings, army raising, tribute, and inter-BBS packet dispatch. This is the largest and most complex file in the codebase. Also processes incoming IBBS packets during maintenance. |
user.c | (~68KB) Player and clan data management: file I/O, user list maintenance, clan creation and deletion, and travel state. |
fight.c | (~50KB) Combat engine: damage calculations, the battle loop, and post-fight effects including experience gain, loot, and casualties. |
village.c | Village state management. Loads and saves VILLAGE.DAT; manages tax rates, market prices, colour schemes, and village flags. |
Inter-BBS Networking
| File | Description |
|---|---|
ibbs.c | (~101KB) The IBBS subsystem. Reads and writes Fidonet-style packet files, processes all incoming packet types from remote BBSes, and drives the IBBS portion of daily maintenance. See section 4 for packet details; see docs/ibbs-notes.txt for the full protocol. |
myibbs.c | Low-level Fidonet netmail message creation. Based on OpenDoors sample code by Brian Pirie. Constructs outbound netmail messages with kludge lines and INTL/FMPT/TOPT headers, with data files as attachments. Do not modify this file unless you have a clear understanding of the Fidonet message format. |
packet.h | Packet-type constants (PT_*). See section 4.1. |
interbbs.h | Additional inter-BBS protocol definitions. |
Gameplay Systems
| File | Description |
|---|---|
alliance.c | Alliance management ADT: create, join, and disband player alliances. |
alliancem.c | Alliance menus and player-facing interaction. |
quests.c | Quest system. Loads QUESTS.INI, executes compiled .e event scripts, and tracks per-clan completion state. |
spells.c | Spell system: casting, effects, and learning. |
items.c | Item system: inventory, buying, and equipping. |
npc.c | NPCs in town (streets, church, market, etc.). Loads NPC data from clans.pak. These are not the monsters fought in the mines. |
class.c | Player classes and races (PClass struct). |
trades.c | Trade and commerce between clans. |
pawn.c | Pawn shop: selling and buying used items. |
voting.c | Village leader voting mechanics. |
news.c | Village news bulletin system. |
mail.c | Internal mail between clans. |
maint.c | Daily maintenance: stat recalculation, IBBS sync, online flag cleanup, score tabulation. |
help.c | Interactive help system; displays .hlp files. |
scores.c | Score calculation and leaderboard generation. |
event.c | Mine event system. Runs compiled .e scripts for encounters within the mines. |
menus.c | Main menu rendering and navigation. |
menus2.c | Additional menus (split from menus.c when that file grew too large). |
Data Serialization
| File | Description |
|---|---|
serialize.c | Serializes structs to byte buffers using pack_* macros. All data written to disk passes through here. |
deserialize.c | Deserializes byte buffers back into structs using unpack_* macros. Mirrors serialize.c. |
myopen.c | File I/O wrappers. Applies XOR encryption when reading and writing, manages buffer allocation, and wraps fopen/fread/fwrite. |
myopen.h | Declares all BUF_SIZE_* constants. These MUST match the actual serialized byte size of each struct. Update here when adding struct fields. |
See section 3.2 for serialization rules and the full BUF_SIZE list.
Platform Abstraction
| File | Description |
|---|---|
unix_wrappers.c | Unix-specific: _fsopen() with fcntl locking, delay() via nanosleep(), FindFileList() via glob(). |
win_wrappers.c | Windows-specific: Win32 file enumeration, error dialog helpers, FreeFileList(). |
platform.c | Platform detection and initialization. |
door.c | BBS door interface (OpenDoors integration): string output, semaphore files, chat, status bar, file display, local-game loading. |
Conditional compilation uses #ifdef UNIXY for Unix-specific code and #ifdef WIN for Windows. Platform detection is handled by mk/Platform.gmake, which sets UNIXY or WIN and selects the appropriate wrapper file.
Content and Strings
| File | Description |
|---|---|
language.c | Loads the compiled language file (strings.xl) into memory. Provides string lookup by index. |
mstrings.h | CRITICAL. Every user-visible string in the game is referenced through a macro defined here. The macros expand to language file lookups by index. Never call od_printf() or similar functions with a string literal for user-visible text. Add new strings to data/strings.txt instead. |
See section 3.4 for the full strings workflow.
Utility Modules
| File | Description |
|---|---|
input.c | User input handling and key mapping. |
video.c | Terminal output: ANSI colour codes, screen layout, text input from the user. Hardware-specific; see section 6.1 before modifying. |
random.c | Random number generation (my_random()). |
parsing.c | String parsing utilities. |
misc.c | Miscellaneous helpers (date calculations, etc.). |
tools.c | General-purpose helper functions. |
readcfg.c | INI/config file reader, used by clansini.c. |
clansini.c | Loads and parses CLANS.INI: NPC file list, race and class overrides, language file path, etc. |
semfile.c | Semaphore file management (ONLINE.FLG). |
system.c | Low-level system calls for the door environment. |
console.c | Error output and console text cursor helpers. |
reg.c | Legacy registration stub (no longer enforced; registration was removed in the open-source release). |
Tools and Utilities
Game Utilities
| File | Description |
|---|---|
config.c | CONFIG -- interactive setup for node and BBS configuration. Run once before the game. |
reset.c | RESET -- initializes or resets all game data files. Must be run after CONFIG on first install. |
pcedit.c | PCEDIT -- sysop tool for editing player character data directly. |
install.c | INSTALL -- installation helper (separate binary, not part of DOOR_BINARIES or DEVKIT_BINARIES). |
DevKit Tools
Built by gmake devkit:
| File | Description |
|---|---|
langcomp.c | Compiles strings.txt into strings.xl. |
ecomp.c | Compiles .evt event scripts into binary .e files. |
mcomp.c | Compiles monster data text files. |
mclass.c | Compiles class and race data. |
mitems.c | Compiles item data. |
mspells.c | Compiles spell data. |
makenpc.c | Compiles NPC definitions. |
makepak.c | Bundles compiled data files into clans.pak. |
chew.c | Creates .GUM compressed archives for distribution. |
gum.c | GUM archive format implementation (used by chew). |
qtest.c | Quest script tester: runs a single .e file outside of the full game environment. |
Each devkit tool is documented in devkit/*.txt. See especially devkit/clandev.txt for a comprehensive content-creation guide.
Data Structures (structs.h)
Almost all game data structures are defined in src/structs.h. The 42 structs are organized by function:
Configuration and Runtime System
| Struct | Description |
|---|---|
struct IniFile | File handles for all loaded data files. |
struct NodeData | BBS node config: number, dropfile dir, FOSSIL address, IRQ. |
struct config | Global sysop config: BBS name, IBBS settings, logging flags. |
struct system | Runtime state: dates, working directory, node number, local-game flags. |
Core Game State
| Struct | Description |
|---|---|
struct game_data | Active game instance: start date, IBBS flag, next clan ID, game settings. Persisted in GAME.DAT. |
struct game | Wrapper for game_data with init flag. |
struct village_data | Village state: name, taxes, markets, colour scheme, flags. In VILLAGE.DAT. |
struct village | Wrapper for village_data with init flag. |
struct empire | Empire/domain: vault gold, land, buildings, alliance, spy data. |
struct ResetData | Data written by RESET, read at startup. |
Player and Combat
| Struct | Description |
|---|---|
struct pc | Player character: name, HP/SP, 6 attributes, equipped items, race, class, XP, level, training points, spells. |
struct clan | Clan: ID, username, clan name/symbol, quests completed, NPC members, items, empire link. |
struct Army | Military unit: footmen, axemen, knights, followers, combat rating. |
struct Strategy | Combat strategy: attack/defend length and intensity, loot level. |
struct SpellsInEffect | Active spell on a combatant: spell ID and remaining energy. |
struct AttackResult | Battle outcome: success flag, damage, casualties, gold and land captured. |
struct PClass | Player class or race: name, attribute modifiers, starting HP/gold, spells. |
Items, Spells, and Content
| Struct | Description |
|---|---|
struct item_data | Item: name, type, attribute effects, market cost, village type restriction. |
struct Spell | Spell: name, type flags, damage/heal text strings, SP cost. |
struct BuildingType | Empire building: name, attack hit zones, land and energy use, construction cost. |
struct Language | Loaded language file: string offsets and the full text pool. |
Social, NPCs, and Alliances
| Struct | Description |
|---|---|
struct Alliance | Player alliance: ID, name, creator, member list, shared empire, items. |
struct NPCInfo | NPC: name, topic list, loyalty score, wander type, village type restriction. |
struct NPCNdx | NPC runtime: name, clan membership, current location. |
struct Topic | Conversation topic: visibility flags, display name, script label. |
Trade and Messages
| Struct | Description |
|---|---|
struct TradeList | Trade offer: gold, followers, troops. |
struct TradeData | Trade request: offer/ask lists, clans. |
struct Msg_Txt | Message body: text buffer, line offsets. |
struct Message | Message header: to/from clan IDs and names, type, alliance, BBSID. |
Inter-BBS Packets and Nodes
| Struct | Description |
|---|---|
struct Packet | IBBS packet header: type, GameID, date, source/destination BBSID, payload length. |
struct AttackPacket | Attack payload: armies, target empire, source/dest BBSIDs. |
struct SpyAttemptPacket | Spy mission: spy stats, target BBSID. |
struct SpyResultPacket | Spy result: success, empire data. |
struct ibbs_node_info | Node: BBS name, village, Fidonet address, routing, mailer type. |
struct ibbs_node_reset | Reset tracking per remote node. |
struct ibbs_node_recon | Recon tracking per remote node. |
struct ibbs_node_attack | Attack packet index per remote node. |
struct ibbs | Top-level IBBS state: this BBS's ID, node array, flags. |
Other
| Struct | Description |
|---|---|
struct UserInfo | User record in userlist.dat: ClanID, name, deleted flag. |
struct UserScore | Leaderboard entry: ClanID, symbol, name, score points. |
struct LeavingData | Clan travel state: active, destination, troop counts. |
struct Door | Door session state: initialized flag, pause-screen setting, colour mode. |
Serialization System
Game data files are written and read using explicit serialization rather than raw struct dumps. This ensures a stable on-disk format that is independent of compiler layout and padding decisions.
The serialize.c / deserialize.c pair provides pack_* and unpack_* macros for each supported type:
| Pack / Unpack | Size | Description |
|---|---|---|
pack_char / unpack_char | 1 byte | Character |
pack_int8 / unpack_int8 | 1 byte | Signed integer |
pack_int16_t / unpack_int16_t | 2 bytes | Signed, little-endian |
pack_int32_t / unpack_int32_t | 4 bytes | Signed, little-endian |
pack_bool / unpack_bool | 1 byte | Boolean |
pack_int16_tArr | n × 2 bytes | Array of int16_t values |
Both serialize and deserialize functions return SIZE_MAX on buffer overflow or underflow.
XOR encryption is applied by myopen.c at the file I/O layer. Each data file type has its own constant (see section 3.3).
Buffer Sizes (BUF_SIZE_* in myopen.h)
Buffer sizes for every serialized struct are declared as BUF_SIZE_* constants in myopen.h. The current values are:
| Constant | Size | Constant | Size |
|---|---|---|---|
BUF_SIZE_Alliance | 2121 | BUF_SIZE_Army | 27 |
BUF_SIZE_AttackPacket | 192 | BUF_SIZE_AttackResult | 263 |
BUF_SIZE_clan | 2249 | BUF_SIZE_empire | 141 |
BUF_SIZE_game_data | 105 | BUF_SIZE_item_data | 62 |
BUF_SIZE_Language | 4032 | BUF_SIZE_LeavingData | 27 |
BUF_SIZE_Message | 210 | BUF_SIZE_MessageHeader | 190 |
BUF_SIZE_Msg_Txt | 84 | BUF_SIZE_NPCInfo | 1266 |
BUF_SIZE_NPCNdx | 29 | BUF_SIZE_Packet | 38 |
BUF_SIZE_pc | 140 | BUF_SIZE_PClass | 69 |
BUF_SIZE_Spell | 39 | BUF_SIZE_SpellsInEffect | 4 |
BUF_SIZE_SpyAttemptPacket | 88 | BUF_SIZE_SpyResultPacket | 186 |
BUF_SIZE_Strategy | 5 | BUF_SIZE_Topic | 98 |
BUF_SIZE_TradeData | 86 | BUF_SIZE_TradeList | 24 |
BUF_SIZE_UserInfo | 65 | BUF_SIZE_UserScore | 61 |
BUF_SIZE_village_data | 304 |
- Add
pack_*/unpack_*calls toserialize.canddeserialize.c. - Update the corresponding
BUF_SIZE_*constant inmyopen.h. - The CRC field must always be the last field serialized.
XOR Encryption Constants
Data files are XOR-encrypted byte-by-byte with a fixed per-type key defined in src/defines.h. This prevents casual editing of binary data files but is not a security mechanism. A determined attacker with access to the source code can decode any file trivially.
| Constant | Value | Used For |
|---|---|---|
XOR_VILLAGE | 9 | village.dat |
XOR_GAME | 9 | game.dat |
XOR_USER | 9 | userlist.dat, per-clan files |
XOR_PC | 9 | Player character files |
XOR_MSG | 69 | Message files |
XOR_ITEMS | 23 | Item files |
XOR_ALLIES | 0xA3 | Alliance files |
XOR_TRADE | 0x2B | Trade data files |
XOR_IBBS | 0x8A | IBBS state files |
XOR_PACKET | 0x47 | Outgoing packet files |
XOR_IPS | 0x94 | IPS data |
XOR_TRAVEL | 0x3B | LeavingData / travel state files |
XOR_ULIST | 0xCE | User list broadcast packets |
XOR_DISBAND | 0x79 | Disband / removal packets |
Language and String System
User-visible strings are not hardcoded in C source. They live in data/strings.txt and are compiled into the binary language file data/strings.xl by the langcomp devkit tool.
Workflow for Adding or Changing a String
- Add or edit the entry in
data/strings.txt. - Run:
langcomp strings.txt strings.xl - Run:
makepak pak.lst clans.pak(or copystrings.xlmanually to the game directory for quick testing).
In C code, strings are referenced through macros in src/mstrings.h:
od_printf( STR_WELCOME ); od_printf( STR_CLAN_DISBANDED );
Each macro expands to a lookup in the loaded Language struct by index. The macros are the authoritative list of all user-visible strings. Do not add string literals to source code for any text that a player will see.
Packet Types
Inter-BBS packets are identified by the PT_* constants in packet.h.
| Constant | Value | Description |
|---|---|---|
PT_CLANMOVE | 0 | Clan migrating to a remote BBS. Carries up to 6 player character records. |
PT_DATAOK | 1 | Acknowledgment of a received PT_CLANMOVE. |
PT_RESET | 2 | Game reset with new game_data payload. |
PT_GOTRESET | 3 | Acknowledgment of PT_RESET. |
PT_RECON | 4 | Reconnaissance request. |
PT_GOTRECON | 5 | Reconnaissance response. |
PT_NEWUSER | 6 | New user registration. Always sent to the League Controller (BBS ID 1) first. |
PT_DELUSER | 7 | Delete user (system-generated, e.g. duplicate purge by the League Controller). |
PT_ADDUSER | 8 | Add user. Broadcast FROM the LC to all nodes after PT_NEWUSER deduplication. |
PT_SUBUSER | 9 | Subtract user (player-initiated removal). |
PT_COMEBACK | 10 | Clan returning from another BBS. |
PT_MSJ | 11 | Message posted across BBSes. |
PT_ATTACK | 12 | Inter-BBS military attack. |
PT_ATTACKRESULT | 13 | Attack result from the defending BBS. |
PT_SPY | 14 | Spy mission attempt. |
PT_SPYRESULT | 15 | Spy result from the target BBS. |
PT_SCOREDATA | 16 | Score data from one BBS. |
PT_SCORELIST | 17 | Score list broadcast. |
PT_NEWNDX | 18 | New node index (WORLD.NDX update). |
PT_ULIST | 19 | Full user list broadcast from the LC. Overwrites records for remote users only; never clobbers the home node's own users. |
Packet Filenames
Outgoing packet files follow the naming convention:
clFFFTTTDDI
| Field | Description |
|---|---|
FFF | 3-digit zero-padded source BBS ID |
TTT | 3-digit zero-padded destination BBS ID |
DD | 2-character league ID |
I | Sequence index (starts at 'a', increments per batch) |
Example: cl001002AAa is the first outbound packet from BBS 001 to BBS 002 in league "AA".
Packets are written to the outbound/ directory and attached to outgoing Fidonet netmail messages by myibbs.c.
BACKUP.DAT tracks all in-flight packets. During maintenance, stale PT_CLANMOVE entries cause the clan to be re-added locally; stale PT_ATTACK entries return troops to the attacking side.
League Structure and Roles
Each BBS in the league is identified by a numeric BBSID. BBS ID 1 is the League Controller (LC), which is the authoritative node.
New User Registration Flow
- User registers on BBS X. BBS X sends
PT_NEWUSERto the LC. - LC checks for duplicate names across all known users.
- LC broadcasts
PT_ADDUSERto every node in the league. - All nodes add the user to their local userlist on receipt.
Periodically, the LC sends a full PT_ULIST to all nodes to re-synchronize the user list. PT_ULIST updates only records for users whose home BBS is not the receiving node; local users are never overwritten.
Configuration Files
| File | Description |
|---|---|
WORLD.NDX | Node list: BBSID, BBS name, village name, Fidonet address, and routing. See release/worldndx.smp. |
ROUTE.CFG | Routing and forwarding rules. See release/route.smp. |
The full IBBS protocol, including packet flow diagrams and edge case handling, is in docs/ibbs-notes.txt.
Further Reading
docs/ibbs-notes.txt is the authoritative IBBS protocol reference. It documents every packet type in detail, the BACKUP.DAT recovery mechanism, and the full sequence for common operations like clan migration, inter-BBS attacks, and league resets.
Data Files Overview
The game reads all content from clans.pak, a custom archive file similar in concept to a Quake PAK file. The PAK is assembled from compiled binary data by the makepak devkit tool.
Files Inside clans.pak
| File | Contents |
|---|---|
strings.xl | Compiled language strings |
monsters.mon | Monster definitions |
items.itm | Item definitions |
classes.cls | Player class definitions |
races.cls | Race definitions |
spells.spl | Spell definitions |
npcquote.q | NPC dialogue |
clans.npc | NPC definitions |
eventmon.mon | Event encounter monster definitions |
*.e | Compiled event scripts |
*.hlp | In-game help files |
*.asc | ANSI/ASCII screen art |
The source text files for all of the above live in data/ and are compiled by devkit tools into their binary forms. pak.lst in data/ lists exactly which compiled files are included in the PAK and what internal path each one gets.
Building Data Files
All commands below are run from the data/ directory (or provide paths accordingly). The tools must be on your PATH or invoked with their full path from bin/.
| Command | Output |
|---|---|
langcomp strings.txt strings.xl | Language file |
mcomp monsters.txt monsters.mon | Monsters |
mcomp eventmon.txt eventmon.mon | Event monsters |
mcomp npc-pc.txt npc-pc.mon | NPC PC stats |
mitems items.txt items.itm | Items |
mspells spells.txt spells.spl | Spells |
mclass classes.txt classes.cls | Classes |
mclass races.txt races.cls | Races |
makenpc npc-pc.mon npcquote.q clans.npc | NPC index |
ecomp foo.evt foo.e | Event script |
makepak pak.lst clans.pak | Bundle into PAK |
After modifying any source data file, you must recompile it and rebuild clans.pak before the changes will appear in the game.
The genall.bat script in data/ was the original batch runner for all of the above steps. Under Unix, run the commands individually or write a shell script to automate them.
Each tool is documented in devkit/*.txt. See devkit/clandev.txt for a comprehensive guide to creating quests, NPCs, and monsters.
Event Scripts
Events are scripts for mine encounters and quests. They are written in a simple compiled scripting language and processed by event.c and quests.c at runtime.
Key Scripting Commands
| Command | Description |
|---|---|
Event BlockName | Begin a named block. |
End | End the block (stop execution). |
Text "message" | Display text to the player. |
Input [Label] [text] | Numbered-choice menu; jumps to Label. |
Option [Char] [Label] | Single-keystroke option. |
Fight [monster] [...] | Initiate a combat encounter. |
# comment | Line comment. |
Scripts are compiled from .evt (source) to .e (binary) using ecomp. The compiled .e files are what the game actually loads.
Registering Quests in QUESTS.INI
| Field | Description |
|---|---|
Name | Display name shown to the player in the quest list. |
File | Path to the compiled .e script. |
Index | Entry-point block name in the script. |
Known | Quest is visible from the start. |
Quest Help Entries (QUESTS.HLP)
Quest descriptions shown in the help system live in QUESTS.HLP. Each entry follows this format:
^Quest Name Description text. ^END
See devkit/ecomp.txt for the full event command reference, and devkit/event1.evt through event5.evt for worked examples. The QUESTS.INI and QUESTS.HLP in the release/ directory are the reference versions for the built-in quest set.
Help Files
The in-game F1/? help system loads plain ASCII .hlp files bundled in clans.pak. Each file covers one topic area. Current files:
army.hlp |
bulletin.hlp |
church.hlp |
citizen.hlp |
clans.hlp |
combat.hlp |
empire.hlp |
items.hlp |
menus.hlp |
newbie.hlp |
races.hlp |
ruler.hlp |
spells.hlp |
stats.hlp |
strategy.hlp |
village.hlp |
war.hlp |
wizard.hlp |
To add a new help topic, create a .hlp file, add it to pak.lst, and register it in help.c. Help files are plain text; ANSI colour codes are supported.
Internal %-codes
rputs() in door.c processes %-codes embedded in any displayed text. The general-purpose codes (%P, %C, %L, %R, %D, %B, %V) and the game-state codes (%F, %M, %1, %2, %X, %Q, %N, %T, %Y) are documented in clans.txt section 1.9.
The following codes are used internally by strings.txt and the combat system. They depend on transient state that is only valid during specific game routines and are not useful in quest packs or sysop-editable files:
| Code | Description |
|---|---|
%SS | Spell cast source name (Spells_szCastSource) |
%SD | Spell cast destination name (Spells_szCastDestination) |
%SV | Spell cast value (Spells_CastValue) |
%Z | Truncate line -- returns from rputs() immediately |
%SS, %SD, and %SV are populated only during spell resolution in fight.c. Outside that context, they contain stale or empty values.
%Z silently discards the rest of the current rputs() call. It has no practical use in content files.
Current Issues
XOR encryption is trivially weak
The per-file XOR keys in defines.h are visible in the source. Anyone with the source can decode and modify any data file. This has always been the case; the original author acknowledged it explicitly. Do not rely on it for security. See section 6.2 for the recommended approach.
video.c is hardware-specific
The terminal output layer carries assumptions from the original DOS/serial-port environment. It works correctly on current platforms but is fragile. Review the full file before making any changes to the terminal output layer.
ibbs.c and empire.c are large and complex
At ~100KB each, these files pre-date the current serialization and platform abstraction layers and contain legacy patterns. Changes here require extra care and thorough manual testing.
Some user-visible strings are still hardcoded
Despite the language system, a number of string literals remain in the C source that should be in strings.txt. Any such string is a localization gap and a maintenance burden.
No automated tests
All verification is manual via the test/ directory. A bug in the IBBS path, for example, can only be caught by running a two-node local league and exercising the specific code path.
Suggested Improvements
These are the original author's recommendations, still valid:
1. Replace the Fidonet transport with TCP sockets
The current IBBS system requires a configured Fidonet mailer (Binkley or compatible) at every node. This is the biggest practical barrier to setting up an IBBS league. A direct TCP packet exchange between nodes would eliminate this dependency.
2. Replace XOR encryption with real packet authentication
Ideally, sign packets with a per-league private key so that nodes can verify packet origin. This would close the cheating vector in competitive leagues.
3. Harden the IBBS state machine
The BACKUP.DAT recovery logic handles the common cases, but desync between nodes is still possible in some edge cases (e.g., a node missing a PT_ADDUSER and later receiving a PT_ULIST that includes that user's data).
Additional modernization ideas:
4. Add an automated test harness
The quest event interpreter (quests.c / event.c) is the most tractable place to start; individual .evt scripts can be tested in isolation with qtest.
5. Move remaining hardcoded strings to data/strings.txt
A grep for od_printf(" in the source will find them.
6. Add CRC validation to all deserialization paths
Currently, some deserialize functions check CRC and some do not. Consistent validation would catch file corruption much earlier and make debugging easier.
Build Targets
| Target | Description |
|---|---|
gmake game | Build game binaries and copy them, along with clans.pak and release config, to the project root directory. |
gmake devkit | Build devkit tools and copy them, along with devkit/ documentation, to the project root directory. |
gmake installer | Assemble a distribution .zip. Builds all binaries and data, stages them in stage/, runs chew, and outputs: Clans-<os>-<arch>-0_97b1.zip |
gmake devkit-installer | Assemble the devkit .zip: ClansDevKit-<os>-<arch>-0_12.zip |
gmake install | Install to PREFIX. Copies game binaries to $(INSTALLDIR) and data to the same. |
gmake clean | Remove object files and local binary copies from the project root. |
gmake deepclean | Full clean including the bin/ directory. |
Release Artifacts
Complete Game Installation
| Category | Files |
|---|---|
| Binaries | clans, config, pcedit, reset |
| Data | clans.pak (monsters, items, spells, events, strings, NPCs, help files, and screen art) |
| Config | clans.ini, quests.ini |
| IBBS | route.cfg, world.ndx (league-specific; see samples in release/worldndx.smp and route.smp) |
| Windows | runclans.bat (launcher; edit paths for your setup) |
| Docs | release/clans.txt (sysop reference) |
| AI quest | release/questgen.txt (LLM quest generation guide), release/prompt.md (prompt for the LLM) |
DevKit Installation
| Category | Files |
|---|---|
| Binaries | chew, ecomp, langcomp, makenpc, makepak, mcomp, mclass, mitems, mspells, qtest |
| Docs | devkit/*.txt (tool reference documentation) |
| Examples | devkit/event1.evt through event5.evt, devkit/example.ini |
Copyright Notice
"Originally copyright 1997-2002 Allen Ussher. Open source contributors. GPL v2."