Skip to content

Attack

Attack info, presets and sending attacks behind client.attack.

attack.service

Sending attacks, filling their waves the way the game's "Fill waves" button does, and the saved attack presets.

Classes:

Name Description
AttackService

Send attacks, fill their waves, and read the attack pre-calculation.

Attributes:

Name Type Description
logger

logger module-attribute

logger = logging.getLogger(__name__)

AttackService

AttackService(client: 'EmpireClient')

Bases: BaseService

Send attacks, fill their waves, and read the attack pre-calculation.

Reached as client.attack.

Methods:

Name Description
send_attack

Send an attack from a castle to a target position.

get_attack_info

Get the attack pre-calculation for a target.

get_presets

Get your unlocked attack preset slots.

save_preset

Save an army into an unlocked preset slot.

rename_preset

Rename a preset slot.

fill_waves

Build the waves for an attack from a castle's inventory.

read_inventory

What a castle can send, as a pool the fill methods draw from.

fill_attack

Build a complete attack: every wave, plus the courtyard wave.

send_attack

send_attack(source_x: int, source_y: int, target_x: int, target_y: int, waves: list[AttackWave], commander_id: int, kingdom_id: Kingdom | None = None, attack_type: AttackType = ATTACK, wait_time: int = 0, horse_booster_id: int = -1, feathers: bool = False, use_premium_commander: bool = False, share_battle_view: bool = False, loot_priority: LootPriority = NO, slowdown: int = 0, yard_wave: WodAmountMapping | Sequence[WodAmount] | None = None, capacity: WaveCapacity | None = None, yard_capacity: int | None = None, support_tools: Sequence[Tool | int | None] | None = None, collector_booster: Mapping[CurrencyId, int] | Mapping[int, int] | None = None, send_anyway: bool = False, min_soldiers: int | None = None, timeout: float = 5.0, *, spend_rubies: bool = False) -> bool

Send an attack from a castle to a target position.

Waves without units are dropped, matching the game client. Setting feathers forces the horse field to -1, again as the client does.

commander_id has no default on purpose. Every id the gli C list reports is a real commander, 0 included -- it is the free starting one, and a live send with LID=0 comes back with that commander under AAM.UM.L. The server validates the id before it looks at the army: an id outside the list is INVALID_LORD_ID (219), and a castellan already posted to a castle is LORD_IS_USED (256). -14 is the premium commander (TravelConst.COMMANDER_PREMIUM); the client sends it with BPC 1 (use_premium_commander), which uses a free premium commander or costs rubies, so it is never a default. Led by it, the attack 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.

Known gap: for a conquer attack (AttackType.CONQUER, which CastleAttackData.sendAttack, bundle line 133852, sends exactly when isConquerAttack, bundle line 30550) the client sends BPC 0 without asking for rubies, whichever commander leads, unless the target is a capital, village, kings tower, resource isle, monument or laboratory (CastlePostAttackDialog.startAttack, bundle line 38360). The target's area type is not known here, so such an attack is checked like any other; use_premium_commander=False with spend_rubies=True sends what the client sends there.

Parameters:

Name Type Description Default
source_x int

Source absolute X coordinate

required
source_y int

Source absolute Y coordinate

required
target_x int

Target absolute X coordinate

required
target_y int

Target absolute Y coordinate

required
waves list[AttackWave]

Attack waves, front to back

required
kingdom_id Kingdom | None

The source area's kingdom; read from your castle list by the source position when not given

None
commander_id int

Commander to lead the attack, a Commander.commander_id from client.commanders.get_commanders()

required
attack_type AttackType

See AttackType (default: a normal attack)

ATTACK
wait_time int

Wait time before the troops return

0
horse_booster_id int

Horse type for the speed bonus (-1 = none)

-1
feathers bool

Use feathers for the speed boost

False
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
share_battle_view bool

Let others watch the battle

False
loot_priority LootPriority

Resource to loot first (CombatConst.LOOT_PRIO_*); the client offers the choice from player level 20

NO
slowdown int

Seconds the arrival is delayed by; costs rubies

0
yard_wave WodAmountMapping | Sequence[WodAmount] | None

The courtyard wave, one slot per entry: FilledAttack.yard, or {Unit.X: 100}

None
capacity WaveCapacity | None

The capacities these waves were sized against. Given one, an overfull army, or one below the minimum for its level, is refused here rather than by the server

None
yard_capacity int | None

The courtyard's capacity, checked the same way

None
support_tools Sequence[Tool | int | None] | None

The support tools, one per slot, None (or -1) for an empty one

None
collector_booster Mapping[CurrencyId, int] | Mapping[int, int] | None

Collector event boosters by currency and amount, e.g. {CurrencyId.SAMURAI_MEDAL_BOOSTER: 5}

None
send_anyway bool

Send although one of your attacks is already on its way there (FC 1), as the client's confirmation dialog does

False
min_soldiers int | None

The fewest units the waves must carry together, such as FilledAttack.min_soldiers or combat.min_attack_soldiers(...); taken from capacity when not given. Without either nothing is checked

None
timeout float

Timeout in seconds

5.0
spend_rubies bool

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

False

Returns:

Type Description
bool

True when the server accepted the attack, False when it rejected it,

bool

such as with MOVEMENT_HAS_NO_UNITS (100) for too few units

Raises:

Type Description
AttackInProgressError

One of your attacks is already on its way there; it carries that attack's arrival time and size. Retry with send_anyway=True to send regardless

AttackBelowMinimumError

The waves carry fewer units than min_soldiers, or than the minimum at capacity's level; the client refuses such an attack

PremiumCommanderCostError

The premium commander leads, may cost 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

ValueError

No wave carries any units, a container is overfull, or the horse or slowdown costs rubies and spend_rubies is False

UnknownCastleError

No kingdom_id given and no area of yours in the castle list is at the source position

AmbiguousCastleError

No kingdom_id given and areas of yours sit at the source position in several kingdoms

EmpireTimeoutError / ConnectionClosedError / NetworkError

transport failures

Client: CastleAttackData.sendAttack (bundle line 133852) sends the source area's kingdomID as KID

get_attack_info

get_attack_info(target_x: int, target_y: int, source_x: int, source_y: int, kingdom_id: Kingdom = GREEN, *, area_type: MapItemType = CASTLE, conquer: bool = False, timeout: float = 10.0) -> AttackInfoResponse

Get the attack pre-calculation for a target.

This is what the game's own attack dialog asks for: the target's map row, the attacker's inventory and commanders, and the attacker's effects already scoped to this target. Each kind of target answers its own command, picked from area_type as the client does.

Parameters:

Name Type Description Default
target_x int

Target X coordinate

required
target_y int

Target Y coordinate

required
source_x int

