Skip to content

Movements

Army movements behind client.movements.

movements.service

The army movements the state tracks.

Classes:

Name Description
MovementsService

Army movements: listing them and recalling your own.

MovementsService

MovementsService(client: 'EmpireClient')

Bases: BaseService

Army movements: listing them and recalling your own.

Reached as client.movements.

Methods:

Name Description
get_movements

Request army movements from server.

recall

Recall one of your movements (mcm), as the client's retreat and "send home" buttons do.

get_incoming_attacks

Get all incoming attack movements.

get_incoming_movements

Get all incoming movements.

get_outgoing_movements

Get all outgoing movements.

get_movements

get_movements(wait: bool = True, timeout: float = 5.0) -> list[Movement]

Request army movements from server.

Parameters:

Name Type Description Default
wait bool

If True, wait for response before returning

True
timeout float

Timeout in seconds when waiting; with wait=False, how long to wait for a gam request in flight before sending, and how long the reply is kept from other gam requests at most

5.0

Returns:

Type Description
list[Movement]

List of Movement objects, read from state after the response has

list[Movement]

been applied. With wait=False this is whatever state holds

list[Movement]

right now, which is not yet the answer to this request.

Raises:

Type Description
CommandError

wait=True and the server rejected 'gam'

EmpireTimeoutError

wait=True and no response within timeout, or wait=False and a gam request in flight for all of timeout

recall

recall(movement_id: int, timeout: float = 5.0) -> MovementWrapper

Recall one of your movements (mcm), as the client's retreat and "send home" buttons do.

Which movements the client lets you recall is listed on CancelMovementRequest: in short, your own that are still heading to their target (attacks only within 600 seconds of leaving, with exceptions) and your supports in either direction. The reply also updates state and fires GameState.on_movement_recalled.

Parameters:

Name Type Description Default
movement_id int

The movement to recall (Movement.movement_id)

required
timeout float

Timeout in seconds

5.0

Returns:

Type Description
MovementWrapper

The movement as the reply has it now, normally on its way home

Raises:

Type Description
CommandError

The server refused the recall, such as INVALID_STARTAREA_FOR_CANCEL_MOVEMENT (189)

Client: CastleAskRetreatDialog.onClick (bundle line 33234), MCMCommand.executeCommand (bundle line 126041)

get_incoming_attacks

get_incoming_attacks() -> list[Movement]

Get all incoming attack movements.

get_incoming_movements

get_incoming_movements() -> list[Movement]

Get all incoming movements.

get_outgoing_movements

get_outgoing_movements() -> list[Movement]

Get all outgoing movements.

movements.tracked

A tracked army movement, with its timing and troops.

Classes:

Name Description
MovementResources

Resources a movement carries: the goods among its market goods or travel loot, added up.

Movement

A live army movement (Attack, Support, Transport, ...) tracked in state.

Attributes:

Name Type Description
logger
DUNGEON_OWNER_IDS

Owner ids the game counts as dungeon owners. Robber barons, kingdom dungeons,

logger module-attribute

logger = logging.getLogger(__name__)

DUNGEON_OWNER_IDS module-attribute

DUNGEON_OWNER_IDS = frozenset({*(NPCOwner.ROBBER_BARON - i for i in range(13)), *(NPCOwner.DESERT_DUNGEON - i for i in range(5)), NPCOwner.DESERT_BOSS_DUNGEON, NPCOwner.ICE_BOSS_DUNGEON, NPCOwner.VOLCANO_BOSS_DUNGEON, *range(NPCOwner.ISLAND_VILLAGE, NPCOwner.ISLAND_VILLAGE + 5), NPCOwner.BLUE_FACTION_KING, NPCOwner.RED_FACTION_KING, NPCOwner.KINGS_TOWER, NPCOwner.MONUMENT, NPCOwner.CLASSIC_LABORATORY, NPCOwner.ICE_LABORATORY, NPCOwner.DESERT_LABORATORY, NPCOwner.VOLCANO_LABORATORY, NPCOwner.RANDOM_DUNGEON_EVENT, NPCOwner.APRIL_DUNGEON_EVENT, NPCOwner.ST_PATRICKS_DAY_DUNGEON_EVENT, NPCOwner.EASTER_DUNGEON_EVENT, *(NPCOwner.NOMAD_CAMP - i for i in range(4)), NPCOwner.SAMURAI_CAMP, NPCOwner.TUTORIAL_DUNGEON, NPCOwner.THORNKING_DUNGEON, NPCOwner.THORNKING_VILLAGE, NPCOwner.THORNKING_COW_DUNGEON, NPCOwner.SEA_QUEEN_DUNGEON, NPCOwner.SEA_QUEEN_SHIPS, NPCOwner.TREASURE_HUNT_DUNGEON, NPCOwner.UNDERWORLD_DUNGEON, NPCOwner.UNDERWORLD_VILLAGE, NPCOwner.ALLIANCE_NOMAD_CAMP, NPCOwner.DAIMYO_CASTLE, NPCOwner.ALIEN_INVASION, NPCOwner.RED_ALIEN_INVASION, *(owner for owner in NPCOwner if owner.name.startswith('COLLECTOR_')), NPCOwner.WOLF_KING, NPCOwner.ARE_PORTAL, NPCOwner.UNKNOWN_EVENT_OWNER})

