Skip to content

Castle

Castles, buildings, resources and transfers behind client.castle.

castle.service

Your castles: resources, buildings, the construction queue, and sending goods and troops.

It covers:

  • Castle management (list, join, rename)
  • Resources and production
  • Buildings and the construction queue
  • Sending resources, support and units
  • Tax collection

Action methods return True when the server accepted the action and False when it rejected it with an error code; transport failures (timeout, disconnect) raise. Query methods raise on any failure, and so do start_tax and collect_tax, which return their replies.

Methods that take one of your castles send its kingdom from the castle list the server sent at login (client.state.get_castles()), as the client does, and raise UnknownCastleError for an id not in it, or AmbiguousCastleError for an id listed in several of your kingdoms.

Building methods act on the castle joined last (CastleService.join) and name buildings by object id, a BuildingRow.object_id from join(...).buildings; upgrade_building, upgrade_defense and finish_construction take the castle and join it themselves. Their typed replies (BuildResponse and the others) can be read with client.request.

Classes:

Name Description
CastleService

Your castles, their buildings and resources, and sending goods and troops.

CastleService

CastleService(client: 'EmpireClient')

Bases: BaseService

Your castles, their buildings and resources, and sending goods and troops.

Reached as client.castle.

Usage
client = EmpireClient(...)
client.login()

# Get all castles
castles = client.castle.get_all()
for c in castles:
    print(f"{c.castle_name} at ({c.x}, {c.y})")

# Join a castle and read its buildings
castle = client.castle.join(castle_id=12345)

# Get resources
resources = client.castle.get_resources(castle_id=12345)

Methods:

Name Description
get_all

Get list of all player's castles.

get_details

Get resources and stationed units for one castle.

select

Select/jump to a castle (makes it the active castle).

join

Join a castle, making it the active castle, and return its state.

join_area

Join an area by its position (jaa), making it the active area, and return its state.

rename

Rename one of your castles.

get_resources

Get one of your castles' resources.

get_production

Get the joined castle's production area: production, storage, population and the rest.

get_build_queue

Get the joined castle's construction slots.

build

Place a new building in the joined castle.

upgrade_building

Upgrade a building in one of your castles.

move_building

Move a building in the joined castle.

sell_decoration

Sell a decoration placed in the joined castle.

destroy_building

Start taking a building in the joined castle down.

finish_construction

Finish a building's running build or upgrade at once.

skip_construction_time

Shorten a building's running construction with a minute skip.

upgrade_defense

Upgrade a wall, gate or tower of one of your castles, by its object id.

repair_building

Repair a damaged building in the joined castle.

repair_all

Repair every damaged building in the joined castle at once, for rubies; see "Spending

buy_expansion

Buy an expansion of the joined castle's grounds, with resources (NORMAL) or rubies (PREMIUM).

open_treasure_chest

Open a treasure chest found on an expansion of the joined castle.

collect_mine

Collect what a mine in the joined castle has produced.

collect_resource_cart

Collect the joined castle's resource cart of one resource.

get_tax_info

Read the tax collection status.

start_tax

Start a tax collection.

collect_tax

Collect the tax.

send_resources

Send resources from one of your castles to a castle on the map, by carriage.

get_market_info

List every castle's market carriages, those not on the road, and its resources.

send_support

Send support troops from a castle to a target location.

send_troops

Send troops from one of your areas to another of your own, where they stay.

get_travel_info

Get the pre-calculation of troops sent from one of your areas to another, before send_troops.

transfer_units_to_kingdom

Send units from one of your castles to another kingdom.

transfer_goods_to_kingdom

Send goods from one of your castles to your castle in another kingdom.

skip_kingdom_transfer_time

Shorten the units or goods on their way to a kingdom with a minute skip item.

get_horses

The horses one of your castles can send movements with, by wod id.

get_all

get_all(timeout: float = 5.0) -> list[CastleInfo]

Get list of all player's castles.

Sends the logged-in player's id, as the client does, once the state knows it.

Example
castles = client.castle.get_all()
for c in castles:
    print(f"{c.castle_name} (ID: {c.castle_id}) at ({c.x}, {c.y})")

get_details

get_details(castle_id: int, timeout: float = 5.0) -> DetailedCastleInfo

Get resources and stationed units for one castle.

The server answers with every castle of yours; the one asked for is picked out here.

Parameters:

Name Type Description Default
castle_id int

One of your castles, from client.castle.get_all() (CastleInfo.castle_id) or client.state.get_castles() (Castle.id)

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
UnknownCastleError

The reply lists no castle with that id

AmbiguousCastleError

The reply lists the id in several kingdoms

Example
details = client.castle.get_details(12345)
print(f"Wood: {details.wood}, units: {details.units}")

select

select(castle_id: int, timeout: float = 5.0) -> bool

Select/jump to a castle (makes it the active castle).

Use join for the castle's state the server sends back.

Parameters:

Name Type Description Default
castle_id int

One of your castles, a Castle.id from client.state.get_castles()

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
UnknownCastleError

castle_id is not in your castle list

AmbiguousCastleError

castle_id repeats across your kingdoms

Client: C2SJoinCastleVO sent with the castle's objectId and kingdomID (JoinAreaAndSavePositionCommand.execute, bundle line 100892)

Example
if client.castle.select(12345):
    print("Castle selected!")

join

join(castle_id: int, timeout: float = 5.0) -> SelectCastleResponse

Join a castle, making it the active castle, and return its state.

Parameters:

Name Type Description Default
castle_id int

One of your castles, a Castle.id from client.state.get_castles()

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
UnknownCastleError

castle_id is not in your castle list

AmbiguousCastleError

castle_id repeats across your kingdoms

CommandError

The server refused the join.

Client: C2SJoinCastleVO sent with the castle's objectId and kingdomID (JoinAreaAndSavePositionCommand.execute, bundle line 100892)

Example
castle = client.castle.join(12345)
if castle.buildings:
    print([b.wod_id for b in castle.buildings.buildings])

join_area

join_area(x: int, y: int, kingdom_id: Kingdom = GREEN, timeout: float = 5.0) -> SelectCastleResponse

Join an area by its position (jaa), making it the active area, and return its state.

The client joins this way the objects it may visit that are not castles: outposts, capitals, metropolises and faction camps that are not destroyed, and a kingdom castle just after its first naming. It joins main and kingdom castles otherwise by id, as join does. It never joins anything else (see JoinAreaRequest).

Parameters:

Name Type Description Default
x int

Map x of the area

required
y int

Map y of the area

required
kingdom_id Kingdom

The kingdom it lies in

GREEN
timeout float

Timeout in seconds

5.0

Raises:

Type Description
CommandError

The server refused the join; a live server answered INVALID_POSITION (6) for objects the client never joins, such as NPC camps and kings towers

Client: JoinAreaAndSavePositionCommand.execute (bundle line 100892), JAACommand.executeCommand (bundle line 130190)

rename

rename(castle_id: int, new_name: str, *, is_initial_name: bool = False, spend_rubies: bool = False, timeout: float = 5.0) -> bool

Rename one of your castles.

Looks the castle up in the castle list for the type and kingdom the server needs, so this costs one extra round trip. Pass is_initial_name=True to name a newly acquired castle, such as a monument or laboratory, instead of renaming one. A rename costs PlayerConst.CHANGE_CASTLE_NAME_C2 (2500) rubies unless a premium account runs; see "Spending rubies" in the guides.

Parameters:

Name Type Description Default
castle_id int

One of your castles, from client.castle.get_all() (CastleInfo.castle_id)

required
new_name str

The new name

required
is_initial_name bool

Name a newly acquired castle instead of renaming one, which is free

False
spend_rubies bool

Allow a rename while no premium account is known to run

False
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

A rename, no premium account is known to run, and spend_rubies is False

UnknownCastleError

castle_id is not one of your castles.

AmbiguousCastleError

castle_id repeats across your kingdoms

Client: CastleRenameCastleDialog.showLoaded and sendCommand (bundle lines 57219, 57231) price a rename, sent with 1; CastleNameCastleDialog.sendCommand (bundle line 32589) sends the first name with 0 and shows no price; CHANGE_CASTLE_NAME_C2 (dll line 19599)

Example
if client.castle.rename(12345, "My Fortress", spend_rubies=True):
    print("Castle renamed!")

get_resources

get_resources(castle_id: int, timeout: float = 5.0) -> CastleResources

Get one of your castles' resources.

Parameters:

Name Type Description Default
castle_id int

One of your castles, a Castle.id from client.state.get_castles()

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
UnknownCastleError

castle_id is not in your castle list

AmbiguousCastleError

castle_id repeats across your kingdoms

Client: C2SGetCastleResourcesVO sent with the castle's objectId and kingdomID (CastleTransferResourcesDialog.onSelectCastle, bundle line 38076)

Example
resources = client.castle.get_resources(12345)
print(f"Wood: {resources.wood}")

get_production

get_production(timeout: float = 5.0) -> CastleProductionArea

Get the joined castle's production area: production, storage, population and the rest.

Join the castle first with join; the server answers for the joined castle.

Example
client.castle.join(12345)
area = client.castle.get_production()
print(f"Wood/hr: {area.production.wood}")

get_build_queue

get_build_queue(timeout: float = 5.0) -> ConstructionList

Get the joined castle's construction slots.

Example
queue = client.castle.get_build_queue()
print(queue.building_object_ids, queue.free_slots)

build

build(wod_id: int, x: int, y: int, rotation: int = 0, *, spend_rubies: bool = False, private_offer_id: int = -1, district_object_id: int = -1, timeout: float = 5.0) -> bool

Place a new building in the joined castle.

Spends rubies when the building has a ruby price (BuildingDef.rubies_to_build at your level), and with spend_rubies on missing resources; see "Spending rubies" in the guides.

Parameters:

Name Type Description Default
wod_id int

The building type's wod id

required
x int

Castle grid x, -1 when placing into a district

required
y int

Castle grid y, -1 when placing into a district

required
rotation int

Rotation

0
spend_rubies bool

Allow a ruby price, and pay the missing resources with rubies

False
private_offer_id int

The private offer the purchase uses, -1 for none

-1
district_object_id int

Object id of the district to place into, -1 for none

-1
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

The building has a ruby price, or is not in the game data, and spend_rubies is False

GameDataNotLoadedError

spend_rubies is False and the game data cannot be loaded

Client: IsoServerCommands.buyObjectFromShop (bundle line 63766); ABasicBuildingVO.costC2 (bundle lines 17950-17953)

upgrade_building

upgrade_building(castle_id: int, object_id: int, *, spend_rubies: bool = False, private_offer_id: int = -1, timeout: float = 5.0) -> bool

Upgrade a building in one of your castles.

Joins the castle first, as the client is inside it, and prices the building from the join's reply: the level it upgrades to, by BuildingDef.rubies_to_build at your level, needs spend_rubies when it has a ruby price; see "Spending rubies" in the guides. spend_rubies also pays missing resources with rubies.

Parameters:

Name Type Description Default
castle_id int

One of your castles, a Castle.id from client.state.get_castles()

required
object_id int

The building's object id

required
spend_rubies bool

Allow a ruby price, and pay the missing resources with rubies

False
private_offer_id int

The prime sale offer the upgrade uses, -1 for none

-1
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

The next level has a ruby price, or the building or its next level is not known, and spend_rubies is False

GameDataNotLoadedError

spend_rubies is False and the game data cannot be loaded

UnknownCastleError / AmbiguousCastleError / CommandError

see join

Client: RingMenuButtonUpgradeBuilding.onClick (bundle line 50092), IsoServerCommands.upgradeBuilding (bundle line 63774); the price is the next level's costC2, BuildingCostHelper.getUpgradeCostList (bundle lines 41106-41116) through AShopVO.getCost (bundle line 31766) and ABasicBuildingVO.costC2 (bundle lines 17950-17953)

move_building

move_building(object_id: int, x: int, y: int, rotation: int = 0, timeout: float = 5.0) -> bool

Move a building in the joined castle.

sell_decoration

sell_decoration(object_id: int, timeout: float = 5.0) -> bool

Sell a decoration placed in the joined castle.

destroy_building

destroy_building(object_id: int, timeout: float = 5.0) -> bool

Start taking a building in the joined castle down.

finish_construction

finish_construction(castle_id: int, object_id: int, *, spend_rubies: bool = False, timeout: float = 5.0) -> bool

Finish a building's running build or upgrade at once.

Joins the castle first, as the client is inside it, and works out the time left from the join's reply as the client does. With at most FREE_SKIP_TIME (240) seconds left it finishes for free, sent as a free skip; with more left, or the time left not known (a repair or tear-down, an unknown building or player level), it costs rubies; see "Spending rubies" in the guides. A subscription that lengthens the free time is not checked.

Parameters:

Name Type Description Default
castle_id int

One of your castles, a Castle.id from client.state.get_castles()

required
object_id int

The building's object id

required
spend_rubies bool

Allow finishing a construction that costs rubies

False
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

More than 240 seconds are left, or the time left is not known, and spend_rubies is False

GameDataNotLoadedError

the game data cannot be loaded

UnknownCastleError / AmbiguousCastleError / CommandError

see join

Client: IsoServerCommands.fastCompleteBuilding (bundle line 63773) sends FS 1 when CastleSpecialEventData.hasSkipForFree (bundle line 139853), which calculateSkipCosts (bundle line 139849) decides from getTimeLeftForBuilding (bundle line 17967); FREE_SKIP_TIME (dll line 18962)

skip_construction_time

skip_construction_time(object_id: int, minute_skip: Currency | str, timeout: float = 5.0) -> bool

Shorten a building's running construction with a minute skip.

Parameters:

Name Type Description Default
object_id int

The building's object id

required
minute_skip Currency | str

The minute skip to use, Currency.SKIP_1_MINUTE to SKIP_24_HOURS; its key ("MS1") also works, for a skip newer than the generated enum

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

minute_skip is no minute skip, or the special currencies hold none of it

Client: BuildingMinuteSkipProperties.getMinuteSkipCommand (bundle line 50021), CastleMinuteSkipDialog.showLoaded (bundle line 7671)

upgrade_defense

upgrade_defense(castle_id: int, object_id: int, *, spend_rubies: bool = False, private_offer_id: int = -1, timeout: float = 5.0) -> bool

Upgrade a wall, gate or tower of one of your castles, by its object id.

Joins the castle first, as the client is inside it, and prices the building from the join's reply: the level it upgrades to, by BuildingDef.rubies_to_build at your level, needs spend_rubies when it has a ruby price; see "Spending rubies" in the guides. spend_rubies also pays missing resources with rubies.

Parameters:

Name Type Description Default
castle_id int

One of your castles, a Castle.id from client.state.get_castles()

required
object_id int

The wall's, gate's or tower's object id

required
spend_rubies bool

Allow a ruby price, and pay the missing resources with rubies

False
private_offer_id int

The private offer the upgrade uses, -1 for none

-1
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

The next level has a ruby price, or the object or its next level is not known, and spend_rubies is False

GameDataNotLoadedError

spend_rubies is False and the game data cannot be loaded

UnknownCastleError / AmbiguousCastleError / CommandError

see join

Client: C2SIsoUpgradeDefenceVO, sent with PWR 1 by CastleResourceWaitDialogProperties.getResourceSkipCommand (bundle line 35220); the price as for upgrade_building

repair_building

repair_building(object_id: int, *, spend_rubies: bool = False, private_offer_id: int = -1, timeout: float = 5.0) -> bool

Repair a damaged building in the joined castle.

Parameters:

Name Type Description Default
object_id int

The building's object id

required
spend_rubies bool

Pay the missing resources with rubies; see "Spending rubies" in the guides

False
private_offer_id int

The private offer the repair uses, -1 for none

-1
timeout float

Timeout in seconds

5.0

Client: IsoServerCommands.repairBuilding (bundle line 63770); with rubies, CastleResourceWaitDialogProperties.getResourceSkipCommand (bundle line 35221)

repair_all

repair_all(*, spend_rubies: bool = False, timeout: float = 5.0) -> bool

Repair every damaged building in the joined castle at once, for rubies; see "Spending rubies" in the guides.

The price follows the damage's total repair time and is not worked out here, so this always needs spend_rubies.

Parameters:

Name Type Description Default
spend_rubies bool

Allow the rubies it costs; without it nothing is sent

False
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

spend_rubies is False

Client: CastleRepairTipDialog (bundle lines 97414, 97430) prices it with AreaData.getTotalRepairCostC2 (bundle line 131460), ConstructionConst.getRepairAllCostsC2 (dll lines 18966-18969)

buy_expansion

buy_expansion(x: int, y: int, rotation: int = 0, expansion_type: ExpansionType = NORMAL, *, spend_rubies: bool = False, timeout: float = 5.0) -> bool

Buy an expansion of the joined castle's grounds, with resources (NORMAL) or rubies (PREMIUM).

Parameters:

Name Type Description Default
x int

Castle grid x of the expansion

required
y int

Castle grid y of the expansion

required
rotation int

Rotation

0
expansion_type ExpansionType

