Skip to content

Messages

The mailbox, its reports and battle reports behind client.messages.

messages.service

The mailbox, mail and battle reports.

Classes:

Name Description
MessagesService

The mailbox, mail and battle reports.

Attributes:

Name Type Description
BattleReportDetail

BattleReportDetail module-attribute

BattleReportDetail = Literal['short', 'middle', 'full']

MessagesService

MessagesService(client: EmpireClient)

Bases: BaseService

The mailbox, mail and battle reports.

Accessible via client.messages. The mailbox is kept as the client keeps it: the login data's sne section fills it, each sne push row replaces the message with its id or is added, and dms and ams answers remove or archive.

Client: CastleMessageData (bundle lines 134955-134991); GBDCommand.exec (bundle line 129381) and SNECommand.exec (bundle line 125499) both call parse_SNE (bundle line 134961)

Methods:

Name Description
on_new_messages

Call callback with the login data's sne section and each sne push, after mailbox is updated.

read

Read a message's body.

mark_read

Mark a message read. The client has no handler for an answer, so none is waited for.

archive

Move a message to the archive, which holds 20.

delete

Delete one message.

delete_many

Delete several messages at once.

send_message

Send a message to a player.

get_battle_report

Read a battle report from the mailbox.

forward_battle_report

Forward a battle report to other players.

Attributes:

Name Type Description
mailbox list[MessageInfo]

The messages from the login data and the pushes since, in the order they first arrived.

mailbox property

mailbox: list[MessageInfo]

The messages from the login data and the pushes since, in the order they first arrived.

on_new_messages

on_new_messages(callback: Callable[[SystemNotificationEvent], object]) -> None

Call callback with the login data's sne section and each sne push, after mailbox is updated.

Removing a callback not registered is a no-op.

Unregister a callback with on_new_messages.remove(callback).

read

read(message_id: int, timeout: float = 5.0) -> ReadMessageResponse

Read a message's body.

Parameters:

Name Type Description Default
message_id int

The message's MessageInfo.message_id, e.g. from mailbox

required

Raises:

Type Description
MessageUnavailableError

error 66 (no such message) or 225 (too old to read), which the client answers alike (RMSCommand.executeCommand, bundle line 125454)

CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

mark_read

mark_read(message_id: int) -> None

Mark a message read. The client has no handler for an answer, so none is waited for.

The message in mailbox is marked read too, as the client marks its own copy.

archive

archive(message_id: int, timeout: float = 5.0) -> ArchiveMessageResponse

Move a message to the archive, which holds 20.

Raises:

Type Description
CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

delete

delete(message_id: int, timeout: float = 5.0) -> list[int]

Delete one message.

Returns:

Type Description
list[int]

The ids the server reports deleted

Raises:

Type Description
CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

delete_many

delete_many(message_ids: list[int], timeout: float = 5.0) -> list[int]

Delete several messages at once.

Returns:

Type Description
list[int]

The ids the server reports deleted

Raises:

Type Description
CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

send_message

send_message(receiver_name: str, subject: str, text: str, timeout: float = 5.0) -> None

Send a message to a player.

The limits are the client's: a subject of at most 20 characters, and a text of at most 1300 with at least 3 that are not whitespace (ClientConstMessage.isValidText, bundle line 44214). The receiver's name field takes 15 characters, more on special and crossplay servers, so its length is left to the server.

Raises:

Type Description
ValueError

the receiver is empty, or the subject or text breaks a limit

CommandError

the server refused the message, e.g. error 68 (no such receiver), 69 (the receiver ignores you) or 70 (a bad word)

EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

get_battle_report

get_battle_report(message_id: int, detail: BattleReportDetail = 'short', timeout: float = 5.0) -> BattleReport

Read a battle report from the mailbox.

The short log comes first, a bls with IM 0 as the client always sends it; the middle and detail logs are then asked for by its log_id, one request each, as the client asks when its battle report dialogs open them.

Parameters:

Name Type Description Default
message_id int

A battle log's MessageInfo.message_id: one in mailbox whose is_battle_log is true

required
detail BattleReportDetail

"short" for the overview (players, loot, area, commanders), "middle" adds the waves per flank, "full" adds every unit per flank and wave

'short'

Raises:

Type Description
ValueError

detail is none of the three

MessageUnavailableError

error 66 (no such message) or 225 (too old to read) for the short log, which the client shows as a log that does not exist (BLSCommand, bundle line 125196)

CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

Client: CastleMessageData.getBattleLogShort, getBattleLogMiddle and getBattleLogDetailed (bundle lines 134905-134907)

forward_battle_report

forward_battle_report(message_id: int, player_ids: list[int], timeout: float = 5.0) -> None

Forward a battle report to other players.

Parameters:

Name Type Description Default
message_id int

A battle log's MessageInfo.message_id

required
player_ids list[int]

The recipients. The client offers the members of your alliance other than you, AllianceMember.player_id from client.alliance.get_local_members()

required

Raises:

Type Description
ValueError

no recipients

CommandError

the server refused; the client handles no error specially

EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

Client: CastleForwardMessageDialog.sendMessage (bundle line 60740), MFBCommand.executeCommand (bundle line 125396)

messages.models.mailbox

Mailbox, mail and report protocol models.