Owner ids the game counts as dungeon owners. Robber barons, kingdom dungeons, villages and nomad camps are runs of ids (see NPCOwner).

MovementResources pydantic-model

Bases: EmpireModel

Resources a movement carries: the goods among its market goods or travel loot, added up.

Keys are the client's collectable server keys (CollectableItem*VO.SERVER_KEY). Goods sent as an old-style amounts list count as well; coins, rubies and currencies are in Movement.goods only.

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(extra='ignore', populate_by_name=True)

wood pydantic-field

wood: int = 0

Wire key W

stone pydantic-field

stone: int = 0

Wire key S

food pydantic-field

food: int = 0

Wire key F

coal pydantic-field

coal: int = 0

Wire key C

oil pydantic-field

oil: int = 0

Wire key O

glass pydantic-field

glass: int = 0

Wire key G

iron pydantic-field

iron: int = 0

Wire key I

aquamarine pydantic-field

aquamarine: int = 0

Wire key A

honey pydantic-field

honey: int = 0

Wire key HONEY

mead pydantic-field

mead: int = 0

Wire key MEAD

beef pydantic-field

beef: int = 0

Wire key BEEF

total property

total: int

Total resources in transport.

is_empty property

is_empty: bool

Check if no resources are being transported.

Movement pydantic-model

Bases: EmpireModel

A live army movement (Attack, Support, Transport, ...) tracked in state.

This is the movement type the client returns from get_movements and passes to on_incoming_attack callbacks, and the one exported as empire_core.Movement.

The raw gam reply is modelled as empire_core.movements.GetMovementsResponse: a list of MovementWrapper entries, each holding a MovementRecord. Use those only when parsing packets by hand.

Whether a movement is yours or aimed at you depends on the local player's id, which state stamps on every movement as local_player_id.

Client: BasicMapmovementVO, ArmyAttackMapmovementVO.

Fields are snake_case with the wire key as the alias (movement_id is MID), so a raw gam record validates unchanged and either name works when building one.

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(extra='ignore', populate_by_name=True)

movement_id pydantic-field

movement_id: int = -1

Wire key MID

Movement id

movement_type pydantic-field

movement_type: int = 0

Wire key T

MovementType value

progress_time pydantic-field

progress_time: int = 0

Wire key PT

Seconds travelled at last_updated

total_time pydantic-field

total_time: int = 0

Wire key TT

Seconds the trip takes

direction pydantic-field

direction: int = 0

Wire key D

1 = returning home, 0 = heading to the target

target_id pydantic-field

target_id: int = -1

Wire key TID

Player id owning the target area

kingdom_id pydantic-field

kingdom_id: int = 0

Wire key KID

Kingdom id

source_id pydantic-field

source_id: int = -1

Wire key SID

Player id owning the source area

owner_id pydantic-field

owner_id: int = -1

Wire key OID

Player id owning the movement

horse_booster pydantic-field

horse_booster: EnumOrInt[Horse] | None = None

Wire key HBW

The horse booster; None for none or when paid with feathers

target_area pydantic-field

target_area: MovementArea | None = None

Wire key TA

Target area row; None when there is none

source_area pydantic-field

source_area: MovementArea | None = None

Wire key SA

Source area row; None when there is none

target_area_id pydantic-field

target_area_id: int = -1