NORMAL or PREMIUM; PREMIUM costs rubies, see "Spending rubies" in the guides

NORMAL
spend_rubies bool

Allow a PREMIUM expansion

False
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

expansion_type is PREMIUM and spend_rubies is False

Client: CastleExpansionVE (bundle line 86849) needs userHasC2ForBuy for a premium expansion before sendBuyRequestToServer (bundle line 86854)

open_treasure_chest

open_treasure_chest(object_id: int, timeout: float = 5.0) -> bool

Open a treasure chest found on an expansion of the joined castle.

collect_mine

collect_mine(object_id: int, timeout: float = 5.0) -> bool

Collect what a mine in the joined castle has produced.

collect_resource_cart

collect_resource_cart(cart_type: ResourceCartType, timeout: float = 5.0) -> bool

Collect the joined castle's resource cart of one resource.

get_tax_info

get_tax_info(timeout: float = 5.0) -> TaxInfo

Read the tax collection status.

Example
tax = client.castle.get_tax_info()
print(tax.status, tax.remaining_seconds, tax.expected_income)

Client: C2SGetTaxInfoVO (bundle line 37186), TXICommand (bundle line 128792)

start_tax

start_tax(tax_type: int, *, spend_rubies: bool = False, timeout: float = 5.0) -> StartTaxResponse

Start a tax collection.

Tax types 0 to 6 collect for TAX_DURATIONS[tax_type] seconds. Type 0 is free and types 1 to 4 cost a tenth of their income in coins. Types 5 and 6 cost TAX_RUBY_COSTS rubies unless a premium account, a VIP level or the tax research waives it; this method refuses them unless spend_rubies is True, and does not check for a waiver.

Parameters:

Name Type Description Default
tax_type int

The tax type, 0 to 6

required
spend_rubies bool

Allow types 5 and 6, which may spend rubies

False
timeout float

Timeout in seconds

5.0

Returns:

Type Description
StartTaxResponse

The reply: coins and rubies after, and the tax status.

Raises:

Type Description
ValueError

tax_type is not 0 to 6, or costs rubies and spend_rubies is False

CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

Client: C2SStartCollectTaxVO (bundle line 91088), sent by CastleCollectTaxElement.onStartTaxCollection (bundle line 91069), which shows a ruby cost when CollectTaxElementVO.hasC2CostWithoutPremium (bundle line 91050); TaxConst (dll lines 19771-19790)

collect_tax

collect_tax(timeout: float = 5.0) -> CollectTaxResponse

Collect the tax.

Collecting while the collection runs brings the share of its income earned so far; the client asks first when that is nothing.

Returns:

Type Description
CollectTaxResponse

The reply: the coins collected, coins and rubies after, and the tax status.

Raises:

Type Description
CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

Client: C2SCollectTaxVO (bundle line 41083), sent by CastleCollectTaxDialog.collectTax (bundle line 91027) after onCollectTaxClick (bundle line 91015); TaxConst.getCollectedMoney (dll line 19784); TXCCommand (bundle line 128747)

send_resources

send_resources(source_castle_id: int, target_x: int, target_y: int, goods: dict[Resource, int], *, horse_booster_id: int = -1, feathers: bool = False, slowdown: int = 0, spend_rubies: bool = False, timeout: float = 5.0) -> bool

Send resources from one of your castles to a castle on the map, by carriage.

A horse that costs rubies, unless paid with feathers, and a slowdown spend rubies; see "Spending rubies" in the guides.

Nothing is sent when the goods fail a check the client makes before sending: an amount that is not a positive int (the client drops zeros and refuses a send of nothing; this raises on a zero instead), goods from more than one of the dialog's tabs (classic, kingdom, mead), or, once the player is known and has no legend level, anything but wood, stone and food (the client then shows only that tab). The client sends one tab's goods per send, and the server refuses a send that mixes tabs with INVALID_PARAMETER_VALUE (seen live for wood with coal and coal with honey).

Not checked, since they depend on the target or on data this call does not have: the target owner's level for kingdom resources and legend level for mead goods, at most 2 goods (wood and stone) to a monument or laboratory, the carriage capacity and the castle's stock.

Parameters:

Name Type Description Default
source_castle_id int

The castle the carriages leave from, one of yours

required
target_x int

Map x of the target castle

required
target_y int

Map y of the target castle

required
goods dict[Resource, int]

Amount per resource, all from one tab, such as {Resource.WOOD: 1000, Resource.STONE: 500} or {Resource.COAL: 500, Resource.OIL: 500}

required
horse_booster_id int

The horse's wod id, -1 for none; sent as -1 whenever feathers are used, as the client does

-1
feathers bool

Pay for the horse with feathers

False
slowdown int

Seconds to delay the arrival by; costs rubies

0
spend_rubies bool

Allow a horse or slowdown that costs rubies

False
timeout float

Timeout in seconds

5.0

Raises:

Type Description
UnsendableGoodsError

The goods fail one of the checks above

UnknownCastleError

source_castle_id is not in your castle list

AmbiguousCastleError

source_castle_id repeats across your kingdoms

ValueError

The horse or slowdown costs rubies and spend_rubies is False

GameDataNotLoadedError

A horse is picked without feathers and the game data cannot be loaded

Client: CastlePostSendGoodsDialog.sendGoods (bundle line 33378), which sends castleList.getKingdomIdByCastleId of the source castle as KID; CastleSendGoodsDialog.sendGoodsCastle (bundle line 27202), which refuses a zero sum; CollectableParser._createGoodsList (bundle line 40530), which drops zero amounts; CastleSendGoodsComponent.rewardList (bundle line 44340), which sends one tab's goods, and setTabVisibility (bundle line 44261), which shows the kingdom and mead tabs only to a legend. Not mirrored: applySpecialLevelRestrictions (bundle line 44289) and isBoosterArea (bundle line 44356).

get_market_info

get_market_info(timeout: float = 5.0) -> list[MarketCastle]

List every castle's market carriages, those not on the road, and its resources.

Example
for castle in client.castle.get_market_info():
    print(castle.castle_id, castle.available_carriages, "/", castle.total_carriages)

send_support

send_support(source_castle_id: int, target_x: int, target_y: int, units: WodAmountMapping | Sequence[WodAmount], commander_id: int, wait_time: int = 12, use_premium_commander: bool = False, horse_booster_id: int = -1, feathers: bool = False, slowdown: int = 0, timeout: float = 5.0, *, spend_rubies: bool = False) -> bool

Send support troops from a castle to a target location.

The server refuses a support to your own area with NO_SELF_DESTRUCTION (92); move troops between your own areas with send_troops.

A support led by the premium commander is refused before sending when it may cost rubies, unless spend_rubies is True; see client.commanders.premium_send. So is a horse that costs rubies, unless paid with feathers, and a slowdown; see "Spending rubies" in the guides.

Parameters:

Name Type Description Default
source_castle_id int

The castle the troops leave from, one of yours: CastleInfo.castle_id from client.castle.get_all() or Castle.id from client.state.get_castles()

required
target_x int

Target X coordinate

required
target_y int

Target Y coordinate

required
units WodAmountMapping | Sequence[WodAmount]

The units, then any tools: {Unit.X: 100}, or pairs one per slot

required
commander_id int

Commander to lead the support, a Commander.commander_id from client.commanders.get_commanders(). There is no default: 0 is a real commander (the free starting one), and the client never sends a support without a commander (with none picked it leads with the premium one, -14)

required
wait_time int

Station duration in hours (0-12, default: 12)

12
use_premium_commander bool

Lead with the premium commander (commander_id -14). It uses one of your free premium commanders, or costs rubies when none is left and no premium account runs

False
horse_booster_id int

Type of horses for speed bonus (-1 = none, default: -1); sent as -1 whenever feathers are used, as the client does

-1
feathers bool

Pay for the movement with feathers

False
slowdown int

Movement slowdown modifier (0 = none, default: 0); costs rubies

0
timeout float

Timeout in seconds

5.0
spend_rubies bool

Send even when the premium commander, the horse or the slowdown costs rubies

False

Raises:

Type Description
PremiumCommanderCostError

The premium commander leads, may cost rubies, and spend_rubies is False

ValueError

The horse or slowdown costs rubies and spend_rubies is False

GameDataNotLoadedError

The premium commander leads, VIP time runs and the game data cannot be loaded; or a horse is picked without feathers before it was called

send_troops

send_troops(source_x: int, source_y: int, target_x: int, target_y: int, units: WodAmountMapping | Sequence[WodAmount], commander_id: int, *, kingdom_id: Kingdom = GREEN, use_premium_commander: bool = False, spend_rubies: bool = False, horse_booster_id: int = -1, feathers: bool = False, slowdown: int = 0, timeout: float = 5.0) -> bool

Send troops from one of your areas to another of your own, where they stay.

This is how the client moves troops between your own areas; a support (send_support) to your own area is refused with NO_SELF_DESTRUCTION (92). While the troops are heading out they can be recalled with client.movements.recall.

Targets the client offers: your castle (main or kingdom castle), your outposts not under conquer control, your villages and resource isles, your king's towers, monuments and laboratories, and your faction camps. Sources: your castles and outposts (not occupied ones), villages, resource isles, king's towers, monuments and laboratories. Both must sit in the same kingdom; to move troops between kingdoms use transfer_units_to_kingdom.

Rubies as for send_support: the premium commander, a horse that costs rubies unless paid with feathers, and a slowdown need spend_rubies.

Client: CastleTroopSupportData.sendTroops (bundle line 38420); the targets from the canBeTroupsSended overrides (CastleMapobjectVO bundle line 18917, OutpostMapobjectVO 18819, KingstowerMapobjectVO 19075, FactionCampMapobjectVO 21530, VillageMapobjectVO 22673, UpgradableLandmarkMapobjectVO 42648), the sources from CastleStartAttackDialog.fillCastleList (bundle line 14725)

Parameters:

Name Type Description Default
source_x int

Map x of the area the troops leave from

required
source_y int

Map y of the area the troops leave from

required
target_x int

Map x of the area they go to

required
target_y int

Map y of the area they go to

required
units WodAmountMapping | Sequence[WodAmount]

The units, then any tools: {Unit.X: 100}, or pairs one per slot

required
commander_id int

Commander to lead them, a Commander.commander_id from client.commanders.get_commanders(); 0 is the free starting one

required
kingdom_id Kingdom

The kingdom both areas sit in

GREEN
use_premium_commander bool

Lead with the premium commander (commander_id -14). It uses one of your free premium commanders, or costs rubies when none is left and no premium account runs

False
spend_rubies bool

Send even when the premium commander, the horse or the slowdown costs rubies

False
horse_booster_id int

The horse's wod id, -1 for none; sent as -1 whenever feathers are used, as the client does

-1
feathers bool

Pay for the horse with feathers

False
slowdown int

Seconds to delay the arrival by; costs rubies

0
timeout float

Timeout in seconds

5.0

Raises:

Type Description
PremiumCommanderCostError

The premium commander leads, may cost rubies, and spend_rubies is False; see client.commanders.premium_send

ValueError

The horse or slowdown costs rubies and spend_rubies is False

GameDataNotLoadedError

The premium commander leads, VIP time runs and the game data cannot be loaded; or a horse is picked without feathers before it was called

get_travel_info

get_travel_info(source_x: int, source_y: int, target_x: int, target_y: int, *, kingdom_id: Kingdom = GREEN, timeout: float = 5.0) -> GetTravelInfoResponse

Get the pre-calculation of troops sent from one of your areas to another, before send_troops.

This is what the client asks for when it opens the dialog to send troops: the target's map row and owner, and your units and tools at the source. The commanders it carries update client.state. The travel time and cost are not in the reply; the client works them out from the units picked.

Parameters:

Name Type Description Default
source_x int

Map x of the area the troops would leave from

required
source_y int

Map y of the area the troops would leave from

required
target_x int

Map x of the area they would go to

required
target_y int

Map y of the area they would go to

required
kingdom_id Kingdom

The kingdom both areas sit in

GREEN
timeout float

Timeout in seconds

5.0

Raises:

Type Description
CommandError

The server refused it, NOT_IN_OWNED_CASTLE when the source is not one of your areas

Client: C2STroopSupportInfoVO (bundle line 72087), sent by CastleStartAttackDialog (bundle line 14831); STICommand (bundle line 129095)

transfer_units_to_kingdom

transfer_units_to_kingdom(source_castle_id: int, target_kingdom_id: Kingdom, units: WodAmountMapping | Sequence[WodAmount], *, target_castle_id: int = -1, timeout: float = 5.0) -> bool

Send units from one of your castles to another kingdom.

Parameters:

Name Type Description Default
source_castle_id int

The castle the units leave from, one of yours

required
target_kingdom_id Kingdom

The kingdom to send them to

required
units WodAmountMapping | Sequence[WodAmount]

The units, {Unit.X: 100}

required
target_castle_id int

Object id of a picked target castle, -1 for none

-1
timeout float

Timeout in seconds

5.0

Raises:

Type Description
UnknownCastleError

source_castle_id is not in your castle list

AmbiguousCastleError

source_castle_id repeats across your kingdoms

Client: CastleTransferTroopsToKingdomProperties.getUnitTransferCommand (bundle line 37448) sends the source castle's kingdomID as SKID

transfer_goods_to_kingdom

transfer_goods_to_kingdom(source_castle_id: int, target_kingdom_id: Kingdom, goods: dict[Resource, int], *, timeout: float = 5.0) -> bool

Send goods from one of your castles to your castle in another kingdom.

The goods pass the checks of send_resources, since the client sends them from the same send component: positive amounts, one tab per send, classic goods only below legend level. The source castle may not sit in the target kingdom or in Berimond, which the client leaves out of its castle picker, and the target kingdom must hold a castle of yours, which the client sends the goods to. The travel tax and the target's storage are left to the server.

Parameters:

Name Type Description Default
source_castle_id int

The castle the goods leave from, one of yours

required
target_kingdom_id Kingdom

The kingdom to send them to

required
goods dict[Resource, int]

Amount per resource, all from one tab, such as {Resource.WOOD: 1000}

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
UnsendableGoodsError

The goods fail one of the checks of send_resources

ValueError

The source castle sits in the target kingdom or in Berimond, or no castle of yours sits in the target kingdom

UnknownCastleError

source_castle_id is not in your castle list

AmbiguousCastleError

source_castle_id repeats across your kingdoms

Client: CastleTransferResourcesDialog.sendResources (bundle line 38082) sends the picked castle's kingdomID as SKID; its castle picker leaves out CastleTransferResToKingdomProperties.exceptingKingdomIDs (bundle line 55533), and targetInitialized (bundle line 55553) needs getMainCastleByKingdomID of the target kingdom; CastleSendGoodsComponent.setTabVisibility (bundle line 44261)

skip_kingdom_transfer_time

skip_kingdom_transfer_time(kingdom_id: Kingdom, transfer_type: KingdomTransferType, minute_skip: Currency | str, timeout: float = 5.0) -> bool

Shorten the units or goods on their way to a kingdom with a minute skip item.

Each kingdom has at most one units transfer and one goods transfer on the way; client.state.get_kingdoms() lists them. The full skip the client also offers (kst) costs rubies and is not offered here.

Parameters:

Name Type Description Default
kingdom_id Kingdom

The kingdom the transfer goes to

required
transfer_type KingdomTransferType

KingdomTransferType.UNITS or GOODS

required
minute_skip Currency | str

The minute skip to use, Currency.SKIP_1_MINUTE to SKIP_24_HOURS; its key ("MS1") also works, for a skip newer than the generated enum

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

minute_skip is no minute skip, or the special currencies hold none of it

Client: KingdomUnitsTravelMinuteSkipProperties.getMinuteSkipCommand (bundle line 37497), KingdomGoodsTravelMinuteSkipProperties.getMinuteSkipCommand (bundle line 55567); CastleMinuteSkipDialog.showLoaded (bundle line 7671); MSKCommand (bundle line 125795)

get_horses

get_horses(castle_id: int) -> list[HorseStats] | None

The horses one of your castles can send movements with, by wod id.

Read from the login data's gpc section and its pushes; nothing is sent. A horse whose is_instant_spy_horse is set can be paid with rubies or, sent as feathers=True, with feathers; the client shows it twice, once per payment. An id missing from the game data is left out, as the client leaves it out.

Parameters:

Name Type Description Default
castle_id int

One of your castles, Castle.id from client.state.get_castles(); its kingdom is taken from the castle list

required

Returns:

Type Description
list[HorseStats] | None

The horses sorted by wod id, or None when no gpc named the castle yet

Raises:

Type Description
GameDataNotLoadedError

the game data cannot be loaded

UnknownCastleError

castle_id is not in your castle list

AmbiguousCastleError

castle_id repeats across your kingdoms

Client: CastleHorsesVO.parseParamObject (bundle line 139177), CastlePermanentCastleData.getCastleByWorldAreaId (bundle line 139145)

castle.models.actions

Castle actions.

Commands:

  • jca: Join a castle (answered as jaa)
  • jaa: Join an area by its position
  • arc: Rename castle
  • rst: Relocate castle

Classes:

Name Description
SelectCastleRequest

Join a castle, making it the session's active castle.

JoinAreaRequest

Join an area by its position rather than by castle id.

SelectCastleResponse

The joined area's state, the jaa the server answers a join with.

RenameCastleRequest

Rename a castle.

RenameCastleResponse

Response to castle rename.

RelocateCastleRequest

Start a relocation to a new position.

RelocateCastleResponse

Response to castle relocation.

SelectCastleRequest pydantic-model

Bases: BaseRequest

Join a castle, making it the session's active castle.

Command: jca (answered as 'jaa') Payload: {"CID": castle_id, "KID": kingdom_id}

Castle-scoped reads such as gui answer for the joined castle.

Client: C2SJoinCastleVO (bundle line 5841); its MY_CASTLE is -1

Fields:

castle_id pydantic-field

castle_id: int

Wire key CID

The castle to join, CastleInfo.castle_id from client.castle.get_all() or Castle.id from client.state.get_castles()

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The castle's kingdom

JoinAreaRequest pydantic-model

Bases: BaseRequest

Join an area by its position rather than by castle id.

Command: jaa Payload: {"PX": x, "PY": y, "KID": kingdom_id}

The client joins a map object this way only when it may visit it and it is not a castle: JoinAreaAndSavePositionCommand sends jca for a CastleMapobjectVO (main and kingdom castles) and jaa for anything else. Its callers pass either your own areas from the castle lists or, from a double click on the world map or the ring menu's visit button, an object whose canBeVisited is true: outposts, capitals and metropolises, and faction camps that are not destroyed. Every other object (NPC camps, dungeons, kings towers, villages, monuments and the rest) says false, and the client never joins it; a live server answered INVALID_POSITION (6) for NPC camps and a kings tower. ARCCommand also joins a kingdom castle by position after its first naming, and a live server accepted that too. Joining another player's outpost is what the client allows; whether the server accepts it is unverified.

Client: C2SJoinAreaVO (bundle line 56128), whose key order the fields follow; sent by JoinAreaAndSavePositionCommand.execute (bundle line 100892) and ARCCommand.executeCommand (bundle line 124969); CastleWorldMapScreen.onDoubleClickCastle (bundle line 39601) and ButtonVisitCastleComponent (bundle line 110159) check canBeVisited, on InteractiveMapobjectVO (bundle line 3678), OutpostMapobjectVO (bundle line 18829), CastleMapobjectVO (bundle line 18924) and FactionCampMapobjectVO (bundle line 21538)

Fields:

x pydantic-field

x: int

Wire key PX

Map x

y pydantic-field

y: int

Wire key PY

Map y

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The kingdom it lies in

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a jaa reply is for this area: its KID and, when sent, the joined area's row position.

Client: JAACommand (bundle line 130190) switches to the reply's KID; AreaFactory.parseAreaInfo (bundle line 130226) reads the area from gca.A with WorldmapObjectFactory.parseWorldMapArea (bundle line 5343), whose parser for every area type takes the position from A[1] and A[2] (BasicMapobjectVO, InteractiveMapobjectVO, CapitalMapobjectVO, MetropolMapobjectVO, FactionCampMapobjectVO and the rest, bundle lines 3631-76548). A treasure camp (T 8) is the exception: its area is not read from A, so its reply is not checked for position.

SelectCastleResponse pydantic-model

Bases: BaseResponse

The joined area's state, the jaa the server answers a join with.

Command: jaa, the answer to both jca and a jaa by position; the client reads both with the same code. Payload: {"KID": kingdom_id, "T": area_type, "gca": {...}, "grc": {...}, "gpa": {...}, ...}

A block that cannot be read is None, so it does not cost the rest. The other blocks (spl0 to spl3, gui, uap, hin, sin, abpi, crai) are kept as sent.

Client: JAACommand.executeCommand (bundle line 130190), AreaDataUpdater.parseJAA (bundle line 131496)

Fields:

Validators:

kingdom_id pydantic-field

kingdom_id: int = 0

Wire key KID

The joined area's kingdom

area_type pydantic-field

area_type: MapItemType | None = None

Wire key T

The joined area's type; None for one MapItemType lacks

buildings pydantic-field

buildings: CastleBuildings | None = None

Wire key gca

The area's buildings; None when the reply has none

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The area's resources; None when the reply has none

production_area pydantic-field

production_area: CastleProductionArea | None = None

Wire key gpa

The area's production area; None when the reply has none

slum_level pydantic-field

slum_level: SlumLevel | None = None

Wire key csl

The area's slum level; None when the reply has none

area_booster pydantic-field

area_booster: AreaBooster | None = None

Wire key gab

The area's builder discount; None when the reply has none

mines pydantic-field

mines: MineStatusList | None = None

Wire key gsm

The area's mines; None when the reply has none

resource_carts pydantic-field

resource_carts: ResourceCartInfo | None = None

Wire key rci

The area's resource carts; None when the reply has none

RenameCastleRequest pydantic-model

Bases: BaseRequest

Rename a castle.

Command: arc Payload: {"CID": castle_id, "P": 0 or 1, "KID": kingdom_id, "AT": area_type, "N": name}

The keys follow the client's order: the constructor initialises CID, P, KID and AT before it sets N, which it encodes as it encodes any text it sends.

Client: C2SRenameCastleVO (bundle line 42424)

Fields:

castle_id pydantic-field

castle_id: int

Wire key CID

The castle to rename, a CastleInfo.castle_id from client.castle.get_all()

is_rename pydantic-field

is_rename: int = 1

Wire key P

1 to rename, 0 to name a newly acquired castle such as a monument

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The castle's kingdom

castle_type pydantic-field

castle_type: MapItemType

Wire key AT

The castle's area type

castle_name pydantic-field

castle_name: str

Wire key N

The new name

accepts_reply

accepts_reply(payload: Any) -> bool

Whether an arc reply is about this castle: its CID, and its KID when sent, are the ones asked for.

Client: ARCCommand.executeCommand (bundle line 124966) looks the renamed castle up by the reply's CID and KID (bundle line 124968).

RenameCastleResponse pydantic-model

Bases: BaseResponse

Response to castle rename.

Command: arc Payload: {"CID": castle_id, "KID": kingdom_id, "P": 0 or 1}

Client: ARCCommand.executeCommand (bundle line 124966), which looks the castle up by CID and KID, and joins a kingdom castle's area again after a first naming (P 0)

Fields:

castle_id pydantic-field

castle_id: int

Wire key CID

The renamed castle

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The castle's kingdom

is_rename pydantic-field

is_rename: int = 1

Wire key P

1 for a rename, 0 for a first naming

RelocateCastleRequest pydantic-model

Bases: BaseRequest

Start a relocation to a new position.

Command: rst Payload: {"PX": x, "PY": y}

The client sends only the position: no castle id and no kingdom.

Client: C2SStartRelocationVO (bundle line 109634), sent by CastleRelocateDialog.onClick (bundle line 109619)

Fields:

x pydantic-field

x: int

Wire key PX

Map x of the new position

y pydantic-field

y: int

Wire key PY

Map y of the new position

RelocateCastleResponse pydantic-model

Bases: BaseResponse

Response to castle relocation.

Command: rst

Client: RSTCommand.executeCommand (bundle line 126817)

Fields:

castle.models.buildings

Building commands.

Every command acts on the castle joined with jca (client.castle.select) and names a building by its object id, a BuildingRow.object_id from the castle's CastleBuildings (client.castle.join).

Commands:

  • ebu: Build
  • eup: Upgrade a building
  • emo: Move a building
  • sbd: Sell a decoration
  • edo: Take a building down
  • fco: Finish a construction at once
  • msb: Shorten a construction with a minute skip
  • eud: Upgrade the wall, gate or a tower
  • rbu: Repair a building
  • ira: Repair every building
  • ebe: Buy a castle expansion
  • etc: Open an expansion's treasure chest

Classes:

Name Description
BuildRequest

Place a new building in the joined castle.

BuildResponse

The new building.

UpgradeBuildingRequest

Upgrade a building in the joined castle.

UpgradeBuildingResponse

The upgraded buildings.

MoveBuildingRequest

Move a building in the joined castle.

MoveBuildingResponse

The moved building.

SellBuildingRequest

Sell a decoration placed in the joined castle.

SellBuildingResponse

The sold decoration.

DestroyBuildingRequest

Start taking a building in the joined castle down.

DestroyBuildingResponse

The building being taken down.

FastCompleteRequest

Finish a building's running construction at once: for free with FS 1, else for rubies.

FastCompleteResponse

The finished building.

TimeSkipBuildingRequest

Shorten a building's running construction with a minute skip.

TimeSkipBuildingResponse

The construction slots after a minute skip.

UpgradeWallRequest

Upgrade the joined castle's wall, gate or one of its towers.

UpgradeWallResponse

The upgraded wall, gate or tower.

RepairBuildingRequest

Repair a damaged building in the joined castle.

RepairBuildingResponse

The building being repaired.

RepairAllRequest

Repair every damaged building in the joined castle at once.

RepairAllResponse

The repaired buildings.

BuyExtensionRequest

Buy an expansion of the joined castle's grounds.

BuyExtensionResponse

The castle after the expansion.

CollectExtensionGiftRequest

Open a treasure chest found on an expansion of the joined castle.

CollectExtensionGiftResponse

The opened chest.

BuildRequest pydantic-model

Bases: BaseRequest

Place a new building in the joined castle.

Command: ebu Payload: {"WID": wod_id, "X": x, "Y": y, "R": rotation, "PWR": 0 or 1, "PO": offer_id, "DOID": district_id}

Keys follow the client's order: the constructor initialises PWR before PO. To place a building into a district the client sends X and Y as -1 and the district's object id as DOID.

Client: C2SIsoBuyObjectVO (bundle line 31942), sent by IsoServerCommands.buyObjectFromShop (bundle line 63766)

Fields:

wod_id pydantic-field

wod_id: int

Wire key WID

The building type's wod id

x pydantic-field

x: int

Wire key X

Castle grid x, -1 when placing into a district

y pydantic-field

y: int

Wire key Y

Castle grid y, -1 when placing into a district

rotation pydantic-field

rotation: int = 0

Wire key R

Rotation

pay_with_rubies pydantic-field

pay_with_rubies: bool = False

Wire key PWR

private_offer_id pydantic-field

private_offer_id: int = -1

Wire key PO

district_object_id pydantic-field

district_object_id: int = -1

Wire key DOID

Object id of the district to place into, -1 for none

BuildResponse pydantic-model

Bases: _BuildingReply

The new building.

Command: ebu Payload: {"NO": row, "grc": {..}, "scl": {..}, "gcu": {..}, "sin": .., "fbe": ..}

sin and fbe are kept as sent.

Client: EBUCommand.executeCommand (bundle line 122760), AreaDataUpdater.parseEBU (bundle line 131512)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

building pydantic-field

building: BuildingRow | None = None

Wire key NO

The new building

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The castle's resources after

construction_list pydantic-field

construction_list: ConstructionList | None = None

Wire key scl

The construction slots after

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

UpgradeBuildingRequest pydantic-model

Bases: BaseRequest

Upgrade a building in the joined castle.

Command: eup Payload: {"OID": object_id, "PWR": 0 or 1, "PO": offer_id}

Keys follow the client's order: the constructor initialises PWR before PO.

Client: C2SIsoUpgradeObjectVO (bundle line 31971)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

pay_with_rubies pydantic-field

pay_with_rubies: bool = False

Wire key PWR

private_offer_id pydantic-field

private_offer_id: int = -1

Wire key PO

UpgradeBuildingResponse pydantic-model

Bases: _BuildingReply

The upgraded buildings.

Command: eup Payload: {"O": [row, ...], "grc": {..}, "scl": {..}, "gcu": {..}}

Client: EUPCommand.executeCommand (bundle line 122860), AreaDataUpdater.parseEUP (bundle line 131516)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

buildings pydantic-field

buildings: list[BuildingRow]

Wire key O

The changed buildings

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The castle's resources after

construction_list pydantic-field

construction_list: ConstructionList | None = None

Wire key scl

The construction slots after

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

MoveBuildingRequest pydantic-model

Bases: BaseRequest

Move a building in the joined castle.

Command: emo Payload: {"OID": object_id, "X": x, "Y": y, "R": rotation}

Client: C2SIsoMoveObjectVO (bundle line 63796), sent by IsoServerCommands.moveObject (bundle line 63776) for a building outside a district

Fields:

object_id pydantic-field

object_id: int

Wire key OID

x pydantic-field

x: int

Wire key X

Castle grid x

y pydantic-field

y: int

Wire key Y

Castle grid y

rotation pydantic-field

rotation: int = 0

Wire key R

Rotation

MoveBuildingResponse pydantic-model

Bases: _BuildingReply

The moved building.

Command: emo Payload: {"MO": row}

Client: EMOCommand.executeCommand (bundle line 122815), which also reads the reply to a failed move, AreaDataUpdater.parseEMO (bundle line 131518)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

building pydantic-field

building: BuildingRow | None = None

Wire key MO

The building where it now stands

SellBuildingRequest pydantic-model

Bases: BaseRequest

Sell a decoration placed in the joined castle.

Command: sbd Payload: {"OID": object_id}

Client: C2SellBuildingDeco (bundle line 77740), sent from the sell dialog of a placed decoration (bundle line 28587)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

SellBuildingResponse pydantic-model

Bases: _BuildingReply

The sold decoration.

Command: sbd Payload: {"OID": object_id, "gcu": {..}}

Client: SBDCommand.executeCommand (bundle line 131722), AreaDataUpdater.parseSBD (bundle line 131522)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

object_id pydantic-field

object_id: int = -1

Wire key OID

Object id of the removed decoration

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

DestroyBuildingRequest pydantic-model

Bases: BaseRequest

Start taking a building in the joined castle down.

Command: edo Payload: {"OID": object_id}

Client: C2SIsoDisassembleObjectVO (bundle line 41065)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

DestroyBuildingResponse pydantic-model

Bases: _BuildingReply

The building being taken down.

Command: edo Payload: {"O": row, "scl": {..}}

Client: EDOCommand.executeCommand (bundle line 122781), AreaDataUpdater.parseEDO (bundle line 131513), which reads the reply itself as the row

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

building pydantic-field

building: BuildingRow | None = None

Wire key O

The building

construction_list pydantic-field

construction_list: ConstructionList | None = None

Wire key scl

The construction slots after

FastCompleteRequest pydantic-model

Bases: BaseRequest

Finish a building's running construction at once: for free with FS 1, else for rubies.

Command: fco Payload: {"OID": object_id, "FS": 0 or 1}

Client: C2SIsoFastCompleteObjectVO (bundle line 31951), sent by IsoServerCommands.fastCompleteBuilding (bundle line 63773) with FS 1 when CastleSpecialEventData.hasSkipForFree (bundle line 139853)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

free_skip pydantic-field

free_skip: bool = False

Wire key FS

Finish for free, the time left being within the free skip time

FastCompleteResponse pydantic-model

Bases: _BuildingReply

The finished building.

Command: fco Payload: {"O": row, "gcu": {..}}

Client: FCOCommand.executeCommand (bundle line 122908), AreaDataUpdater.parseFCO (bundle line 131519)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

building pydantic-field

building: BuildingRow | None = None

Wire key O

The building

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

TimeSkipBuildingRequest pydantic-model

Bases: BaseRequest

Shorten a building's running construction with a minute skip.

Command: msb Payload: {"OID": object_id, "MST": minute_skip}

Keys follow the client's order: the constructor initialises OID and sets MST after it.

Client: C2SMinuteSkipBuildingVO (bundle line 81363), built by BuildingMinuteSkipProperties.getMinuteSkipCommand (bundle line 50021); CastleMinuteSkipDialog.onScrollItemClick passes the currency's jsonKey (bundle line 7722)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

minute_skip pydantic-field

minute_skip: EnumOrStr['Currency']

Wire key MST

The minute skip used, Currency.SKIP_1_MINUTE to SKIP_24_HOURS; sent as its key

TimeSkipBuildingResponse pydantic-model

Bases: _BuildingReply

The construction slots after a minute skip.

Command: msb Payload: {"scl": {..}}

Client: MSBCommand.executeCommand (bundle line 125767)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

construction_list pydantic-field

construction_list: ConstructionList | None = None

Wire key scl

The construction slots after

UpgradeWallRequest pydantic-model

Bases: BaseRequest

Upgrade the joined castle's wall, gate or one of its towers.

Command: eud Payload: {"OID": object_id, "PO": offer_id, "PWR": 0 or 1}

Client: C2SIsoUpgradeDefenceVO (bundle line 41075)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

Object id of the wall, gate or tower, a BuildingRow.object_id

private_offer_id pydantic-field

private_offer_id: int = -1

Wire key PO

pay_with_rubies pydantic-field

pay_with_rubies: bool = False

Wire key PWR

UpgradeWallResponse pydantic-model

Bases: _BuildingReply

The upgraded wall, gate or tower.

Command: eud Payload: {"N": row, "grc": {..}, "gcu": {..}}