Attacking castle's X coordinate

required
source_y int

Attacking castle's Y coordinate

required
kingdom_id Kingdom

Kingdom both sit in

GREEN
area_type MapItemType

The target's area type, the first field of its map row

CASTLE
conquer bool

Ask for the conquest pre-calculation instead, for an outpost, capital or metropolis

False
timeout float

Timeout in seconds

10.0

Raises:

Type Description
ValueError

The client has no pre-calculation this library models for that area type

CommandError

The server rejected the request

get_presets

get_presets(timeout: float = 5.0) -> list[AttackPreset]

Get your unlocked attack preset slots.

Example
for preset in client.attack.get_presets():
    army = preset.army()
    print(preset.index, preset.name, army.to_wave() if army else None)

Raises:

Type Description
CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

Client: C2SGetPreDefinedAttackSetupVO (bundle line 141803), sent by FightPresetData.loadDataFromServer (bundle line 141779)

save_preset

save_preset(index: int, army: PresetArmy | AttackWave, timeout: float = 5.0) -> bool

Save an army into an unlocked preset slot.

A wave is saved as the client saves one: its filled slots, without support tools.

Parameters:

Name Type Description Default
index int

The preset slot, an AttackPreset.index from get_presets

required
army PresetArmy | AttackWave

The army, a PresetArmy or a wave

required
timeout float

Timeout in seconds

5.0

Returns:

Type Description
bool

True when the server accepted it, False when it refused it.

Client: C2SUpdatePreDefinedAttackSetupVO (bundle line 141820), sent by FightPresetData.savePresetArmy (bundle line 141780) from AttackDialogPresets.handleSavePresetRequested (bundle line 101661), which saves only an unlocked slot

rename_preset

rename_preset(index: int, name: str, timeout: float = 5.0) -> bool

Rename a preset slot.

Parameters:

Name Type Description Default
index int

The preset slot, an AttackPreset.index from get_presets

required
name str

The new name, at most 15 characters, none of SMARTFOX_INVALID_CHARS

required
timeout float

Timeout in seconds

5.0

Returns:

Type Description
bool

True when the server accepted it, False when it refused it.

Raises:

Type Description
ValueError

The client's rename dialog would refuse the name

Client: C2SUpdatePresetNameVO (bundle line 141794), sent by RenameFightPresetDialog.sendCommand (bundle line 45609) after its validate (bundle line 45606) and its 15-character limit (bundle line 45597)

fill_waves

fill_waves(castle_id: int, *, level: int | None = None, camp_victories: int | None = None, camp_kingdom_id: Kingdom = GREEN, space_id: int | None = None, area_type: MapItemType | int | None = None, landmark_min_level: int = 0, area_bonuses: list[Bonus] | None = None, inventory: Inventory | None = None, player_target: bool | None = None, defense: dict[Flank, DefenderFlankEffects] | None = None, attacker: AttackerFlankEffects | None = None, commander: Commander | None = None, conquer: bool = False, wave_bonus: int = 0, general_skill_ids: Sequence[int] | None = None, legend_skill_ids: Sequence[int] | None = None, global_effects: Iterable[GlobalEffectTimer] | None = None, support_tools: Sequence[Tool | int | None] | None = None, target_is_player: bool = False, owner_id: int | None = None, owner_legend_level: int = 0, attacker_legend_level: int | None = None, under_conquer_control: bool = False, active_raid_boss_id: int | None = None, flank_bonus_percent: float = 0.0, front_bonus_percent: float = 0.0, tool_bonus: float = 0.0, options: FillOptions | None = None, timeout: float = 5.0) -> list[AttackWave]

Build the waves for an attack from a castle's inventory.

Sizes itself the way the game's auto-fill does: the number of waves, each flank's capacity and its unlocked slots all follow from the attacker's level, and each slot takes the stack that best counters whichever of the target's defenses is proportionally weaker.

Only units are placed. The game's button also fills tool slots, which needs the tool effect tables resolved, so waves from here carry no siege tools yet.

Client: AttackDialogWaveHandler.initWaves and updateMaxUnitCount (bundle lines 102525-102541), CastleAttackArmyVO.init (55831) and the CastleAttackWaveVO constructor (99927).

Parameters:

Name Type Description Default
castle_id int

Castle whose troops to draw from, one of yours: a Castle.id from client.state.get_castles()

required
level int | None

The target owner's level, which is what sizes a wave

None
camp_victories int | None

An NPC camp's victory count, to derive its defense from the game data - see MapAreaItem.victory_count

None
camp_kingdom_id Kingdom

Kingdom the camp sits in

GREEN
space_id int | None

Kingdom the target sits in, which some tools are limited to; the camp's kingdom when not given

None
landmark_min_level int

A capital's or metropolis's own defense level, which the client reads from its landmark at runtime

0
area_bonuses list[Bonus] | None

The aci AE list, from get_attack_info(...).attacker_bonuses(). With entries, it replaces the commander's own AE (see commander_bonuses)

None
inventory Inventory | None

Troops to draw from, read from the castle when not given. The waves deduct what they take, so a caller filling more than one thing from one pool passes the same object each time

None
area_type MapItemType | int | None

The target's area type, which scopes the general's effects; NPC camps are area type 2

None
player_target bool | None

True when attacking a player, False for an NPC. Decides which PvP- or PvE-only effects count; when not given it follows owner_id (getFilterStrategyAttackOrDefence)

None
defense dict[Flank, DefenderFlankEffects] | None

Explicit per-flank defense, overriding camp_victories

None
attacker AttackerFlankEffects | None

Attacker multipliers; built from commander, the legend skills and support_tools when omitted

None
commander Commander | None

The commander leading the attack, whose equipment and effects supply the attack multipliers used to score units

None
conquer bool

A conquest attack carries extra waves

False
wave_bonus int

Extra waves on top of the legend skill, effect type 156 and the support tools

0
support_tools Sequence[Tool | int | None] | None

The support tools the attack will carry, as sent in AST: one wod id per slot, -1 for an empty one. They buff every flank and can add waves

None
general_skill_ids Sequence[int] | None

Unlocked skill ids of the general leading the attack, from gie; its unit-limit skills size the wave

None
legend_skill_ids Sequence[int] | None

The player's unlocked legend skills, from skl or GameData.require().legend_skill(tree, group, level). Which of them count follows the three rules of LegendaryFight

None
target_is_player bool

True when the target belongs to a player

False
owner_id int | None

The target owner's player id, see owner_id_from_row. An alien invasion camp's is known from its area type. Without one, no legend skill counts

None
owner_legend_level int

The target owner's legend level, the LL of its owner record in a map scan

0
attacker_legend_level int | None

The attacker's legend level; the local player's when not given

