Skip to content

Player

Player profiles, search, research and the mercenary camp behind client.player.

player.service

Other players' details, player search, your research and the mercenary camp.

Classes:

Name Description
PlayerDetailsBulkResult

What PlayerService.get_player_details_bulk got for each player.

PlayerService

Other players' details, finding a player by name, your research and the mercenary camp's missions.

PlayerDetailsBulkResult dataclass

PlayerDetailsBulkResult(found: dict[int, GetPlayerInfoResponse] = dict(), failed: dict[int, CommandError | PacketError] = dict(), timed_out: list[int] = list())

What PlayerService.get_player_details_bulk got for each player.

Attributes:

Name Type Description
found dict[int, GetPlayerInfoResponse]

The replies, by player id.

failed dict[int, CommandError | PacketError]

Players whose request the server refused, or whose reply could not be read.

timed_out list[int]

Players whose reply did not come in time.

complete bool

Whether every player was found.

found class-attribute instance-attribute

found: dict[int, GetPlayerInfoResponse] = field(default_factory=dict)

The replies, by player id.

failed class-attribute instance-attribute

failed: dict[int, CommandError | PacketError] = field(default_factory=dict)

Players whose request the server refused, or whose reply could not be read.

timed_out class-attribute instance-attribute

timed_out: list[int] = field(default_factory=list)

Players whose reply did not come in time.

complete property

complete: bool

Whether every player was found.

PlayerService

PlayerService(client: 'EmpireClient')

Bases: BaseService

Other players' details, finding a player by name, your research and the mercenary camp's missions.

Reached as client.player.

Methods:

Name Description
get_player_info

Get detailed player information (gdi), including castle list with

get_player_details

Alias for get_player_info (both use the 'gdi' command).

get_player_details_bulk

Get detailed info for several players, one gdi request after another.

search_player_by_name
start_research

Start a research, paying what it costs.

skip_research

Shorten the running research with a minute skip from your inventory.

list_missions

The mercenary camp's missions, and when new ones come.

start_mission

Start an open mercenary mission, paying its price in coins.

collect_mission

Collect a finished mercenary mission's rewards.

get_player_info

get_player_info(player_id: int, timeout: float = 5.0) -> GetPlayerInfoResponse

Get detailed player information (gdi), including castle list with capture info.

Parameters:

Name Type Description Default
player_id int

A player's id: search_player_by_name(name).get_player().owner_id, an owner record's MapObject.owner_id from a map scan, or an AllianceMember.player_id from client.alliance.get_members()

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
CommandError / EmpireTimeoutError / ConnectionClosedError

see EmpireClient.send

get_player_details

get_player_details(player_id: int, timeout: float = 5.0) -> GetPlayerInfoResponse

Alias for get_player_info (both use the 'gdi' command).

get_player_details_bulk

get_player_details_bulk(player_ids: list[int], timeout: float = 5.0, send_delay: float = 0.0) -> PlayerDetailsBulkResult

Get detailed info for several players, one gdi request after another.

Each request waits for its own reply before the next goes out: a gdi error reply names no player, so only one request in flight at a time tells which player it belongs to. Each runs as get_player_info does, under the gdi command lock and taking only a reply whose O.OID is the player asked for.

Parameters:

Name Type Description Default
player_ids list[int]

Player ids to fetch, found as for get_player_info; repeats are fetched once

required
timeout float

Seconds to wait for each reply

5.0
send_delay float

Seconds to wait between two requests; by default none, as each already waits for the one before it to be answered

0.0

Returns:

Type Description
PlayerDetailsBulkResult

Which players were found, which failed and why, and which timed out

Raises:

Type Description
ConnectionClosedError / NetworkError

the connection failed; the players fetched so far are lost

ReceiveThreadError

called on the receive thread

search_player_by_name

search_player_by_name(player_name: str, timeout: float = 5.0) -> SearchPlayerResponse

start_research

start_research(research: Research | int, *, spend_rubies: bool = False, timeout: float = 5.0) -> bool

Start a research, paying what it costs.

Missing resources are never paid with rubies: the request sends PWR 0, as the research dialog does. Some researches cost rubies themselves (ResearchDef.cost_rubies), and the client sends the same request for them; this method refuses those unless spend_rubies is True. Other costs, legendary tokens included, are a normal game currency and are paid. The reply's research reaches client.state.get_research().

Parameters:

Name Type Description Default
research Research | int

The research to start

required
spend_rubies bool

Allow a research that costs rubies, on either kind of server

False
timeout float

Timeout in seconds

5.0

Raises:

Type Description
GameDataNotLoadedError

the game data cannot be loaded

ValueError

The game data has no such research, or it costs rubies and spend_rubies is False

Client: ResearchInfo.buyResearch (bundle line 79704) sends C2SResearchStartVO whatever the costs; updateBuyArea shows the ruby overlay when getFinalCosts holds rubies (bundle lines 79679-79680), and AResearchVO.getBaseCosts takes the temporary server's costs there (bundle line 61530); RESCommand (bundle line 126872)

skip_research

skip_research(minute_skip: Currency | str, timeout: float = 5.0) -> bool

Shorten the running research with a minute skip from your inventory.

The reply's research reaches client.state.get_research().

Parameters:

Name Type Description Default
minute_skip Currency | str

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

required
timeout float

Timeout in seconds

5.0

Raises:

Type Description
ValueError

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

Client: ResearchMinuteSkipProperties.getMinuteSkipCommand (bundle line 79784), CastleMinuteSkipDialog.showLoaded (bundle line 7671), MSRCommand (bundle line 125825)

list_missions

list_missions(timeout: float = 5.0) -> MercenaryMissionsResponse

The mercenary camp's missions, and when new ones come.

Sends mpe with MID -1, as the client does when it opens the camp and when new missions are due; the reply also reaches client.state.get_mercenary_missions().

Client: CastleMercenaryOverviewDialog.showLoaded and CastleMercenaryData.onNewMissions (bundle lines 51996, 28851)

start_mission

start_mission(mission_id: int, timeout: float = 5.0) -> bool

Start an open mercenary mission, paying its price in coins.