Client: EUDCommand.executeCommand (bundle line 122845), AreaDataUpdater.parseEUD (bundle line 131515)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

building pydantic-field

building: BuildingRow | None = None

Wire key N

The upgraded object

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The castle's resources after

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

RepairBuildingRequest pydantic-model

Bases: BaseRequest

Repair a damaged building in the joined castle.

Command: rbu Payload: {"OID": object_id, "PO": offer_id, "PWR": 0 or 1}

Client: C2SIsoRepairBuildingVO (bundle line 31961)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

private_offer_id pydantic-field

private_offer_id: int = -1

Wire key PO

pay_with_rubies pydantic-field

pay_with_rubies: bool = False

Wire key PWR

RepairBuildingResponse pydantic-model

Bases: _BuildingReply

The building being repaired.

Command: rbu Payload: {"O": row, "scl": {..}, "grc": {..}, "gcu": {..}}

Client: RBUCommand.executeCommand (bundle line 123162), AreaDataUpdater.parseRBU (bundle line 131507)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

building pydantic-field

building: BuildingRow | None = None

Wire key O

The building

construction_list pydantic-field

construction_list: ConstructionList | None = None

Wire key scl

The construction slots after

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The castle's resources after

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

RepairAllRequest pydantic-model

Bases: BaseRequest

Repair every damaged building in the joined castle at once.

Command: ira Payload: {}

Client: C2SIsoRepairAllVO (bundle line 37983)

RepairAllResponse pydantic-model

Bases: _BuildingReply

The repaired buildings.

Command: ira Payload: {"gpa": {..}, "B": [row, ...], "gcu": {..}}

Client: IRACommand.executeCommand (bundle line 123079), AreaDataUpdater.parseIRA (bundle line 131508)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

production_area pydantic-field

production_area: CastleProductionArea | None = None

Wire key gpa

The castle's production area after

buildings pydantic-field

buildings: list[BuildingRow]

Wire key B

The repaired buildings

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

BuyExtensionRequest pydantic-model

Bases: BaseRequest

Buy an expansion of the joined castle's grounds.

Command: ebe Payload: {"X": x, "Y": y, "R": rotation, "CT": expansion_type}

Client: C2SIsoBuyExpansionVO (bundle line 63787), sent by IsoServerCommands.buyExpansion (bundle line 63769) with the expansion's IsoExpansionEnum id

Fields:

x pydantic-field

x: int

Wire key X

Castle grid x of the expansion

y pydantic-field

y: int

Wire key Y

Castle grid y of the expansion

rotation pydantic-field

rotation: int = 0

Wire key R

Rotation

expansion_type pydantic-field

expansion_type: ExpansionType = ExpansionType.NORMAL

Wire key CT

Pay with resources (NORMAL) or rubies (PREMIUM)

BuyExtensionResponse pydantic-model

Bases: _BuildingReply

The castle after the expansion.

Command: ebe Payload: {"gca": {..}, "grc": {..}, "scl": {..}, "gcu": {..}, "sin": ..}

sin is kept as sent.

Client: EBECommand.executeCommand (bundle line 122743), AreaDataUpdater.parseEBE (bundle line 131511)

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

castle_buildings pydantic-field

castle_buildings: CastleBuildings | None = None

Wire key gca

The castle's buildings after

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The castle's resources after

construction_list pydantic-field

construction_list: ConstructionList | None = None

Wire key scl

The construction slots after

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

CollectExtensionGiftRequest pydantic-model

Bases: BaseRequest

Open a treasure chest found on an expansion of the joined castle.

Command: etc Payload: {"OID": object_id}

Client: C2SExtensionTreasureChestVO (bundle line 86720), sent by CastleTreasureChestBuildingDialog (bundle line 86704)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

The treasure chest's object id

CollectExtensionGiftResponse pydantic-model

Bases: _BuildingReply

The opened chest.

Command: etc Payload: {"RID": reward_id, "OID": object_id}

Client: AreaDataUpdater.parseETC (bundle line 131517); CastleTreasureChestBuildingDialog.onEtcArrived (bundle line 86706) reads RID through int()

Fields:

Validators:

  • _resources
  • _production_area
  • _castle_buildings
  • _construction_list
  • _row
  • _rows

reward_id pydantic-field

reward_id: ClientInt = 0

Wire key RID

The reward list the chest held

object_id pydantic-field

object_id: ClientInt = -1

Wire key OID

Object id of the removed chest

castle.models.castles

The player's castle list.

Commands:

  • gcl: Get castles list

Classes:

Name Description
PlayerCastle

One row of a castle list's AI entries, read by field position as its

GetCastlesRequest

Get a player's castle list.

CastleInfo

One of the player's locations: a castle list entry and its map row.

GetCastlesResponse

The player's castle list.

logger module-attribute

logger = logging.getLogger(__name__)

PlayerCastle pydantic-model

Bases: BasePayload

One row of a castle list's AI entries, read by field position as its area type's client parser reads it.

  • Castles, outposts, kingdom castles and faction camps (InteractiveMapobjectVO.parseAreaInfo): object id 3, owner 4, keep, wall, gate, tower and moat 5 to 9 through int() with keep, wall and gate at least 1, name 10, attack cooldown 11, sabotage cooldown 12, seconds since the last spy 13, outpost type 14, occupier 15, kingdom 16, equipment skin 17 and temporary sabotage protection 19 (on when it is 1); 11 to 19 through int(), so one the row lacks reads as 0.
  • Capitals and metropolises: as above, but the levels as sent, 13 as sent, the occupier at 14 and the skin at 15, both as sent, and no outpost type.
  • Kings towers: object id 3, owner 4, kingdom 5, seconds since the last spy 6, name 7.
  • Monuments: object id 3, owner 4, monument type 5, level 6, kingdom 7, seconds since the last spy 8, name 9.
  • Laboratories: object id 3, owner 4, level 5, kingdom 6, seconds since the last spy 7, name 8.
  • Faction capitals: owner 3, and no object id, name or kingdom.

The client stores the row's kingdom as sent; one that is not a Kingdom reads as the kingdom the row is listed under. An occupier the row lacks reads as -1 here, where the client's int() would read 0: a deliberate leniency, so a short row does not look occupied.

Client: WorldmapObjectFactory.parseWorldMapArea (bundle line 5343), InteractiveMapobjectVO.parseAreaInfo (bundle line 3631), CastleMapobjectVO.parseAreaInfo (bundle line 18910), FactionCampMapobjectVO.parseAreaInfo (bundle line 21526), CapitalMapobjectVO.parseAreaInfo (bundle line 18729), MetropolMapobjectVO.parseAreaInfo (bundle line 21609), KingstowerMapobjectVO.parseAreaInfo (bundle line 19055), MonumentMapobjectVO.parseAreaInfo (bundle line 21652), LaboratoryMapobjectVO.parseAreaInfo (bundle line 25900), FactionCapitalMapobjectVO.parseAreaInfo (bundle line 22764)

Fields:

kingdom pydantic-field

kingdom: Kingdom = Kingdom.GREEN

The castle's kingdom

location_id pydantic-field

location_id: int | None = None

The castle's object id; None for a type with none

x pydantic-field

x: int = 0

Map x

y pydantic-field

y: int = 0

Map y

castle_type pydantic-field

castle_type: MapItemType = MapItemType.EMPTY

The row's area type

owner_id pydantic-field

owner_id: int = 0

Player id of the owner

name pydantic-field

name: str | None = None

The castle's name; None for a type with none

occupier_id pydantic-field

occupier_id: int = -1

Player id of the occupier, -1 when there is none

keep_level pydantic-field

keep_level: int | None = None

Keep level; None for a type with none

wall_level pydantic-field

wall_level: int | None = None

Wall level; None for a type with none

gate_level pydantic-field

gate_level: int | None = None

Gate level; None for a type with none

tower_level pydantic-field

tower_level: int | None = None

Tower level; None for a type with none

moat_level pydantic-field

moat_level: int | None = None

Moat level; None for a type with none

landmark_level pydantic-field

landmark_level: int | None = None

A monument's or laboratory's level; None for any other type

monument_type pydantic-field

monument_type: int | None = None

A monument's type; None for any other type

attack_cooldown_seconds pydantic-field

attack_cooldown_seconds: int | None = None

Seconds until it can be attacked again; None for a type with none

sabotage_cooldown_seconds pydantic-field

sabotage_cooldown_seconds: int | None = None

Seconds until it can be sabotaged again; None for a type with none

seconds_since_spy pydantic-field

seconds_since_spy: int | None = None

Seconds since it was last spied on; None for a type with none

outpost_type pydantic-field

outpost_type: int | None = None

The outpost type; None for a type with none

equipment_skin_id pydantic-field

equipment_skin_id: int | None = None

Unique id of the castle skin equipped; None for a type with none

has_sabotage_protection pydantic-field

has_sabotage_protection: bool = False

Whether a temporary sabotage protection is on

is_occupied property

is_occupied: bool

Whether someone occupies this castle; the client's isOccupied is an occupier id above -1.

from_list classmethod

from_list(data: Any, kingdom: Kingdom = GREEN) -> 'PlayerCastle'

Read a gcl.C[].AI[].AI row; kingdom is the block it is listed under.

Raises:

Type Description
ValueError

The row is not a list, its area type is not one a castle list holds, or it is too short for its type's layout. ValidationError is one, for a field of the wrong type

GetCastlesRequest pydantic-model

Bases: BaseRequest

Get a player's castle list.

Command: gcl Payload: {"PID": player_id}

The client always sends the logged-in player's id. Without one the payload is {}, which the server answers with your castle list.

Client: C2SGetCastleListVO (bundle line 60768); its MY_CASTLELIST is -1, but no caller sends it

Fields:

player_id pydantic-field

player_id: int | None = None

Wire key PID

Your player id; None for none

CastleInfo pydantic-model

Bases: BasePayload

One of the player's locations: a castle list entry and its map row.

Entry: {"AI": [row], "OGT": .., "OGC": .., "AOT": .., "CAT": .., "TA": ..}

The row's values are read by its area type's layout, the field positions PlayerCastle lists. OGT, OGC, AOT, CAT and TA are the entry's keys, read through int() (OGT and OGC only when they are not 0), so one the entry lacks reads as 0. KID is the row's own kingdom when it carries one (InteractiveMapobjectVO.parseAreaInfo, bundle line 3637), else the block the entry is listed under. A faction capital's row carries no object id, so it has no CastleInfo.

Client: CastleListVO.parseCastleList (bundle line 13698), which reads the row with WorldmapObjectFactory.parseWorldMapArea

Fields:

castle_id pydantic-field

castle_id: int = 0

The castle's object id

castle_name pydantic-field

castle_name: str = ''

The castle's name

x pydantic-field

x: int = 0

Map x

y pydantic-field

y: int = 0

Map y

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The castle's kingdom: the row's own, else the block it is listed under

castle_type pydantic-field

castle_type: MapItemType = MapItemType.EMPTY

The castle's area type

owner_id pydantic-field

owner_id: int = 0

Player id of the owner

occupier_id pydantic-field

occupier_id: int = -1

Player id of the occupier, -1 when there is none

keep_level pydantic-field

keep_level: int | None = None

Keep level; None for a type with none

wall_level pydantic-field

wall_level: int | None = None

Wall level; None for a type with none

gate_level pydantic-field

gate_level: int | None = None

Gate level; None for a type with none

tower_level pydantic-field

tower_level: int | None = None

Tower level; None for a type with none

moat_level pydantic-field

moat_level: int | None = None

Moat level; None for a type with none

landmark_level pydantic-field

landmark_level: int | None = None

A monument's or laboratory's level; None for any other type

monument_type pydantic-field

monument_type: int | None = None

A monument's type; None for any other type

attack_cooldown_seconds pydantic-field

attack_cooldown_seconds: int | None = None

Seconds until it can be attacked again; None for a type with none

sabotage_cooldown_seconds pydantic-field

sabotage_cooldown_seconds: int | None = None

Seconds until it can be sabotaged again; None for a type with none

seconds_since_spy pydantic-field

seconds_since_spy: int | None = None

Seconds since it was last spied on; None for a type with none

outpost_type pydantic-field

outpost_type: int | None = None

The outpost type; None for a type with none

equipment_skin_id pydantic-field

equipment_skin_id: int | None = None

Unique id of the castle skin equipped; None for a type with none

has_sabotage_protection pydantic-field

has_sabotage_protection: bool = False

Whether a temporary sabotage protection is on

open_gate_seconds pydantic-field

open_gate_seconds: int = 0

Wire key OGT

Seconds the gate stays open

open_gate_counter pydantic-field

open_gate_counter: int = 0

Wire key OGC

How often the gate has been opened

abandon_outpost_seconds pydantic-field

abandon_outpost_seconds: int = 0

Wire key AOT

Seconds until the outpost is abandoned; not positive when it is not

cancel_abandon_seconds pydantic-field

cancel_abandon_seconds: int = 0

Wire key CAT

Seconds left to cancel abandoning the outpost

no_abandon_seconds pydantic-field

no_abandon_seconds: int = 0

Wire key TA

Seconds before the outpost may be abandoned again

is_occupied property

is_occupied: bool

Whether someone occupies it; the client's isOccupied is an occupier id above -1.

position property

position: Position

Get castle position as Position object.

from_entry classmethod

from_entry(entry: dict[str, Any], kingdom: Kingdom = GREEN) -> CastleInfo | None

Parse a gcl.C[].AI[] entry; its AI row is read by its area type (see PlayerCastle).

None for a row the client reads but that names no castle: a faction capital, which has no object id, or a faction camp that is not on the map.

GetCastlesResponse pydantic-model

Bases: BaseResponse

The player's castle list.

Command: gcl Payload: {"PID": player_id, "C": [{"KID": kingdom, "AI": [{"AI": [row...], "AOT": .., "TA": ..}, ...]}, ...]}

Entries are flattened across kingdoms into castles.

Client: CastleListVO.parseCastleList (bundle line 13698), which gdi uses for its gcl block too.

Fields:

Validators:

  • _flatten_kingdoms

player_id pydantic-field

player_id: int = 0

Wire key PID

castles pydantic-field

castles: list[CastleInfo]

castle.models.collect

Mines and resource carts, and collecting from them.

Commands:

  • cmr: Collect a mine
  • gsm: The joined castle's mines (pushed)
  • rcc: Collect a resource cart
  • rci: The joined castle's resource carts (pushed)

Classes:

Name Description
CollectMineResourcesRequest

Collect what a mine in the joined castle has produced.

MineStatus

One mine's state, an entry of the gsm block's M.

MineStatusList

The joined castle's mines: the gsm push, and the block of that name in

CollectMineResourcesResponse

The mines after collecting.

CollectResourceCartRequest

Collect the joined castle's resource cart of one resource.

ResourceCart

One resource cart, an entry of the rci block's RC.

ResourceCartInfo

The joined castle's resource carts, wood, stone and food: the rci push,

CollectResourceCartResponse

The collected cart.

logger module-attribute

logger = logging.getLogger(__name__)

CollectMineResourcesRequest pydantic-model

Bases: BaseRequest

Collect what a mine in the joined castle has produced.

Command: cmr Payload: {"OID": object_id}

Client: C2SCollectMineResources (bundle line 84598), sent by AMineBuildingVE.onMouseClick (bundle line 51410)

Fields:

object_id pydantic-field

object_id: int

Wire key OID

The mine's object id, a BuildingRow.object_id

MineStatus pydantic-model

Bases: BasePayload

One mine's state, an entry of the gsm block's M.

Client: CastleMineData.parse_GSM (bundle line 50363) and triggerInfoFlash (bundle line 50371)

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

object_id pydantic-field

object_id: int = -1

Wire key OID

The mine's object id

remaining_collection_amount pydantic-field

remaining_collection_amount: int = 0

Wire key RC

What is left to collect

next_collect_seconds pydantic-field

next_collect_seconds: int = 0

Wire key NC

Seconds until the mine can be collected again, -1 when it is done

coins pydantic-field

coins: int | None = None

Wire key C1

Coins just collected; None for none

rubies pydantic-field

rubies: int | None = None

Wire key C2

Rubies just collected; None for none

MineStatusList pydantic-model

Bases: BaseResponse

The joined castle's mines: the gsm push, and the block of that name in the cmr and jaa replies.

Command: gsm Payload: {"M": [{"OID": .., "RC": .., "NC": ..}, ...]}

Client: GSMCommand.executeCommand (bundle line 125752), CastleMineData.parse_GSM (bundle line 50363)

Fields:

Validators:

mines pydantic-field

mines: list[MineStatus]

Wire key M

Each mine

CollectMineResourcesResponse pydantic-model

Bases: BaseResponse

The mines after collecting.

Command: cmr Payload: {"gsm": {"M": [{"OID": .., "RC": .., "NC": .., "C1": ..}, ...]}, "gcu": {...}}

Client: CMRCommand.executeCommand (bundle line 125737)

Fields:

Validators:

mines pydantic-field

mines: MineStatusList | None = None

Wire key gsm

The castle's mines after

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

CollectResourceCartRequest pydantic-model