Target area id

source_area_id pydantic-field

source_area_id: int = -1

Source area id

target_x pydantic-field

target_x: int = -1

Target x

target_y pydantic-field

target_y: int = -1

Target y

source_x pydantic-field

source_x: int = -1

Source x

source_y pydantic-field

source_y: int = -1

Source y

target_type pydantic-field

target_type: int = -1

Target area type (MapItemType)

local_player_id pydantic-field

local_player_id: int = -1

Player id of the receiving account, -1 if unknown

units pydantic-field

units: WodAmounts

Units and tools, by how many

estimated_size pydantic-field

estimated_size: int = 0

Army size estimate when the army is hidden

resources pydantic-field

resources: MovementResources

Goods carried

target_name pydantic-field

target_name: str = ''

Target area name

source_name pydantic-field

source_name: str = ''

Source area name

target_player_name pydantic-field

target_player_name: str = ''

Name of the target's owner

source_player_name pydantic-field

source_player_name: str = ''

Name of the movement's owner

target_alliance_name pydantic-field

target_alliance_name: str = ''

Alliance name of the target's owner

source_alliance_name pydantic-field

source_alliance_name: str = ''

Alliance name of the movement's owner

created_at pydantic-field

created_at: float

When state first saw this movement

last_updated pydantic-field

last_updated: float

When the last packet for it was applied

commander pydantic-field

commander: Commander | None = None

The commander leading the army, as the UM block sends it; None when there is none

wait_total pydantic-field

wait_total: int = 0

Seconds the army stays at its target

wait_passed pydantic-field

wait_passed: int = 0

Seconds of that wait already passed

force_cancelable pydantic-field

force_cancelable: bool = False

The movement can be force-cancelled

spy pydantic-field

spy: MovementSpy | None = None

A spy mission's type, accuracy or damage, spy count and risk; None for any other

owner pydantic-field

owner: MovementOwner | None = None

Owner record of the movement's owner

target_owner pydantic-field

target_owner: MovementOwner | None = None

Owner record of the target's owner

attack_type pydantic-field

attack_type: int | None = None

AttackType value

is_shadow pydantic-field

is_shadow: bool = False

Shadow movement

support_tools pydantic-field

support_tools: SupportToolSlots = ()

Support tools sent along, as the attack sent them; None for an empty slot

auto_skip_cooldown_type pydantic-field

auto_skip_cooldown_type: EnumOrInt[AutoSkipCooldownType] = AutoSkipCooldownType.OFF

How the target's cooldown is skipped on arrival

advisor_type pydantic-field

The attack advisor that sent the attack

advisor_movement_count pydantic-field

advisor_movement_count: int = 0

Attacks in the advisor series

advisor_movement_number pydantic-field

advisor_movement_number: int = 0

This attack's place in the series

advisor_is_last pydantic-field

advisor_is_last: bool = False

Last attack of the series

market_carriages pydantic-field

market_carriages: int = 0

Carriages of a market transport

goods pydantic-field

goods: CollectableRows = ()

Goods a market transport carries, or loot

movement_type_enum property

movement_type_enum: MovementType | None

The movement's type (T) as a MovementType, None for an id the client does not define.

target_type_enum property

target_type_enum: MapItemType | None

The target area's type (TA[0]) as a MapItemType, None for an id the client does not define.

movement_type_name property

movement_type_name: str

Get the name of the movement type.

time_remaining property

time_remaining: int

Seconds until arrival, advancing with wall-clock time.

Extrapolated from the last packet snapshot (total_time - progress_time at last_updated), so it keeps counting down between updates.

progress_percent property

progress_percent: float

estimated_arrival property

estimated_arrival: float

When the army reaches its target (Unix time).

estimated_end property

estimated_end: float

When the movement is over: arrival plus whatever wait at the target is left.

Client: BasicMapmovementVO._endWaitTimeStamp.

owner_alliance_id property

owner_alliance_id: int

Alliance of the movement's owner, -1 if none or unknown.

target_alliance_id property

target_alliance_id: int

Alliance of the target's owner, -1 if none or unknown.

battle_time property

battle_time: float

When the battle starts (Unix time): arrival plus the wait at the target.

Client: BasicMapmovementVO.parseUnitMovement.

is_stationed property

is_stationed: bool