Commands:

  • sne: New or changed mailbox messages (push)
  • rms, mmr, ams, dms, sms: Read, mark read, archive, delete and send mail
  • bsd: Spy report (the client's S2C_SPY_LOG_DETAIL)
  • mfs: Forward a spy report

Classes:

Name Description
MessageInfo

One mailbox message: an entry of sne's MSG.

SpyLogHeader

What a spy log's header says: subtypeSpy+subtypeResult+areaType#kingdomID+ownerID+areaName.

BattleLogHeader

What a battle log's header says.

PatchNoteHeader

What a patch note's header says: teaserId+patchNoteId+collected.

EventAnnouncementHeader

What an event announcement's header says: subtype#eventId#durationTS.

SystemNotificationEvent

New mailbox messages, pushed by the server.

GetSpyReportRequest

Request a spy report from the mailbox.

ForwardSpyLogRequest

Forward a spy report to other players.

SpyReportArea

The spied area, a spy report's AI block.

SpyReportResponse

A spy report.

ReadMessageRequest

Read a message's body.

ReadMessageResponse

A message's body.

MarkMessageReadRequest

Mark a message read.

ArchiveMessageRequest

Move a message to the archive.

ArchiveMessageResponse

The archived message.

DeleteMessageRequest

Delete one message.

DeleteMessagesRequest

Delete several messages.

DeleteMessagesResponse

The deleted messages.

SendMessageRequest

Send a message to a player.

SendMessageResponse

The answer to sending a message.

Functions:

Name Description
repair_header

A message header decoded as the client decodes it before reading it.

Attributes:

Name Type Description
SPY_VALIDITY

SpyConst.SPY_VALIDITY (dll line 19739): seconds a spy report's army stays current.

MAX_MAILBOX_SIZE

Messages the mailbox holds. Client: MessageConst.MAX_MAILBOX_SIZE (dll line 19516)

MAX_MAILBOX_ARCHIVE_SIZE

Archived messages the mailbox holds. Client: MessageConst.MAX_MAILBOX_ARCHIVE_SIZE (dll line 19516)

MAX_MAILBOX_BATTLE_AND_SPY_REPORTS

Battle and spy reports the mailbox holds. Client: MAX_MAILBOX_BATTLE_AND_SPY_REPORTS (dll line 19516)

MAX_SUBJECT_LENGTH

Characters the client's subject field takes. Client: MessageConst.MAX_LENGTH_SUBJECT (dll line 19516)

MAX_TEXT_LENGTH

Characters the client's text field takes. Client: MessageConst.MAX_LENGTH_TEXT (dll line 19516)

MIN_TEXT_LENGTH

Non-whitespace characters a text needs. Client: ClientConstMessage.MINIMUM_LENGTH (bundle line 44219)

logger module-attribute

logger = logging.getLogger(__name__)

SPY_VALIDITY module-attribute

SPY_VALIDITY = 172800

SpyConst.SPY_VALIDITY (dll line 19739): seconds a spy report's army stays current.

MAX_MAILBOX_SIZE module-attribute

MAX_MAILBOX_SIZE = 50

Messages the mailbox holds. Client: MessageConst.MAX_MAILBOX_SIZE (dll line 19516)

MAX_MAILBOX_ARCHIVE_SIZE module-attribute

MAX_MAILBOX_ARCHIVE_SIZE = 20

Archived messages the mailbox holds. Client: MessageConst.MAX_MAILBOX_ARCHIVE_SIZE (dll line 19516)

MAX_MAILBOX_BATTLE_AND_SPY_REPORTS module-attribute

MAX_MAILBOX_BATTLE_AND_SPY_REPORTS = 25

Battle and spy reports the mailbox holds. Client: MAX_MAILBOX_BATTLE_AND_SPY_REPORTS (dll line 19516)

MAX_SUBJECT_LENGTH module-attribute

MAX_SUBJECT_LENGTH = 20

Characters the client's subject field takes. Client: MessageConst.MAX_LENGTH_SUBJECT (dll line 19516)

MAX_TEXT_LENGTH module-attribute

MAX_TEXT_LENGTH = 1300

Characters the client's text field takes. Client: MessageConst.MAX_LENGTH_TEXT (dll line 19516)

MIN_TEXT_LENGTH module-attribute

MIN_TEXT_LENGTH = 3

Non-whitespace characters a text needs. Client: ClientConstMessage.MINIMUM_LENGTH (bundle line 44219)

MessageInfo pydantic-model

Bases: BasePayload

One mailbox message: an entry of sne's MSG.

Client: AMessageVO.loadFromParamArray (bundle line 3808), reached through CastleMessageData.parse_SNE and CastleMessageFactory.parseMessage (bundle line 135102).

Fields:

Validators:

message_id pydantic-field

message_id: int

Message id

message_type pydantic-field

message_type: EnumOrInt[MessageType]

Message type; a plain int for a type the client does not define

header pydantic-field

header: str = ''

Message header; its layout depends on message_type

sender_name pydantic-field

sender_name: str = ''

Sender's name

sender_id pydantic-field

sender_id: ClientInt = -1

Sender's player id

seconds_since_sent pydantic-field

seconds_since_sent: int | float | None = 0

Seconds since the message was sent; None when the row has no number for it

is_read pydantic-field

is_read: bool = False

The message has been read

is_archived pydantic-field

is_archived: bool = False

The message is archived

is_forwarded pydantic-field

is_forwarded: bool = False

The message was forwarded

decoded_header property

decoded_header: str

header decoded as the client decodes it before reading it; see repair_header.

subject property

subject: str | None

The subject of player mail, the alliance newsletter and every type the client reads as mail.

None for the types whose header has a layout of its own (reports, notices, offers).

Client: MessageUserVO.parseMessageHeader and parseSubject (bundle line 61340) turn the decoded header's carriage returns and newlines into spaces

is_spy_log property

is_spy_log: bool

A spy log: a message about a player's area (SPY_PLAYER) or an NPC's (SPY_NPC).

is_battle_log property

is_battle_log: bool

A battle log: a battle report, read with client.messages.get_battle_report.

spy_log_header

spy_log_header() -> SpyLogHeader | None

This message's spy log header, or None when it is no readable spy log.

battle_log_header

battle_log_header() -> BattleLogHeader | None

This message's battle log header, or None when it is no readable battle log.

patch_note_header

patch_note_header() -> PatchNoteHeader | None

This message's patch note header, or None when it is no patch note.

event_announcement_header

event_announcement_header() -> EventAnnouncementHeader | None

This message's event announcement header, or None when it is no event announcement.

SpyLogHeader dataclass

SpyLogHeader(log_type: SpyLogType | None, result: LogResult | None, area_type: int | None = None, kingdom_id: int | None = None, owner_id: int | None = None, area_name: str | None = None, area_id: int | None = None)

What a spy log's header says: subtypeSpy+subtypeResult+areaType#kingdomID+ownerID+areaName.

The part after # is areaName+areaID instead for a sabotage or plague monk log about a player's area that ended in the attacker's success or the defender's failure. A number the header lacks, or that parseInt cannot read, is None; so is a subtype or result the client defines no constant for.

It is read from the header as repair_header decodes it, as AMessageVO.loadFromParamArray (bundle line 3809) decodes every header before parsing it.

Client: CastleMessageFactory.parseMessage (bundle line 135102), MessageSpyPlayerVO.parseMessageHeader (bundle lines 137651-137653), MessageSpyNpcVO.parseMessageHeader (bundle lines 137635-137636)

Methods:

Name Description
from_message

The header of a spy log, or None when the message is no spy log or its header has no # part.

Attributes:

Name Type Description
log_type SpyLogType | None
result LogResult | None
area_type int | None
kingdom_id int | None
owner_id int | None
area_name str | None
area_id int | None
spies_lost bool

The mission failed: the attacker failed or the defender succeeded.

has_army_report bool

A military mission that succeeded, whose report lists the spied army.

log_type instance-attribute

log_type: SpyLogType | None

result instance-attribute

result: LogResult | None

area_type class-attribute instance-attribute

area_type: int | None = None

kingdom_id class-attribute instance-attribute

kingdom_id: int | None = None

owner_id class-attribute instance-attribute

owner_id: int | None = None

area_name class-attribute instance-attribute

area_name: str | None = None

area_id class-attribute instance-attribute

area_id: int | None = None

spies_lost property

spies_lost: bool

The mission failed: the attacker failed or the defender succeeded.

Client: AMessageSpyVO.isFailedSpyLog (bundle line 40204)

has_army_report property

has_army_report: bool

A military mission that succeeded, whose report lists the spied army.

Client: MessageSpyPlayerVO.hasDetailedSpyLog (bundle line 137672), MessageSpyNpcVO.hasDetailedSpyLog (bundle line 137642)

from_message classmethod

from_message(message: MessageInfo) -> SpyLogHeader | None

The header of a spy log, or None when the message is no spy log or its header has no # part.

MessageSpyPlayerVO reads nothing from a header without the part after #, and MessageSpyNpcVO throws on one.

BattleLogHeader dataclass

BattleLogHeader(area_type: int | None, attack_type: BattleLogAttackType | None, result: LogResult | None, treasure_map_id: int | None = -1, treasure_map_area_type: int | None = -1, kingdom_id: int | None = None, owner_id: int | None = None, area_name: str = '')

What a battle log's header says.

The header is areaType+subtypeAttack+subtypeResult[+tMapID[+tMapAreaType]], then #kingdomID+ownerID[+areaName].

A number the header lacks, or that parseInt cannot read, is None; the two treasure map numbers are -1 when the header has none, as the client keeps them. An attack type or result the client defines no constant for is None.

It is read from the header as repair_header decodes it.

Client: CastleMessageFactory.parseMessage (bundle line 135117), MessageBattleLogVO.parseMessageHeader (bundle lines 135579-135580), MessageConst.SUBTYPE_META_DATA_SPLITTER (dll line 19516)

Methods:

Name Description
from_message

The header of a battle log, or None when the message is no battle log or its header has no # part.

Attributes:

Name Type Description
area_type int | None
attack_type BattleLogAttackType | None
result LogResult | None
treasure_map_id int | None
treasure_map_area_type int | None
kingdom_id int | None
owner_id int | None
area_name str

area_type instance-attribute

area_type: int | None

attack_type instance-attribute

attack_type: BattleLogAttackType | None

result instance-attribute

result: LogResult | None

treasure_map_id class-attribute instance-attribute

treasure_map_id: int | None = -1

treasure_map_area_type class-attribute instance-attribute

treasure_map_area_type: int | None = -1

kingdom_id class-attribute instance-attribute

kingdom_id: int | None = None

owner_id class-attribute instance-attribute

owner_id: int | None = None

area_name class-attribute instance-attribute

area_name: str = ''

from_message classmethod

from_message(message: MessageInfo) -> BattleLogHeader | None

The header of a battle log, or None when the message is no battle log or its header has no # part.

MessageBattleLogVO throws on a header without the part after #.

PatchNoteHeader dataclass

PatchNoteHeader(teaser_id: int | None, patch_note_id: int | None, collected: bool = False)

What a patch note's header says: teaserId+patchNoteId+collected.

A number the header lacks, or that parseInt cannot read, is None.

Client: MessageChangelistVO.parseMessageHeader (bundle line 136053), reached through CastleMessageFactory.parseMessage (bundle line 135102)

Methods:

Name Description
from_message

The header of a patch note, or None when the message is no patch note.

Attributes:

Name Type Description
teaser_id int | None
patch_note_id int | None
collected bool

teaser_id instance-attribute

teaser_id: int | None

patch_note_id instance-attribute

patch_note_id: int | None

collected class-attribute instance-attribute

collected: bool = False

from_message classmethod

from_message(message: MessageInfo) -> PatchNoteHeader | None

The header of a patch note, or None when the message is no patch note.

EventAnnouncementHeader dataclass

EventAnnouncementHeader(event: Event | int | None, end_timestamp: int | float | None)

What an event announcement's header says: subtype#eventId#durationTS.

event is None when the header has no event part; end_timestamp is None when it has no end the client can compare, and such an announcement never expires, as for the client.

Client: MessageEventAnnouncementVO.parseMessageHeader (bundle line 136509), split on MessageConst.SUBTYPE_META_DATA_SPLITTER (dll line 19516)

Methods:

Name Description
from_message

The header of an event announcement, or None when the message is no event announcement.

is_expired

Whether the announced event's time is over, by the wall clock (now in unix seconds).

Attributes:

Name Type Description
event Event | int | None
end_timestamp int | float | None

event instance-attribute

event: Event | int | None

end_timestamp instance-attribute

end_timestamp: int | float | None

from_message classmethod

from_message(message: MessageInfo) -> EventAnnouncementHeader | None

The header of an event announcement, or None when the message is no event announcement.

is_expired

is_expired(now: float | None = None) -> bool

Whether the announced event's time is over, by the wall clock (now in unix seconds).

Client: EventAnnouncementData.isAvailable (bundle line 53097), now / 1000 > durationTS

SystemNotificationEvent pydantic-model

Bases: BaseResponse

New mailbox messages, pushed by the server.

Command: sne

Client: SNECommand.exec (bundle line 125499), CastleMessageData.parse_SNE (bundle line 134961).

Fields:

Validators:

messages pydantic-field

messages: list[MessageInfo]

Wire key MSG

The new messages

GetSpyReportRequest pydantic-model

Bases: BaseRequest

Request a spy report from the mailbox.

Command: bsd Payload: {"MID": message_id}

Client: C2SGetSpyLog (bundle line 139994), sent by CastleSpyData.getSpyLog (bundle line 139963) for every spy log opened

Fields:

message_id pydantic-field

message_id: int

Wire key MID

The report's message id: MessageInfo.message_id of a spy log, or SpyResult.message_id

ForwardSpyLogRequest pydantic-model

Bases: BaseRequest

Forward a spy report to other players.

Command: mfs Payload: {"MID": message_id, "PID": [player_id, ...]}

The server answers error 167 (ALREADY_HAS_SPY_REPORT) when a recipient has the report already, which the client takes as done.

Client: C2SForwardSpyLogVO (bundle lines 128594-128595), whose constructor sets MID before PID; MFSCommand.executeCommand (bundle line 125409)

Fields:

message_id pydantic-field

message_id: int

Wire key MID

The report's message id: MessageInfo.message_id of a spy log, or SpyResult.message_id

player_ids pydantic-field

player_ids: list[int]

Wire key PID

Recipients, e.g. your alliance's other members: AllianceMember.player_id from client.alliance.get_local_members()

SpyReportArea pydantic-model

Bases: BasePayload

The spied area, a spy report's AI block.

Which of the optional keys the client reads depends on the area's type: RT for an outpost, village, monument or resource isle, DL for a dungeon or an NPC owner, SPC for a faction area, DAR and DDCID for a daimyo castle or township, ACVC and AID for an alliance battle ground tower.

Client: InteractiveMapobjectVO.parseAreaInfoBattleLog (bundle line 3638) and its overrides: OutpostMapobjectVO (18787), DaimyoCastleMapObjectVO (19652), FactionInteractiveMapobjectVO (21579), MonumentMapobjectVO (21656), DaimyoTownshipMapObjectVO (21739), DungeonMapobjectVO (22058), VillageMapobjectVO (22627), ABGAllianceTowerMapobjectVO (32282), ResourceIsleMapobjectVO (34610), AAlienInvasionMapobjectVO (41548); CastleSpyLogVO.parseSpyLog (bundle line 60579) for DP, DL and RT

Fields:

area_type pydantic-field

area_type: MapItemType

Wire key AT

The area's type

x pydantic-field

x: int

Wire key X

Map x

y pydantic-field

y: int

Wire key Y

Map y

kingdom pydantic-field

kingdom: Kingdom = Kingdom.GREEN

Wire key K

The area's kingdom

name pydantic-field

name: str = ''

Wire key N

The area's name; empty for an NPC area the game names

map_id pydantic-field

map_id: int | None = None

Wire key MID

Map id

skin_id pydantic-field

skin_id: ClientInt = 0

Wire key EID

Unique id of the castle skin item, 0 for none

owner_id pydantic-field

owner_id: int | None = None

Wire key DP

The owner's player id, below 0 for an NPC; for a faction invasion camp, its dungeon type

level pydantic-field

level: int | None = None

Wire key DL

The NPC's level, when owner_id is below 0

keep_level pydantic-field

keep_level: ClientInt = 0

Wire key KL

Keep level

wall_level pydantic-field

wall_level: ClientInt = 0

Wire key WL

Wall level

gate_level pydantic-field

gate_level: ClientInt = 0

Wire key GL

Gate level

tower_level pydantic-field

tower_level: ClientInt = 0

Wire key TL

Tower level

moat_level pydantic-field

moat_level: ClientInt = 0

Wire key ML

Moat level

area_subtype pydantic-field

area_subtype: int | None = None

Wire key RT

Outpost type, village type or monument type, or a resource isle's isle id

special_camp_id pydantic-field

special_camp_id: int | None = None

Wire key SPC

A faction area's special camp id

daimyo_rank pydantic-field

daimyo_rank: int | None = None

Wire key DAR

A daimyo castle's or township's rank

daimyo_camp_id pydantic-field

daimyo_camp_id: int | None = None

Wire key DDCID

A daimyo castle's or township's difficulty camp id

victory_count pydantic-field

victory_count: int | None = None

Wire key ACVC

An alliance tower's victory count

alliance_id pydantic-field

alliance_id: int | None = None

Wire key AID

The alliance holding an alliance tower

SpyReportResponse pydantic-model

Bases: BaseResponse

A spy report.

Command: bsd Payload:

{"MID": message_id, "CID": area_object_id, "SID": spy_owner_id, "PID": owner_id,
 "SC": spies, "GC": guards, "SA": accuracy_or_damage, "SR": risk,
 "S": [left, middle, right, keep, stronghold, support, reserve], "AS": seconds_since_spy,
 "B": {castellan}, "LS": [legend_skill_id, ...], "R": [collectables], "RS": cooldown_seconds,
 "OI": {owner record}, "SO": {owner record}, "AI": {area}, "DAR": daimyo_rank}

An AI block whose AT, X or Y is missing or not one the library knows reads as area None, where the client builds an empty map object or fails: deliberately more lenient, so the rest of the report still parses.

A report with no army lists no defenders because the mission brought none back, not because the area is empty. The server answers error 130 (NO_SPY_DATA) or 66 (NO_SUCH_MESSAGE) when there is no report, which the client shows as "no spy data".

Client: BSDCommand.executeCommand (bundle lines 125223-125226), CastleSpyLogVO.parseSpyLog (bundle line 60579), CastleSpyArmyInfoVO.parseArmyInfo (bundle line 30699)

Fields:

Validators:

message_id pydantic-field

message_id: int | None = None

Wire key MID

The report's message id

area_object_id pydantic-field

area_object_id: ClientInt = 0

Wire key CID

The spied area's object id, -1 when it has none

spy_owner_id pydantic-field

spy_owner_id: ClientInt = 0

Wire key SID

Player id of whoever sent the spies

owner_id pydantic-field

owner_id: ClientInt = 0

Wire key PID

Player id of the spied area's owner; below 0 for an NPC

spy_count pydantic-field

spy_count: ClientInt = 0

Wire key SC

Spies sent

guard_count pydantic-field

guard_count: ClientInt = 0

Wire key GC

Guards at the spied area

accuracy_or_damage pydantic-field

accuracy_or_damage: ClientInt = 0

Wire key SA

Accuracy percent, or damage percent for sabotage and plague monks

risk pydantic-field

risk: ClientInt = 0

Wire key SR

Risk of being caught, percent

army pydantic-field

army: SpyArmyBlock = None

Wire key S

The spied defenders by the position they hold; None when the report has no army

seconds_since_spy pydantic-field

seconds_since_spy: int = -1

Wire key AS

Seconds between the spying and this reply; -1 without an army

defending_castellan pydantic-field

defending_castellan: Castellan | None = None

Wire key B

The castellan defending the spied area, without its equipment; None when there is none

legend_skill_ids pydantic-field

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

Wire key LS

The defender's legend skills

resources pydantic-field

resources: CollectableRows = ()

Wire key R

What an economy mission saw (CastleSpyLogVO.parseSpyLog reads it with CollectableParserS2CParamList.createList, bundle line 60579)

dungeon_cooldown_seconds pydantic-field

dungeon_cooldown_seconds: int | None = None

Wire key RS

Seconds until the spied dungeon can be attacked again; 0 or less when it can be now

owner pydantic-field

owner: MapObject | None = None

Wire key OI

The spied area owner's record

spy_owner pydantic-field

spy_owner: MapObject | None = None

Wire key SO

The spy owner's record

area pydantic-field

area: SpyReportArea | None = None

Wire key AI

The spied area

has_army property

has_army: bool

Whether the report lists the spied army.

remaining_validity_seconds property

remaining_validity_seconds: int

Seconds the army stays current, counted from this reply; 0 without an army.

Client: CastleSpyArmyInfoVO.remainingSpyInfoTime (bundle line 30702), SpyConst.SPY_VALIDITY (dll line 19739)

is_fresh property

is_fresh: bool

Whether the army was still current when the report was read.

ReadMessageRequest pydantic-model

Bases: BaseRequest

Read a message's body.

Command: rms Payload: {"MID": message_id}

Client: C2SReadMessagesVO (bundle line 19620), sent by CastleMessageData.getBodyForTextMessage (bundle line 134908)

Fields:

message_id pydantic-field

message_id: int

Wire key MID

The message's MessageInfo.message_id

ReadMessageResponse pydantic-model

Bases: BaseResponse

A message's body.

Command: rms

Client: RMSCommand.executeCommand (bundle line 125455) hands MTXT and ABI on; CastleReadDialog.displayCurrentMessage (bundle line 138112) decodes MTXT as chat text. Only the attack order dialogs read ABI, through CastleBookmarkData.parseBookmarkObject (bundle lines 53040, 91271, 33427-33429)

Fields:

Validators:

body pydantic-field

body: str | None = None

Wire key MTXT

The body, still encoded

bookmark pydantic-field

bookmark: Bookmark | None = None

Wire key ABI

An alliance attack order's bookmark; None for other messages

decoded_body property

decoded_body: str

The body decoded as chat text.

MarkMessageReadRequest pydantic-model

Bases: BaseRequest

Mark a message read.

Command: mmr Payload: {"MID": message_id}

The client has no handler for an mmr answer.

Client: C2SMarkMessageReadVO (bundle line 90970), sent when a message is opened (bundle line 90961)

Fields:

message_id pydantic-field

message_id: int

Wire key MID

The message's MessageInfo.message_id

ArchiveMessageRequest pydantic-model

Bases: BaseRequest

Move a message to the archive.

Command: ams Payload: {"MID": message_id}

Client: C2SArchiveMessageVO (bundle line 135032), sent by CastleMessageData.archiveMessage (bundle line 134988)

Fields:

message_id pydantic-field

message_id: int

Wire key MID

The message's MessageInfo.message_id

ArchiveMessageResponse pydantic-model

Bases: BaseResponse

The archived message.

Command: ams

Client: AMSCommand.executeCommand (bundle line 125141), CastleMessageData.parseAMS (bundle line 134962)

Fields:

message_id pydantic-field

message_id: int | None = None

Wire key MID

The archived message

DeleteMessageRequest pydantic-model

Bases: BaseRequest

Delete one message.

Command: dms Payload: {"MID": message_id}

Client: C2SDeleteMessageVO (bundle line 135068), sent by CastleMessageData.deleteMessage (bundle line 134987)

Fields:

message_id pydantic-field

message_id: int

Wire key MID

The message's MessageInfo.message_id

DeleteMessagesRequest pydantic-model

Bases: BaseRequest

Delete several messages.

Command: dms Payload: {"MIDS": [message_id, ...]}

Client: C2SDeleteMessagesVO (bundle line 106651), sent by the inbox's delete-all (bundle line 39344)

Fields:

message_ids pydantic-field

message_ids: list[int]

Wire key MIDS

The messages' MessageInfo.message_id values

DeleteMessagesResponse pydantic-model

Bases: BaseResponse

The deleted messages.

Command: dms

Client: DMSCommand.exec (bundle line 125282) reads MID as one id or a list of ids

Fields:

Validators:

message_ids pydantic-field

message_ids: list[int]

Wire key MID

The deleted messages

SendMessageRequest pydantic-model

Bases: BaseRequest

Send a message to a player.

Command: sms Payload: {"RN": receiver_name, "MH": subject, "TXT": text}; subject and text encoded as chat text

Client: C2SSendMessageVO (bundle line 135085), sent by CastleMessageData.sendNewMessage (bundle line 134986)

Fields:

receiver_name pydantic-field

receiver_name: str

Wire key RN

The receiving player's name

subject pydantic-field

subject: str

Wire key MH

The subject, encoded

text pydantic-field

text: str

Wire key TXT

The text, encoded

create classmethod

create(receiver_name: str, subject: str, text: str) -> SendMessageRequest

A message, subject and text encoded as C2SSendMessageVO encodes them.

SendMessageResponse pydantic-model

Bases: BaseResponse

The answer to sending a message.

Command: sms

Client: SMSCommand.executeCommand (bundle line 125470); on error 70 (USAGE_OF_BADWORDS) it reads the first bad word from BW

Fields:

repair_header

repair_header(header: str | None) -> str

A message header decoded as the client decodes it before reading it.

Client: AMessageVO.repairSpecialCharacters (bundle line 3820): chat text decoding, then a character cut off mid-encoding at the end of a shortened header

messages.models.battle_logs

Battle report protocol models.

Commands:

  • bls: A battle report's short log, the overview
  • blm: Its middle log: the waves per flank, the courtyard and the commanders
  • bld: Its detail log: every unit per flank and wave
  • mfb: Forward a battle report

Classes:

Name Description
GetBattleLogShortRequest

Request a battle report's short log.

GetBattleLogMiddleRequest

Request a battle report's middle log.

GetBattleLogDetailRequest

Request a battle report's detail log.

ForwardBattleLogRequest

Forward a battle report to other players.

ForwardBattleLogResponse

The answer to forwarding a battle report; the client reads nothing from it.

BattleLogUnit

One unit or tool type in a battle log: [wod_id, amount, lost].

SideUnits

One side's units in a battle log: [player_id, [wod_id, amount, lost], ...].

UnitsBySide

The attacker's and the defender's units: [attacker_row, defender_row], each a SideUnits row.

FlankTotals

One flank's totals in a middle log: [soldiers, tools_used, soldiers_lost].

MiddlePlayerWave

One side of a middle log wave: [player_id, left, middle, right], each flank a FlankTotals row.

MiddleWave

One wave of a middle log: [attacker, defender], each a MiddlePlayerWave row.

DetailFlank

One flank of a detail log wave: [soldiers, tools, effects].

DetailPlayerWave

One side of a detail log wave: [player_id, left, middle, right, courtyard], each a DetailFlank row.

DetailWave

One wave of a detail log: [attacker, defender], each a DetailPlayerWave row.

AbilityWaveValue

Where a general's ability took effect: [wave_id, value, flank_name].

BattleLogAbility

A general's ability that took effect in the battle: [ability_id, [[wave_id, value, flank_name], ...]].

BattleParticipant

One player in a battle: a PBI row.

BattleLogArea

The area fought over, a short log's AI block.

BattleLogMeta

A short log's MS: area_type+attack_type+result[+treasure_map_id[+treasure_map_area_type]].

BattleLogShortResponse

A battle report's short log: who fought, who won, the loot and the area.

MiddleCourtyard

The courtyard in a middle log: [[player_id, troops, lost], [player_id, troops, lost]].

BattleLogMiddleResponse

A battle report's middle log: the waves per flank, the courtyard, the support tools and the commanders.

BattleLogDetailResponse

A battle report's detail log: every unit and tool type per flank and wave.

BattleReport

A battle report as MessagesService.get_battle_report reads it: the short log, and the others asked for.

Attributes:

Name Type Description
ATTACKER_FRONT

BattleParticipant.front of an attacking player. Client: BattleLogVO.hasWonState (bundle line 138387)

DEFENDER_FRONT

BattleParticipant.front of a defending player. Client: BattleLogVO.isDefender (bundle line 138388)

logger module-attribute

logger = logging.getLogger(__name__)

ATTACKER_FRONT module-attribute

ATTACKER_FRONT = 0

BattleParticipant.front of an attacking player. Client: BattleLogVO.hasWonState (bundle line 138387)

DEFENDER_FRONT module-attribute

DEFENDER_FRONT = 1

BattleParticipant.front of a defending player. Client: BattleLogVO.isDefender (bundle line 138388)

GetBattleLogShortRequest pydantic-model

Bases: BaseRequest

Request a battle report's short log.

Command: bls Payload: {"MID": message_id, "IM": 0 or 1}

With IM 1 the reply carries the middle and detail logs too, as blm and bld. The client itself sends IM 0.

Client: C2SBattleLogShortVO (bundle line 135059), sent by CastleMessageData.getBattleLogShort (bundle line 134905)

Fields:

Validators:

message_id pydantic-field

message_id: int

Wire key MID

The battle log's MessageInfo.message_id

include_details pydantic-field

include_details: int = 0

Wire key IM

1 to have the reply carry the middle and detail logs too, else 0

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a reply is this message's log: its MID, when sent, is MID.

GetBattleLogMiddleRequest pydantic-model

Bases: BaseRequest

Request a battle report's middle log.

Command: blm Payload: {"LID": log_id}

Client: C2SBattleLogMiddleVO (bundle line 135050), sent by CastleMessageData.getBattleLogMiddle (bundle line 134906)

Fields:

log_id pydantic-field

log_id: int

Wire key LID

The battle log id: BattleLogShortResponse.log_id

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a reply is this log's: its LID, when sent, is LID; the client files it under int(LID).

GetBattleLogDetailRequest pydantic-model

Bases: BaseRequest

Request a battle report's detail log.

Command: bld Payload: {"LID": log_id}

Client: C2SBattleLogDetailVO (bundle line 135041), sent by CastleMessageData.getBattleLogDetailed (bundle line 134907)

Fields:

log_id pydantic-field

log_id: int

Wire key LID

The battle log id: BattleLogShortResponse.log_id

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a reply is this log's: its LID, when sent, is LID; the client files it under int(LID).

ForwardBattleLogRequest pydantic-model

Bases: BaseRequest

Forward a battle report to other players.

Command: mfb Payload: {"MID": message_id, "PID": [player_id, ...]}

Client: C2SForwardBattleLogVO (bundle lines 128585-128586), whose constructor sets MID before PID; sent by CastleForwardMessageDialog.sendMessage (bundle line 60740)

Fields:

message_id pydantic-field

message_id: int

Wire key MID

The battle log's MessageInfo.message_id

player_ids pydantic-field

player_ids: list[int]

Wire key PID

Recipients, e.g. your alliance's other members: AllianceMember.player_id from client.alliance.get_local_members()

ForwardBattleLogResponse pydantic-model

Bases: BaseResponse

The answer to forwarding a battle report; the client reads nothing from it.

Command: mfb

Client: MFBCommand.executeCommand (bundle line 125396)

Fields:

BattleLogUnit pydantic-model

Bases: BasePayload

One unit or tool type in a battle log: [wod_id, amount, lost].

Client: LogUnitVO.initByParams (bundle line 61363)

Fields:

Validators:

  • _from_row

wod_id pydantic-field

wod_id: ClientInt

Unit or tool type id

amount pydantic-field

amount: ClientInt

How many took part

lost pydantic-field

lost: ClientInt

How many were lost; the server sends it as 0 or a negative number

SideUnits pydantic-model

Bases: BasePayload

One side's units in a battle log: [player_id, [wod_id, amount, lost], ...].

The client keeps the wire order of units only until it sorts them by the game's unit order; this model keeps the wire order.

Client: BattleLogVO.parseYardDetailed and parseSupportTools (bundle lines 138341, 138352) skip the first entry and read the rest as LogUnitVO (bundle line 61363)

Fields:

Validators:

player_id pydantic-field

player_id: int | float | None = None

The side's player id, as the server sends it

units pydantic-field

The side's unit or tool types

UnitsBySide pydantic-model

Bases: BasePayload

The attacker's and the defender's units: [attacker_row, defender_row], each a SideUnits row.

Client: BattleLogVO.parseYardDetailed and parseSupportTools (bundle lines 138341, 138352)

Fields:

Validators:

  • _from_row

attacker pydantic-field

attacker: SideUnits

The attacking side

defender pydantic-field

defender: SideUnits

The defending side

FlankTotals pydantic-model

Bases: BasePayload

One flank's totals in a middle log: [soldiers, tools_used, soldiers_lost].

Client: FlankInfoVO.initByParams (bundle line 61395)

Fields:

Validators:

  • _from_row

soldiers pydantic-field

soldiers: ClientInt = 0

Soldiers that fought

tools_used pydantic-field

tools_used: ClientInt = 0

Tools used

soldiers_lost pydantic-field

soldiers_lost: ClientInt = 0

Soldiers lost; the server sends it as 0 or a negative number

soldiers_survived property

soldiers_survived: int

soldiers plus soldiers_lost, as the client counts them.

Client: FlankInfoVO.soldierAmountSurvived (bundle line 61402)

MiddlePlayerWave pydantic-model

Bases: BasePayload

One side of a middle log wave: [player_id, left, middle, right], each flank a FlankTotals row.

A pre-combat wave's sides have no player id: [left, middle, right]. A flank the row lacks is None.

Client: MediumPlayerBattleWaveVO.initByParams (bundle line 138761)

Fields:

Validators:

  • _from_row

player_id pydantic-field

player_id: int | float | None = None

The side's player id; None in a pre-combat wave

left pydantic-field

left: FlankTotals | None = None

The left flank

middle pydantic-field

middle: FlankTotals | None = None

The middle flank

right pydantic-field

right: FlankTotals | None = None

The right flank

soldiers_survived property

soldiers_survived: int

Soldiers that survived on all three flanks.

Client: MediumPlayerBattleWaveVO.soldierAmountSurvived (bundle line 138774)

MiddleWave pydantic-model

Bases: BasePayload

One wave of a middle log: [attacker, defender], each a MiddlePlayerWave row.

Client: MediumBattleWaveVO.initByParams (bundle line 138735)

Fields:

Validators:

  • _from_row

attacker pydantic-field

attacker: MiddlePlayerWave

The attacking side

defender pydantic-field

defender: MiddlePlayerWave

The defending side

got_through_wall property

got_through_wall: bool

Some attacking soldiers survived the wave.

Client: MediumBattleWaveVO.gotThroughWall (bundle line 138740)

DetailFlank pydantic-model

Bases: BasePayload

One flank of a detail log wave: [soldiers, tools, effects].

effects is [wall_bonus, gate_bonus, moat_bonus] and comes only for the defending side; without it the three bonuses are None.

Client: FlankVO.initByParams and parseEffects (bundle lines 138694, 138705)

Fields:

Validators:

soldiers pydantic-field

soldiers: list[BattleLogUnit]

Soldier types

tools pydantic-field

Tool types

wall_bonus pydantic-field

wall_bonus: int | None = None

The wall's defence bonus, percent

gate_bonus pydantic-field

gate_bonus: int | None = None

The gate's defence bonus, percent

moat_bonus pydantic-field

moat_bonus: int | None = None

The moat's defence bonus, percent

DetailPlayerWave pydantic-model

Bases: BasePayload

One side of a detail log wave: [player_id, left, middle, right, courtyard], each a DetailFlank row.

A flank the row lacks is empty, as the client reads it.

Client: DetailedPlayerBattleWaveVO.initByParams (bundle line 138635)

Fields:

Validators:

player_id pydantic-field

player_id: int | float | None = None

The side's player id, as the server sends it

left pydantic-field

The left flank

middle pydantic-field

middle: DetailFlank

The middle flank

right pydantic-field

right: DetailFlank

The right flank

courtyard pydantic-field

courtyard: DetailFlank

The courtyard

DetailWave pydantic-model

Bases: BasePayload

One wave of a detail log: [attacker, defender], each a DetailPlayerWave row.

Client: DetailedBattleWaveVO.initByParams (bundle line 138614)

Fields:

Validators:

  • _from_row

attacker pydantic-field

attacker: DetailPlayerWave

The attacking side

defender pydantic-field

defender: DetailPlayerWave

The defending side

AbilityWaveValue pydantic-model

Bases: BasePayload

Where a general's ability took effect: [wave_id, value, flank_name].

Client: BattleLogAbilityVO.parseFromParamOj (bundle line 138588); getValueForWave and isTriggerdInWave (bundle lines 138593-138595) match the wave and the flank name; CastleBattleLogPopUpDialog.getFlankNameBattleLog (bundle lines 135871-135877) names the flanks

Fields:

Validators:

wave_id pydantic-field

wave_id: ClientInt = 0

The wave's number in the battle

value pydantic-field

value: ClientNumber = 0

The ability's value in that wave

flank_name pydantic-field

flank_name: EnumOrStr[BattleLogFlank] | None = None

The flank; None when the entry names none

BattleLogAbility pydantic-model

Bases: BasePayload

A general's ability that took effect in the battle: [ability_id, [[wave_id, value, flank_name], ...]].

Client: BattleLogAbilityVO.parseFromParamOj (bundle line 138584)

Fields:

Validators:

ability_id pydantic-field

ability_id: EnumOrInt['GeneralAbility'] | None = None

The ability; None when the entry names none

wave_values pydantic-field

wave_values: list[AbilityWaveValue]

Where it took effect

BattleParticipant pydantic-model

Bases: BasePayload

One player in a battle: a PBI row.

The client looks each player up among the owner records (PI) and stops reading the rows at one it has no record for; this model keeps every row. Join player_id to the reply's owners for the player's record.

Client: BattleLogVO.loadFromParamArrayPBI (bundle line 138313), BattleParticipantVO (bundle line 138788); FactionConst.BLUE_FACTION 0 and RED_FACTION 1 (dll line 19333)

Fields:

Validators:

  • _from_row

player_id pydantic-field

player_id: ClientInt = 0

Player id; below 0 for an NPC

front pydantic-field

front: ClientInt = 0

0 for an attacking player, 1 for a defending one

start_army_size pydantic-field

start_army_size: ClientInt = 0

Soldiers the player brought

lost_units pydantic-field

lost_units: ClientInt = 0

Soldiers the player lost; the server sends it as 0 or a negative number

loot pydantic-field

loot: CollectableRows = ()

What the player looted (BattleLogVO.loadFromParamArrayPBI reads it with CollectableParserS2CParamList.createList, bundle line 138318)

fame_points pydantic-field

fame_points: ClientInt = 0

Glory points won or lost

xp pydantic-field

xp: ClientInt = 0

Experience won

kingstower_bonus pydantic-field

kingstower_bonus: ClientInt = 0

Bonus from the king's towers

faction_points pydantic-field

faction_points: ClientInt = 0

Berimond faction points

highest_fame_title_bonus pydantic-field

highest_fame_title_bonus: ClientInt = 0

Bonus from the highest glory title

wounded_units pydantic-field

wounded_units: ClientInt = 0

Soldiers wounded instead of lost

morale_boost pydantic-field

morale_boost: ClientInt = 0

Morale boost

capital_tokens pydantic-field

capital_tokens: ClientInt = 0

Capital tokens won

reputation_blue pydantic-field

reputation_blue: ClientInt = 0

Reputation won with the blue faction

reputation_red pydantic-field

reputation_red: ClientInt = 0

Reputation won with the red faction

is_defender property

is_defender: bool

The player defended.

Client: BattleLogVO.isDefender (bundle line 138388)

BattleLogArea pydantic-model

Bases: SpyReportArea

The area fought over, a short log's AI block.

The reply's SSID, DAR and, for an alliance battle ground tower, its AID, N, ACVC and E are read into this block, as the client copies them.

Client: BattleLogVO.fillFromParamObject (bundle lines 138294-138303) and InteractiveMapobjectVO.parseAreaInfoBattleLog (bundle line 3638) with its overrides (see SpyReportArea)

Fields:

Validators:

treasure_map_node_id pydantic-field

treasure_map_node_id: ClientInt = 0

Wire key NID

The treasure map node fought over; 0 or less for none

alliance_crest pydantic-field

alliance_crest: dict[str, Any] | None = None

Wire key E

An alliance tower's alliance crest, as sent

BattleLogMeta dataclass

BattleLogMeta(area_type: int, attack_type: BattleLogAttackType | None, result: LogResult | None, treasure_map_id: int = 0, treasure_map_area_type: str | int = 0)

A short log's MS: area_type+attack_type+result[+treasure_map_id[+treasure_map_area_type]].

A battle log's mailbox header starts with the same numbers. A subtype or result the client defines no constant for is None.

Client: BattleLogVO.addMetadataFromMessage (bundle line 138334) and the metaData_* getters (bundle lines 138461-138469)

Methods:

Name Description
parse

Read MS; a missing number reads as 0, as the client's int() reads it.

Attributes:

Name Type Description
area_type int
attack_type BattleLogAttackType | None
result LogResult | None
treasure_map_id int
treasure_map_area_type str | int

area_type instance-attribute

area_type: int

attack_type instance-attribute

attack_type: BattleLogAttackType | None

result instance-attribute

result: LogResult | None

treasure_map_id class-attribute instance-attribute

treasure_map_id: int = 0

treasure_map_area_type class-attribute instance-attribute

treasure_map_area_type: str | int = 0

parse classmethod

parse(text: str) -> BattleLogMeta

Read MS; a missing number reads as 0, as the client's int() reads it.

SupporterWounded pydantic-model

Bases: BasePayload

Soldiers a supporting player had wounded: a WSU row, [player_id, wounded_units].

Client: BattleLogVO.updateSupporterWoundedUnitCount (bundle lines 138329-138332)

Fields:

Validators:

  • _from_row

player_id pydantic-field

player_id: ClientInt = 0

The supporting player

wounded_units pydantic-field

wounded_units: ClientInt = 0

Soldiers wounded instead of lost

BattleLogShortResponse pydantic-model

Bases: BaseResponse

A battle report's short log: who fought, who won, the loot and the area.

Command: bls Payload:

{"MID": message_id, "LID": log_id, "MT": 6, "DW": defender_won, "MS": "area+attack+result",
 "AI": {area}, "PBI": [[player_id, front, army, lost, loot, fame, xp, ...]], "PI": [{owner}],
 "AL": {commander}, "DB": {castellan}, "PS": seconds_since_battle, ...}

With IM 1 it carries the middle and detail logs as blm and bld. The server answers error 66 (NO_SUCH_MESSAGE) or 225 (MESSAGEDATA_TOO_OLD) for a log it no longer has, which the client shows as a log that does not exist.

Client: BLSCommand.executeCommand (bundle lines 125190-125196), CastleMessageData.parseBattleLogShort (bundle line 134992), BattleLogVO.fillFromParamObject (bundle line 138294), which reads EQF and GF (bundle line 138304) and ASCT, AAT, AAC and AAN (bundle line 138312)

Fields:

Validators:

message_id pydantic-field

message_id: int | None = None

Wire key MID

The battle log's message id

log_id pydantic-field

log_id: ClientInt = 0

Wire key LID

The battle log id, for the middle and detail logs

message_type pydantic-field

message_type: EnumOrInt[MessageType] = 0

Wire key MT

The message type: BATTLE_LOG for a battle log

defender_won pydantic-field

defender_won: bool = False

Wire key DW

The defender won

meta pydantic-field

meta: str = ''

Wire key MS

area_type+attack_type+result[+treasure_map_id[+map_area_type]]

area pydantic-field

area: BattleLogArea | None = None

Wire key AI

The area fought over

participants pydantic-field

participants: list[BattleParticipant]

Wire key PBI

The players

owners pydantic-field

owners: list[MapObject]

Wire key PI

The players' owner records

honor pydantic-field

honor: ClientInt = 0

Wire key H

Honor won or lost

survival_rate pydantic-field

survival_rate: ClientInt = 0

Wire key SR

Survival rate, percent

found_equipment pydantic-field

found_equipment: Equipment | None = None

Wire key EQF

Equipment found in the battle; None for none

found_gem pydantic-field

found_gem: EnumOrInt['Gem'] | None = None

Wire key GF

Gem found in the battle

found_minute_skip pydantic-field

found_minute_skip: Collectable | None = None

Wire key MSF

The time skip found in the battle, one of its currency; None for none

rage_points pydantic-field

rage_points: int = -1

Wire key RP

Rage points; -1 when the reply has none

jump_disabled pydantic-field

jump_disabled: bool = False

Wire key DJ

The client offers no jump to the area

seconds_since_battle pydantic-field

seconds_since_battle: int | float | None = None

Wire key PS

Seconds since the battle; None when the reply has none

attacker_home_castle_id pydantic-field

attacker_home_castle_id: int | None = None

Wire key AHC

The attacker's home castle

attacker_had_hospital pydantic-field

attacker_had_hospital: bool = False

Wire key AHH

The attacker had a hospital

attacker_hospital_full pydantic-field

attacker_hospital_full: bool = False

Wire key AHF

The attacker's hospital was full

defender_home_castle_id pydantic-field

defender_home_castle_id: int | None = None

Wire key DHC

The defender's home castle

defender_had_hospital pydantic-field

defender_had_hospital: bool = False

Wire key DHH

The defender had a hospital

defender_hospital_full pydantic-field

defender_hospital_full: bool = False

Wire key DHF

The defender's hospital was full

attacker_only_auxiliaries pydantic-field

attacker_only_auxiliaries: bool = False

Wire key AUA

The attacker fought with auxiliaries only

defender_only_auxiliaries pydantic-field

defender_only_auxiliaries: bool = False

Wire key DUA

The defender fought with auxiliaries only

supporters_wounded pydantic-field

supporters_wounded: tuple[SupporterWounded, ...] = ()

Wire key WSU

Soldiers each supporting player had wounded

attacking_commander pydantic-field

attacking_commander: Commander | None = None

Wire key AL

The attacking commander

defending_castellan pydantic-field

defending_castellan: Castellan | None = None

Wire key DB

The defending castellan

auto_skip_costs pydantic-field

auto_skip_costs: CurrencyIdRows = ()

Wire key ASMS

The currencies the auto-skip cost (an amount above 0) or refunded (0 or below), as sent

auto_skip_rubies pydantic-field

auto_skip_rubies: ClientInt = 0

Wire key ASC

Rubies the auto-skip cost; 0 or less for none

auto_skip_type pydantic-field

auto_skip_type: EnumOrInt[AutoSkipCooldownType] | None = None

Wire key ASCT

How the target's cooldown was skipped; None when the reply has none

advisor_type pydantic-field

Wire key AAT

The attack advisor that sent the attack

advisor_movement_count pydantic-field

advisor_movement_count: ClientInt = 0

Wire key AAC

Movements the attack advisor sent

advisor_movement_number pydantic-field

advisor_movement_number: ClientInt = 0

Wire key AAN

This movement's number among them

attacker_alliance_subscribers pydantic-field

attacker_alliance_subscribers: ClientInt = 0

Wire key AAS

Subscribed members of the attacker's alliance

attacker_had_subscription pydantic-field

attacker_had_subscription: bool = False

Wire key AHP

The attacker had a subscription

defender_alliance_subscribers pydantic-field

defender_alliance_subscribers: ClientInt = 0

Wire key DAS

Subscribed members of the defender's alliance

defender_had_subscription pydantic-field

defender_had_subscription: bool = False

Wire key DHP

The defender had a subscription

old_charge_points pydantic-field

old_charge_points: int = 0

Wire key CPO

The attacker's charge points before

old_charge_rank pydantic-field

old_charge_rank: int = 0

Wire key CRO

The attacker's charge rank before

new_charge_points pydantic-field

new_charge_points: int = 0

Wire key CPN

The attacker's charge points after

new_charge_rank pydantic-field

new_charge_rank: int = 0

Wire key CRN

The attacker's charge rank after

defender_old_charge_points pydantic-field

defender_old_charge_points: int = 0

Wire key DCPO

The defender's charge points before

defender_old_charge_rank pydantic-field

defender_old_charge_rank: int = 0

Wire key DCRO

The defender's charge rank before

defender_new_charge_points pydantic-field

defender_new_charge_points: int = 0

Wire key DCPN

The defender's charge points after

defender_new_charge_rank pydantic-field

defender_new_charge_rank: int = 0

Wire key DCRN

The defender's charge rank after

includes_details pydantic-field

includes_details: bool = False

Wire key IM

The reply carries the middle and detail logs

middle pydantic-field

middle: BattleLogMiddleResponse | None = None

Wire key blm

The middle log, when includes_details

detail pydantic-field

detail: BattleLogDetailResponse | None = None

Wire key bld

The detail log, when includes_details

parsed_meta property

parsed_meta: BattleLogMeta

meta read as the client reads it.

attackers property

attackers: list[BattleParticipant]

The attacking players.

defenders property

defenders: list[BattleParticipant]

The defending players.

winners property

The players on the winning side.

Client: BattleLogVO.loadFromParamArrayPBI and hasWonState (bundle lines 138322, 138387)

losers property

The players on the losing side.

auto_skip_paid property

auto_skip_paid: tuple[Collectable, ...]

What the auto-skip cost: the currencies with an amount above 0, then auto_skip_rubies when above 0.

Client: BattleLogVO.fillFromParamObject (bundle lines 138308-138311), _autoSkipCosts[0]

auto_skip_refunded property

auto_skip_refunded: tuple[Collectable, ...]

What the auto-skip refunded: the currencies with an amount of 0 or below, as a positive amount.

Client: BattleLogVO.fillFromParamObject (bundle lines 138308-138309), _autoSkipCosts[1]

owner_of

owner_of(participant: BattleParticipant) -> MapObject | None

The owner record for a participant, or None when the reply has none.

supporter_wounded_units

supporter_wounded_units(player_id: int) -> int

Soldiers a supporting player had wounded, or -1 when the reply lists none for them.

Client: BattleLogVO.updateSupporterWoundedUnitCount (bundle line 138329)

MiddleCourtyard pydantic-model

Bases: BasePayload

The courtyard in a middle log: [[player_id, troops, lost], [player_id, troops, lost]].

Client: BattleLogVO.parseYard (bundle line 138375)

Fields:

Validators:

  • _from_rows

attacker_troops pydantic-field

attacker_troops: int = 0

Attacking soldiers that reached the courtyard

attacker_lost pydantic-field

attacker_lost: int = 0

Of those, lost; the server sends it as 0 or a negative number

defender_troops pydantic-field

defender_troops: int = 0

Defending soldiers in the courtyard

defender_lost pydantic-field

defender_lost: int = 0

Of those, lost; the server sends it as 0 or a negative number

has_defender_info pydantic-field

has_defender_info: bool = False

The log has the defender's numbers

BattleLogMiddleResponse pydantic-model

Bases: BaseResponse

A battle report's middle log: the waves per flank, the courtyard, the support tools and the commanders.

Command: blm Payload:

{"LID": log_id, "Y": [[player_id, troops, lost], ...], "W": [[attacker, defender], ...],
 "PW": [attacker, defender], "SD": [[player_id, [wod_id, amount, lost], ...], ...],
 "RW": [soldiers, tools_used, soldiers_lost], "AL": {commander}, "DB": {castellan},
 "PS": seconds_since_battle, "AA": [abilities], "DA": [abilities], "DUST": used, ...}

W sides start with the player id, PW sides do not. The reply's S and EW are not read by the client.

Client: BLMCommand.executeCommand (bundle line 125173), CastleMessageData.parseBattleLogMiddle (bundle line 135002), BattleLogVO.parseMiddle (bundle line 138369)

Fields:

Validators:

log_id pydantic-field

log_id: ClientInt = 0

Wire key LID

The battle log id

courtyard pydantic-field

courtyard: MiddleCourtyard

Wire key Y

The courtyard

waves pydantic-field

waves: list[MiddleWave]

Wire key W

The waves, in order

pre_combat_wave pydantic-field

pre_combat_wave: MiddleWave | None = None

Wire key PW

What each side brought before the waves; None when not sent

support_tools pydantic-field

support_tools: UnitsBySide

Wire key SD

Support tools each side used

reinforcements pydantic-field

reinforcements: FlankTotals

Wire key RW

The reinforcement wave's totals

attacking_commander pydantic-field

attacking_commander: Commander | None = None

Wire key AL

The attacking commander

defending_castellan pydantic-field

defending_castellan: Castellan | None = None

Wire key DB

The defending castellan

jump_disabled pydantic-field

jump_disabled: bool = False

Wire key DJ

The client offers no jump to the area

seconds_since_battle pydantic-field

seconds_since_battle: int | float | None = None

Wire key PS

Seconds since the battle; None when the reply has none

attacker_triggered_gems pydantic-field

attacker_triggered_gems: tuple[EnumOrInt['Gem'], ...] = ()

Wire key AGT

The attacker's gems that triggered

defender_triggered_gems pydantic-field

defender_triggered_gems: tuple[EnumOrInt['Gem'], ...] = ()

Wire key DGT

The defender's gems that triggered

attacker_legend_skill_ids pydantic-field

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

Wire key ALS

The attacker's legend skills

defender_legend_skill_ids pydantic-field

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

Wire key DLS

The defender's legend skills

defender_used_support_tools pydantic-field

defender_used_support_tools: bool = False

Wire key DUST

The defender used support tools

attacker_abilities pydantic-field

attacker_abilities: list[BattleLogAbility]

Wire key AA

The attacking general's abilities that took effect

defender_abilities pydantic-field

defender_abilities: list[BattleLogAbility]

Wire key DA

The defending general's abilities that took effect

BattleLogDetailResponse pydantic-model

Bases: BaseResponse

A battle report's detail log: every unit and tool type per flank and wave.

Command: bld Payload:

{"LID": log_id, "Y": [[player_id, [wod_id, amount, lost], ...], ...],
 "W": [[attacker, defender], ...], "PW": [attacker, defender], "EW": [attacker, defender],
 "RW": [[wod_id, amount, lost], ...]}

Each side of a wave is [player_id, left, middle, right, courtyard] and each flank [soldiers, tools, effects]. The client sorts the unit lists by the game's unit order; these models keep the wire order.

Client: BLDCommand.executeCommand (bundle line 125156), CastleMessageData.parseBattleLogDetail (bundle line 135005), BattleLogVO.parseDetails (bundle line 138335)

Fields:

Validators:

log_id pydantic-field

log_id: ClientInt = 0

Wire key LID

The battle log id

courtyard pydantic-field

courtyard: UnitsBySide

Wire key Y

Each side's courtyard units

waves pydantic-field

waves: list[DetailWave]

Wire key W

The waves, in order

pre_combat_wave pydantic-field

pre_combat_wave: DetailWave | None = None

Wire key PW

What each side brought before the waves; None when not sent

post_combat_wave pydantic-field

post_combat_wave: DetailWave | None = None

Wire key EW

What fought after the waves; None when not sent

reinforcements pydantic-field

reinforcements: list[BattleLogUnit]

Wire key RW

Reinforcement units

BattleReport dataclass

BattleReport(short: BattleLogShortResponse, middle: BattleLogMiddleResponse | None = None, detail: BattleLogDetailResponse | None = None)

A battle report as MessagesService.get_battle_report reads it: the short log, and the others asked for.

Attributes:

Name Type Description
short BattleLogShortResponse
middle BattleLogMiddleResponse | None
detail BattleLogDetailResponse | None

short instance-attribute

middle class-attribute instance-attribute

middle: BattleLogMiddleResponse | None = None

detail class-attribute instance-attribute

detail: BattleLogDetailResponse | None = None