Bases: BaseRequest

Collect the joined castle's resource cart of one resource.

Command: rcc Payload: {"RT": cart_type}

Client: C2SIsoResourceCartCollectVO (bundle line 87566), sent by ResourceCartSurroundingsVE.onMouseClick (bundle line 87542)

Fields:

cart_type pydantic-field

cart_type: ResourceCartType

Wire key RT

The cart's resource

ResourceCart pydantic-model

Bases: BasePayload

One resource cart, an entry of the rci block's RC.

Client: ResourceCartData.parseRciItem (bundle line 81053)

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

cart_type pydantic-field

cart_type: ResourceCartType | None = None

Wire key RT

The cart's resource; None for an unknown index

amount pydantic-field

amount: int = 0

Wire key A

What the cart carries

remaining_seconds pydantic-field

remaining_seconds: int | float | None = None

Wire key RS

Seconds until the cart is ready

ResourceCartInfo pydantic-model

Bases: BaseResponse

The joined castle's resource carts, wood, stone and food: the rci push, and the block of that name in the rcc and jaa replies.

Command: rci Payload: {"RC": [{"RT": .., "A": .., "RS": ..}, ...]}

The client reads the first three entries, one cart each.

Client: RCICommand.executeCommand (bundle line 123196), CastleResourceCartsData.parse_RCI (bundle line 28798)

Fields:

Validators:

carts pydantic-field

Wire key RC

Each cart

CollectResourceCartResponse pydantic-model

Bases: BaseResponse

The collected cart.

Command: rcc Payload: {"RT": cart_type, "A": amount, "grc": {...}, "rci": {"RC": [...]}}

Client: RCCCommand.executeCommand (bundle line 123181), CastleResourceCartsData.parse_RCC (bundle line 28796)

Fields:

Validators:

cart_type pydantic-field

cart_type: ResourceCartType | None = None

Wire key RT

The collected cart's resource; None for an unknown index

amount pydantic-field

amount: int = 0

Wire key A

How much was collected

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The castle's resources after

carts pydantic-field

carts: ResourceCartInfo | None = None

Wire key rci

The castle's carts after

castle.models.details

Detailed castle info and the production area block.

Commands:

  • dcl: Get detailed castle info

Classes:

Name Description
GetDetailedCastleRequest

Get resources, units and production data for every castle the player owns.

ResourceProduction

Hourly production per resource; the client reads D<key> / 10.

StorageCapacity

Storage capacity per resource (MR<key>), a number as sent; anything else reads as 0.

ProductionBonus

Production bonus per resource in percent (<key>M); 100 means no bonus.

SafeAmount

The block's SAFE_<key> amounts per resource.

CastleProductionArea

A castle's production area, the gpa block.

DetailedCastleInfo

One castle's entry in the detailed castle list.

GetDetailedCastleResponse

Detail for every castle the player owns.

logger module-attribute

logger = logging.getLogger(__name__)

GetDetailedCastleRequest pydantic-model

Bases: BaseRequest

Get resources, units and production data for every castle the player owns.

Command: dcl Payload: {"CD": 1}

The client's constructor defaults CD to 1, and some dialogs send 0; what it selects is not traced, so this sends the default.

Client: C2SGetDetailedCastleListVO (bundle line 7922)

Fields:

cd pydantic-field

cd: int = 1

Wire key CD

The client's CD flag, 1 by default

ResourceProduction pydantic-model

Bases: _ProductionAreaSection

Hourly production per resource; the client reads D<key> / 10.

Fields:

Validators:

wood pydantic-field

wood: float = 0.0

Wire key DW

stone pydantic-field

stone: float = 0.0

Wire key DS

food pydantic-field

food: float = 0.0

Wire key DF

coal pydantic-field

coal: float = 0.0

Wire key DC

oil pydantic-field

oil: float = 0.0

Wire key DO

glass pydantic-field

glass: float = 0.0

Wire key DG

iron pydantic-field

iron: float = 0.0

Wire key DI

aquamarine pydantic-field

aquamarine: float = 0.0

Wire key DA

honey pydantic-field

honey: float = 0.0

Wire key DHONEY

mead pydantic-field

mead: float = 0.0

Wire key DMEAD

beef pydantic-field

beef: float = 0.0

Wire key DBEEF

StorageCapacity pydantic-model

Bases: _ProductionAreaSection

Storage capacity per resource (MR<key>), a number as sent; anything else reads as 0.

Fields:

Validators:

wood pydantic-field

wood: int | float = 0

Wire key MRW

stone pydantic-field

stone: int | float = 0

Wire key MRS

food pydantic-field

food: int | float = 0

Wire key MRF

coal pydantic-field

coal: int | float = 0

Wire key MRC

oil pydantic-field

oil: int | float = 0

Wire key MRO

glass pydantic-field

glass: int | float = 0

Wire key MRG

iron pydantic-field

iron: int | float = 0

Wire key MRI

aquamarine pydantic-field

aquamarine: int | float = 0

Wire key MRA

honey pydantic-field

honey: int | float = 0

Wire key MRHONEY

mead pydantic-field

mead: int | float = 0

Wire key MRMEAD

beef pydantic-field

beef: int | float = 0

Wire key MRBEEF

ProductionBonus pydantic-model

Bases: _ProductionAreaSection

Production bonus per resource in percent (<key>M); 100 means no bonus.

Fields:

Validators:

wood pydantic-field

wood: float = 0.0

Wire key WM

stone pydantic-field

stone: float = 0.0

Wire key SM

food pydantic-field

food: float = 0.0

Wire key FM

coal pydantic-field

coal: float = 0.0

Wire key CM

oil pydantic-field

oil: float = 0.0

Wire key OM

glass pydantic-field

glass: float = 0.0

Wire key GM

iron pydantic-field

iron: float = 0.0

Wire key IM

aquamarine pydantic-field

aquamarine: float = 0.0

Wire key AM

honey pydantic-field

honey: float = 0.0

Wire key HONEYM

mead pydantic-field

mead: float = 0.0

Wire key MEADM

beef pydantic-field

beef: float = 0.0

Wire key BEEFM

SafeAmount pydantic-model

Bases: _ProductionAreaSection

The block's SAFE_<key> amounts per resource.

The server sends them, but the client never reads them, so what they measure is not confirmed.

Fields:

Validators:

wood pydantic-field

wood: float = 0.0

Wire key SAFE_W

stone pydantic-field

stone: float = 0.0

Wire key SAFE_S

food pydantic-field

food: float = 0.0

Wire key SAFE_F

coal pydantic-field

coal: float = 0.0

Wire key SAFE_C

oil pydantic-field

oil: float = 0.0

Wire key SAFE_O

glass pydantic-field

glass: float = 0.0

Wire key SAFE_G

iron pydantic-field

iron: float = 0.0

Wire key SAFE_I

aquamarine pydantic-field

aquamarine: float = 0.0

Wire key SAFE_A

honey pydantic-field

honey: float = 0.0

Wire key SAFE_HONEY

mead pydantic-field

mead: float = 0.0

Wire key SAFE_MEAD

beef pydantic-field

beef: float = 0.0

Wire key SAFE_BEEF

CastleProductionArea pydantic-model

Bases: BasePayload

A castle's production area, the gpa block.

The per-resource sections are aliased key by key and validated from the same block, so each wire key appears once in this module. The whole-number fields are read through int(), as the client reads them; the other numbers read anything that is not a number as 0, as the client's arithmetic does (MP is 0 unless it is truthy).

Client: AreaDataUpdater.parseGPA (bundle line 131506), AreaDataCommonInfo.parseGPA (bundle line 130993), AreaDataStorageItem.parseGPA (bundle line 131380), AreaDataMorality.parseGPA (bundle line 131272), DetailedCastleVO.parseGpaData (bundle line 140979)

Fields:

Validators:

population pydantic-field

population: int = 0

Wire key P

Population

neutral_deco_points pydantic-field

neutral_deco_points: int = 0

Wire key NDP

Decoration points

sickness pydantic-field

sickness: int = 0

Wire key S

Sickness

riot pydantic-field

riot: int = 0

Wire key R

Riot

guards pydantic-field

guards: int = 0

Wire key GRD

Guards, before research boosts

build_speed_percent pydantic-field

build_speed_percent: int = 100

Wire key BDB

Construction speed in percent

metropolis_food_bonus pydantic-field

metropolis_food_bonus: float = 0.0

Wire key MP

A metropolis's food production bonus

unit_capacity pydantic-field

unit_capacity: int = 0

Wire key US

How many units the castle holds

auxiliary_capacity pydantic-field

auxiliary_capacity: int = 0

Wire key AUS

How many auxiliaries the castle holds

morale pydantic-field

morale: int = 0

Wire key M

Morale

faction_buff pydantic-field

faction_buff: float = 0.0

Wire key RFPPA

Faction strength balance, 0 to 1: below 0.5 boosts red faction morale, above 0.5 blue

food_consumption_delta pydantic-field

food_consumption_delta: float = 0.0

Wire key DFC

Food consumption per hour, times 10

food_consumption_reduction_percent pydantic-field

food_consumption_reduction_percent: int = 100

Wire key FCR

Food consumption in percent of the base

mead_consumption_delta pydantic-field

mead_consumption_delta: float = 0.0

Wire key DMEADC

Mead consumption per hour, times 10

mead_consumption_reduction_percent pydantic-field

mead_consumption_reduction_percent: int = 100

Wire key MEADCR

Mead consumption in percent of the base

beef_consumption_delta pydantic-field

beef_consumption_delta: float = 0.0

Wire key DBEEFC

Beef consumption per hour, times 10

beef_consumption_reduction_percent pydantic-field

beef_consumption_reduction_percent: int = 100

Wire key BEEFCR

Beef consumption in percent of the base

barracks_speed pydantic-field

barracks_speed: float = 0.0

Wire key RS1

Barracks production speed

workshop_speed pydantic-field

workshop_speed: float = 0.0

Wire key RS2

Siege workshop production speed

defense_workshop_speed pydantic-field

defense_workshop_speed: float = 0.0

Wire key RS3

Defense workshop production speed

hospital_speed pydantic-field

hospital_speed: float = 0.0

Wire key RSH

Hospital healing speed

production pydantic-field

production: ResourceProduction

Production per hour

storage_capacity pydantic-field

storage_capacity: StorageCapacity

Storage capacity

production_bonus_percent pydantic-field

production_bonus_percent: ProductionBonus

Production bonus in percent, 100 for none

safe_amount pydantic-field

safe_amount: SafeAmount

The block's SAFE_ amounts

food_consumption_per_hour property

food_consumption_per_hour: float

Hourly food consumption (DFC / 10).

DetailedCastleInfo pydantic-model

Bases: BasePayload

One castle's entry in the detailed castle list.

The client reads AID, D, the 11 resources (through int() wherever it uses them), MC, AC, SHI, gpa and the flags B, WS, DW and H (through Boolean()); KID is the kingdom block the entry is listed under. The server also sends OGT, AOT, HI and TU, which the client does not read from this reply: it reads hospital and travelling units from gui (CastleMilitaryData.parse_GUI, bundle line 138833). Each unit array reads as an inventory: an id sent twice adds up (UnitInventoryDictionary, bundle lines 5533-5538).

Client: CastleUserCastleListDetailed.parseData (bundle line 140906), DetailedCastleVO.parseData (bundle line 140973)

Fields:

Validators:

castle_id pydantic-field

castle_id: int

Wire key AID

The castle's object id

kingdom_id pydantic-field

kingdom_id: int = 0

Wire key KID

The castle's kingdom

wood pydantic-field

wood: int = 0

Wire key W

Wood in stock

stone pydantic-field

stone: int = 0

Wire key S

Stone in stock

food pydantic-field

food: int = 0

Wire key F

Food in stock

coal pydantic-field

coal: int = 0

Wire key C

Coal in stock

oil pydantic-field

oil: int = 0

Wire key O

Oil in stock

glass pydantic-field

glass: int = 0

Wire key G

Glass in stock

iron pydantic-field

iron: int = 0

Wire key I

Iron in stock

aquamarine pydantic-field

aquamarine: int = 0

Wire key A

Aquamarine in stock

honey pydantic-field

honey: int = 0

Wire key HONEY

Honey in stock

mead pydantic-field

mead: int = 0

Wire key MEAD

Mead in stock

beef pydantic-field

beef: int = 0

Wire key BEEF

Beef in stock

defense_value pydantic-field

defense_value: int = 0

Wire key D

Defense value

has_barracks pydantic-field

has_barracks: bool = False

Wire key B

Whether the castle has barracks

has_siege_workshop pydantic-field

has_siege_workshop: bool = False

Wire key WS

Whether it has a siege workshop

has_defense_workshop pydantic-field

has_defense_workshop: bool = False

Wire key DW

Whether it has a defense workshop

has_hospital pydantic-field

has_hospital: bool = False

Wire key H

Whether it has a hospital

market_carriages pydantic-field

market_carriages: int = 0

Wire key MC

The castle's market carriages

open_gate_seconds pydantic-field

open_gate_seconds: int = 0

Wire key OGT

Seconds the gate stays open

abandon_outpost_seconds pydantic-field

abandon_outpost_seconds: int = -1

Wire key AOT

Seconds until the outpost is abandoned, -1 when it is not

units pydantic-field

units: WodAmounts

Wire key AC

Units and tools stationed here

stronghold_units pydantic-field

stronghold_units: WodAmounts

Wire key SHI

Units in the stronghold (safe house)

hospital_units pydantic-field

hospital_units: WodAmounts

Wire key HI

Wounded units in the hospital; the client reads them from gui, not from this reply

travelling_units pydantic-field

travelling_units: WodAmounts

Wire key TU