None
under_conquer_control bool

True when the target is held under conquer control; the legend rules then rate its owner by level rather than by the area's minimum owner level

False
active_raid_boss_id int | None

The boss of the alliance raid-boss event running now, None when none is; GameData.require().raid_boss(name) finds one by name. Tools tied to other raid bosses are left out

None
global_effects Iterable[GlobalEffectTimer] | None

The global effects running, as the timers of GlobalEffectEvent.effects; the Event.GLOBAL_EFFECT event's in state when not given. A timer that has ended counts for nothing. These are the only thing that buffs a unit's attack value. The booster's boost to the ones bie lists is read from state

None
flank_bonus_percent float

Extra flank bonus, added to whatever the general contributes

0.0
front_bonus_percent float

Extra middle bonus, added the same way

0.0
tool_bonus float

Extra flank tool capacity

0.0
options FillOptions | None

Which flanks to fill and which units to allow

None
timeout float

Timeout for the inventory request

5.0

Returns:

Type Description
list[AttackWave]

One wave per filled wave, ready to pass to send_attack

Raises:

Type Description
GameDataNotLoadedError

the game data cannot be loaded

ValueError

No level was given and none is known for the player

UnknownCastleError

No inventory given and castle_id is not in your castle list

AmbiguousCastleError

No inventory given and castle_id repeats across your kingdoms

read_inventory

read_inventory(castle_id: int, *, timeout: float = 5.0) -> Inventory

What a castle can send, as a pool the fill methods draw from.

Tools belong in it as well as units: the flanks place them. Boost items are tools by their slot types but no strategy picks them, and the soldier pass ignores anything that is not a unit.

Parameters:

Name Type Description Default
castle_id int

Castle whose troops to read, one of yours: a Castle.id from client.state.get_castles()

required
timeout float

Timeout in seconds

5.0

Returns:

Type Description
Inventory

An Inventory the fill methods deduct from as they place

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

fill_attack

fill_attack(castle_id: int, *, target_x: int | None = None, target_y: int | None = None, kingdom_id: Kingdom | None = None, source_x: int | None = None, source_y: int | None = None, target_level: int | None = None, target_is_player: bool = False, target_owner_id: int | None = None, target_owner_legend_level: int | None = None, camp_victories: int | None = None, camp_kingdom_id: Kingdom = GREEN, target_row: list[Any] | None = None, area_type: MapItemType | int | None = None, landmark_min_level: int = 0, under_conquer_control: bool = False, area_bonuses: list[Bonus] | None = None, spy_army: SpyArmy | None = None, defending_castellan: Commander | None = None, defender_legend_skill_ids: Sequence[int] | None = None, commander: Commander | None = None, general_skill_ids: Sequence[int] | None = None, legend_skill_ids: Sequence[int] | None = None, global_effects: Iterable[GlobalEffectTimer] | None = None, support_tools: Sequence[Tool | int | None] | None = None, conquer: bool = False, active_raid_boss_id: int | None = None, tool_bonus: float = 0.0, yard_bonus: float = 0.0, yard_boost: float = 0.0, options: FillOptions | None = None, timeout: float = 5.0) -> FilledAttack

Build a complete attack: every wave, plus the courtyard wave.

Give it a target and it reads the rest itself.

With target_x/target_y it asks the server for the attack pre-calculation, the command the client uses for the target's area type (the conquest one with conquer), and takes what that carries: the target's map row and so its structures, the spied defenders per flank, the defending castellan, and the area effects that widen the flanks. A camp's victory count comes out of the same row, and a player's level from the owner records beside it. The general's skills and the player's own are read with gie and skl.

Every one of those can be passed instead, which skips the request that would have found it. Pass no coordinates and nothing is read: then target_level or camp_victories is required, as before.

A scan of the target's tile, made when its area type is not known, leaves the session on the map, as the client is when it attacks from there: the castle is joined again only for the inventory read, and only when the pre-calculation did not carry the inventory.

Each read is best effort against a refusal: the server refuses the pre-calculation for a target this player may not hit, and the fill goes on with what the other reads found. Each refusal is named in the result's unread with its CommandError, and logged as a warning, or at info level for the pre-calculation's INVALID_AREA. A timeout, a dropped connection or an unreadable reply raises.

Parameters:

Name Type Description Default
castle_id int

Castle whose troops to draw from, one of yours: a Castle.id from client.state.get_castles()

required
target_x int | None

Target's map x, which lets this method read the rest

None
target_y int | None

Target's map y

None
kingdom_id Kingdom | None

Kingdom the target sits in; the source castle's when not given

None
source_x int | None

Attacking castle's x, needed for the pre-calculation; looked up from the castle list when not given

None
source_y int | None

Attacking castle's y

None
target_level int | None

The target owner's level, which sizes each flank. Read from the map when coordinates are given, or derived from camp_victories for a camp

None
target_is_player bool

True for a player's castle or outpost

False
target_owner_id int | None

The target owner's player id, which decides the legend skills; taken from target_row when not given

None
target_owner_legend_level int | None

The target owner's legend level. Read from the owner records of a one-tile scan when coordinates are given, 0 otherwise

None
camp_victories int | None

An NPC camp's victory count

None
camp_kingdom_id Kingdom

Kingdom the camp sits in

GREEN
target_row list[Any] | None

The target's raw map row, for a castle's structures. Its first field is the area type, so passing the row is enough

None
landmark_min_level int

A capital's or metropolis's own defense level

0
area_bonuses list[Bonus] | None

Effects on this attack from outside the commander, from get_attack_info(...).attacker_bonuses()

None
under_conquer_control bool

True when the target is held under conquer control, which sizes the courtyard from the area's own defense level rather than its current owner's

False
spy_army SpyArmy | None

A spied castle's defenders per flank, from get_attack_info(...).spy_army. Without it a castle target is modeled as fortification alone, with no defending army

None
defending_castellan Commander | None

The castellan holding the target, from get_attack_info(...).defending_castellan(). Its equipment raises the fortification and multiplies the defenders, differently per flank

None
defender_legend_skill_ids Sequence[int] | None

The defender's legend skills, the attack pre-calculation's LS list. With a spy report they raise the defenders and the fortification

None
area_type MapItemType | int | None

The target's area type, which scopes effects and decides which tools may be carried; taken from target_row when not given

None
commander Commander | None

The commander leading the attack

None
general_skill_ids Sequence[int] | None

Its general's unlocked skills. Left out, they are read with gie for the general this commander carries

None
legend_skill_ids Sequence[int] | None

The player's legend skills. Left out, they are read with skl

None
global_effects Iterable[GlobalEffectTimer] | None

The global effects running, as the timers of GlobalEffectEvent.effects; the Event.GLOBAL_EFFECT event's in state when not given. The booster's boost to the ones bie lists is read from state

