Skip to content

Getting started

This page takes you from an empty virtual environment to a script that logs in, reads your castles, scans a kingdom and handles the errors it can meet on the way.

Install

uv add empire-core
pip install empire-core

EmpireCore supports Python 3.10 and newer and needs pydantic 2.5 or newer. It ships a py.typed marker, so mypy and pyright check your calls against its models.

Working on the library itself
git clone https://github.com/eschnitzler/EmpireCore.git
cd EmpireCore
uv sync --extra dev    # a plain `uv sync` has no pytest, ruff or mypy
uv run pytest

To build these docs locally, uv run --group docs mkdocs serve.

Log in

from empire_core import EmpireClient

client = EmpireClient(username="your_user", password="your_pass")
client.login()

login() runs the game client's handshake: the WebSocket connection, the SmartFox version check and zone login, joining the lobby room, the game's own version check (vck) and finally the account login (lli). It returns once the server has sent the login data (gbd), which fills client.state with your player and castles. The login and requests page draws the whole exchange.

Every failure raises. A wrong password raises LoginError, too many attempts LoginCooldownError, a banned account AccountBannedError, and an account that lives on another server WrongServerError. The connection and its threads are closed before the error reaches you, so a failed login leaks nothing.

Choose a server and client version

The default configuration connects to the EmpireEx_21 zone. Each world lives on its own server, so another world needs that server's address as well as its zone; instance_for_zone looks both up in the game's server list:

from empire_core import EmpireClient, EmpireConfig, instance_for_zone

config = EmpireConfig.for_instance(instance_for_zone("EmpireEx_2"))
client = EmpireClient(username="your_user", password="your_pass", config=config)

An account in accounts.json with "world": "EmpireEx_2" does this for you. fetch_network_instances() lists every world of a network.

Each login sends the version of the live game client, read from the client's files, so a game update needs no library release. Set client_version (on EmpireConfig, or on an account in accounts.json) only to pin a version.

Log in again without the password

After a login the server pushes a login token, which the client keeps in client.login_token. The push arrives just after the login data, so read it a moment after login() returns:

token = client.login_token
later = EmpireClient(username="your_user", login_token=token)
later.login()

login(recaptcha_token=...) sends a reCAPTCHA v3 token, or calls a function for one, the way the browser does. The library cannot make one itself; logins are accepted without it today.

Close the client

A logged-in client runs a receive thread, a keepalive thread and a callback thread. Use the client as a context manager so all of them stop however your code exits:

with EmpireClient(username="your_user", password="your_pass") as client:
    client.login()
    ...

Without the with block, call client.close() yourself, in a finally. The context manager does not log in for you, so the errors of login() stay visible where you call it.

Read your castles

castles = client.castle.get_all()
for castle in castles:
    print(castle.castle_id, castle.castle_name, castle.kingdom_id)

resources = client.castle.get_resources(castle_id=castles[0].castle_id)
print(resources.wood, resources.stone)

Methods that act on one of your castles take its id alone: the kingdom comes from the castle list the server sent at login. An id that is not one of your castles raises UnknownCastleError, and one listed in several of your kingdoms raises AmbiguousCastleError.

client.castle.get_all() asks the server. What the login data already told the client is in client.state, readable without a request:

player = client.state.get_local_player()      # None until the login data arrives
for castle in client.state.get_castles():
    print(castle.id, castle.name)

State is only as fresh as the last packet that carried it; see State and freshness before trusting resources read from it.

Game data

Unit stats, building prices and the other static tables come from the game's items file. An API that needs them loads it on first use, from the disk cache or a download, so nothing has to be loaded first. That first download is large; call client.load_game_data() up front to take it before timing-sensitive work. See Game data.

Scan the map

from empire_core import Kingdom, MapItemType

result = client.map.scan_kingdom(Kingdom.GREEN, item_types=[MapItemType.CASTLE])
print(f"{len(result.items)} castles, {len(result.failed_chunks)} chunks failed")

for item in result.items[:5]:
    print(item.name, item.x, item.y, item.owner_id)

A full scan walks the kingdom breadth-first from your castle and can take a few minutes. Always look at failed_chunks: a partial scan is not an empty kingdom. Map scanning shows how to re-scan cheaply.

Handle errors

Everything the library raises derives from EmpireError:

from empire_core import CommandError, EmpireError, EmpireTimeoutError

try:
    castles = client.castle.get_all()
except CommandError as e:
    print(f"the server refused {e.command} with code {e.code}")
except EmpireTimeoutError:
    print("no reply in time")
except EmpireError as e:
    print(f"{type(e).__name__}: {e}")

Error handling lists every error and when you meet it.

A complete script

The repository's examples run end to end. This one logs in, lists your castles and prints the movements in flight:

examples/login_and_castles.py
"""Log in, list your castles, and read live troop movements.

The smallest useful end-to-end script: connect, read state, disconnect.

Run it with credentials in the environment (never inline them in a file that
might get committed)::

    export GGE_USERNAME=your_user
    export GGE_PASSWORD=your_pass
    python examples/login_and_castles.py
"""

import logging
import os
import sys

from empire_core import EmpireClient, EmpireError

# INFO is the right level for an application. DEBUG additionally dumps every
# protocol frame the library sends and receives, which is useful when reverse
# engineering a command but far too noisy otherwise.
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
logging.getLogger("websocket").setLevel(logging.WARNING)


def main() -> int:
    username = os.getenv("GGE_USERNAME")
    password = os.getenv("GGE_PASSWORD")
    if not username or not password:
        print("Set GGE_USERNAME and GGE_PASSWORD first.", file=sys.stderr)
        return 2

    client = EmpireClient(username=username, password=password)
    try:
        # login() opens the connection and raises on failure - LoginError for
        # bad credentials, LoginCooldownError when the server is rate limiting,
        # EmpireTimeoutError when it never answers.
        client.login()

        castles = client.castle.get_all()
        print(f"\n{len(castles)} castle(s):")
        for castle in castles:
            print(
                f"  [{castle.castle_id}] {castle.castle_name!r} at ({castle.x}, {castle.y}) type {castle.castle_type}"
            )

        if castles:
            first = castles[0]
            resources = client.castle.get_resources(castle_id=first.castle_id)
            print(f"\nResources in {first.castle_name!r}: {resources}")

        movements = client.movements.get_movements()
        print(f"\n{len(movements)} movement(s) in flight:")
        for movement in movements:
            print(f"  {movement}")

    except EmpireError as e:
        # Every failure this library raises derives from EmpireError.
        print(f"{type(e).__name__}: {e}", file=sys.stderr)
        return 1
    finally:
        # close() stops the receive and keepalive threads. Skipping it leaves
        # the process alive with a connected socket.
        client.close()

    return 0


if __name__ == "__main__":
    sys.exit(main())

Next steps

  • Guides


    One page per part of the game: alliance, castle, army, attacks, spies, the map and more.

  • API reference


    Every service, model and enum, with field descriptions and wire keys.