Lists the missions first and decides from that reply, as the client decides from its mission state: only an open mission starts, and only while no mission runs or waits to be collected. While one runs the client would first finish it for rubies; that is left out.

Parameters:

Name Type Description Default
mission_id int

A MercenaryMission.mission_id from list_missions

required
timeout float

Timeout in seconds, for each request

5.0

Raises:

Type Description
ValueError

The missions list no such mission, it is not open, or another mission runs or waits to be collected

Client: CastleMercenaryMissionItem.startMission, showStartAndRefreshButton and startMissionCallback (bundle lines 87377, 87361, 87379)

collect_mission

collect_mission(mission_id: int, timeout: float = 5.0) -> bool

Collect a finished mercenary mission's rewards.

The collect is the same mpe as the ruby skip of a running mission, which costs ceil(remaining seconds / 60 * 5) rubies. So this lists the missions first and sends the collect only for one the server lists as collectable, as started with no RD (finished for the client, and a skip of it would cost 0 rubies), or as started with its time run out at least two seconds ago, since RD is rounded. A started mission whose RD still counts down is refused. Another session can still change the mission between the list and the send; that window is not closed.

Parameters:

Name Type Description Default
mission_id int

A MercenaryMission.mission_id from list_missions

required
timeout float

Timeout in seconds, for each request

5.0

Raises:

Type Description
ValueError

The missions list no such mission, or it is not collectable by that rule

Client: CastleMercenaryMissionItem.showCollectButton and collectMissionRewards (bundle lines 87365, 87382); CastleMercenaryMissionItemVO.fillFromParamObject sets no end time without RD (bundle line 81134); CastleMercenarySkipMissionDialog.confirmSkip (bundle line 87459) sends the same id, MercenaryConst.getSkipC2Cost (dll line 19508)

player.models.account

Account sections of the login data, and the pushes and replies that bring them again.

Commands:

  • drt: seconds until the daily reset
  • gatp: the officers' school training program running now
  • bie: the global effects a booster event boosts
  • pgl: your gift packages and how many you may still send

The timed models count from received_at, as the client counts from its timer.

Classes:

Name Description
DailyResetResponse

When the daily reset comes.

OfficerBonus

The bonus a training program gives: an effect and its values, [effect_id, [value, ...]].

OfficerTraining

The officers' school training program running now.

BoostedGlobalEffectsResponse

The global effects the running global effect booster event makes stronger.

PlayerGift

One gift package you hold: [package_id, amount].

PlayerGiftsResponse

Your gift packages, to send to other players, and how many you may still send.

DailyResetResponse pydantic-model

Bases: TimedResponse

When the daily reset comes.

Command: drt, as a login section of gbd only.

Client: CastleTimerData.parse_DRT and timeTillDailyResetInSec (bundle lines 112810-112811)

Fields:

seconds pydantic-field

seconds: ClientNumber = 0

Wire key STR

Seconds until the daily reset when the values were read

remaining_seconds

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

Seconds until the daily reset, 0 at least.

OfficerBonus pydantic-model

Bases: BasePayload

The bonus a training program gives: an effect and its values, [effect_id, [value, ...]].

Client: OfficersSchoolEffectVO.setBonusVOByValueArray (bundle line 144678), which looks the effect up by id and reads the values with BonusVO.parseFromValueArray

Fields:

Validators:

  • _from_row

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

effect pydantic-field

effect: EnumOrInt['Effect']

The effect

values pydantic-field

values: tuple[int | float, ...] = ()

The effect's values, in the effect's order

OfficerTraining pydantic-model

Bases: TimedPayload

The officers' school training program running now.

Not a response model: its E is the program's bonus, not an error code.

Command: gatp, as a login section of gbd and a reply; a gtp updates it.

Client: GATPCommand (bundle line 126126), OfficersSchoolData.parse_GATP (bundle line 144606), OfficersSchoolEffectVO.setBonusVOByValueArray and setActiveTime (bundle lines 144678, 144681), OfficersSchoolData.getBonusByEffectType (bundle line 144634)

Fields:

Validators:

slot_id pydantic-field

slot_id: ClientInt = -1

Wire key S

The program's slot, -1 for none

bonus pydantic-field

bonus: OfficerBonus | None = None

Wire key E

The program's bonus; None for none

seconds pydantic-field

seconds: ClientNumber = -1

Wire key RS

Seconds the program runs when the values were read, -1 for none

is_set property

is_set: bool

Whether the client takes this as a running program: a slot with time left, or a bonus.

remaining_seconds

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

Seconds the program still runs, 0 at least; its bonus counts only while this is above 0.

BoostedGlobalEffectsResponse pydantic-model

Bases: BaseResponse

The global effects the running global effect booster event makes stronger.

The running global effects themselves come with the global effects event, not here.

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

Client: BIECommand (bundle line 122579), GlobalEffectData.parse_GIE (bundle line 143676), which skips a block without GE, and isEffectBoosted (bundle line 143643)

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

global_effect_ids pydantic-field

global_effect_ids: tuple[EnumOrInt['GlobalEffect'], ...] = ()

Wire key GE

The boosted global effects

PlayerGift pydantic-model

Bases: BasePayload

One gift package you hold: [package_id, amount].

Client: PlayerGiftVO.parseFromArray (bundle line 52178)

Fields:

Validators:

  • _from_row

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

package_id pydantic-field

package_id: ClientInt = 0

The event package's id

amount pydantic-field

amount: ClientInt = 0

How many you hold

PlayerGiftsResponse pydantic-model

Bases: BaseResponse

Your gift packages, to send to other players, and how many you may still send.

Command: pgl, as a login section of gbd and a reply.

Client: PGLCommand (bundle line 127961), PlayerGiftData.parse_PGL (bundle line 113571), which skips a block without G; sendablePackageAmount (bundle line 113613), read at bundle line 44174

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

gifts pydantic-field

gifts: tuple[PlayerGift, ...] = ()

Wire key G

The gift packages you hold

sendable_amount pydantic-field

sendable_amount: ClientNumber = 0

Wire key RA

Gift packages you may still send; none at 0 or less

can_send property

can_send: bool

Whether you may send another gift package.

player.models.economy

Loot boxes and mercenary missions, as the login data and their pushes send them.