None
support_tools Sequence[Tool | int | None] | None

The support tools the attack will carry, as sent in AST; pass the same list to send_attack

None
conquer bool

A conquest attack carries two extra waves

False
active_raid_boss_id int | None

The boss of the alliance raid-boss event running now, None when none is; see GameData.require().raid_boss(name)

None
tool_bonus float

Extra flank tool capacity on top of the legend skill

0.0
yard_bonus float

Absolute courtyard capacity bonus, effect type 179

0.0
yard_boost float

Courtyard capacity boost, effect type 180

0.0
options FillOptions | None

Which flanks to fill and which units to allow

None
timeout float

Timeout for the inventory request

5.0

Returns:

Type Description
FilledAttack

The waves and the courtyard wave, ready for send_attack,

FilledAttack

with the minimum the waves had to reach and what could not be read

Raises:

Type Description
AttackBelowMinimumError

The waves could not be filled with the fewest units the client lets an attack on this target carry (combat.min_attack_soldiers); the fill is on the error's attack

UnknownCastleError

castle_id is not in your castle list and its kingdom, position or inventory had to be read from it

AmbiguousCastleError

So, and castle_id repeats across your kingdoms

ValueError

area_type is not an area type, or the target's has no pre-calculation modelled

EmpireTimeoutError / ConnectionClosedError / PacketError

A read of the target failed

attack.models.counter

The attack counter: past a threshold, every attack's travel costs more.

Commands:

  • gai: the attack counter, a login section of gbd and a push

Classes:

Name Description
AttackCounterResponse

Your recent attacks and the threshold past which their travel costs grow.

AttackCounterResponse pydantic-model

Bases: BaseResponse

Your recent attacks and the threshold past which their travel costs grow.

Command: gai, as a login section of gbd and a push the client never asks for.

Client: GAICommand (bundle line 122051), AttackCounterVO.parseParamObject (bundle line 65960), read by CastleAttackInfoVO.getTravelCost (bundle line 30651) and AttackDialogWaveHandler.fillAttackCosts (bundle line 102551)

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

attack_count pydantic-field

attack_count: ClientNumber = 0

Wire key AC

Attacks counted

attack_count_threshold pydantic-field

attack_count_threshold: ClientNumber = 0

Wire key ACTH

Attacks counted before travel costs grow

growth_rate pydantic-field

growth_rate: ClientNumber = 0

Wire key ACGR

How fast the travel costs grow per attack past the threshold

travel_cost_surcharge property

travel_cost_surcharge: float

The coins an attack costs on top, as a multiple of its travel cost before bonuses; 0 up to the threshold.

Client: TravelConst.getAttackTravelCostC1 (dll line 19839)

attack.models.dungeon_skips

Dungeon cooldown skips.

Commands:

  • msd: Shorten a dungeon's cooldown with a minute skip
  • sdc: Skip a dungeon's cooldown

Classes:

Name Description
MinuteSkipDungeonRequest

Shorten a dungeon's cooldown with a minute-skip item.

MinuteSkipDungeonResponse

The dungeon's map row after a minute skip.

SkipDungeonCooldownRequest

Skip a dungeon's whole cooldown.

SkipDungeonCooldownResponse

The dungeon's map row after its cooldown was skipped.

MinuteSkipDungeonRequest pydantic-model

Bases: BaseRequest

Shorten a dungeon's cooldown with a minute-skip item.

A dungeon is an NPC target, treasure-map dungeons included, that has to recover before it can be attacked again. Fields follow the client's key order: the constructor initialises X, Y, MID, NID before it sets MST and KID.

Command: msd Client: C2SMinuteSkipDungeonVO (bundle line 72337), built by SkippableCooldownMinuteSkipProperties.getMinuteSkipCommand (bundle line 72324); CastleMinuteSkipDialog.onScrollItemClick passes the currency's jsonKey as MST (bundle line 7722)

Fields:

x pydantic-field

x: int

Wire key X

Dungeon map x

y pydantic-field

y: int

Wire key Y

Dungeon map y

map_id pydantic-field

map_id: int = -1

Wire key MID

Treasure-map id, -1 for an ordinary dungeon

node_id pydantic-field

node_id: int = -1

Wire key NID

Treasure-map node id, -1 for an ordinary dungeon

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

Kingdom id

MinuteSkipDungeonResponse pydantic-model

Bases: BaseResponse

The dungeon's map row after a minute skip.

Command: msd Client: MSDCommand.executeCommand (bundle line 125782), which parses AI with WorldmapObjectFactory.parseWorldMapArea (DungeonMapobjectVO for a camp)

Fields:

Validators:

  • _parse_row → area

area pydantic-field

area: MapAreaItem | None = None

Wire key AI

The dungeon's updated map row, with its victories and remaining cooldown

SkipDungeonCooldownRequest pydantic-model

Bases: BaseRequest

Skip a dungeon's whole cooldown.

Command: sdc Client: C2SSkipDungeonCooldownVO (bundle line 46101), built by SkippableCooldownMinuteSkipProperties.getFullSkipCommand (bundle line 72323)

Fields:

x pydantic-field

x: int

Wire key X

Dungeon map x

y pydantic-field

y: int

Wire key Y

Dungeon map y

kingdom_id pydantic-field

kingdom_id: Kingdom

Wire key KID

Kingdom id

map_id pydantic-field

map_id: int = -1

Wire key MID

Treasure-map id, -1 for an ordinary dungeon

node_id pydantic-field

node_id: int = -1

Wire key NID

Treasure-map node id, -1 for an ordinary dungeon

SkipDungeonCooldownResponse pydantic-model

Bases: BaseResponse

The dungeon's map row after its cooldown was skipped.

Command: sdc Client: SDCCommand.executeCommand (bundle line 122333), which parses AI with WorldmapObjectFactory.parseWorldMapArea (DungeonMapobjectVO for a camp)

Fields:

Validators:

  • _parse_row → area

area pydantic-field

area: MapAreaItem | None = None

Wire key AI

The dungeon's updated map row, with its victories and remaining cooldown

attack.models.info

Attack pre-calculation for a castle.

Commands:

  • aci: Attack pre-calculation

Classes:

Name Description
GetAttackInfoRequest

Ask for the attack pre-calculation against a castle.

AttackTargetArea

The target's map row and owner records, the gaa block of a pre-calculation reply.

AttackInfoResponse

What every attack pre-calculation reply carries, whatever the target.

GetAttackInfoResponse

The attack pre-calculation for a castle, and the base of every attack reply.

logger module-attribute

logger = logging.getLogger(__name__)

GetAttackInfoRequest pydantic-model

Bases: BaseRequest

