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 |
|
AttackService
¶
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 |
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 ( |
False
|
share_battle_view
|
bool
|
Let others watch the battle |
False
|
loot_priority
|
LootPriority
|
Resource to loot first ( |
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: |
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.
|
None
|
send_anyway
|
bool
|
Send although one of your attacks is already on its way
there ( |
False
|
min_soldiers
|
int | None
|
The fewest units the waves must carry together, such
as |
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 |
AttackBelowMinimumError
|
The waves carry fewer units than
|
PremiumCommanderCostError
|
The premium commander leads, may cost rubies,
and |
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 |
UnknownCastleError
|
No |
AmbiguousCastleError
|
No |
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
Raises:
| Type | Description |
|---|---|
CommandError / EmpireTimeoutError / ConnectionClosedError
|
see |
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 |
required |
army
|
PresetArmy | AttackWave
|
The army, a |
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 a preset slot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
The preset slot, an |
required |
name
|
str
|
The new name, at most 15 characters, none of |
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 |
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 |
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 |
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 |
None
|
defense
|
dict[Flank, DefenderFlankEffects] | None
|
Explicit per-flank defense, overriding |
None
|
attacker
|
AttackerFlankEffects | None
|
Attacker multipliers; built from |
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
|
None
|
general_skill_ids
|
Sequence[int] | None
|
Unlocked skill ids of the general leading the
attack, from |
None
|
legend_skill_ids
|
Sequence[int] | None
|
The player's unlocked legend skills, from
|
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
|
None
|
owner_legend_level
|
int
|
The target owner's legend level, the |
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 |
False
|
active_raid_boss_id
|
int | None
|
The boss of the alliance raid-boss event
running now, None when none is; |
None
|
global_effects
|
Iterable[GlobalEffectTimer] | None
|
The global effects running, as the timers of
|
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 |
Raises:
| Type | Description |
|---|---|
GameDataNotLoadedError
|
the game data cannot be loaded |
ValueError
|
No level was given and none is known for the player |
UnknownCastleError
|
No |
AmbiguousCastleError
|
No |
read_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 |
required |
timeout
|
float
|
Timeout in seconds |
5.0
|
Returns:
| Type | Description |
|---|---|
Inventory
|
An |
Raises:
| Type | Description |
|---|---|
GameDataNotLoadedError
|
The game data cannot be loaded |
UnknownCastleError
|
|
AmbiguousCastleError
|
|
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 |
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
|
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 |
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 |
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
|
None
|
defending_castellan
|
Commander | None
|
The castellan holding the target, from
|
None
|
defender_legend_skill_ids
|
Sequence[int] | None
|
The defender's legend skills, the
attack pre-calculation's |
None
|
area_type
|
MapItemType | int | None
|
The target's area type, which scopes effects and decides
which tools may be carried; taken from |
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 |
None
|
legend_skill_ids
|
Sequence[int] | None
|
The player's legend skills. Left out, they are
read with |
None
|
global_effects
|
Iterable[GlobalEffectTimer] | None
|
The global effects running, as the timers of
|
None
|
support_tools
|
Sequence[Tool | int | None] | None
|
The support tools the attack will carry, as sent in
|
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 |
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 |
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
( |
UnknownCastleError
|
|
AmbiguousCastleError
|
So, and |
ValueError
|
|
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
gbdand 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:
-
error_code(int) -
attack_count(ClientNumber) -
attack_count_threshold(ClientNumber) -
growth_rate(ClientNumber)
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
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:
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:
-
error_code(int) -
area(MapAreaItem | None)
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:
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:
-
error_code(int) -
area(MapAreaItem | None)
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 |
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. |
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:
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
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
kingdom_id
pydantic-field
¶
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
¶
Wire key LS
The defender's legend skills, part of the spy report
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
¶
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
¶
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
¶
The attacker's units and tools from gui.I, as {wod_id: count}.
Client: CastleAttackInfoVO.fillFromParamObject (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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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 |
AttackPreset |
One unlocked preset slot from the |
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
¶
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(WodAmountSlots) -
left_tools(WodAmountSlots) -
right_tools(WodAmountSlots) -
middle_units(WodAmountSlots) -
left_units(WodAmountSlots) -
right_units(WodAmountSlots) -
support_tools(SupportToolSlots)
support_tools
pydantic-field
¶
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
¶
The six flat arrays the client saves, empty slots dropped.
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:
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": "GASCommand.executeCommand (bundle line 122070) into
FightPresetData.parsePresets (bundle line 141770), which counts every
entry present as an unlocked slot
Fields:
-
error_code(int) -
presets(list[AttackPreset])
Validators:
-
_skip_missing_entries→presets
SavePresetRequest
pydantic-model
¶
Bases: BaseRequest
Save an army into a preset slot.
Command: sas
Payload: {"S": index, "A": "C2SUpdatePreDefinedAttackSetupVO (bundle line 141820), with
A from FightPresetVO.unitsAsString, a JSON.stringify (bundle
line 141843); sent by FightPresetData.savePresetArmy
Fields:
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:
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. |
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(int) -
source_y(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
commander_id(int) -
wait_time(int) -
horse_booster_id(int) -
use_premium_commander(int) -
attack_type(AttackType) -
share_battle_view(int) -
loot_priority(LootPriority) -
send_anyway(int) -
feathers(int) -
slowdown(int) -
collector_attack(int) -
countdown(int) -
waves(list[AttackWave]) -
collector_booster(CurrencyAmounts) -
support_tools(SupportToolSlots) -
yard_wave(WodAmountSlots) -
auto_skip_cooldown(AutoSkipCooldownType)
kingdom_id
pydantic-field
¶
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
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)
collector_attack
pydantic-field
¶
collector_attack: int = 0
Wire key ICA
1 for a collector event attack
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
¶
Wire key AST
Support tools, one per slot, None for an empty one; the client sends every slot
yard_wave
pydantic-field
¶
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
¶
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:
-
error_code(int) -
attack_movement(MovementWrapper | None) -
currencies(CurrencyBlock) -
owners(list[MovementOwner]) -
arrival_seconds(int | float | None) -
army_size(int | float | None)
Validators:
-
_keep_the_raw_movement -
_movement_or_none→attack_movement -
_readable_owners→owners
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
¶
owners: list[MovementOwner]
Wire key O
Owner records for the movement's areas
arrival_seconds
pydantic-field
¶
Wire key TS
On ATTACK_IN_PROGRESS: seconds until the attack already on its way arrives
army_size
pydantic-field
¶
Wire key AS
On ATTACK_IN_PROGRESS: the size of the attack already on its way
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:
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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
¶
Wire key KID
Kingdom id of the target
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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
¶
Wire key KID
Kingdom id of the target
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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:
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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:
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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:
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
available_barons(int) -
max_barons(int)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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:
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army
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:
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:
-
error_code(int) -
source_castle_id(int) -
target_x(int) -
target_y(int) -
kingdom_id(Kingdom) -
attacker_effects(CommanderEffects) -
spy_army(SpyArmyBlock) -
spy_age_seconds(int) -
spied_castellan(Commander | None) -
spied_castellan_fallback(Commander | None) -
defender_legend_skill_ids(tuple[EnumOrInt['LegendSkill'], ...]) -
kings_tower_bonus(float) -
home_workshop_level(int) -
target_area(AttackTargetArea) -
unit_inventory(UnitInventory) -
commander_roster(CommanderRoster) -
morality(float)
Validators:
-
_note_castellan_source -
_castellan_or_none→spied_castellan,spied_castellan_fallback -
_legend_skills→defender_legend_skill_ids -
_no_spy_report_without_an_army