The army has arrived and is waiting at its target, as a support does.

Client: SupportDefenceMapmovementVO.isStationed.

is_returning property

is_returning: bool

The army is on its way home, whatever its type.

is_mine property

is_mine: bool

The local player owns this movement.

is_outgoing property

is_outgoing: bool

One of the local player's armies heading to its target.

is_incoming property

is_incoming: bool

Another player's army heading to one of the local player's areas.

The daimyo township counts as yours, as in the client, except for an alien attack, which comes only at you. Armies moving between your own areas count as outgoing, not incoming, and so does no occupation (is_occupation): the forces holding an area are not on their way to it.

Client: ArmyAttackMapmovementVO.isAttackingMovement (bundle line 14389), AlienAttackMovementVO.isAttackingMovement (bundle line 33073). An occupation is a SiegeMapmovementVO, which keeps BasicMapmovementVO.isAttackingMovement (bundle line 19438, always false); the movement overview's attack category (FilterAttack, dialog_moveOverview_catAttack, bundle line 67996) adds one on your area by a clause of its own, and lists it as "Occupying forces" (RenderSiege, bundle line 67408), not as an attack under way.

is_attack property

is_attack: bool

Any attack type, including NPC, alien, faction and event attacks.

is_support property

is_support: bool

A support (defence) army.

is_occupation property

is_occupation: bool

An occupation: the forces holding an area a capture attack won, until it is captured or they are driven off.

is_transport property

is_transport: bool

A market transport of resources. Troops moved between own castles are is_travel.

is_travel property

is_travel: bool

Troops moved between the owner's own areas.

An army's way home also arrives as a new TRAVEL movement, with D == 1.

is_spy property

is_spy: bool

A spy mission.

unit_count property

unit_count: int

Total number of units in this movement (includes all unit types).

troop_count property

troop_count: int

Count of actual troops only (excludes equipment/tools).

Note: without loaded game data, the first access loads it through GameData.load (blocking HTTP unless cached on disk). If that fails, all units are counted and the load is retried after a cooldown.

has_arrived

has_arrived() -> bool

Check if movement has arrived (time remaining <= 0).

format_time_remaining

format_time_remaining() -> str

Format time remaining as human-readable string.

movements.models

Army movement models: gam, the recall (mcm), and the movement wrappers pushed with abr and asr.

Classes:

Name Description
GetMovementsRequest

Get all active troop movements.

MovementArea

A movement's TA or SA area row, read as a map row.

MovementRecord

The movement itself: the M inside each gam wrapper.

MovementArmy

A visible army: three flanks plus the courtyard wave, each {Unit or Tool: amount}.

MovementUnitInfo

Commander and wait details: a wrapper's UM.

MovementMarket

A market transport's cargo: a wrapper's MM.

MovementSpy

A spy mission's details: a wrapper's S.

MovementWrapper

One entry of gam's M list, and the A of an abr/asr push or an mcm reply.

MovementOwner

An owner record: one entry of O in gam, abr and asr.

GetMovementsResponse

Response containing active movements.

CancelMovementRequest

Recall one of your movements, turning it back home.

CancelMovementResponse

The reply to a recall: the movement as it now stands.

logger module-attribute

logger = logging.getLogger(__name__)

GetMovementsRequest pydantic-model

Bases: BaseRequest

Get all active troop movements.

Command: gam Payload: {}

Client: C2SGetAllMovementsVO (bundle line 129713, no fields)

MovementArea pydantic-model

Bases: BasePayload

A movement's TA or SA area row, read as a map row.

area_type, x and y are the first three positions, which every area type has; item is the whole row read by the area type's own parser, None when the client reads no row of that type, or (with a warning) when a value is of the wrong kind. The row carries no kingdom of its own, so it is read in the movement's KID: build one from {"row": [...], "kingdom": KID}, or from the bare row, read in the green kingdom.

Client: BasicMapmovementVO.loadFromParamObject reads SA and TA with WorldmapObjectFactory.parseWorldMapArea (bundle lines 19383, 5343), as the map reads its rows

Fields:

Validators:

  • _from_row

area_type pydantic-field

area_type: int

Area type

x pydantic-field

x: int

Map x

y pydantic-field

y: int

Map y

item pydantic-field

item: MapAreaItem | None = None

The row as its area type's parser reads it; None when it cannot be read