Ask for the attack pre-calculation against a castle.

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

Client: C2SGetAttackCastleInfosVO (bundle line 72015), ClientConstSF.C2S_GET_ATTACK_CASTLE_INFOS (bundle line 71).

Fields:

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

source_x pydantic-field

source_x: int

Wire key SX

Attacking castle's map x

source_y pydantic-field

source_y: int

Wire key SY

Attacking castle's map y

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

AttackTargetArea pydantic-model

Bases: BasePayload

The target's map row and owner records, the gaa block of a pre-calculation reply.

Client: CastleAttackInfoVO.fillFromParamObject (bundle line 30620) parses AI with WorldmapObjectFactory.parseWorldMapArea; ACICommand and ABICommand pass OI to OtherPlayerData.parseOwnerInfoArray

Fields:

Validators:

  • _readable_records → owners

area pydantic-field

area: TargetRow = None

Wire key AI

The target's map row

owners pydantic-field

owners: list[MapObject]

Wire key OI

Owner records

AttackInfoResponse pydantic-model

Bases: BaseResponse

What every attack pre-calculation reply carries, whatever the target.

Payload:

{"SCID": source_castle_id, "KID": ..,
 "AE": [[effect_id, [value], source_tag], ...],
 "S": [left, middle, right, keep, stronghold, support, reserve],
 "AS": spy_age_seconds, "abe": {castellan}, "B": {castellan}, "LS": [...],
 "KTB": kings_tower_bonus, "HAWL": home_workshop_level,
 "gaa": {"AI": [target map row], "OI": [owner records]},
 "gui": {"I": [[wod_id, count], ...], "SHI": [[wod_id, count], ...]},
 "gli": {"C": [...], "B": [...]}}

MB is left to the subclasses: it is the morality in an attack reply and the maximum barons in an outpost conquest reply.

AE is already scoped by the server to this target. S, AS, the castellan and LS form the spy report; the client reads them only when S is not empty, and otherwise treats the target as never spied.

Client: CastleAttackInfoVO.fillFromParamObject (bundle lines 30620-30633), CastleFightScreenVO.fillFromParamObject for AE (bundle line 30501), which hands it to SimpleEffectSource.parseEffects (bundle line 38429), CastleSpyArmyInfoVO.parseArmyInfo (bundle line 30699).

Fields:

Validators:

source_castle_id pydantic-field

source_castle_id: int = 0

Wire key SCID

Attacking castle's id

target_x pydantic-field

target_x: int = 0

Wire key TX

Target map x

target_y pydantic-field

target_y: int = 0

Wire key TY

Target map y

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The attacking castle's kingdom

attacker_effects pydantic-field

attacker_effects: CommanderEffects

Wire key AE

Area effects on this attack, already scoped to the target

spy_army pydantic-field

spy_army: SpyArmyBlock = None

Wire key S

The spied defenders by the position they hold; None without a spy report

spy_age_seconds pydantic-field

spy_age_seconds: int = -1

Wire key AS

Seconds since the target was spied; -1 when there is no spy report

spied_castellan pydantic-field

spied_castellan: Commander | None = None

Wire key abe

The castellan defending the target, preferred over spied_castellan_fallback

spied_castellan_fallback pydantic-field

spied_castellan_fallback: Commander | None = None

Wire key B

The castellan defending the target when spied_castellan is missing

defender_legend_skill_ids pydantic-field

defender_legend_skill_ids: tuple[EnumOrInt['LegendSkill'], ...] = ()

Wire key LS

The defender's legend skills, part of the spy report

kings_tower_bonus pydantic-field

kings_tower_bonus: float = 0

Wire key KTB

Kings tower bonus

home_workshop_level pydantic-field

home_workshop_level: int = 0

Wire key HAWL

Level of the attacking castle's workshop, which unlocks support tools

target_area pydantic-field

target_area: AttackTargetArea

Wire key gaa

The target's map row and owner records

unit_inventory pydantic-field

unit_inventory: UnitInventory

Wire key gui

The attacker's units and tools, and its stronghold units

commander_roster pydantic-field

commander_roster: CommanderRoster

Wire key gli

The attacker's commanders and castellans

attacker_bonuses

attacker_bonuses() -> list['Bonus']

The area effects that apply to this attack, already scoped by the server.

These are not the commander's - they are the kingdom-wide and event effects the attack picks up on top of it, and they include the flank and front unit-amount bonuses that decide how many troops a wave holds.

defending_castellan

defending_castellan() -> Commander | None

The castellan holding the target, from abe or else B.

None without a spy report, since the client builds the defending castellan only when S is not empty. The client does not handle an empty entry, so that is None as well.

Client: CastleAttackInfoVO.fillFromParamObject (t.abe||t.B, bundle line 30632), CastleSpyArmyInfoVO.parseArmyInfo (bundle line 30699), LordFactory.createLord (bundle line 26399).

target_row

target_row() -> list[Any]

The target's raw map row from gaa.AI, or an empty list.

Client: WorldmapObjectFactory.parseWorldMapArea(t.gaa.AI) in CastleAttackInfoVO.fillFromParamObject (bundle line 30620).

owner_records

owner_records() -> list[MapObject]

The owner records under gaa.OI.

Client: ACICommand.executeCommand (bundle line 122164) and ABICommand.executeCommand (bundle line 122128) pass them to OtherPlayerData.parseOwnerInfoArray; the other pre-calculation commands do not read them.

inventory

inventory() -> dict[int, int]

The attacker's units and tools from gui.I, as {wod_id: count}.

Client: CastleAttackInfoVO.fillFromParamObject (bundle line 30620).

stronghold_inventory

stronghold_inventory() -> dict[int, int]

The attacker's stronghold units from gui.SHI, as {wod_id: count}.

Client: CastleAttackInfoVO.fillFromParamObject into a StrongholdUnitInventory (bundle line 30620).

GetAttackInfoResponse pydantic-model

Bases: AttackInfoResponse

The attack pre-calculation for a castle, and the base of every attack reply.

Command: aci

Client: CastleAttackData.parse_ACI (bundle line 133821), CastleAttackInfoVO.fillFromParamObject reads MB (bundle line 30620).

Fields:

Validators:

morality pydantic-field

morality: float = 0

Wire key MB

Morality bonus of the attack

attack.models.presets

Attack presets.

Commands:

  • gas: Get attack presets
  • sas: Save an attack preset
  • upan: Rename an attack preset

Classes:

Name Description
GetPresetsRequest

Get the player's saved attack presets.

PresetArmy

A preset's army, decoded from its A string.

AttackPreset

One unlocked preset slot from the gas reply.

GetPresetsResponse

The unlocked preset slots.

SavePresetRequest

Save an army into a preset slot.

SavePresetResponse