Units on their way in (gui's in_production); the client reads them from gui, not from this reply

production_area pydantic-field

production_area: CastleProductionArea | None = None

Wire key gpa

The castle's production area; None when the entry has none

GetDetailedCastleResponse pydantic-model

Bases: BaseResponse

Detail for every castle the player owns.

Command: dcl Payload: {"PID": player_id, "C": [{"KID": kingdom, "AI": [{"AID": castle_id, "W": .., "AC": [..], "gpa": {..}}]}]}

Client: DCLCommand.executeCommand (bundle line 129299)

Fields:

Validators:

  • _flatten_kingdoms

player_id pydantic-field

player_id: int = 0

Wire key PID

castles pydantic-field

castle

castle(castle_id: int) -> DetailedCastleInfo | None

The listed castle with this id, or None.

Raises:

Type Description
AmbiguousCastleError

the id is listed in several kingdoms

castle.models.kingdoms

Your kingdoms: which are unlocked, and the units and goods on their way to one.

Commands:

  • kpi: kingdom info, a login section of gbd, a reply, and inside the transfer replies

Classes:

Name Description
KingdomUnlock

One kingdom's unlock, warehouse and slum, an entry of a kpi's UL.

KingdomTransfer

Units or goods on their way to a kingdom, an entry of a kpi's UT or RT.

KingdomInfoResponse

Your kingdoms' unlocks, and the units and goods on their way to them.

KingdomUnlock pydantic-model

Bases: TimedPayload

One kingdom's unlock, warehouse and slum, an entry of a kpi's UL.

Client: CastleKingdomVO.parseUnlockInfo (bundle line 134696); hasContor read at bundle line 50209

Fields:

Validators:

kingdom_id pydantic-field

kingdom_id: EnumOrInt[Kingdom] = -1

Wire key KID

The kingdom; -1 when not sent

is_unlocked pydantic-field

is_unlocked: bool = False

Wire key U

Whether the kingdom is unlocked

has_warehouse pydantic-field

has_warehouse: bool = False

Wire key C

Whether the kingdom has a warehouse, which its resource villages need

slum_level pydantic-field

slum_level: ClientInt = 0

Wire key SL

The kingdom's slum level

paid_wood pydantic-field

paid_wood: ClientNumber = 0

Wire key SPW

Wood paid towards the next slum level

paid_stone pydantic-field

paid_stone: ClientNumber = 0

Wire key SPS

Stone paid towards the next slum level

paid_food pydantic-field

paid_food: ClientNumber = 0

Wire key SPF

Food paid towards the next slum level

paid_coins pydantic-field

paid_coins: ClientNumber = 0

Wire key SPC1

Coins paid towards the next slum level

reset_seconds pydantic-field

reset_seconds: ClientNumber = 0

Wire key KRS

Seconds until the kingdom resets when the values were read; 0 for none

reward_set pydantic-field

reward_set: ClientNumber = 0

Wire key CRS

The kingdom's reward set; 0 for none

remaining_reset_seconds

remaining_reset_seconds(now: float | None = None) -> float | None

Seconds until the kingdom resets, 0 at least; None when it does not reset.

KingdomTransfer pydantic-model

Bases: TimedPayload

Units or goods on their way to a kingdom, an entry of a kpi's UT or RT.

The units come as [wod_id, amount] rows, which the client keeps as they are and names by their unit (toolTipText); here each is a UNITS collectable, its item the unit or tool.

Client: CastleKingdomUnitTransferVO.fillFromParamObject and toolTipText (bundle lines 134663, 134675), CastleKingdomGoodsTransferVO.fillFromParamObject (bundle line 134640), which reads the goods with CollectableParserS2CParamList

Fields:

Validators:

kingdom_id pydantic-field

kingdom_id: EnumOrInt[Kingdom] = Kingdom.GREEN

Wire key KID

The kingdom they go to

seconds pydantic-field

seconds: ClientInt = 0

Wire key RS

Seconds until they arrive when the values were read

units pydantic-field

units: tuple[Collectable, ...] = ()

Wire key I

The units and tools; units only

goods pydantic-field

goods: CollectableRows = ()

Wire key G

The goods; goods only

remaining_seconds

remaining_seconds(now: float | None = None) -> float

Seconds until they arrive, 0 at least.

KingdomInfoResponse pydantic-model

Bases: BaseResponse

Your kingdoms' unlocks, and the units and goods on their way to them.

A kpi updates only the kingdoms it lists; the client keeps the others as they were, and client.state.get_kingdoms() does the same. The transfers are replaced whole. The Berimond faction block (fki) belongs to the faction event and is not read here.

Command: kpi, as a login section of gbd, a reply, and inside the kgt, kst, kut, msk and fjf replies.

Client: KPICommand (bundle line 124684), CastleKingdomData.parse_KPI (bundle line 134539)

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

kingdoms pydantic-field

kingdoms: tuple[KingdomUnlock, ...] = ()

Wire key UL

The kingdoms this packet lists

unit_transfers pydantic-field

unit_transfers: tuple[KingdomTransfer, ...] = ()

Wire key UT

Units on their way to another kingdom

goods_transfers pydantic-field

goods_transfers: tuple[KingdomTransfer, ...] = ()

Wire key RT

Goods on their way to another kingdom

kingdom

kingdom(kingdom_id: Kingdom | int) -> KingdomUnlock | None

One kingdom, None when it is not listed.

castle.models.market

Sending resources between castles, and the market overview.

Commands:

  • crm: Send resources to a castle
  • cmi: List your castles' carriages and resources

Classes:

Name Description
CreateMarketMovementRequest

Send resources from one of your castles to a castle on the map, by carriage.

CreateMarketMovementResponse

The carriage on its way.

MarketInfoRequest

List your castles' market carriages and resources.

MarketCastle

One castle in the market overview.

MarketInfoResponse

Your castles' market carriages and resources.

logger module-attribute

logger = logging.getLogger(__name__)

ResourceAmounts module-attribute

ResourceAmounts = Annotated[dict[Resource, int], BeforeValidator(_resource_rows), PlainSerializer(lambda amounts: [[resource.value, amount] for resource, amount in amounts.items()])]

Resources by amount, sent as [[resource_key, amount], ...] in insertion order.

Client: CollectableParserC2SCosts.createCostsListForServer (bundle line 62921) adds up a repeated item (combineDuplicatedItems) and then writes [itemType.serverKey, amount] per item

CreateMarketMovementRequest pydantic-model

Bases: BaseRequest

Send resources from one of your castles to a castle on the map, by carriage.

Command: crm Payload: {"KID": kingdom_id, "SID": source_castle_id, "TX": x, "TY": y, "HBW": horse, "PTT": 0 or 1, "SD": slowdown, "G": [[resource_key, amount], ...]}

Keys follow the client's order: the constructor initialises KID to SD and sets G after them. KID is the source castle's kingdom. A horse paid with feathers is sent as HBW -1 with PTT 1. The client merges repeated resources in G into one entry each.

Client: C2SCreateMarketMovementVO (bundle line 68348), sent by CastlePostSendGoodsDialog.sendGoods (bundle line 33377); G is built by CollectableParserC2SCosts.createCostsListForServer (bundle line 62921)

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The source castle's kingdom

source_castle_id pydantic-field

source_castle_id: int

Wire key SID

One of your castles, CastleInfo.castle_id from client.castle.get_all() or Castle.id from client.state.get_castles()

target_x pydantic-field

target_x: int

Wire key TX

Map x of the target castle

target_y pydantic-field

target_y: int

Wire key TY

Map y of the target castle

horse_booster_id pydantic-field

horse_booster_id: int = -1

Wire key HBW

The horse booster's wod id, -1 for none or when paid with feathers

feathers pydantic-field

feathers: int = 0

Wire key PTT

1 when the horse is paid with feathers

slowdown pydantic-field

slowdown: int = 0

Wire key SD

Seconds the arrival is delayed by

goods pydantic-field

Wire key G

The amount of each resource to send, sent as [key, amount] pairs such as ['W', 1000]

CreateMarketMovementResponse pydantic-model

Bases: BaseResponse

The carriage on its way.

Command: crm Payload: {"gcu": {...}, "grc": {...}, "O": [owner, ...], "A": movement}

O (the owners involved) and A (the new movement) are kept as sent.

Client: CRMCommand.executeCommand (bundle line 125981)

Fields:

Validators:

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The joined castle's resources after

MarketInfoRequest pydantic-model

Bases: BaseRequest

List your castles' market carriages and resources.

Command: cmi Payload: {"S": scope, "KID": kingdom_id}

The client always sends {"S": 1, "KID": -1}: every kingdom.

Client: C2SMarketInfoVO (bundle line 27012)

Fields:

scope pydantic-field

scope: MarketScope = MarketScope.ALL_KINGDOMS

Wire key S

Which castles to list

kingdom_id pydantic-field

kingdom_id: int = -1

Wire key KID

The kingdom to list, -1 with ALL_KINGDOMS

MarketCastle pydantic-model

Bases: BasePayload

One castle in the market overview.

AE (the castle's area effects) is kept as sent.

Client: CastleTradeVO.fillFromParamObject (bundle line 140345), which reads every number through int()

Fields:

kingdom_id pydantic-field

kingdom_id: ClientInt = 0

Wire key KID

The castle's kingdom

castle_id pydantic-field

castle_id: ClientInt = 0

Wire key CID

The castle's object id

total_carriages pydantic-field

total_carriages: ClientInt = 0

Wire key TC

The castle's market carriages

available_carriages pydantic-field

available_carriages: ClientInt = 0

Wire key AC

Carriages not on the road

wood pydantic-field

wood: ClientInt = 0

Wire key W

Wood in stock

stone pydantic-field

stone: ClientInt = 0

Wire key S

Stone in stock

food pydantic-field

food: ClientInt = 0

Wire key F

Food in stock

coal pydantic-field

coal: ClientInt = 0

Wire key C

Coal in stock

oil pydantic-field

oil: ClientInt = 0

Wire key O

Oil in stock

glass pydantic-field

glass: ClientInt = 0

Wire key G

Glass in stock

iron pydantic-field

iron: ClientInt = 0

Wire key I

Iron in stock

honey pydantic-field

honey: ClientInt = 0

Wire key HONEY

Honey in stock

mead pydantic-field

mead: ClientInt = 0

Wire key MEAD

Mead in stock

beef pydantic-field

beef: ClientInt = 0

Wire key BEEF

Beef in stock

MarketInfoResponse pydantic-model

Bases: BaseResponse

Your castles' market carriages and resources.

Command: cmi Payload: {"C": [{"KID": .., "CID": .., "TC": .., "AC": .., "W": .., ...}, ...]}

Client: CMICommand.executeCommand (bundle line 128944), CastleTradeInfoVO.fillCastleList (bundle line 140325)

Fields:

Validators:

castles pydantic-field

castles: list[MarketCastle]

Wire key C

Each of your castles

castle.models.objects

A castle's buildings and its construction queue.

Commands:

  • scl: Show the construction list

Classes:

Name Description
BuildingRow

One placed object in a castle: a building, wall, gate, tower, ground or

ConstructionList

A castle's construction slots, the scl block.

FieldEfficiency

Resource field efficiency per resource (RA<key>).

PlacedConstructionItem

A construction item on a building: one CIL entry.

BuildingConstructionItems

The construction items on one building: one CI entry.

CastleBuildings

A castle's buildings, the gca block.

ShowConstructionListRequest

Get the joined castle's construction slots.

ShowConstructionListResponse

The joined castle's construction slots.

Functions:

Name Description
building_or_none

A reply's building row, or None when it has none or it cannot be read.

building_rows

Each readable row of a list of building rows.

Attributes:

Name Type Description
FREE_SLOT
LOCKED_SLOT

logger module-attribute

logger = logging.getLogger(__name__)

FREE_SLOT module-attribute

FREE_SLOT = -1

LOCKED_SLOT module-attribute

LOCKED_SLOT = -2

BuildingRow pydantic-model

Bases: BasePayload

One placed object in a castle: a building, wall, gate, tower, ground or fixed object, read by field position.

  • 0 wod id, 1 object id, 2 x, 3 y, 4 rotation (1 for a building inside a district); each through int()
  • 5 seconds of the running construction done, 6 state, 7 hit points, 8 the construction boost at the start, times 100, 9 efficiency, 10 damage type (0 when absent)
  • 11 onwards: values the building's type reads, such as a unit-producing building's production speed
  • 14 the district the building sits in, 15 its slot there
  • 16 the upgrade target's wod id, -1 when absent

A row may come wrapped as {"O": [...]}. Where the client reads an absent 1 to 4, 14 or 15 as 0, so does this. Fields 5 to 10 are read only by the building classes (ABasicBuildingVO), which the client picks by the wod id's group and name; this reads them whenever the row reaches index 5 and leaves them None otherwise, which is not verified to match the client's class for every wod id.

Client: IsoHelperData.createIsoObjectVOByServer (bundle line 63049), AVisualVO.parseServerObject (bundle line 17801), AIsoObjectVO.parseServerObject (bundle line 11265), ABasicBuildingVO.parseServerObject (bundle lines 17866-17868, index 5 is _constructionCompletionInSec), AUnitProductionBuildingVO.parseServerObject (bundle line 36254)

Fields:

wod_id pydantic-field

wod_id: int

The building type's wod id

object_id pydantic-field

object_id: int = 0

The object's id in its castle, which building commands name

x pydantic-field

x: int = 0

Castle grid x

y pydantic-field

y: int = 0

Castle grid y

rotation pydantic-field

rotation: int = 0

Rotation

construction_completed_seconds pydantic-field

construction_completed_seconds: int | None = None

Seconds of the running construction done when the row was sent

state pydantic-field

state: BuildingState | None = None

What the building is doing

hit_points pydantic-field

hit_points: int | None = None

Hit points, 100 when undamaged

construction_boost_at_start pydantic-field

construction_boost_at_start: float | None = None

The construction speed boost when the construction started, as a factor

efficiency pydantic-field

efficiency: int | None = None

Efficiency in percent

damage_type pydantic-field

damage_type: int | None = None

Damage type, 0 for none

district_id pydantic-field

district_id: int = 0

Object id of the district the building sits in, 0 for none

district_slot_id pydantic-field

district_slot_id: int = 0

The building's slot in its district

upgrade_target_wod_id pydantic-field

upgrade_target_wod_id: int = -1

Wod id the building is upgrading to, -1 for none

is_in_district property

is_in_district: bool

Whether the building sits in a district.

from_list classmethod

from_list(data: Any) -> BuildingRow

Read a building row, bare or wrapped as {"O": [...]}.

Raises:

Type Description
ValueError

It is not a row.

ConstructionSlot pydantic-model

Bases: BasePayload

One construction slot of a castle.

Client: ConstructionSlotVO (bundle lines 131242-131250), filled by AreaDataConstructionList.parseList (bundle line 131170)

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

position pydantic-field

position: int

The slot's place in the list, from 0

object_id pydantic-field

object_id: int

Object id of the building under construction there; FREE_SLOT (-1) or LOCKED_SLOT (-2)

is_waiting pydantic-field

is_waiting: bool = False

A waiting slot: its position is at or past the castle's construction slots

is_free property

is_free: bool

Whether nothing is being built there (ConstructionConst.SLOTSTATEUNLOCKED).

is_locked property

is_locked: bool

Whether the slot is still locked (ConstructionConst.SLOTSTATELOCKED).

ConstructionList pydantic-model

Bases: BasePayload

A castle's construction slots, the scl block.

Client: AreaDataConstructionList.parseSCL / parseList (bundle lines 131173, 131166), which read each OIDL entry through int() into a ConstructionSlotVO at its position

Fields:

Validators:

  • _slots

slots pydantic-field

slots: tuple[ConstructionSlot, ...] = ()

Wire key OIDL

The slots, in order

slot_count pydantic-field

slot_count: int | float = 1

Wire key SSC

Number of construction slots

free_slots property

free_slots: int

Number of free slots.

building_object_ids property

building_object_ids: list[int]

Object ids of the buildings under construction.

FieldEfficiency pydantic-model

Bases: _ProductionAreaSection

Resource field efficiency per resource (RA<key>).

Fields:

wood pydantic-field

wood: ClientInt = 0

Wire key RAW

stone pydantic-field

stone: ClientInt = 0

Wire key RAS

food pydantic-field

food: ClientInt = 0

Wire key RAF

coal pydantic-field

coal: ClientInt = 0

Wire key RAC

oil pydantic-field

oil: ClientInt = 0

Wire key RAO

glass pydantic-field

glass: ClientInt = 0

Wire key RAG

iron pydantic-field

iron: ClientInt = 0

Wire key RAI

aquamarine pydantic-field

aquamarine: ClientInt = 0

Wire key RAA

honey pydantic-field

honey: ClientInt = 0

Wire key RAHONEY

mead pydantic-field

mead: ClientInt = 0

Wire key RAMEAD

beef pydantic-field

beef: ClientInt = 0

Wire key RABEEF

PlacedConstructionItem pydantic-model

Bases: BasePayload

A construction item on a building: one CIL entry.

Client: ABasicBuildingVO.parseConstructionItems (bundle lines 18005-18010), which puts the item at its slot type's first index plus int(S) and gives a temporary item the RS seconds left

Fields:

construction_item_id pydantic-field

construction_item_id: EnumOrInt['ConstructionItem']

Wire key CID

The construction item

slot pydantic-field

slot: ClientInt = 0

Wire key S

Its slot among the slots of its item's slot type

remaining_seconds pydantic-field

remaining_seconds: ClientNumber = 0

Wire key RS

Seconds a temporary item has left; 0 for a permanent one

BuildingConstructionItems pydantic-model

Bases: BasePayload

The construction items on one building: one CI entry.

Client: ABasicBuildingVO.parseConstructionItems (bundle lines 18000-18006), which takes the entry whose OID is the building's

Fields:

Validators:

object_id pydantic-field

object_id: ClientInt

Wire key OID

The building's object id

items pydantic-field

items: tuple[PlacedConstructionItem, ...] = ()

Wire key CIL

Its construction items

CastleBuildings pydantic-model

Bases: BasePayload

A castle's buildings, the gca block.

The block's A (the castle's map row) and O (its owner) are kept as sent.

Client: AreaDataUpdater.parseGCA (bundle line 131497), IsoDataObjectGroupInnerBuilding.parseGCA (bundle line 80810), IsoDataObjectGroupDefence.parseGCA (bundle line 80220), IsoDataObjectGroupGround.parseGCA (bundle line 80771), IsoDataObjectGroupFixedPosition.parseGCA (bundle line 80743), AreaDataStorageItem.parseGCA (bundle line 131381), AreaDataUpdater.parseConstructionItems (bundle line 131524)

Fields:

Validators:

buildings pydantic-field

buildings: list[BuildingRow]

Wire key BD

Buildings inside the walls

walls pydantic-field

walls: list[BuildingRow]

Wire key D

The wall and the moat

gates pydantic-field

gates: list[BuildingRow]

Wire key G

The gate

towers pydantic-field

towers: list[BuildingRow]

Wire key T

The towers

grounds pydantic-field

grounds: list[BuildingRow]

Wire key BG

Ground tiles and expansions

fixed_positions pydantic-field

fixed_positions: list[BuildingRow]

Wire key FP

Objects at fixed positions

construction_list pydantic-field

construction_list: ConstructionList | None = None

Wire key scl

The construction slots; None when the block has none

construction_items pydantic-field

construction_items: tuple[BuildingConstructionItems, ...] = ()

Wire key CI

The construction items on each building that has any

field_efficiency pydantic-field

field_efficiency: FieldEfficiency

Resource field efficiency per resource

all_objects

all_objects() -> list[BuildingRow]

Every object in the castle, buildings first.

find

find(object_id: int) -> BuildingRow | None

The object with this id, or None.

ShowConstructionListRequest pydantic-model

Bases: BaseRequest

Get the joined castle's construction slots.

Command: scl Payload: {}

Client: C2SShowConstructionListVO (bundle line 63754)

ShowConstructionListResponse pydantic-model

Bases: ConstructionList, BaseResponse

The joined castle's construction slots.

Command: scl Payload: {"OIDL": [object_id, ...], "SSC": slot_count}

Client: SCLCommand.executeCommand (bundle line 123374)

Fields:

Validators:

  • _slots

block_or_none

block_or_none(model: type[_B], value: Any) -> _B | None

A reply's block read as model, or None when it is not an object or cannot be read.

building_or_none

building_or_none(value: Any) -> BuildingRow | None

A reply's building row, or None when it has none or it cannot be read.

building_rows

building_rows(value: Any) -> list[BuildingRow]

Each readable row of a list of building rows.

The client throws on a null or malformed entry; here one costs only itself.

castle.models.permanent

What each castle has unlocked: its units and its horses.

Commands:

  • gpc: Permanent castle data, a login section and a push

Classes:

Name Description
CastleUnitUnlocks

The units and tools a castle can recruit, and those it has still locked.

PermanentCastle

One castle's unlocked units and horses.

PermanentCastleDataResponse

Every castle's unlocked units and horses.

logger module-attribute

logger = logging.getLogger(__name__)

CastleUnitUnlocks pydantic-model

Bases: EmpireModel

The units and tools a castle can recruit, and those it has still locked.

Client: CastleUnitsVO.parseParamObject (bundle lines 139200-139207) looks each id up among the units rows and keeps the soldiers, auxiliaries and tools

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(populate_by_name=True)

unlocked_units pydantic-field

unlocked_units: tuple[UnitOrTool, ...] = ()

Wire key U

The units and tools the castle can recruit

locked_units pydantic-field

locked_units: tuple[UnitOrTool, ...] = ()

Wire key L

The units and tools still locked there

PermanentCastle pydantic-model

Bases: EmpireModel

One castle's unlocked units and horses.

Client: CastlePermanentCastleVO.parseParamObject (bundle line 139158), CastleHorsesVO.parseParamObject (bundle line 139177)

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(populate_by_name=True)

castle_id pydantic-field

castle_id: ClientInt

Wire key AID

The castle's object id

kingdom_id pydantic-field

kingdom_id: Kingdom

Wire key KID

The castle's kingdom

units pydantic-field

Wire key U

Units and tools unlocked and locked there

horse_ids pydantic-field

horse_ids: list[EnumOrInt['Horse']]

Wire key UH

The horses the castle's movements can use

PermanentCastleDataResponse pydantic-model

Bases: BaseResponse

Every castle's unlocked units and horses.

Command: gpc, as a login section of gbd and as a push Payload: {"A": [{"AID": castle_id, "KID": kingdom, "U": {"U": [wod_id, ...], "L": [wod_id, ...]}, "UH": [wod_id, ...]}, ...]}

The client never asks for it: there is no C2S constant for gpc. An update names only the castles it changes; castles it leaves out keep what they had.

Client: GPCCommand.executeCommand (bundle line 123044), CastlePermanentCastleData.parseGPC (bundle line 139128), read from gbd (bundle line 129381)

Fields:

Validators:

castles pydantic-field

Wire key A

One entry per castle

castle.models.resources

A castle's resources and production.

Commands:

  • grc: Get a castle's resources
  • gpa: Get the joined castle's production area

Classes:

Name Description
CastleResources

A castle's resources, the grc block.

GetResourcesRequest

Get one of your castles' resources.

GetResourcesResponse

A castle's resources.

GetProductionRequest

Get the joined castle's production area.

GetProductionResponse

The joined castle's production area.

CastleResources pydantic-model

Bases: BasePayload

A castle's resources, the grc block.

Client: CastleResourcesVO.parseGRC (bundle line 131537), which reads AID and KID through int(); AreaDataStorageItem.parseGRC (bundle line 131382) reads each amount through int()

Fields:

castle_id pydantic-field

castle_id: ClientInt = 0

Wire key AID

The castle's object id

kingdom_id pydantic-field

kingdom_id: ClientInt = 0

Wire key KID

The castle's kingdom

wood pydantic-field

wood: ClientInt = 0

Wire key W

Wood in stock

stone pydantic-field

stone: ClientInt = 0

Wire key S

Stone in stock

food pydantic-field

food: ClientInt = 0

Wire key F

Food in stock

coal pydantic-field

coal: ClientInt = 0

Wire key C

Coal in stock

oil pydantic-field

oil: ClientInt = 0

Wire key O

Oil in stock

glass pydantic-field

glass: ClientInt = 0

Wire key G

Glass in stock

iron pydantic-field

iron: ClientInt = 0

Wire key I

Iron in stock

aquamarine pydantic-field

aquamarine: ClientInt = 0

Wire key A

Aquamarine in stock

honey pydantic-field

honey: ClientInt = 0

Wire key HONEY

Honey in stock

mead pydantic-field

mead: ClientInt = 0

Wire key MEAD

Mead in stock

beef pydantic-field

beef: ClientInt = 0

Wire key BEEF

Beef in stock

GetResourcesRequest pydantic-model

Bases: BaseRequest

Get one of your castles' resources.

Command: grc Payload: {"AID": castle_id, "KID": kingdom_id}

Client: C2SGetCastleResourcesVO (bundle line 19925), sent for a castle picked in the resource transfer dialog (bundle line 38076)

Fields:

castle_id pydantic-field

castle_id: int

Wire key AID

One of your castles, CastleInfo.castle_id from client.castle.get_all() or Castle.id from client.state.get_castles()

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The castle's kingdom

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a grc reply is about this castle: its AID, when sent, is the one asked for.

Client: GRCCommand.exec (bundle line 123063) through AreaDataUpdater.parseGRC (bundle line 131501), which applies a reply only to the area whose id is its AID.

GetResourcesResponse pydantic-model

Bases: CastleResources, BaseResponse

A castle's resources.

Command: grc Payload: {"AID": castle_id, "KID": kingdom_id, "W": .., "S": .., "F": .., ...}

Client: GRCCommand.exec (bundle line 123063), which hands the reply to AreaDataUpdater.parseGRC (bundle line 131501)

Fields:

GetProductionRequest pydantic-model

Bases: BaseRequest

Get the joined castle's production area.

Command: gpa Payload: {}

The server answers for the castle joined with jca.

Client: C2SGetCastleProductionDataVO (bundle line 25082)

castle.models.support

Sending support to a castle, and troops between your own areas.

Commands:

  • cds: Send support
  • cat: Send troops to another of your own areas
  • sti: Travel pre-calculation for troops sent to another of your own areas

Classes:

Name Description
SendSupportRequest

Send support troops to a castle.

SendSupportResponse

The support on its way.

SendTroopsRequest

Send troops from one of your areas to another of your own areas, where they stay.

SendTroopsResponse

The troops on their way.

GetTravelInfoRequest

Ask for the pre-calculation of troops sent from one of your areas to another of your own.

TravelTargetArea

The target's map row and its owner, the gaa block of an sti reply.

TravelUnits

Your units and tools at the source, the gui block of an sti reply.

GetTravelInfoResponse

The pre-calculation of troops sent between your own areas.

SendSupportRequest pydantic-model

Bases: BaseRequest

Send support troops to a castle.

Command: cds Payload: { "SID": source_castle_id, "TX": target_x, "TY": target_y, "LID": commander_id, "WT": wait_time, "HBW": horse_booster_id (-1 when PTT is set), "BPC": use_premium_commander, "PTT": feathers, "SD": slowdown, "A": [[unit_id, count], ...] }

The client sends no kingdom id. Fields follow its key order. BPC is 1 only when the premium commander (LID -14, TravelConst.COMMANDER_PREMIUM) leads the army, which uses one of the player's premium commanders or, when none are left, costs rubies (CastlePostAttackDialog.startAttack sends sendMovement(0) for any other commander). PTT 1 pays for the movement with feathers.

Client: C2SCreateDefenceSupportMovementVO (bundle line 133893), built by CastleAttackData.sendSupport (bundle line 133855)

Fields:

source_castle_id pydantic-field

source_castle_id: int

Wire key SID

One of your castles, CastleInfo.castle_id from client.castle.get_all() or Castle.id from client.state.get_castles()

target_x pydantic-field

target_x: int

Wire key TX

target_y pydantic-field

target_y: int

Wire key TY

commander_id pydantic-field

commander_id: int

Wire key LID

A Commander.commander_id from client.commanders.get_commanders(); 0 is the free starting commander, -14 the premium one

wait_time pydantic-field

wait_time: int = 12

Wire key WT

horse_booster_id pydantic-field

horse_booster_id: int = -1

Wire key HBW

The horse booster's wod id, -1 for none or when paid with feathers

use_premium_commander pydantic-field

use_premium_commander: int = 0

Wire key BPC

1 when the premium commander leads

feathers pydantic-field

feathers: int = 0

Wire key PTT

1 when the horse is paid with feathers

slowdown pydantic-field

slowdown: int = 0

Wire key SD

Seconds the arrival is delayed by

units pydantic-field

units: WodAmountSlots

Wire key A

The units, then any tools, one pair per filled slot

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a cds reply is the movement this sent: its target area (A.M.TA) is TX/TY.

Client: CDSCommand (bundle line 125939) reads the new movement from A, whose TA is the target area (BasicMapmovementVO). An army heading home targets your own castle and is not taken.

SendSupportResponse pydantic-model

Bases: BaseResponse

The support on its way.

Command: cds Payload: {"gcu": {...}, "O": [owner, ...], "A": movement}

O (the owners involved) and A (the new movement) are kept as sent.

Client: CDSCommand.executeCommand (bundle line 125939)

Fields:

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

SendTroopsRequest pydantic-model

Bases: BaseRequest

Send troops from one of your areas to another of your own areas, where they stay.

Command: cat Payload: { "SX": source_x, "SY": source_y, "TX": target_x, "TY": target_y, "KID": kingdom_id, "LID": commander_id, "WT": 0, "HBW": horse_booster_id (-1 when PTT is set), "BPC": use_premium_commander, "PTT": feathers, "SD": slowdown, "A": [[wod_id, amount], ...] }

Fields follow the client's key order: the constructor initialises SX to SD and sets A after them. The source is named by its map position, not its id. KID is the target's kingdom, and the source must sit in the same one. A lists the units, then the tools. The client always sends WT 0: only a support sets a wait time. A support (cds) to your own area is refused with NO_SELF_DESTRUCTION (92); this is the command the client uses for it.

Client: C2SCreateArmyTravelMovementVO (bundle line 99772), built by CastleTroopSupportData.sendTroops (bundle line 38420) from CastlePostAttackDialog.sendTroops (bundle line 38373); the army from CastleTroopSupportVO.getArmy (bundle line 99737)

Fields:

source_x pydantic-field

source_x: int

Wire key SX

Map x of the area the troops leave from

source_y pydantic-field

source_y: int

Wire key SY

Map y of the area the troops leave from

target_x pydantic-field

target_x: int

Wire key TX

Map x of the area they go to

target_y pydantic-field

target_y: int

Wire key TY

Map y of the area they go to

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The kingdom both areas sit in

commander_id pydantic-field

commander_id: int

Wire key LID

A Commander.commander_id from client.commanders.get_commanders(); 0 is the free starting commander, -14 the premium one

wait_time pydantic-field

wait_time: int = 0

Wire key WT

Wait time at the target

horse_booster_id pydantic-field

horse_booster_id: int = -1

Wire key HBW

The horse booster's wod id, -1 for none or when paid with feathers

use_premium_commander pydantic-field

use_premium_commander: int = 0

Wire key BPC

1 when the premium commander leads

feathers pydantic-field

feathers: int = 0

Wire key PTT

1 when the horse is paid with feathers

slowdown pydantic-field

slowdown: int = 0

Wire key SD

Seconds the arrival is delayed by

units pydantic-field

units: WodAmountSlots

Wire key A

The units, then any tools, one pair per filled slot

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a cat reply is the movement this sent: its target area (A.M.TA) is TX/TY.

Client: CATCommand (bundle line 125924) reads the new movement from A, whose TA is the target area (BasicMapmovementVO). An army heading home targets its source and is not taken.

SendTroopsResponse pydantic-model

Bases: BaseResponse

The troops on their way.

Command: cat Payload: {"O": [owner, ...], "A": movement, "gcu": {...}}

O (the owners involved) and A (the new movement) are kept as sent.

Client: CATCommand.executeCommand (bundle line 125924)

Fields:

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

GetTravelInfoRequest pydantic-model

Bases: BaseRequest

Ask for the pre-calculation of troops sent from one of your areas to another of your own.

Command: sti Payload: {"SX": source_x, "SY": source_y, "TX": target_x, "TY": target_y, "KID": kingdom_id}

Keys follow the client's order: the constructor initialises SX, SY, TX, TY and KID, though it takes the target first. KID is the target's kingdom.

Client: C2STroopSupportInfoVO (bundle line 72087), sent by CastleStartAttackDialog (bundle line 14831)

Fields:

source_x pydantic-field

source_x: int

Wire key SX

Map x of the area the troops would leave from

source_y pydantic-field

source_y: int

Wire key SY

Map y of the area the troops would leave from

target_x pydantic-field

target_x: int

Wire key TX

Map x of the area they would go to

target_y pydantic-field

target_y: int

Wire key TY

Map y of the area they would go to

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The kingdom both areas sit in

TravelTargetArea pydantic-model

Bases: BasePayload

The target's map row and its owner, the gaa block of an sti reply.

Unlike an attack pre-calculation's, OI is one owner record, not a list: the client reads it with parseOwnerInfo, which takes no record without an OID.

Client: CastleTroopSupportVO.fillFromParamObject (bundle line 99736), CastleOtherPlayerData.parseOwnerInfo (bundle line 138996)

Fields:

Validators:

  • _owner_record → owner

area pydantic-field

area: TargetRow = None

Wire key AI

The target's map row

owner pydantic-field

owner: MapObject | None = None

Wire key OI

The target's owner; None when not named

TravelUnits pydantic-model

Bases: BasePayload

Your units and tools at the source, the gui block of an sti reply.

Client: CastleTroopSupportVO.fillFromParamObject (bundle line 99736) reads I into a UnitInventoryDictionary and SHI into a StrongholdUnitInventory

Fields:

units pydantic-field

units: WodAmounts

Wire key I

Units and tools at the source

stronghold pydantic-field

stronghold: WodAmounts

Wire key SHI

Units stored in its stronghold

GetTravelInfoResponse pydantic-model

Bases: BaseResponse

The pre-calculation of troops sent between your own areas.

Command: sti Payload:

{"SCID": source_castle_id, "KID": ..,
 "gaa": {"AI": [target map row], "OI": {owner record}},
 "gui": {"I": [[wod_id, count], ...], "SHI": [[wod_id, count], ...]},
 "AE": [[effect_id, [value], source_tag], ...],
 "gli": {"C": [...], "B": [...]}}

gli (your commanders) updates client.state; AE, the area effects on the movement, is read as an attack pre-calculation's is. The reply carries no travel time or cost: the client works them out from the distance and the units picked. A source that is not one of your castles is refused with NOT_IN_OWNED_CASTLE.

Client: STICommand.executeCommand (bundle line 129099), which passes gli to CastleLordData.parse_GLI; CastleTroopSupportData.parse_STI (bundle line 38417); CastleTroopSupportVO.fillFromParamObject (bundle line 99736), whose base CastleFightScreenVO.fillFromParamObject reads AE (bundle line 30501)

Fields:

source_castle_id pydantic-field

source_castle_id: int = 0

Wire key SCID

The castle the troops would leave from

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The source castle's kingdom

target_area pydantic-field

target_area: TravelTargetArea

Wire key gaa

The target's map row and owner

units pydantic-field

units: TravelUnits

Wire key gui

Your units and tools at the source

area_effects pydantic-field

area_effects: CommanderEffects

Wire key AE

Area effects on the movement

castle.models.tax

Tax collection.

Commands:

  • txi: Read the tax collection status
  • txs: Start a tax collection
  • txc: Collect the tax

Classes:

Name Description
TaxInfo

The tax collection status, the TX of a txi block.

GetTaxInfoRequest

Read the tax collection status.

TaxInfoResponse

The tax collection status: the txi reply, also nested in txs and txc.

StartTaxRequest

Start a tax collection of one tax type.

StartTaxResponse

The tax status after starting a collection.

CollectTaxRequest

Collect the tax, also cutting a running collection short.

CollectTaxResponse

The collected tax.

Attributes:

Name Type Description
TAX_DURATIONS tuple[int, ...]

Seconds each tax type collects for, by tax type 0 to 6.

TAX_RUBY_COSTS tuple[int, ...]

Rubies each tax type costs to start before any waiver, by tax type.

TAX_DURATIONS module-attribute

TAX_DURATIONS: tuple[int, ...] = (600, 1800, 5400, 10800, 21600, 43200, 86400)

Seconds each tax type collects for, by tax type 0 to 6.

Client: TaxConst.COLLECTOR_DURATION (dll line 19772)

TAX_RUBY_COSTS module-attribute

TAX_RUBY_COSTS: tuple[int, ...] = (0, 0, 0, 0, 0, 125, 300)

Rubies each tax type costs to start before any waiver, by tax type.

Types 5 and 6 cost rubies unless a premium account, a VIP level or the tax research waives it; types 1 to 4 cost a tenth of their income in coins and type 0 is free.

Client: TaxConst.START_COST_C2, START_COST_C1_PERC, getCollectorC2Costs (dll lines 19773-19779)

TaxInfo pydantic-model

Bases: BasePayload

The tax collection status, the TX of a txi block.

Client: TaxInfoVO.fillFromParamObject (bundle line 29538)

Fields:

Validators:

tax_type pydantic-field

tax_type: int = 0

Wire key TT

The running or finished collection's tax type, 0 to 6; -1 for none

remaining_seconds pydantic-field

remaining_seconds: int = 0

Wire key RT

Seconds until the collection is done, as of the reply

expected_income pydantic-field

expected_income: int = 0

Wire key EM

Coins the collection brings before boosts

population pydantic-field

population: int = 0

Wire key PO

The population the income is based on

is_boosted pydantic-field

is_boosted: bool = False

Wire key IB

Whether the collector was bribed

vip_bonus pydantic-field

vip_bonus: int = 0

Wire key VB

The VIP tax bonus in percent

status property

status: TaxStatus

Whether a collection runs or waits to be collected, as of the reply.

The client keeps the end time, the reply's time plus RT, and counts down to it: a collection is collecting while the remaining time is 0 or more, so one with RT 0 is still collecting as of the reply, and ready to collect once the end time has passed.

Client: TaxInfoVO.taxState and remainingCollectionTimeInSeconds (bundle lines 29542, 29544), fillFromParamObject (bundle line 29540)

duration_seconds property

duration_seconds: int | None

How long this tax type collects for; None for no or an unknown tax type.

GetTaxInfoRequest pydantic-model

Bases: BaseRequest

Read the tax collection status.

Command: txi Payload: {}

Client: C2SGetTaxInfoVO (bundle line 37186), sent by CastleCollectTaxStartDialog.showLoaded (bundle line 52962) and CastleCollectTaxDialog.showLoaded (bundle line 91005)

TaxInfoResponse pydantic-model

Bases: BaseResponse

The tax collection status: the txi reply, also nested in txs and txc.

Command: txi Payload: {"TX": {"TT": .., "RT": .., "EM": .., "PO": .., "IB": .., "VB": ..}}

Client: TXICommand.executeCommand (bundle line 128792), CastleTaxData.parse_TXI (bundle line 112797)

Fields:

tax pydantic-field

tax: TaxInfo

Wire key TX

The tax collection status

StartTaxRequest pydantic-model

Bases: BaseRequest

Start a tax collection of one tax type.

Command: txs Payload: {"TT": tax_type, "TX": 3}

Client: C2SStartCollectTaxVO (bundle line 91088), sent by CastleCollectTaxElement.onStartTaxCollection (bundle line 91069)

Fields:

tax_type pydantic-field

tax_type: int

Wire key TT

The tax type, 0 to 6, an index into TAX_DURATIONS

tx pydantic-field

tx: int = 3

Wire key TX

The client's TX value, always 3

StartTaxResponse pydantic-model

Bases: BaseResponse

The tax status after starting a collection.

Command: txs Payload: {"gcu": {...}, "txi": {"TX": {...}}}

Client: TXSCommand.executeCommand (bundle line 128807)

Fields:

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after the start

tax_info pydantic-field

tax_info: TaxInfoResponse | None = None

Wire key txi

The tax status after the start; None when the reply has none

CollectTaxRequest pydantic-model

Bases: BaseRequest

Collect the tax, also cutting a running collection short.

Command: txc Payload: {"TR": 29}

Client: C2SCollectTaxVO (bundle line 41083), sent by IsoServerCommands.collectTax (bundle line 63777) and CastleCollectTaxDialog.collectTax (bundle line 91027)

Fields:

tr pydantic-field

tr: int = 29

Wire key TR

The client's TR value, always 29

CollectTaxResponse pydantic-model

Bases: BaseResponse

The collected tax.

Command: txc Payload: {"gcu": {...}, "txi": {"TX": {...}}, "CT": coins}

Client: TXCCommand.executeCommand (bundle line 128747)

Fields:

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after collecting

tax_info pydantic-field

tax_info: TaxInfoResponse | None = None

Wire key txi

The tax status after collecting; None when the reply has none

collected pydantic-field

collected: int = 0

Wire key CT

Coins collected

castle.models.transfers

Moving units and goods to another kingdom.

Commands:

  • kut: Transfer units from a castle to another kingdom
  • kgt: Transfer goods from a castle to another kingdom
  • msk: Shorten a transfer to another kingdom with a minute skip

Classes:

Name Description
KingdomUnitTransferRequest

Send units from one of your castles to another kingdom.

KingdomUnitTransferResponse

The transfer's result.

KingdomGoodsTransferRequest

Send goods from one of your castles to your castle in another kingdom.

KingdomGoodsTransferResponse

The transfer's result.

MinuteSkipKingdomTransferRequest

Shorten the units or goods on their way to a kingdom with a minute skip.

MinuteSkipKingdomTransferResponse

The transfers after a minute skip.

KingdomUnitTransferRequest pydantic-model

Bases: BaseRequest

Send units from one of your castles to another kingdom.

Command: kut Payload: {"SCID": source_castle_id, "SKID": source_kingdom, "TKID": target_kingdom, "CID": target_castle_id, "A": [[wod_id, amount], ...]}

Keys follow the client's order: the constructor initialises SCID, SKID, TKID and CID before it sets A. CID is the picked target castle's object id, -1 when none is picked.

Client: C2SKingdomUnitTransferVO (bundle line 95305), built by CastleTransferTroopsToKingdomProperties.getUnitTransferCommand (bundle line 37448) with the units as getAsWodAmountArray pairs

Fields:

source_castle_id pydantic-field

source_castle_id: int

Wire key SCID

One of your castles, CastleInfo.castle_id from client.castle.get_all() or Castle.id from client.state.get_castles()

source_kingdom_id pydantic-field

source_kingdom_id: Kingdom = Kingdom.GREEN

Wire key SKID

The source castle's kingdom

target_kingdom_id pydantic-field

target_kingdom_id: Kingdom

Wire key TKID

The kingdom to send the units to

target_castle_id pydantic-field

target_castle_id: int = -1

Wire key CID

Object id of a picked target castle, -1 for none

units pydantic-field

units: WodAmountSlots

Wire key A

The units, one pair per unit

KingdomUnitTransferResponse pydantic-model

Bases: BaseResponse

The transfer's result.

Command: kut Payload: {"gcu": {...}, "gui": {...}, "kpi": {...}}

gui (the source castle's units after) is kept as sent; kpi updates client.state.get_kingdoms().

Client: KUTCommand.executeCommand (bundle line 124732)

Fields:

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

KingdomGoodsTransferRequest pydantic-model

Bases: BaseRequest

Send goods from one of your castles to your castle in another kingdom.

Command: kgt Payload: {"SCID": source_castle_id, "SKID": source_kingdom, "TKID": target_kingdom, "G": [[resource_key, amount], ...]}

Keys follow the client's order: the constructor initialises SCID, SKID and TKID before it sets G. G is built as the market's is, a repeated resource merged into one entry.

Client: C2SKingdomGoodsTransferVO (bundle line 98402), built by CastleTransferResToKingdomProperties.getSendResourcesCommand (bundle line 55548) from CastleTransferResourcesDialog.sendResources (bundle line 38082), which builds G with createCostsListForServer (bundle line 38086)

Fields:

source_castle_id pydantic-field

source_castle_id: int

Wire key SCID

One of your castles, CastleInfo.castle_id from client.castle.get_all() or Castle.id from client.state.get_castles()

source_kingdom_id pydantic-field

source_kingdom_id: Kingdom = Kingdom.GREEN

Wire key SKID

The source castle's kingdom

target_kingdom_id pydantic-field

target_kingdom_id: Kingdom

Wire key TKID

The kingdom to send the goods to

goods pydantic-field

Wire key G

The amount of each resource to send, sent as [key, amount] pairs such as ['W', 1000]

KingdomGoodsTransferResponse pydantic-model

Bases: BaseResponse

The transfer's result.

Command: kgt Payload: {"gcu": {...}, "grc": {...}, "kpi": {...}}

gcu and kpi update client.state (client.state.get_kingdoms() lists the goods on their way); grc is the joined castle's resources after, kept here.

Client: KGTCommand.executeCommand (bundle line 124658)

Fields:

Validators:

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The joined castle's resources after

MinuteSkipKingdomTransferRequest pydantic-model

Bases: BaseRequest

Shorten the units or goods on their way to a kingdom with a minute skip.

Command: msk Payload: {"MST": minute_skip, "KID": "kingdom_id", "TT": "transfer_type"}

Keys follow the client's order. KID is the kingdom the transfer goes to; the client sends KID and TT through toString().

Client: C2SMinuteSkipKingdomTransferVO (bundle line 54609), built by KingdomUnitsTravelMinuteSkipProperties.getMinuteSkipCommand (bundle line 37497) and KingdomGoodsTravelMinuteSkipProperties.getMinuteSkipCommand (bundle line 55567); CastleMinuteSkipDialog.onScrollItemClick passes the currency's jsonKey (bundle line 7722)

Fields:

minute_skip pydantic-field

minute_skip: EnumOrStr['Currency']

Wire key MST

The minute skip used, Currency.SKIP_1_MINUTE to SKIP_24_HOURS; sent as its key

kingdom_id pydantic-field

kingdom_id: Kingdom

Wire key KID

The kingdom the transfer goes to

transfer_type pydantic-field

transfer_type: KingdomTransferType

Wire key TT

Whether the transfer carries units or goods

MinuteSkipKingdomTransferResponse pydantic-model

Bases: BaseResponse

The transfers after a minute skip.

Command: msk Payload: {"kpi": {...}}

kpi updates client.state.get_kingdoms().

Client: MSKCommand.executeCommand (bundle line 125799)

Fields:

castle.models.updates

Castle pushes: units received, building XP, changed buildings, slum level and area booster.

Commands (all pushed by the server):

  • rue: Units received in a castle
  • fbe: A building finished
  • cbx: A building gave XP
  • gdb: Damaged buildings of the joined castle
  • gcb: Buildings of the joined castle whose efficiency changed
  • csl: The joined castle's slum level
  • gab: The joined castle's area booster

Classes:

Name Description
UnitsReceived

Units that finished and arrived in one of your castles: the castle's new count of that unit.

BuildingXP

XP a building in the joined castle gave.

BuildingFinished

A building in the joined castle finished, and the XP it gave.

DamagedBuildings

Buildings of the joined castle that were damaged, as their rows are now.

BuildingEfficiencyChanged

Buildings of the joined castle whose efficiency changed, as their rows are now.

SlumLevel

The joined castle's slum level.

AreaBooster

The joined castle's builder discount.

UnitsReceived pydantic-model

Bases: BaseResponse

Units that finished and arrived in one of your castles: the castle's new count of that unit.

Command: rue Payload: {"AID": castle_id, "SID": kingdom_id, "WID": wod_id, "NUA": amount}

Client: RUECommand.executeCommand (bundle line 125646), CastleUserCastleListDetailed.parse_rue (bundle line 140918), CastleMilitaryData.parse_rue (bundle line 138834)

Fields:

castle_id pydantic-field

castle_id: ClientInt = 0

Wire key AID

The castle's object id

kingdom_id pydantic-field

kingdom_id: ClientInt = 0

Wire key SID

The castle's kingdom

wod_id pydantic-field

wod_id: UnitOrTool = 0

Wire key WID

The unit or tool

amount pydantic-field

amount: ClientInt = 0

Wire key NUA

How many of the unit the castle now holds

BuildingXP pydantic-model

Bases: BaseResponse

XP a building in the joined castle gave.

Command: cbx Payload: {"OID": object_id, "XP": xp}

Client: CBXCommand.executeCommand (bundle line 123345), AreaDataUpdater.parseCBX (bundle line 131521), IsoUpdaterData.onGainedBuildingPoints (bundle line 130308)

Fields:

object_id pydantic-field

object_id: int = -1

Wire key OID

The building's object id

xp pydantic-field

xp: int | float = 0

Wire key XP

The XP gained

BuildingFinished pydantic-model

Bases: BuildingXP

A building in the joined castle finished, and the XP it gave.

Command: fbe Payload: {"OID": object_id, "XP": xp}

Client: FBECommand.exec (bundle line 122879), which reads it as cbx

Fields:

DamagedBuildings pydantic-model

Bases: BaseResponse

Buildings of the joined castle that were damaged, as their rows are now.

Command: gdb Payload: {"B": [row, ...]}

Client: GDBCommand.executeCommand (bundle line 122998), AreaDataUpdater.parseGDB (bundle line 131510), IsoUpdaterData.updateMultipleObjectInfos (bundle line 130259)

Fields:

Validators:

buildings pydantic-field

buildings: list[BuildingRow]

Wire key B

The changed buildings

BuildingEfficiencyChanged pydantic-model

Bases: DamagedBuildings

Buildings of the joined castle whose efficiency changed, as their rows are now.

Command: gcb Payload: {"B": [row, ...]}

Client: GCBCommand.executeCommand (bundle line 122968), AreaDataUpdater.parseGCB (bundle line 131509)

Fields:

Validators:

SlumLevel pydantic-model

Bases: BaseResponse

The joined castle's slum level.

Command: csl Payload: {"SL": level}

Client: CSLCommand.executeCommand (bundle line 122715), AreaDataSlum.parseCSL (bundle line 131305)

Fields:

level pydantic-field

level: int = -1

Wire key SL

The slum level, -1 for none

AreaBooster pydantic-model

Bases: BaseResponse

The joined castle's builder discount.

Command: gab Payload: {"B": discount}

Client: GABCommand.executeCommand (bundle line 122923), AreaDataCommonInfo.parseGAB (bundle line 130992)

Fields:

builder_discount pydantic-field

builder_discount: int | float = 0

Wire key B

The builder discount

castle.models.upkeep

Pushes that keep the castle screen current: construction item expiry and the resource citizen.

Commands:

  • nec: when the next construction item expires, a login section of gbd and a push
  • irc: the resource the citizen in your castle carries, a push

Classes:

Name Description
ConstructionItemExpiryResponse

When the next of your construction items expires, and when one last expired.

ResourcePoolResponse

The resource the citizen walking in your castle carries, to collect by clicking it.

ConstructionItemExpiryResponse pydantic-model

Bases: TimedResponse

When the next of your construction items expires, and when one last expired.

The push's CI (the joined castle's construction items) is not read here.

Command: nec, as a login section of gbd and a push.

Client: NECCommand (bundle line 123294), ConstructionItemData.parse_NEC and hasNewExpiredItems (bundle lines 22976, 22992)

Fields:

next_expiry_seconds pydantic-field

next_expiry_seconds: ClientNumber = -1

Wire key NCRS

Seconds until the next construction item expires when the values were read, -1 for none

last_expired_at pydantic-field

last_expired_at: ClientNumber = -1

Wire key LECT

When a construction item last expired, the server's timestamp; -1 none

remaining_seconds

remaining_seconds(now: float | None = None) -> float | None

Seconds until the next construction item expires, 0 at least; None when none will.

ResourcePoolResponse pydantic-model

Bases: BaseResponse

The resource the citizen walking in your castle carries, to collect by clicking it.

Command: irc, a push.

Client: IRCCommand (bundle line 123090), CastleResourcePoolData.parseIRC (bundle line 139623), which keeps the first collectable of G and sets its amount to 1000 when EG is set. The client drops it when you join a castle (JAACommand, bundle line 130194), as client.state does, and also when the citizen carrying it is collected or leaves (registerNewMovementAsOwner, bundle line 139626), which the state does not follow, so it can outlast the citizen until the next irc.

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

goods pydantic-field

goods: CollectableRows = ()

Wire key G

The goods the citizen carries

has_extra_goods pydantic-field

has_extra_goods: bool = False

Wire key EG

Whether the client counts the goods as 1000, whatever G says

castle.models.wishing_well

The ruby wishing well.

Commands:

  • rww: the wishing well's level and time, a login section of gbd and a reply

Classes:

Name Description
WishingWellResponse

The ruby wishing well: its level and whether it can be started, runs, or waits to be collected.

WishingWellResponse pydantic-model

Bases: TimedResponse

The ruby wishing well: its level and whether it can be started, runs, or waits to be collected.

Command: rww, as a login section of gbd and a successful reply; a reply refused for lack of resources is not read.

Client: RWWCommand (bundle line 126903), CastleRubyWishingWellData.parse_RWW (bundle line 21255), isReadyToStart, isReadyToCollect, isRunning and getRemainingSecondsCalculated (bundle lines 21248-21262)

Fields:

level pydantic-field

level: ClientInt = -1

Wire key L

The wishing well's level, -1 for none

seconds pydantic-field

seconds: ClientNumber = -1

Wire key RT

Seconds left when the values were read: -1 ready to start, 0 ready to collect

is_ready_to_start property

is_ready_to_start: bool

Whether the wishing well can be started, as last read.

is_ready_to_collect property

is_ready_to_collect: bool

Whether the wishing well waits to be collected, as last read.

is_running property

is_running: bool

Whether the wishing well runs, as last read.

remaining_seconds

remaining_seconds(now: float | None = None) -> int

Whole seconds until it is done, 0 at least.