position property

position: Position

object_id property

object_id: int | None

The map object's id, as the area type's row gives it; None when it gives none.

owner_id property

owner_id: int | None

The owner the row names; None when it names none.

name property

name: str

The object's name; empty when the row has none.

MovementRecord pydantic-model

Bases: BasePayload

The movement itself: the M inside each gam wrapper.

Client: BasicMapmovementVO.loadFromParamObject.

Fields:

Validators:

movement_id pydantic-field

movement_id: int

Wire key MID

Movement id

movement_type pydantic-field

movement_type: int = 0

Wire key T

MovementType value

progress_time pydantic-field

progress_time: int = 0

Wire key PT

Seconds travelled when the reply was sent

total_time pydantic-field

total_time: int = 0

Wire key TT

Seconds the trip takes

direction pydantic-field

direction: int = 0

Wire key D

1 = returning home, 0 = heading to the target

target_id pydantic-field

target_id: int = -1

Wire key TID

Player id owning the target area

kingdom_id pydantic-field

kingdom_id: int = 0

Wire key KID

Kingdom id

source_id pydantic-field

source_id: int = -1

Wire key SID

Player id owning the source area

owner_id pydantic-field

owner_id: int = -1

Wire key OID

Player id owning the movement

horse_booster pydantic-field

horse_booster: EnumOrInt['Horse'] | None = None

Wire key HBW

The horse booster; None for none or when paid with feathers

target_area pydantic-field

target_area: MovementArea | None = None

Wire key TA

Target area

source_area pydantic-field

source_area: MovementArea | None = None

Wire key SA

Source area

is_returning property

is_returning: bool

Client: BasicMapmovementVO.isReturnHome.

MovementArmy pydantic-model

Bases: BasePayload

A visible army: three flanks plus the courtyard wave, each {Unit or Tool: amount}.

Client: CastleCompactArmyVO.parseSimpleArmy / parseYardWave (bundle lines 67525, 67529) read each into a UnitInventoryDictionary, which adds up an id sent twice.

Fields:

left pydantic-field

left: WodAmounts

Wire key L

Left flank

middle pydantic-field

middle: WodAmounts

Wire key M

Middle

right pydantic-field

right: WodAmounts

Wire key R

Right flank

courtyard pydantic-field

courtyard: WodAmounts

Wire key RW

Courtyard (yard) wave

MovementUnitInfo pydantic-model

Bases: BasePayload

Commander and wait details: a wrapper's UM.

Client: BasicMapmovementVO.parseUnitMovement (bundle line 19385), which hands L to LordFactory.createLord (bundle line 26399).

Fields:

Validators:

commander pydantic-field

commander: Commander | None = None

Wire key L

Commander leading the army; None when there is none

wait_passed pydantic-field

wait_passed: int = 0

Wire key PWD

Seconds of the wait at the target already passed

wait_total pydantic-field

wait_total: int = 0

Wire key TWD

Seconds the army waits at its target

advisor_type pydantic-field

Wire key AAT

The attack advisor that sent the attack

advisor_movement_count pydantic-field

advisor_movement_count: int = 0

Wire key AAC

Attacks in the advisor series

advisor_movement_number pydantic-field

advisor_movement_number: int = 0

Wire key AAN

This attack's place in the series

advisor_is_last pydantic-field

advisor_is_last: int = 0

Wire key AAL

1 on the series' last attack

MovementMarket pydantic-model

Bases: BasePayload

A market transport's cargo: a wrapper's MM.

Client: MarketMapmovementVO.parse_MM (bundle line 43706), which reads G with CollectableParserS2CParamList.createList (bundle line 40560)

Fields:

carriages pydantic-field

carriages: int = 0

Wire key C

Market carriages used

goods pydantic-field

goods: CollectableRows = ()

Wire key G

Goods carried

MovementSpy pydantic-model

Bases: BasePayload

A spy mission's details: a wrapper's S.

Client: SpyMapmovementVO.parseSpyInfo (bundle line 43748), which reads each through int().

Fields:

spy_type pydantic-field

spy_type: ClientInt = 0

Wire key ST

Kind of spy mission, a SpyType value

accuracy_or_damage pydantic-field

accuracy_or_damage: ClientInt = 0

Wire key SA

Accuracy percent, or damage percent for sabotage

spy_count pydantic-field

spy_count: ClientInt = 0

Wire key SC

Spies sent

risk pydantic-field

risk: ClientInt = 0

Wire key SR

Risk of being caught, percent

spy_type_enum property

spy_type_enum: SpyType | None

spy_type as a SpyType, None for a value the client does not define.

is_sabotage property

is_sabotage: bool

MovementWrapper pydantic-model

Bases: BasePayload

One entry of gam's M list, and the A of an abr/asr push or an mcm reply.

Which of the optional blocks are present depends on the movement type and on what the receiving player may see. Keys this model does not name are kept, as on every payload.

Client: MapmovementFactory.parseMapMovement and the loadFromParamObject of each movement class.

Fields:

Validators:

movement pydantic-field

movement: MovementRecord

Wire key M

The movement record

full_army pydantic-field

full_army: MovementArmy | None = None

Wire key FA

The full army, preferred over army

army pydantic-field

army: MovementArmy | None = None

Wire key GA

Army

army_size pydantic-field

army_size: int | None = None

Wire key GS

Estimated army size when the army is hidden

unit_info pydantic-field

unit_info: MovementUnitInfo | None = None

Wire key UM

Commander and wait details

attack_type pydantic-field

attack_type: int | None = None

Wire key ATT

AttackType value of an attack

is_shadow pydantic-field

is_shadow: bool = False

Wire key SM

Shadow movement

force_cancelable pydantic-field

force_cancelable: bool = False

Wire key FC

The movement can be force-cancelled

support_tools pydantic-field

support_tools: SupportToolSlots = ()

Wire key AST

Support tools sent along, as the attack sent them; None for an empty slot

auto_skip_cooldown_type pydantic-field

auto_skip_cooldown_type: EnumOrInt[AutoSkipCooldownType] = AutoSkipCooldownType.OFF

Wire key ASCT

How the target's cooldown is skipped on arrival

travel_units pydantic-field

travel_units: WodAmounts

Wire key A

Units and tools of a travel movement

travel_goods pydantic-field

travel_goods: CollectableRows = ()

Wire key G

Loot a travel movement carries (ArmyTravelMapMovementVO.loadFromParamObject, bundle line 26798)

market pydantic-field

market: MovementMarket | None = None

Wire key MM

Market transport cargo

spy pydantic-field

spy: MovementSpy | None = None

Wire key S

Spy mission details

visible_army property

visible_army: MovementArmy | None

FA if sent, else GA, as the client picks.

MovementOwner pydantic-model

Bases: BasePayload

An owner record: one entry of O in gam, abr and asr.

Client: WorldMapOwnerInfoVO.fillFromParamObject.

Fields:

Validators:

player_id pydantic-field

player_id: int

Wire key OID

Player id

name pydantic-field

name: str = ''

Wire key N

crest pydantic-field

crest: OwnerCrest | None = None

Wire key E

level pydantic-field

level: int = 0

Wire key L

legend_level pydantic-field

legend_level: int = 0

Wire key LL

beginner_protection_seconds pydantic-field

beginner_protection_seconds: int = -1

Wire key RNP

-1 when not protected

honor pydantic-field

honor: int = 0

Wire key H

might pydantic-field

might: int = 0

Wire key MP

top_x pydantic-field

top_x: int = -1

Wire key TOPX

Top-ranking placement, -1 for none

is_ruin pydantic-field

is_ruin: bool = False

Wire key R

The owner record is flagged as a ruin

alliance_id pydantic-field

alliance_id: int = -1

Wire key AID

-1 for no alliance

alliance_rank pydantic-field

alliance_rank: int = 0

Wire key AR

alliance_name pydantic-field

alliance_name: str = ''

Wire key AN

is_searching_alliance pydantic-field

is_searching_alliance: bool = False

Wire key SA

peace_seconds pydantic-field

peace_seconds: int = 0

Wire key RPT

Remaining peace time

castle_positions pydantic-field

castle_positions: list[OwnerCastlePosition]

Wire key AP

village_positions pydantic-field

village_positions: list[OwnerCastlePosition]

Wire key VP

has_premium pydantic-field

has_premium: bool = False

Wire key PF

has_vip pydantic-field

has_vip: bool = False

Wire key VF

is_dummy pydantic-field

is_dummy: bool = False

Wire key DUM

The owner record is a dummy

achievement_points pydantic-field

achievement_points: int = 0

Wire key AVP

relocation_seconds pydantic-field

relocation_seconds: int = 0

Wire key RRD

Seconds until a relocation ends

faction pydantic-field

faction: OwnerFaction | None = None

Wire key FN

title_prefix_id pydantic-field

title_prefix_id: EnumOrInt['Title'] | None = None

Wire key PRE

The title shown before the name; None for none

title_suffix_id pydantic-field

title_suffix_id: EnumOrInt['Title'] | None = None

Wire key SUF

The title shown after the name; None for none

via_refer_a_friend pydantic-field

via_refer_a_friend: bool = False

Wire key IRF

GetMovementsResponse pydantic-model

Bases: BaseResponse

Response containing active movements.

Command: gam Payload: {"M": [wrapper, ...], "O": [owner record, ...]}

Client: CastleArmyData.parse_GAM.

Fields:

movements pydantic-field

movements: list[MovementWrapper]

Wire key M

Movement wrappers

owners pydantic-field

Wire key O

Owner records for every player the movements name

CancelMovementRequest pydantic-model

Bases: BaseRequest

Recall one of your movements, turning it back home.

Command: mcm Payload: {"MID": movement_id}

The client sends it from its retreat dialog, from "send home" for your supports stationed at an area (one per support), and from the attack advisor's cancel dialog (one per advisor attack). The retreat button is enabled only where the movement's class allows it:

  • attacks (every attack type but alien attacks, which never): your own, heading to the target, from level 5, and no more than 600 seconds after leaving, unless the movement is force-cancelable (FC), attacks nomads while the alliance nomad invasion is not running, or attacks a faction tower
  • supports: your own, in either direction
  • travel between your areas: your own, heading to the target, from a source area that is still yours
  • spies, market transports and occupations: your own, heading to the target
  • treasure hunts and plague monks: never

The movement list also disables it while the source area is under conquer control. The advisor's overview cancels every advisor attack that is not the series' last, returning ones too, without these checks. The server's refusals include INVALID_STARTAREA_FOR_CANCEL_MOVEMENT (189); the client handles none itself and shows its generic server error.

Client: C2SCancelMovementVO (bundle line 33049); sent by CastleAskRetreatDialog.onClick (bundle line 33234), SupportOverviewDialog.onClickRetreat (bundle line 33984) and AdvisorAttackOverviewCancelDialog.cancelAttacks (bundle line 21965), fed by AdvisorAttackOverviewDialog.getCancellableAdvisorMovements (bundle line 57303); canBeRetreated on ArmyAttackMapmovementVO (bundle line 14379), ArmyTravelMapMovementVO (bundle line 26818), SupportDefenceMapmovementVO (bundle line 43782), SpyMapmovementVO (bundle line 43759), MarketMapmovementVO (bundle line 43707), SiegeMapmovementVO (bundle line 33183) and the always-false ones (bundle lines 33079, 43733, 43805); ArmyAttackMapmovementVO.tooLateToBeRetreated (bundle line 14382) with TravelConst.MAX_FALLBACK_TIME (ggs.dll line 19828); ClientConstLevelRestrictions.MIN_LEVEL_RETREAT_MOVEMENTS (bundle line 2139) through CastleUserData.hasLevelFor (bundle line 10027); AMovementRenderStrategy.initRetreatButton (bundle line 14347); MapmovementFactory.parseMapMovement (bundle line 133793)

Fields:

movement_id pydantic-field

movement_id: int

Wire key MID

The movement to recall

accepts_reply

accepts_reply(payload: Any) -> bool

Whether an mcm reply is the movement this recalled: its A.M.MID, when sent, is MID.

Client: MCMCommand.executeCommand (bundle line 126041) reads the recalled movement from A.

CancelMovementResponse pydantic-model

Bases: BaseResponse

The reply to a recall: the movement as it now stands.

Command: mcm Payload: {"A": wrapper}

A is read as one gam entry, replacing the tracked movement. The client reads it unconditionally, so a reply without one is malformed.

Client: MCMCommand.executeCommand (bundle line 126041), which hands [A] to CastleArmyData.parseMapMovementArray (bundle line 133626)

Fields:

movement pydantic-field

movement: MovementWrapper

Wire key A

The recalled movement