Acknowledgement of a saved preset; the client reads nothing from it.

RenamePresetRequest

Rename a preset slot.

RenamePresetResponse

Acknowledgement of a renamed preset; the client reads nothing from it.

Attributes:

Name Type Description
PRESET_NAME_MAX_LENGTH

The longest preset name the client's rename dialog takes.

PRESET_NAME_MAX_LENGTH module-attribute

PRESET_NAME_MAX_LENGTH = 15

The longest preset name the client's rename dialog takes.

Client: RenameFightPresetDialog.PRESET_MAX_CHARS (bundle line 45597)

GetPresetsRequest pydantic-model

Bases: BaseRequest

Get the player's saved attack presets.

Command: gas Payload: {} Client: C2SGetPreDefinedAttackSetupVO (bundle line 141803), sent by FightPresetData.loadDataFromServer

PresetArmy pydantic-model

PresetArmy(*, middle_tools: WodAmountSlotsInput = (), left_tools: WodAmountSlotsInput = (), right_tools: WodAmountSlotsInput = (), middle_units: WodAmountSlotsInput = (), left_units: WodAmountSlotsInput = (), right_units: WodAmountSlotsInput = (), support_tools: SupportToolSlots = (None, None, None))

Bases: EmpireModel

A preset's army, decoded from its A string.

A is a JSON list of flat [wod_id, count, wod_id, count, ...] arrays, one per container of a wave in CastleAttackWaveVO.flanks order: middle, left and right tools, then middle, left and right units. A seventh array, read only when the list has exactly seven entries, holds the support-tool wod ids.

Client: FightPresetVO.getUnitWodId / getUnitCount / getSupportTools (bundle lines 141845-141848), CastleAttackWaveVO constructor and flanks getter (bundle lines 99925, 99983)

Fields:

middle_tools pydantic-field

middle_tools: WodAmountSlots = ()

Middle tool slots

left_tools pydantic-field

left_tools: WodAmountSlots = ()

Left tool slots

right_tools pydantic-field

right_tools: WodAmountSlots = ()

Right tool slots

middle_units pydantic-field

middle_units: WodAmountSlots = ()

Middle unit slots

left_units pydantic-field

left_units: WodAmountSlots = ()

Left unit slots

right_units pydantic-field

right_units: WodAmountSlots = ()

Right unit slots

support_tools pydantic-field

support_tools: SupportToolSlots = (None, None, None)

Support tools, one per slot; None for an empty slot, as a preset without any has

from_arrays classmethod

from_arrays(arrays: list[list[int]]) -> PresetArmy

Decode the list A parses to.

from_wave classmethod

from_wave(wave: AttackWave) -> PresetArmy

The preset the client saves from a wave.

Empty slots are dropped. The client saves no support tools: its setContentFromWave ignores the support tools it is handed and writes six arrays.

Client: FightPresetVO.setContentFromWave (bundle line 141850), called by AttackDialogPresets.fillPresetFromWave (bundle line 101668)

to_arrays

to_arrays() -> list[list[int]]

The six flat arrays the client saves, empty slots dropped.

to_wave

to_wave() -> AttackWave

The army as a wave, without the support tools.

AttackPreset pydantic-model

Bases: BasePayload

One unlocked preset slot from the gas reply.

Client: FightPresetData.parsePresets (bundle line 141770), FightPresetVO.update / deserialize (bundle lines 141830, 141836)

Fields:

index pydantic-field

index: int

Wire key S

Preset slot index

name pydantic-field

name: str | None = None

Wire key SN

Preset name; None or empty means the game's default name

raw_army pydantic-field

raw_army: str | None = None

Wire key A

The army as a JSON string, see PresetArmy

army

army() -> PresetArmy | None

The decoded army, or None when the slot is empty or A does not parse.

GetPresetsResponse pydantic-model

Bases: BaseResponse

The unlocked preset slots.

Command: gas Payload: {"S": [{"S": index, "SN": name, "A": ""}, ...]} Client: GASCommand.executeCommand (bundle line 122070) into FightPresetData.parsePresets (bundle line 141770), which counts every entry present as an unlocked slot

Fields:

Validators:

  • _skip_missing_entries → presets

presets pydantic-field

presets: list[AttackPreset]

Wire key S

Unlocked preset slots

SavePresetRequest pydantic-model

Bases: BaseRequest

Save an army into a preset slot.

Command: sas Payload: {"S": index, "A": ""} Client: C2SUpdatePreDefinedAttackSetupVO (bundle line 141820), with A from FightPresetVO.unitsAsString, a JSON.stringify (bundle line 141843); sent by FightPresetData.savePresetArmy

Fields:

index pydantic-field

index: int

Wire key S

Preset slot index

raw_army pydantic-field

raw_army: str

Wire key A

The army as a compact JSON string

create classmethod

create(index: int, army: PresetArmy) -> SavePresetRequest

Build the request for an army, serialised the way JSON.stringify does.

SavePresetResponse pydantic-model

Bases: BaseResponse

Acknowledgement of a saved preset; the client reads nothing from it.

Command: sas Client: SASCommand.executeCommand (bundle line 122086)

Fields:

RenamePresetRequest pydantic-model

Bases: BaseRequest

Rename a preset slot.

Command: upan Payload: {"S": index, "SN": name}

The client sends the name as typed, unencoded, once its rename dialog has checked it: at most PRESET_NAME_MAX_LENGTH characters, passing TextValide.isSmartFoxValide.

Client: C2SUpdatePresetNameVO (bundle line 141794), sent by FightPresetData.savePresetName from RenameFightPresetDialog.sendCommand (bundle line 45609)

Fields:

index pydantic-field

index: int

Wire key S

Preset slot index

name pydantic-field

name: str

Wire key SN

The new name

RenamePresetResponse pydantic-model

Bases: BaseResponse

Acknowledgement of a renamed preset; the client reads nothing from it.

Command: upan Client: UPANCommand.executeCommand (bundle line 122114)

Fields:

attack.models.send

Sending an attack.

Commands:

  • cra: Create/send attack

Classes:

Name Description
CreateAttackRequest

Send an attack to a target.

CreateAttackResponse

Response to attack creation.

logger module-attribute

logger = logging.getLogger(__name__)

CreateAttackRequest pydantic-model

Bases: BaseRequest

Send an attack to a target.

Command: cra Payload: {"SX": source_x, "SY": source_y, "TX": target_x, "TY": target_y, "KID": kingdom_id, "LID": commander_id, "WT": wait_time, "HBW": horse_booster_id, "BPC": use_premium_commander, "ATT": attack_type, "AV": share_battle_view, "LP": loot_priority, "FC": send_anyway, "PTT": feathers, "SD": slowdown, "ICA": collector_attack, "CD": 99, "A": waves, "BKS": collector_booster, "AST": support_tools, "RW": yard_wave, "ASCT": auto_skip_cooldown}