Commands:

  • gls: your loot boxes and key progress, a push before and a section of gbd
  • mpe: the mercenary missions, a login section of gbd and a reply; also the request that lists, starts and collects them

Classes:

Name Description
LootBoxAmount

One kind of loot box you hold: {"ID": loot box, "AMT": amount}.

LootBoxKeys

The keys collected towards one loot box type: {"ID": loot box type, "AMT": keys}.

LootBoxesResponse

Your loot boxes, and the keys collected towards each loot box type.

MercenaryMission

One mercenary mission, an entry of an mpe's M.

MercenaryMissionsResponse

The mercenary camp's missions and when new ones come.

MercenaryPackageRequest

List the mercenary missions, or start or collect one.

NEXT_MISSIONS_DELAY module-attribute

NEXT_MISSIONS_DELAY = 1

CastleMercenaryData.NEXT_MISSIONS_TIMER_DELAY (bundle line 28881): seconds the client waits past NM

LootBoxAmount pydantic-model

Bases: BasePayload

One kind of loot box you hold: {"ID": loot box, "AMT": amount}.

Client: CastleLootboxData.parse_GLS (bundle line 112332) reads each as an ACollectableItemLootBoxVO

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

loot_box_id pydantic-field

loot_box_id: EnumOrInt['LootBox'] = 0

Wire key ID

The loot box

amount pydantic-field

amount: ClientNumber = 0

Wire key AMT

How many you hold

LootBoxKeys pydantic-model

Bases: BasePayload

The keys collected towards one loot box type: {"ID": loot box type, "AMT": keys}.

Client: CastleLootboxData.parse_GLS and setKeyProgress (bundle lines 112332, 112343)

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

loot_box_type_id pydantic-field

loot_box_type_id: EnumOrInt['LootBoxType'] = 0

Wire key ID

The loot box type

keys pydantic-field

keys: ClientNumber = 0

Wire key AMT

The keys collected

LootBoxesResponse pydantic-model

Bases: BaseResponse

Your loot boxes, and the keys collected towards each loot box type.

A gls lists the loot boxes whole, but key progress only for some types; the client keeps the others as they were, and client.state.get_loot_boxes() does the same.

Command: gls, a push the server sends before gbd and again when they change.

Client: GLSCommand (bundle line 124800), CastleLootboxData.parse_GLS (bundle line 112332)

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

loot_boxes pydantic-field

loot_boxes: tuple[LootBoxAmount, ...] = ()

Wire key ALL

The loot boxes you hold

key_progress pydantic-field

key_progress: tuple[LootBoxKeys, ...] = ()

Wire key KEY

Keys collected per loot box type this packet lists

amount

amount(loot_box_id: LootBox | int) -> int | float

How many of a loot box you hold.

keys

keys(loot_box_type_id: LootBoxType | int) -> int | float

The keys collected towards a loot box type, 0 when none is known.

MercenaryMission pydantic-model

Bases: TimedPayload

One mercenary mission, an entry of an mpe's M.

Client: CastleMercenaryMissionItemVO.fillFromParamObject and remainingTime (bundle lines 81134-81135)

Fields:

mission_id pydantic-field

mission_id: ClientInt = 0

Wire key ID

The mission's id

rewards pydantic-field

rewards: CollectableRows = ()

Wire key R

The rewards

duration_seconds pydantic-field

duration_seconds: ClientNumber = 0

Wire key D

How long the mission takes

price pydantic-field

price: ClientNumber = 0

Wire key P

What starting the mission costs in coins

quality pydantic-field

Wire key Q

The mission's rarity

state pydantic-field

Wire key S

Where the mission stood when the values were read

seconds pydantic-field

seconds: ClientNumber = 0

Wire key RD

Seconds the running mission still takes when the values were read

remaining_seconds

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

Seconds the mission still takes, 0 at least.

seconds_past_end

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

Seconds since the mission's time ran out, below 0 while it still runs.

Client: CastleMercenaryMissionItemVO.remainingTime (bundle line 81135), negated and not clamped

current_state

current_state(now: float | None = None) -> MercenaryMissionState | int

Where the mission stands now: a started mission whose time has run out is collectable.

Client: CastleMercenaryData.parse_MPE and onMissionFinished (bundle lines 28829-28830, 28850)

MercenaryMissionsResponse pydantic-model

Bases: TimedResponse

The mercenary camp's missions and when new ones come.

Command: mpe, as a login section of gbd and a reply.

Client: MPECommand (bundle line 125109), CastleMercenaryData.parse_MPE (bundle line 28824), which drops collected missions (state 3 and up)

Fields:

Validators:

next_missions_seconds pydantic-field

next_missions_seconds: ClientNumber = 0

Wire key NM

Seconds until new missions come when the values were read

missions pydantic-field

missions: tuple[MercenaryMission, ...] = ()

Wire key M

The missions not collected

remaining_next_missions_seconds

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

Seconds until the client asks for new missions, 0 at least.

get_mission

get_mission(mission_id: int) -> MercenaryMission | None

The mission with this id; None when the reply does not list it.

current_mission_state

current_mission_state(now: float | None = None) -> MercenaryMissionState | int

The furthest any mission stands now: OPEN while none runs or waits to be collected.

Client: CastleMercenaryData.currentMissionState (bundle line 28853), the highest state parse_MPE read (bundle line 28829), collectable once onMissionFinished fired (bundle line 28850)

MercenaryPackageRequest pydantic-model

Bases: BaseRequest

List the mercenary missions, or start or collect one.

Command: mpe Payload: {"MID": mission_id}

MID -1 lists the missions. A mission's id starts an open mission, collects a collectable one, and finishes a running one at once for rubies (CastleMercenarySkipMissionDialog.confirmSkip, bundle line 87459); client.player.start_mission and collect_mission send it only for the first two.

Client: C2SMercenaryPackageVO (bundle line 28889); -1 from CastleMercenaryData.onNewMissions and CastleMercenaryOverviewDialog.showLoaded (bundle lines 28851, 51996), a mission's id from CastleMercenaryMissionItem.startMissionCallback and collectMissionRewards (bundle lines 87379, 87382)

Fields:

mission_id pydantic-field

mission_id: int = -1

Wire key MID

The mission to start or collect, -1 to list them

player.models.info

Player protocol models.

Commands:

  • gdi: Get detailed player info (including castle list with capture info)
  • wsp: World search player (search by name)

Classes:

Name Description
LocationCapture

One of the player's locations that someone occupies, from the gdi castle list.

PlayerOwnerInfo

The gdi reply's O block: the player's owner record.

GetPlayerInfoRequest

Get detailed player information including castle list.

GetPlayerInfoResponse

Response containing detailed player information.

SearchPlayerRequest

Find a player by name.

SearchPlayerResponse

Response to a player search.

logger module-attribute

logger = logging.getLogger(__name__)

LocationCapture pydantic-model

Bases: BasePayload

One of the player's locations that someone occupies, from the gdi castle list.

Built from a CastleInfo that is occupied: its occupier_id is above -1, as the client's isOccupied checks.

Client: InteractiveMapobjectVO.parseAreaInfo (bundle line 3631) and CapitalMapobjectVO.parseAreaInfo (bundle line 18729) read the occupier as occupierID

Fields:

location_id pydantic-field

location_id: int = 0

The location's object id

location_type pydantic-field

location_type: MapItemType = MapItemType.EMPTY

The location's area type

x pydantic-field

x: int = 0

Map x

y pydantic-field

y: int = 0

Map y

kingdom pydantic-field

kingdom: Kingdom = Kingdom.GREEN

The location's kingdom

capturer_id pydantic-field

capturer_id: int = -1

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

is_being_captured property

is_being_captured: bool

Whether someone occupies this location: an occupier id above -1.

GetPlayerInfoRequest pydantic-model

Bases: BaseRequest

Get detailed player information including castle list.

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

Returns owner info (O) and castle list (gcl.C) with capture status.

Client: C2SGetDetailPlayerInfo (bundle line 27021)

Fields:

player_id pydantic-field

player_id: int

Wire key PID

A player's id, e.g. MapObject.owner_id from a map scan or client.player.search_player_by_name(), or AllianceMember.player_id

accepts_reply

accepts_reply(payload: Any) -> bool

Whether a gdi reply is about this player: its owner record's OID is PID.

Client: GDICommand.executeCommand (bundle line 129458) reads the player from O.OID; CastleSendGoodsDialog.handleCastlesList (line 27122) keeps only the reply about the player it asked for.

GetPlayerInfoResponse pydantic-model

Bases: BaseResponse

Response containing detailed player information.

Command: gdi Response format: { "O": { ...player fields... }, "gcl": {"PID": ..., "C": [{"KID": ..., "AI": [{"AI": [...], ...}]}]} }

Client: GDICommand.executeCommand (bundle line 129458) reads O with parseOwnerInfo and hands gcl to the same CastleListVO.parseCastleList as the gcl command.

Fields:

Validators:

  • _fold_landmark_lists_into_gcl
  • _castle_list_needs_an_object → castle_list

model_config class-attribute

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

owner pydantic-field

owner: PlayerOwnerInfo | None = None

Wire key O

castle_list pydantic-field

castle_list: GetCastlesResponse

Wire key gcl

The player's castles, outposts and landmarks, shaped as a GetCastlesResponse

player_id property

player_id: int

Get player ID from owner info.

player_name property

player_name: str

Get player name from owner info.

alliance_id property

alliance_id: int

The player's alliance id, -1 when the player is in none or the reply has no owner.

alliance_name property

alliance_name: str

Get alliance name from owner info.

has_bird property

has_bird: bool

Whether the player has peace protection left now; False once it has run out or without owner.

has_beginner_protection property

has_beginner_protection: bool

Whether the player has beginner protection left now; False once it has run out or without owner.

revenge_protection_end property

revenge_protection_end: float | None

When peace protection ends, in time.monotonic() seconds; None without protection or owner.

get_castles

get_castles() -> list[CastleInfo]

All castles, outposts and landmarks across all kingdoms.

get_location_captures

get_location_captures() -> list[LocationCapture]

Extract locations that are being captured.

Returns:

Type Description
list[LocationCapture]

List of LocationCapture objects for locations with active captures.

get_all_captures_by_location

get_all_captures_by_location() -> dict[int, int]

Get a mapping of location_id -> capturer_id for all captures.

Returns:

Type Description
dict[int, int]

Dict mapping location_id to the player_id who is capturing it.

SearchPlayerRequest pydantic-model

Bases: BaseRequest

Find a player by name.

Command: wsp Payload: {"PN": player_name}, the name encoded as chat text

Client: C2SSearchPlayerVO (bundle line 66000), sent by CastleWorldmapData.searchPlayerByName (bundle line 19009)

Fields:

player_name pydantic-field

player_name: str

Wire key PN

The player's name, as typed

SearchPlayerResponse pydantic-model

Bases: BaseResponse

Response to a player search.

Command: wsp

Client: WSPCommand.executeCommand (bundle line 131619) reads gaa.OI with parseOwnerInfoArray and gaa.AI with parseAreaInfos, as the fnm reply does, then CastleWorldmapData.parseSearchInfos (bundle line 19010) opens the area at X/Y, the found player's castle.

Fields:

Validators:

  • _area_needs_an_object → area

model_config class-attribute

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

area pydantic-field

area: MapArea

Wire key gaa

The found player's map rows and owner records

x pydantic-field

x: int | None = None

Wire key X

Map x of the found player's castle

y pydantic-field

y: int | None = None

Wire key Y

Map y of the found player's castle

get_player

get_player() -> MapObject | None

The found player's owner record: the owner of the area at X/Y.

Falls back to the first owner record when no row sits at X/Y or its owner has no record, and None when there are no records.

player.models.profile

The owner record both the alliance member list and the gdi player info send.

AllianceMember (ain, alliance.models.info) and PlayerOwnerInfo (gdi, player.models.info) subclass PlayerProfileBase.

Classes:

Name Description
PlayerProfileBase

A player's owner record.

PlayerProfileBase pydantic-model

Bases: BasePayload

A player's owner record.

Client: WorldMapOwnerInfoVO.fillFromParamObject (bundle line 10794), which both the ain member list and the gdi owner go through (CastleOtherPlayerData.parseOwnerInfo, bundle line 138996); WorldMapOwnerInfoVO.parsePosList (bundle line 10795) for AP and VP.

The protection times count from received_at, as the client counts them from the CachedTimer reading parseOwnerInfo takes (bundle line 138997). received_at_wall is stamped with it, for the *_utc ends: the wall clock at reading.

Fields:

player_id pydantic-field

player_id: ParseInt = 0

Wire key OID

Player id

name pydantic-field

name: str = ''

Wire key N

Player name

emblem pydantic-field

emblem: OwnerCrest | None = None

Wire key E

The player's crest

level pydantic-field

level: ParseInt = 0

Wire key L

Level

legendary_level pydantic-field

legendary_level: ParseInt = 0

Wire key LL

Legendary level

beginner_protection_seconds pydantic-field

beginner_protection_seconds: ParseInt = 0

Wire key RNP

Seconds of beginner protection left

honor pydantic-field

honor: ParseInt = 0

Wire key H

Honor points

might pydantic-field

might: ParseInt = 0

Wire key MP

Might points

top_ranking pydantic-field

top_ranking: ParseInt = -1

Wire key TOPX

Top ranking place

is_ruin pydantic-field

is_ruin: bool = False

Wire key R

The player's castle is a ruin

alliance_id pydantic-field

alliance_id: int = -1

Wire key AID

Alliance id; -1 when the player is in none

alliance_rank pydantic-field

alliance_rank: int | None = None

Wire key AR

Rank in the alliance, an AllianceRank value; None when unreadable

alliance_name pydantic-field

alliance_name: str = ''

Wire key AN

Alliance name

is_searching_alliance pydantic-field

is_searching_alliance: bool = False

Wire key SA

The player is looking for an alliance

revenge_protection_seconds pydantic-field

revenge_protection_seconds: ParseInt = 0

Wire key RPT

Seconds of peace protection left

castle_positions pydantic-field

castle_positions: list[OwnerCastlePosition]

Wire key AP

Castles

village_positions pydantic-field

village_positions: list[OwnerCastlePosition]

Wire key VP

Villages

has_premium_flag pydantic-field

has_premium_flag: bool = False

Wire key PF

The player has the premium flag

has_vip_flag pydantic-field

has_vip_flag: bool = False

Wire key VF

The player has the VIP flag

is_dummy pydantic-field

is_dummy: bool = False

Wire key DUM

A placeholder record, not a real player

achievement_points pydantic-field

achievement_points: ParseInt = 0

Wire key AVP

Achievement points

relocation_remaining_seconds pydantic-field

relocation_remaining_seconds: int = 0

Wire key RRD

Seconds until the player's castle relocation ends

faction pydantic-field

faction: OwnerFaction | None = None

Wire key FN

Faction event standing

alliance_emblem pydantic-field

alliance_emblem: AllianceEmblem | None = None

Wire key aee

The alliance's crest; None outside an alliance

title_suffix pydantic-field

title_suffix: int | None = None

Wire key SUF

Suffix title id; None when unsent

title_prefix pydantic-field

title_prefix: int | None = None

Wire key PRE

Prefix title id; None when unsent

via_refer_a_friend pydantic-field

via_refer_a_friend: bool = False

Wire key IRF

The player joined through a referral

received_at pydantic-field

received_at: float

When the values were read, in time.monotonic() seconds

received_at_wall pydantic-field

received_at_wall: float

When the values were read, in time.time() seconds

is_in_alliance property

is_in_alliance: bool

Whether the player is in an alliance.

Client: WorldMapOwnerInfoVO.isInAlliance (bundle line 10834)

alliance_rank_enum property

alliance_rank_enum: AllianceRank | None

alliance_rank as an AllianceRank, None for a value the client does not define.

is_leader property

is_leader: bool

Whether the player leads their alliance: rank 0.

Client: AllianceInfoVO.allianceLeader (bundle line 25986) is the member whose allianceRank is 0

is_officer property

is_officer: bool

Whether the player holds a rank between leader and member.

has_bird property

has_bird: bool

Whether the player has peace protection left now; False once it has run out.

Client: WorldMapOwnerInfoVO.isPeaceProtected (bundle line 10808)

has_beginner_protection property

has_beginner_protection: bool

Whether the player has beginner protection left now; False once it has run out.

Client: WorldMapOwnerInfoVO.isNoobProtected (bundle line 10806)

revenge_protection_end property

revenge_protection_end: float | None

When peace protection ends, in time.monotonic() seconds; None without protection.

beginner_protection_end property

beginner_protection_end: float | None

When beginner protection ends, in time.monotonic() seconds; None without protection.

revenge_protection_end_utc

revenge_protection_end_utc() -> datetime | None

When peace protection ends, as a UTC datetime; None without protection.

Counted from received_at_wall, not from now, so it stays the same for the same record.

beginner_protection_end_utc

beginner_protection_end_utc() -> datetime | None

When beginner protection ends, as a UTC datetime; None without protection.

Counted from received_at_wall, not from now, so it stays the same for the same record.

remaining_revenge_protection_seconds

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

Seconds of peace protection left, 0 when none.

Client: WorldMapOwnerInfoVO.remainingPeaceTime (bundle line 10804), counted from the CachedTimer reading fillFromParamObject keeps (bundle line 10794)

remaining_beginner_protection_seconds

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

Seconds of beginner protection left, 0 when none.

Client: WorldMapOwnerInfoVO.remainingNoobTime (bundle line 10802), counted from the CachedTimer reading fillFromParamObject keeps (bundle line 10794)

player.models.progress

The player's progress, as the login data and its pushes send it.

Commands:

  • rei: research
  • boi: boosters, the premium account and production slots (bfs: the festival)
  • gmu: might points
  • ufa: glory points
  • ufp: Berimond (faction) points
  • uar: top-X title ranks, the island title and the displayed title systems
  • vli: achievements
  • gri: relocation

Each model is read once and never changes; the times in it count from received_at.

Classes:

Name Description
ResearchInfoResponse

Your research: what is finished and what runs now.

Booster

One booster of a boi's BO list.

Festival

The running festival: a boi's bfs, or a bfs reply.

BoosterInfoResponse

Your boosters, premium account, production slots and festival.

MightPointsResponse

Your might points.

GloryPointsResponse

Your glory points, which give the glory titles.

FactionPointsResponse

Your Berimond (faction) points, which give the Berimond titles.

TopTitleRanking

One title system's top-X standing: a uar's FTM (glory) or BTM (Berimond).

IslandTitle

A uar's ITM. Client: CastleTitleData.parseIslandDataFromServer (bundle lines 21023-21027)

AllianceCityTitle

A uar's ATM. Client: CastleTitleData.parseAllianceCityDataFromServer (bundle lines 21028-21036)

TitleRanksResponse

Your top-X title ranks, Storm Islands title and the title systems your name shows.

AchievementProgress

One achievement's progress, an entry of a vli's RA.

AchievementsResponse

Your achievement points, finished achievements and progress.

RelocationInfoResponse

Your castle relocations: how many, the one running and the cooldown.

Attributes:

Name Type Description
PERMANENT_BOOSTER_DURATION

BoosterConst.PERMANENT_BOOSTER_DURATION (dll): a booster's RT when it never runs out

PERMANENT_BOOSTER_DURATION module-attribute

PERMANENT_BOOSTER_DURATION = 2147483647

BoosterConst.PERMANENT_BOOSTER_DURATION (dll): a booster's RT when it never runs out

Numbers module-attribute

Numbers = Annotated[tuple[int | float, ...], BeforeValidator(_numbers)]

Ints module-attribute

Ints = Annotated[tuple[int, ...], BeforeValidator(_ints)]

ParsedInt module-attribute

ParsedInt = Annotated[int | None, BeforeValidator(js_parse_int)]

ResearchInfoResponse pydantic-model

Bases: TimedResponse

Your research: what is finished and what runs now.

Command: rei, as a login section of gbd, a push, and inside the res and msr replies.

Client: CastleResearchData.parse_REI (bundle line 139330), remainingResearchTimeInSeconds and isSomeResearchActive (bundle lines 139337-139338)

Fields:

bought_research_ids pydantic-field

bought_research_ids: tuple[EnumOrInt['Research'], ...] = ()

Wire key BR

Finished researches

current_research_id pydantic-field

current_research_id: ClientInt = 0

Wire key ARID

The research running now, -1 for none

research_seconds pydantic-field

research_seconds: ClientNumber = 0

Wire key ARRT

Seconds left on the running research when the values were read

remaining_research_seconds

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

Whole seconds left on the running research, 0 when none runs.

is_research_active

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

Whether a research runs: an id other than -1 with time left.

Booster pydantic-model

Bases: TimedPayload

One booster of a boi's BO list.

Client: CastleHeroDefaultBoosterShopVO.parseServerInfo (bundle line 8677), remainingTimeInSeconds and isActive (bundle lines 8678-8682); the subclasses that read B (bundle lines 90338, 90365, 90670, 90864)

Fields:

Validators:

booster_id pydantic-field

booster_id: ClientInt = 0

Wire key ID

The booster's id

level pydantic-field

level: ClientInt = 0

Wire key L

The booster's level

seconds pydantic-field

seconds: ClientInt = 0

Wire key RT

Seconds left when the values were read; PERMANENT_BOOSTER_DURATION for ever

purchase_count pydantic-field

purchase_count: int | float | None = None

Wire key PC

How many times in a row it was bought; None when not sent

bonus_percent pydantic-field

bonus_percent: ClientInt | None = None

Wire key B

Its bonus in percent, if it has one

is_permanent property

is_permanent: bool

Whether the booster never runs out.

remaining_seconds

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

Seconds left, -1 at least; infinite for a permanent booster.

is_active

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

A permanent booster with a level, or one whose time has not run out.

Festival pydantic-model

Bases: TimedPayload

The running festival: a boi's bfs, or a bfs reply.

Client: FestivalVO.fillFromParamObject and parseDuration (bundle lines 90214-90215)

Fields:

festival_type pydantic-field

festival_type: ClientInt = 0

Wire key T

The festival's id, -1 for none

seconds pydantic-field

seconds: ClientNumber = 0

Wire key RT

Seconds left when the values were read

remaining_seconds

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

Seconds left, 0 at least.

is_active

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

Whether the festival still runs.

BoosterInfoResponse pydantic-model

Bases: TimedResponse

Your boosters, premium account, production slots and festival.

A boi lists only some boosters; the client keeps the others as they were, and client.state.get_boosts() does the same.

Command: boi, as a login section of gbd, a push, and inside the replies to buying a booster (bcs, bds, bis, bms, brs, ovs, ups, btx).

Client: CastlePremiumBoostData.parse_BOI (bundle line 15202), PremiumAccountVO.parseServerInfo, isActive, remainingTimeInSeconds and premiumAccountType (bundle lines 90230-90235), parse_bfs (bundle line 15218)

Fields:

Validators:

boosters pydantic-field

boosters: tuple[Booster, ...] = ()

Wire key BO

The boosters this packet lists

premium_seconds pydantic-field

premium_seconds: ClientNumber = 0

Wire key PA

Seconds of premium account left when the values were read

premium_type pydantic-field

premium_type: EnumOrInt[PremiumAccountType] | None = None

Wire key PT

The premium account's type; None when the packet names none

unit_slots pydantic-field

unit_slots: Numbers = ()

Wire key SU

Unit production slots: above 0 bought, below 0 permanent

tool_slots pydantic-field

tool_slots: Numbers = ()

Wire key ST

Tool production slots: above 0 bought, below 0 permanent

festival pydantic-field

festival: Festival | None = None

Wire key bfs

The festival; None when not sent

bought_unit_slots property

bought_unit_slots: int

Unit production slots bought.

permanent_unit_slots property

permanent_unit_slots: int

Permanent unit production slots.

bought_tool_slots property

bought_tool_slots: int

Tool production slots bought.

permanent_tool_slots property

permanent_tool_slots: int

Permanent tool production slots.

premium_remaining_seconds

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

Seconds of premium account left, 0 when it is not active.

is_premium_active

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

Whether the premium account runs.

premium_account_type

premium_account_type(now: float | None = None) -> PremiumAccountType | int | None

The premium account's type while it runs, else None.

booster

booster(booster_id: int) -> Booster | None

One booster, None when it is not listed.

MightPointsResponse pydantic-model

Bases: TimedResponse

Your might points.

Command: gmu, as a login section of gbd and a push. The client reads one only when it has both MP and HMP.

Client: MightData.parse_GMU (bundle line 113517)

Fields:

might_points pydantic-field

might_points: ParsedInt = None

Wire key MP

Might points; None when unreadable

building_might pydantic-field

building_might: ParsedInt = None

Wire key TSBM

Might points from buildings; None when unreadable

highest_might_points pydantic-field

highest_might_points: ParsedInt = None

Wire key HMP

The most might points ever reached; None when unreadable

GloryPointsResponse pydantic-model

Bases: TimedResponse

Your glory points, which give the glory titles.

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

Client: CastleTitleData.parseUFA (bundle line 21038), which parseInt s a string and keeps a number

Fields:

Validators:

glory_points pydantic-field

glory_points: int | float | None = None

Wire key CF

Glory points

highest_glory_points pydantic-field

highest_glory_points: int | float | None = None

Wire key HF

The most glory points ever

FactionPointsResponse pydantic-model

Bases: TimedResponse

Your Berimond (faction) points, which give the Berimond titles.

Command: ufp, a push. The client ignores the copy in gbd.

Client: UFPCommand (bundle line 121010), CastleTitleData.parseUFP (bundle line 21039)

Fields:

faction_points pydantic-field

faction_points: ParsedInt = None

Wire key CFP

Berimond points; None when unreadable

highest_faction_points pydantic-field

highest_faction_points: ParsedInt = None

Wire key HFP

The most Berimond points ever; None when unreadable

TopTitleRanking pydantic-model

Bases: TimedPayload

One title system's top-X standing: a uar's FTM (glory) or BTM (Berimond).

Client: CastleTitleData.parseUAR (bundle line 21022)

Fields:

Validators:

top_rank pydantic-field

top_rank: ClientInt = 0

Wire key CTXT

Your rank for the system's top-X titles, -1 or 0 for none

reset_seconds pydantic-field

reset_seconds: ClientNumber = 0

Wire key RS

Seconds until the top-X titles reset

thresholds pydantic-field

thresholds: tuple[int, ...] = ()

Wire key NTFP

The points the top-X titles need, highest title first

top_player_id pydantic-field

top_player_id: int | None = None

Wire key TOID

The player holding the system's top title; None when not sent

IslandTitle pydantic-model

Bases: BasePayload

A uar's ITM. Client: CastleTitleData.parseIslandDataFromServer (bundle lines 21023-21027)

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

title_id pydantic-field

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

Wire key TID

Your Storm Islands title; None for none

held_title_id property

held_title_id: int

The title id, -1 for none.

AllianceCityTitle pydantic-model

Bases: BasePayload

A uar's ATM. Client: CastleTitleData.parseAllianceCityDataFromServer (bundle lines 21028-21036)

Fields:

Validators:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

title_id pydantic-field

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

Wire key TID

The alliance city title; None for none

player_id pydantic-field

player_id: int | None = None

Wire key PID

The player holding it; None when not sent

TitleRanksResponse pydantic-model

Bases: TimedResponse

Your top-X title ranks, Storm Islands title and the title systems your name shows.

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

Client: CastleTitleData.parseUAR (bundle line 21022)

Fields:

Validators:

glory pydantic-field

Wire key FTM

The glory system

faction pydantic-field

faction: TopTitleRanking

Wire key BTM

The Berimond system

island_title pydantic-field

island_title: IslandTitle

Wire key ITM

Your Storm Islands title

alliance_city_title pydantic-field

alliance_city_title: AllianceCityTitle | None = None

Wire key ATM

The alliance city title; None when not sent

prefix_system pydantic-field

prefix_system: EnumOrStr[TitleSystem] | None = None

Wire key PFX

The title system shown before your name; None when not sent

suffix_system pydantic-field

suffix_system: EnumOrStr[TitleSystem] | None = None

Wire key SFX

The title system shown after your name; None when not sent

AchievementProgress pydantic-model

Bases: BasePayload

One achievement's progress, an entry of a vli's RA.

Client: CastleAchievementData.parse_RA (bundle line 29838), AchievementVO.setProgress (bundle line 92889)

Fields:

model_config class-attribute

model_config: ConfigDict = ConfigDict(frozen=True)

achievement_id pydantic-field

achievement_id: ClientInt = 0

Wire key AID

The achievement's id

progress pydantic-field

progress: Numbers = ()

Wire key P

Progress per condition, in condition order; -1 for a condition done

AchievementsResponse pydantic-model

Bases: TimedResponse

Your achievement points, finished achievements and progress.

A vli adds to what the client knows: finished achievements stay finished, and an achievement it does not list keeps its progress. client.state.get_achievements() does the same.

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

Client: CastleAchievementData.parse_vli, parse_RA and parse_FA (bundle lines 29837-29842)

Fields:

Validators:

achievement_points pydantic-field

achievement_points: ClientInt = 0

Wire key AVP

Achievement points

finished_achievement_ids pydantic-field

finished_achievement_ids: tuple[EnumOrInt['Achievement'], ...] = ()

Wire key FA

Finished achievements

progress pydantic-field

progress: tuple[AchievementProgress, ...] = ()

Wire key RA

Progress of the achievements this packet lists

RelocationInfoResponse pydantic-model

Bases: TimedResponse

Your castle relocations: how many, the one running and the cooldown.

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

Client: CastleUserData.parse_GRI (bundle line 9910), remainingRelocationDuration and remainingRelocationCooldown

Fields:

Validators:

relocation_count pydantic-field

relocation_count: ClientInt = 0

Wire key RLC

Relocations made

relocation_seconds pydantic-field

relocation_seconds: ClientNumber = 0

Wire key RD

Seconds left on the running relocation when the values were read

relocation_mode pydantic-field

relocation_mode: int | None = None

Wire key JM

The relocation's mode: its time counts only while this is 0, and the client redraws the map at OX/OY for 1; None when not sent

cooldown_seconds pydantic-field

cooldown_seconds: ClientNumber = 0

Wire key RMC

Seconds until you may relocate again, when the values were read

destination_x pydantic-field

destination_x: ClientInt = 0

Wire key DX

The relocation's destination X

destination_y pydantic-field

destination_y: ClientInt = 0

Wire key DY

The relocation's destination Y

remaining_relocation_seconds

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

Seconds left on the running relocation, 0 when none runs.

remaining_cooldown_seconds

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

Seconds until you may relocate again, 0 at least.

player.models.research

Starting research and shortening it with a minute skip.

Commands:

  • res: start a research
  • msr: shorten the running research with a minute skip

Finishing a research at once for rubies (C2SResearchFinishInstantVO, bundle line 49093) is left out.

Classes:

Name Description
StartResearchRequest

Start a research, paying what it costs: resources and coins, or rubies for a research that costs them.

StartResearchResponse

Your research, coins and rubies, and the joined castle's resources after starting a research.

SkipResearchRequest

Shorten the running research with a minute skip.

SkipResearchResponse

Your research after a minute skip.

StartResearchRequest pydantic-model

Bases: BaseRequest

Start a research, paying what it costs: resources and coins, or rubies for a research that costs them.

Command: res Payload: {"RID": research_id, "PO": -1, "PWR": 0}

PWR 1 would pay missing resources with rubies (CastleResourceWaitDialogProperties.getResourceSkipCommand, bundle line 35216); this request always sends 0. A research whose own costs hold rubies (ResearchDef.cost_rubies) is sent the same way and spends them; client.player.start_research refuses it unless asked to spend rubies.

Client: C2SResearchStartVO (bundle line 47697), sent by ResearchInfo.buyResearch (bundle line 79704) with the private offer -1 and PWR 0

Fields:

research_id pydantic-field

research_id: EnumOrInt['Research']

Wire key RID

The research to start

private_offer_id pydantic-field

private_offer_id: int = -1

Wire key PO

The private offer the purchase uses, -1 for none

pay_with_rubies pydantic-field

pay_with_rubies: Literal[0] = 0

Wire key PWR

Always 0: missing resources are never paid with rubies

StartResearchResponse pydantic-model

Bases: BaseResponse

Your research, coins and rubies, and the joined castle's resources after starting a research.

Command: res Payload: {"rei": {..}, "gcu": {..}, "grc": {..}}

Client: RESCommand.executeCommand (bundle line 126876), which parses all three

Fields:

research pydantic-field

research: ResearchInfoResponse | None = None

Wire key rei

Your research after

currencies pydantic-field

currencies: CurrencyBlock = None

Wire key gcu

Coins and rubies after

resources pydantic-field

resources: CastleResources | None = None

Wire key grc

The joined castle's resources after

SkipResearchRequest pydantic-model

Bases: BaseRequest

Shorten the running research with a minute skip.

Command: msr Payload: {"MST": minute_skip}

Client: C2SMinuteSkipResearchVO (bundle line 79810), built by ResearchMinuteSkipProperties.getMinuteSkipCommand (bundle line 79784); CastleMinuteSkipDialog passes the currency's jsonKey (bundle line 7722)

Fields:

minute_skip pydantic-field

minute_skip: EnumOrStr['Currency']

Wire key MST

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

SkipResearchResponse pydantic-model

Bases: BaseResponse

Your research after a minute skip.

Command: msr Payload: {"rei": {..}}

Client: MSRCommand.executeCommand (bundle line 125829)

Fields:

research pydantic-field

research: ResearchInfoResponse | None = None

Wire key rei

Your research after

player.titles

The titles you hold, worked out from your points and ranks as the client does.

Client: CastleTitleData (bundle lines 21000-21130)

Functions:

Name Description
titles_in_order

A title system's title ids, from its first title along each title's next one.

held_titles

The glory or Berimond titles you hold, lowest first.

island_title_chain

The island titles you hold with one: it and every title below it, lowest first.

player_title_ids

Every title you hold, as the client lists them: glory, then Berimond, then Storm Islands.

titles_in_order

titles_in_order(game_data: GameData, system: TitleSystem) -> list[Title | int]

A title system's title ids, from its first title along each title's next one.

Client: CastleTitleData.setupNextTitles (bundle line 21016) and orderTitlesInSystem (bundle line 21058)

held_titles

held_titles(game_data: GameData, system: TitleSystem, points: float | None, top_rank: int | None) -> list[Title | int]

The glory or Berimond titles you hold, lowest first.

Each title in order is held while its points threshold is reached, or for a top-X title while your rank is above 0 and within its top X; the first one missed ends the list.

Parameters:

Name Type Description Default
game_data GameData

Loaded tables

required
system TitleSystem

TitleSystem.GLORY or TitleSystem.FACTION

required
points float | None

Your glory points (ufa CF) or Berimond points (ufp CFP); None before they arrived

required
top_rank int | None

The system's uar top rank (CTXT); None before it arrived

required

Client: CastleTitleData.setupThisUsersTitlesinSystem (bundle line 21048), with TitleVO.isTopXTitle

island_title_chain

island_title_chain(game_data: GameData, island_title_id: int) -> list[Title | int]

The island titles you hold with one: it and every title below it, lowest first.

Parameters:

Name Type Description Default
game_data GameData

Loaded tables

required
island_title_id int

Your current Storm Islands title, -1 or an unknown id for none

required

Client: CastleTitleData.getUsersTitleVectorFromSystem for ISLAND_TITLE (bundle line 21119)

player_title_ids

player_title_ids(game_data: GameData, glory: GloryPointsResponse | None, faction: FactionPointsResponse | None, ranks: TitleRanksResponse | None) -> list[Title | int]

Every title you hold, as the client lists them: glory, then Berimond, then Storm Islands.

Parameters:

Name Type Description Default
game_data GameData

Loaded tables

required
glory GloryPointsResponse | None

client.state.get_glory_points()

required
faction FactionPointsResponse | None

client.state.get_faction_points()

required
ranks TitleRanksResponse | None

client.state.get_title_ranks()

required

Client: CastleTitleData.thisUsersTitles (bundle line 21073)