KID is the attacking castle's kingdom. A horse paid with feathers is sent as HBW -1 with PTT 1. CD is always 99.

Fields follow the client's key order: the constructor initialises SX through CD before it sets A, BKS, AST, RW and ASCT.

Client: C2SCreateArmyAttackMovementVO (bundle line 60851), filled by CastleAttackData.sendAttack (bundle line 133852) with the fight screen's collecterBooster, built by CastleFightScreenVO.addCollectorBooster (bundle line 30584) from the booster dialogs' boosterKey, a currency id (bundle lines 55905, 100060, 100154, 100189, 100221).

Fields:

source_x pydantic-field

source_x: int

Wire key SX

Attacking castle's map x

source_y pydantic-field

source_y: int

Wire key SY

Attacking castle's map y

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

The attacking castle's kingdom

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 the attack is sent with

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 (commander_id -14) leads, which uses a premium commander or costs rubies; 0 for any other commander

attack_type pydantic-field

attack_type: AttackType = AttackType.ATTACK

Wire key ATT

The kind of attack

share_battle_view pydantic-field

share_battle_view: int = 0

Wire key AV

1 to let others watch the battle

loot_priority pydantic-field

loot_priority: LootPriority = LootPriority.NO

Wire key LP

Resource to loot first

send_anyway pydantic-field

send_anyway: int = 0

Wire key FC

1 to send although one of your attacks is already on its way there (after ATTACK_IN_PROGRESS)

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

collector_attack pydantic-field

collector_attack: int = 0

Wire key ICA

1 for a collector event attack

countdown pydantic-field

countdown: int = 99

Wire key CD

Always 99

waves pydantic-field

waves: list[AttackWave]

Wire key A

The attack waves, front to back

collector_booster pydantic-field

collector_booster: CurrencyAmounts

Wire key BKS

Collector event boosters by currency, such as CurrencyId.SAMURAI_MEDAL_BOOSTER

support_tools pydantic-field

support_tools: SupportToolSlots = ()

Wire key AST

Support tools, one per slot, None for an empty one; the client sends every slot

yard_wave pydantic-field

yard_wave: WodAmountSlots = ()

Wire key RW

The courtyard wave's unit slots, [-1, 0] for an empty one

auto_skip_cooldown pydantic-field

auto_skip_cooldown: AutoSkipCooldownType = AutoSkipCooldownType.OFF

Wire key ASCT

How the target's cooldown is skipped when the attack lands

accepts_reply

accepts_reply(payload: Any) -> bool

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

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

CreateAttackResponse pydantic-model

Bases: BaseResponse

Response to attack creation.

Command: cra Payload:

{"AAM": {
    "M":  {movement},                  # the created movement
    "UM": {"L": {gli entry}, ...},     # the commander leading it
    "FA": {"L": [[unit_id, count]], "M": [...], "R": [...], "RW": []},
    "AST": [...], "ATT": 0, "ASCT": 0, "FC": 0,
}}

UM.L is the same shape as a gli entry, equipment included, so it is what confirms which commander LID selected. FA is the army the server actually accepted, after it dropped empty flanks.

A success also carries gcu and O; the state manager applies both with the movement. An ATTACK_IN_PROGRESS (234) reply carries TS and AS instead, the countdown and army size of the attack already on its way, which the client shows before offering to send anyway with FC=1. send_attack raises that reply as AttackInProgressError, with both values read off it.

CRACommand hands AAM to CastleArmyData.parseMapMovementArray as [i.AAM], the same read as a gam entry or an abr push, and O to CastleOtherPlayerData.parseOwnerInfoArray, which skips a record without an OID.

Client: CRACommand.executeCommand (bundle line 125954), CastleArmyData.parseMapMovementArray (bundle line 133626), MapmovementFactory.parseMapMovement (bundle line 133793), CastleOtherPlayerData.parseOwnerInfo (bundle line 138996), CurrencyData.parseGCU (bundle line 141191), CastlePostPostAttackFactionDialogProperties (bundle line 40173), CastlePostPostAttackFactionDialog.onClick (bundle line 40155).

Fields:

Validators:

attack_movement pydantic-field

attack_movement: MovementWrapper | None = None

Wire key AAM

The created movement; None when there is none

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after the send; None when the reply has none

owners pydantic-field

Wire key O

Owner records for the movement's areas

arrival_seconds pydantic-field

arrival_seconds: int | float | None = None

Wire key TS

On ATTACK_IN_PROGRESS: seconds until the attack already on its way arrives

army_size pydantic-field

army_size: int | float | None = None

Wire key AS

On ATTACK_IN_PROGRESS: the size of the attack already on its way

leader property

leader: Commander | None

The commander leading the attack, as the server echoed it back.

movement_id property

movement_id: int | None

The created movement's ID, or None when the server sent no movement.

attack.models.target_info

Attack pre-calculation for other targets, and conquest pre-calculation.

Commands:

  • adi, abi, ali, avi, aii: Attack pre-calculation per kind of target
  • coi, cci, cti: Conquest pre-calculation

Classes:

Name Description
GetDungeonAttackInfoRequest

Ask for the attack pre-calculation against an NPC camp.

GetDungeonAttackInfoResponse

The attack pre-calculation for an NPC camp.

GetBossDungeonAttackInfoRequest

Ask for the attack pre-calculation against a boss dungeon.

GetBossDungeonAttackInfoResponse

The attack pre-calculation for a boss dungeon.

GetLandmarkAttackInfoRequest

Ask for the attack pre-calculation against a kings tower, monument or laboratory.

GetLandmarkAttackInfoResponse

The attack pre-calculation for a landmark.

GetVillageAttackInfoRequest

Ask for the attack pre-calculation against a village.

GetVillageAttackInfoResponse

The attack pre-calculation for a village.

GetIslandAttackInfoRequest

Ask for the attack pre-calculation against an isle resource.

GetIslandAttackInfoResponse

The attack pre-calculation for an isle resource.

GetOutpostConquerInfoRequest

Ask for the conquest pre-calculation against an outpost.

GetOutpostConquerInfoResponse

The conquest pre-calculation for an outpost, with the barons it can use.

GetCapitalConquerInfoRequest

Ask for the conquest pre-calculation against a capital.

GetCapitalConquerInfoResponse

The conquest pre-calculation for a capital. The client reads no barons from it.

GetMetropolConquerInfoRequest

Ask for the conquest pre-calculation against a metropolis.

GetMetropolConquerInfoResponse

The conquest pre-calculation for a metropolis. The client reads no barons from it.

GetDungeonAttackInfoRequest pydantic-model

Bases: BaseRequest

Ask for the attack pre-calculation against an NPC camp.

Every target whose map object attacks as a dungeon answers this command: robber baron camps, event and isle dungeons, invasion and alien camps, the wolf king and the alliance raid portal. The server refuses aci for a camp with INVALID_AREA.

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

Client: C2SGetAttackDungeonInfosVO (bundle line 72024), whose key order the fields follow; CastleStartAttackDialog.attackDungeon (bundle line 14834).

Fields:

source_x pydantic-field

source_x: int

Wire key SX

Attacking castle's map x

source_y pydantic-field

source_y: int

Wire key SY

Attacking castle's map y

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

GetDungeonAttackInfoResponse pydantic-model

Bases: GetAttackInfoResponse

The attack pre-calculation for an NPC camp.

Command: adi

Client: ADICommand.executeCommand (bundle line 122187), CastleAttackData.parse_ADI (133830).

Fields:

Validators:

GetBossDungeonAttackInfoRequest pydantic-model

Bases: BaseRequest

Ask for the attack pre-calculation against a boss dungeon.

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

Client: C2SAttackInfoBossDungeonVO (bundle line 71970), CastleStartAttackDialog.attackBossDungeon (bundle line 14837).

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

source_x pydantic-field

source_x: int

Wire key SX

Attacking castle's map x

source_y pydantic-field

source_y: int

Wire key SY

Attacking castle's map y

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

GetBossDungeonAttackInfoResponse pydantic-model

Bases: GetAttackInfoResponse

The attack pre-calculation for a boss dungeon.

Command: abi

Client: ABICommand.executeCommand (bundle line 122128), CastleAttackData.parse_ABI (133827).

Fields:

Validators:

GetLandmarkAttackInfoRequest pydantic-model

Bases: BaseRequest

Ask for the attack pre-calculation against a kings tower, monument or laboratory.

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

Client: C2SAttackInfoLandmarkVO (bundle line 71988), CastleStartAttackDialog.attackLandmark (bundle line 14846).

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

source_x pydantic-field

source_x: int

Wire key SX

Attacking castle's map x

source_y pydantic-field

source_y: int

Wire key SY

Attacking castle's map y

GetLandmarkAttackInfoResponse pydantic-model

Bases: GetAttackInfoResponse

The attack pre-calculation for a landmark.

Command: ali

Client: ALICommand.executeCommand (bundle line 122227), CastleAttackData.parse_ALI (133836).

Fields:

Validators:

GetVillageAttackInfoRequest pydantic-model

Bases: BaseRequest

Ask for the attack pre-calculation against a village.

The client sends no source castle for it.

Command: avi Payload: {"KID": kingdom_id, "TX": target_x, "TY": target_y}

Client: C2SAttackInfoVillageVO (bundle line 71997), CastleStartAttackDialog.attackVillage (bundle line 14844).

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

GetVillageAttackInfoResponse pydantic-model

Bases: GetAttackInfoResponse

The attack pre-calculation for a village.

Command: avi

Client: AVICommand.executeCommand (bundle line 122244), CastleAttackData.parse_AVI (133832).

Fields:

Validators:

GetIslandAttackInfoRequest pydantic-model

Bases: BaseRequest

Ask for the attack pre-calculation against an isle resource.

An isle dungeon attacks as a dungeon and answers adi instead.

Command: aii Payload: {"KID": kingdom_id, "TX": target_x, "TY": target_y}

Client: C2SAttackInfoIslandVO (bundle line 71979), CastleStartAttackDialog.attackIsland (bundle line 14845).

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

GetIslandAttackInfoResponse pydantic-model

Bases: GetAttackInfoResponse

The attack pre-calculation for an isle resource.

Command: aii

Client: AIICommand.executeCommand (bundle line 122204), CastleAttackData.parse_AII (133834).

Fields:

Validators:

GetOutpostConquerInfoRequest pydantic-model

Bases: BaseRequest

Ask for the conquest pre-calculation against an outpost.

Command: coi Payload: {"KID": kingdom_id, "TX": target_x, "TY": target_y}

Client: C2SGetConquerOutpostInfosVO (bundle line 72060), CastleStartAttackDialog.conquerOutpost (bundle line 14839).

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

GetOutpostConquerInfoResponse pydantic-model

Bases: AttackInfoResponse

The conquest pre-calculation for an outpost, with the barons it can use.

The client reads MB twice here: as the morality in the shared parse and as the maximum barons in parseBarons. This model keeps the conquest meaning only.

Command: coi

Client: COICommand.executeCommand (bundle line 122278), CastleAttackData.parse_COI (133838), CastleConquerInfoVO.fillFromParamObject / parseBarons (bundle lines 133904-133907).

Fields:

Validators:

available_barons pydantic-field

available_barons: int = 0

Wire key AB

Barons free to lead the conquest

max_barons pydantic-field

max_barons: int = 0

Wire key MB

Most barons the player may hold

GetCapitalConquerInfoRequest pydantic-model

Bases: BaseRequest

Ask for the conquest pre-calculation against a capital.

Command: cci Payload: {"KID": kingdom_id, "TX": target_x, "TY": target_y}

Client: C2SGetConquerCapitalInfosVO (bundle line 72042), CastleStartAttackDialog.conquerCapital (bundle line 14842).

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

GetCapitalConquerInfoResponse pydantic-model

Bases: GetAttackInfoResponse

The conquest pre-calculation for a capital. The client reads no barons from it.

Command: cci

Client: CCICommand.executeCommand (bundle line 122261), CastleAttackData.parse_CCI (133840), CastleConquerInfoVO.fillFromParamObject (bundle line 133904).

Fields:

Validators:

GetMetropolConquerInfoRequest pydantic-model

Bases: BaseRequest

Ask for the conquest pre-calculation against a metropolis.

Command: cti Payload: {"KID": kingdom_id, "TX": target_x, "TY": target_y}

Client: C2SGetConquerMetropolInfosVO (bundle line 72051), CastleStartAttackDialog.conquerMetropol (bundle line 14843).

Fields:

kingdom_id pydantic-field

kingdom_id: Kingdom = Kingdom.GREEN

Wire key KID

Kingdom id of the target

target_x pydantic-field

target_x: int

Wire key TX

Target map x

target_y pydantic-field

target_y: int

Wire key TY

Target map y

GetMetropolConquerInfoResponse pydantic-model

Bases: GetAttackInfoResponse

The conquest pre-calculation for a metropolis. The client reads no barons from it.

Command: cti

Client: CTICommand.executeCommand (bundle line 122295), CastleAttackData.parse_CTI (133842), CastleConquerInfoVO.fillFromParamObject (bundle line 133904).

Fields:

Validators: