empire-core 0.7.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- empire_core/__init__.py +36 -0
- empire_core/_archive/actions.py +511 -0
- empire_core/_archive/automation/__init__.py +24 -0
- empire_core/_archive/automation/alliance_tools.py +266 -0
- empire_core/_archive/automation/battle_reports.py +196 -0
- empire_core/_archive/automation/building_queue.py +242 -0
- empire_core/_archive/automation/defense_manager.py +124 -0
- empire_core/_archive/automation/map_scanner.py +370 -0
- empire_core/_archive/automation/multi_account.py +296 -0
- empire_core/_archive/automation/quest_automation.py +94 -0
- empire_core/_archive/automation/resource_manager.py +380 -0
- empire_core/_archive/automation/target_finder.py +153 -0
- empire_core/_archive/automation/tasks.py +224 -0
- empire_core/_archive/automation/unit_production.py +719 -0
- empire_core/_archive/cli.py +68 -0
- empire_core/_archive/client_async.py +469 -0
- empire_core/_archive/commands.py +201 -0
- empire_core/_archive/connection_async.py +228 -0
- empire_core/_archive/defense.py +156 -0
- empire_core/_archive/events/__init__.py +35 -0
- empire_core/_archive/events/base.py +153 -0
- empire_core/_archive/events/manager.py +85 -0
- empire_core/accounts.py +190 -0
- empire_core/client/__init__.py +0 -0
- empire_core/client/client.py +459 -0
- empire_core/config.py +87 -0
- empire_core/exceptions.py +42 -0
- empire_core/network/__init__.py +0 -0
- empire_core/network/connection.py +378 -0
- empire_core/protocol/__init__.py +0 -0
- empire_core/protocol/models/__init__.py +339 -0
- empire_core/protocol/models/alliance.py +186 -0
- empire_core/protocol/models/army.py +444 -0
- empire_core/protocol/models/attack.py +229 -0
- empire_core/protocol/models/auth.py +216 -0
- empire_core/protocol/models/base.py +403 -0
- empire_core/protocol/models/building.py +455 -0
- empire_core/protocol/models/castle.py +317 -0
- empire_core/protocol/models/chat.py +150 -0
- empire_core/protocol/models/defense.py +300 -0
- empire_core/protocol/models/map.py +269 -0
- empire_core/protocol/packet.py +104 -0
- empire_core/services/__init__.py +31 -0
- empire_core/services/alliance.py +222 -0
- empire_core/services/base.py +107 -0
- empire_core/services/castle.py +221 -0
- empire_core/state/__init__.py +0 -0
- empire_core/state/manager.py +398 -0
- empire_core/state/models.py +215 -0
- empire_core/state/quest_models.py +60 -0
- empire_core/state/report_models.py +115 -0
- empire_core/state/unit_models.py +75 -0
- empire_core/state/world_models.py +269 -0
- empire_core/storage/__init__.py +1 -0
- empire_core/storage/database.py +237 -0
- empire_core/utils/__init__.py +0 -0
- empire_core/utils/battle_sim.py +172 -0
- empire_core/utils/calculations.py +170 -0
- empire_core/utils/crypto.py +8 -0
- empire_core/utils/decorators.py +69 -0
- empire_core/utils/enums.py +111 -0
- empire_core/utils/helpers.py +252 -0
- empire_core/utils/response_awaiter.py +153 -0
- empire_core/utils/troops.py +93 -0
- empire_core-0.7.3.dist-info/METADATA +197 -0
- empire_core-0.7.3.dist-info/RECORD +67 -0
- empire_core-0.7.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes and common types for GGE protocol models.
|
|
3
|
+
|
|
4
|
+
GGE Protocol Format:
|
|
5
|
+
- Request: %xt%{zone}%{command}%1%{json_payload}%
|
|
6
|
+
- Response: %{command}%{zone}%{error_code}%{json_payload}%
|
|
7
|
+
|
|
8
|
+
Special character encoding for text fields (chat messages, etc.):
|
|
9
|
+
- percent -> %
|
|
10
|
+
- quote -> "
|
|
11
|
+
- apostrophe -> &145;
|
|
12
|
+
- newline -> <br /> or <br>
|
|
13
|
+
- backslash -> %5C
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from enum import IntEnum
|
|
20
|
+
from typing import Any, ClassVar, Type, TypeVar
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
23
|
+
|
|
24
|
+
# Type variable for generic response payloads
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
|
|
27
|
+
# Default zone for packet building
|
|
28
|
+
DEFAULT_ZONE = "EmpireEx_21"
|
|
29
|
+
|
|
30
|
+
# Registry mapping command -> response model class
|
|
31
|
+
_response_registry: dict[str, Type["BaseResponse"]] = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GGECommand:
|
|
35
|
+
"""Registry of all GGE protocol commands."""
|
|
36
|
+
|
|
37
|
+
# Authentication
|
|
38
|
+
LLI = "lli" # Login
|
|
39
|
+
LRE = "lre" # Register
|
|
40
|
+
VPN = "vpn" # Check username availability
|
|
41
|
+
VLN = "vln" # Check if user exists
|
|
42
|
+
LPP = "lpp" # Password recovery
|
|
43
|
+
|
|
44
|
+
# Chat
|
|
45
|
+
ACM = "acm" # Alliance chat message (send/receive)
|
|
46
|
+
ACL = "acl" # Get alliance chat log
|
|
47
|
+
|
|
48
|
+
# Alliance
|
|
49
|
+
AHC = "ahc" # Help member
|
|
50
|
+
AHA = "aha" # Help all
|
|
51
|
+
AHR = "ahr" # Ask for help (request)
|
|
52
|
+
|
|
53
|
+
# Castle
|
|
54
|
+
GCL = "gcl" # Get castles list
|
|
55
|
+
DCL = "dcl" # Get detailed castle info
|
|
56
|
+
JCA = "jca" # Jump to castle
|
|
57
|
+
ARC = "arc" # Rename castle
|
|
58
|
+
RST = "rst" # Relocate castle
|
|
59
|
+
GRC = "grc" # Get resources
|
|
60
|
+
GPA = "gpa" # Get production
|
|
61
|
+
|
|
62
|
+
# Map
|
|
63
|
+
GAM = "gam" # Get active movements
|
|
64
|
+
GAA = "gaa" # Get map chunk (area)
|
|
65
|
+
FNM = "fnm" # Find NPC on map
|
|
66
|
+
ADI = "adi" # Get area/target detailed info
|
|
67
|
+
|
|
68
|
+
# Attack
|
|
69
|
+
CRA = "cra" # Create/send attack
|
|
70
|
+
CSM = "csm" # Send spy mission
|
|
71
|
+
GAS = "gas" # Get attack presets
|
|
72
|
+
MSD = "msd" # Skip attack cooldown (rubies)
|
|
73
|
+
SDC = "sdc" # Skip defense cooldown
|
|
74
|
+
|
|
75
|
+
# Building
|
|
76
|
+
EBU = "ebu" # Build (erect building)
|
|
77
|
+
EUP = "eup" # Upgrade building
|
|
78
|
+
EMO = "emo" # Move building
|
|
79
|
+
SBD = "sbd" # Sell building
|
|
80
|
+
EDO = "edo" # Destroy building
|
|
81
|
+
FCO = "fco" # Fast complete (skip construction)
|
|
82
|
+
MSB = "msb" # Time skip building
|
|
83
|
+
EUD = "eud" # Upgrade wall/defense
|
|
84
|
+
RBU = "rbu" # Repair building
|
|
85
|
+
IRA = "ira" # Repair all
|
|
86
|
+
EBE = "ebe" # Buy extension
|
|
87
|
+
ETC = "etc" # Collect extension gift
|
|
88
|
+
|
|
89
|
+
# Army / Production
|
|
90
|
+
BUP = "bup" # Build units / produce
|
|
91
|
+
SPL = "spl" # Get production queue (LID: 0=soldiers, 1=tools)
|
|
92
|
+
BOU = "bou" # Double production slot
|
|
93
|
+
MCU = "mcu" # Cancel production
|
|
94
|
+
GUI = "gui" # Get units inventory
|
|
95
|
+
DUP = "dup" # Delete units
|
|
96
|
+
|
|
97
|
+
# Hospital
|
|
98
|
+
HRU = "hru" # Heal units
|
|
99
|
+
HCS = "hcs" # Cancel heal
|
|
100
|
+
HSS = "hss" # Skip heal (rubies)
|
|
101
|
+
HDU = "hdu" # Delete wounded
|
|
102
|
+
HRA = "hra" # Heal all
|
|
103
|
+
|
|
104
|
+
# Defense
|
|
105
|
+
DFC = "dfc" # Get defense configuration
|
|
106
|
+
DFK = "dfk" # Change keep defense
|
|
107
|
+
DFW = "dfw" # Change wall defense
|
|
108
|
+
DFM = "dfm" # Change moat defense
|
|
109
|
+
SDI = "sdi" # Get support defense info (alliance member castle defense)
|
|
110
|
+
|
|
111
|
+
# Shop
|
|
112
|
+
SBP = "sbp" # Buy package
|
|
113
|
+
GBC = "gbc" # Set buying castle
|
|
114
|
+
|
|
115
|
+
# Events
|
|
116
|
+
SEI = "sei" # Get events info
|
|
117
|
+
PEP = "pep" # Get event points
|
|
118
|
+
HGH = "hgh" # Get ranking/highscore
|
|
119
|
+
SEDE = "sede" # Select event difficulty
|
|
120
|
+
|
|
121
|
+
# Gifts
|
|
122
|
+
CLB = "clb" # Collect daily login bonus
|
|
123
|
+
GPG = "gpg" # Send gift to player
|
|
124
|
+
|
|
125
|
+
# Quests
|
|
126
|
+
QSC = "qsc" # Complete message quest
|
|
127
|
+
QDR = "qdr" # Complete donation quest
|
|
128
|
+
FCQ = "fcq" # Complete condition
|
|
129
|
+
CTR = "ctr" # Tracking
|
|
130
|
+
|
|
131
|
+
# Account
|
|
132
|
+
GPI = "gpi" # Get player info
|
|
133
|
+
VPM = "vpm" # Register email
|
|
134
|
+
GNCI = "gnci" # Get name change info
|
|
135
|
+
CPNE = "cpne" # Change username
|
|
136
|
+
SCP = "scp" # Change password
|
|
137
|
+
RMC = "rmc" # Request email change
|
|
138
|
+
MNS = "mns" # Email change status
|
|
139
|
+
CMC = "cmc" # Cancel email change
|
|
140
|
+
FCS = "fcs" # Facebook connection status
|
|
141
|
+
|
|
142
|
+
# Settings
|
|
143
|
+
ANI = "ani" # Animation settings
|
|
144
|
+
MVF = "mvf" # Movement filter settings
|
|
145
|
+
OPT = "opt" # Misc options
|
|
146
|
+
HFL = "hfl" # Hospital filter settings
|
|
147
|
+
|
|
148
|
+
# Misc
|
|
149
|
+
TXI = "txi" # Tax info
|
|
150
|
+
TXS = "txs" # Start tax collection
|
|
151
|
+
TXC = "txc" # Collect tax
|
|
152
|
+
GBL = "gbl" # Get bookmarks list
|
|
153
|
+
RUI = "rui" # Ruin info
|
|
154
|
+
RMB = "rmb" # Ruin message
|
|
155
|
+
GFC = "gfc" # Get friends/contacts
|
|
156
|
+
SEM = "sem" # Send email/message
|
|
157
|
+
GLI = "gli" # Get lords info
|
|
158
|
+
GCS = "gcs" # Get tavern offerings
|
|
159
|
+
SCT = "sct" # Make offering
|
|
160
|
+
SIN = "sin" # Building inventory
|
|
161
|
+
SOB = "sob" # Store building
|
|
162
|
+
SDS = "sds" # Sell from inventory
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class HelpType(IntEnum):
|
|
166
|
+
"""Types of help requests in alliance."""
|
|
167
|
+
|
|
168
|
+
HEAL = 2 # Heal wounded soldiers
|
|
169
|
+
REPAIR = 3 # Repair building
|
|
170
|
+
RECRUIT = 6 # Recruit soldiers
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ProductionListType(IntEnum):
|
|
174
|
+
"""Types of production lists."""
|
|
175
|
+
|
|
176
|
+
SOLDIERS = 0
|
|
177
|
+
TOOLS = 1
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class BasePayload(BaseModel):
|
|
181
|
+
"""Base class for all protocol payloads."""
|
|
182
|
+
|
|
183
|
+
model_config = ConfigDict(
|
|
184
|
+
populate_by_name=True,
|
|
185
|
+
extra="allow", # Allow extra fields we don't know about
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def to_payload(self) -> dict[str, Any]:
|
|
189
|
+
"""
|
|
190
|
+
Convert the model to a payload dict suitable for sending.
|
|
191
|
+
|
|
192
|
+
Uses field aliases (e.g., "M" instead of "message") and
|
|
193
|
+
excludes None values.
|
|
194
|
+
"""
|
|
195
|
+
return self.model_dump(by_alias=True, exclude_none=True)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class BaseRequest(BasePayload):
|
|
199
|
+
"""Base class for request payloads sent to the server."""
|
|
200
|
+
|
|
201
|
+
command: ClassVar[str] # The command code (e.g., "acm", "lli")
|
|
202
|
+
|
|
203
|
+
def to_payload(self) -> dict[str, Any]:
|
|
204
|
+
"""
|
|
205
|
+
Convert the model to a payload dict suitable for sending.
|
|
206
|
+
|
|
207
|
+
Uses field aliases and excludes None values.
|
|
208
|
+
"""
|
|
209
|
+
return self.model_dump(by_alias=True, exclude_none=True)
|
|
210
|
+
|
|
211
|
+
def to_packet(self, zone: str = DEFAULT_ZONE) -> str:
|
|
212
|
+
"""
|
|
213
|
+
Build the full XT packet string ready to send.
|
|
214
|
+
|
|
215
|
+
Format: %xt%{zone}%{command}%1%{json_payload}%
|
|
216
|
+
|
|
217
|
+
Note: The request ID is always 1 - GGE doesn't use it for
|
|
218
|
+
request/response matching.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
zone: Game zone (default: EmpireEx_21)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
The formatted packet string
|
|
225
|
+
"""
|
|
226
|
+
payload = self.to_payload()
|
|
227
|
+
return f"%xt%{zone}%{self.command}%1%{json.dumps(payload)}%"
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def get_command(cls) -> str:
|
|
231
|
+
"""Get the command code for this request type."""
|
|
232
|
+
return cls.command
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class BaseResponse(BasePayload):
|
|
236
|
+
"""
|
|
237
|
+
Base class for response payloads received from the server.
|
|
238
|
+
|
|
239
|
+
Responses can be instantiated directly from payload dicts:
|
|
240
|
+
response = AllianceChatMessageResponse(**payload)
|
|
241
|
+
# or
|
|
242
|
+
response = AllianceChatMessageResponse.model_validate(payload)
|
|
243
|
+
|
|
244
|
+
Each response class should define a `command` class variable to enable
|
|
245
|
+
automatic lookup via `get_response_model()`.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
command: ClassVar[str] # The command code this response handles
|
|
249
|
+
|
|
250
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
251
|
+
"""Register response subclasses by their command."""
|
|
252
|
+
super().__init_subclass__(**kwargs)
|
|
253
|
+
# Only register if the class defines its own command
|
|
254
|
+
if "command" in cls.__dict__:
|
|
255
|
+
_response_registry[cls.command] = cls
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_response_model(command: str) -> Type[BaseResponse] | None:
|
|
259
|
+
"""
|
|
260
|
+
Get the response model class for a command.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
command: The command code (e.g., "acm", "gam")
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The response model class, or None if not registered
|
|
267
|
+
"""
|
|
268
|
+
return _response_registry.get(command)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def parse_response(command: str, payload: dict[str, Any]) -> BaseResponse | None:
|
|
272
|
+
"""
|
|
273
|
+
Parse a response payload into the appropriate model.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
command: The command code
|
|
277
|
+
payload: The payload dict from the server
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
The parsed response model, or None if no model is registered
|
|
281
|
+
"""
|
|
282
|
+
model_cls = get_response_model(command)
|
|
283
|
+
if model_cls is None:
|
|
284
|
+
return None
|
|
285
|
+
return model_cls.model_validate(payload)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class ErrorResponse(BaseResponse):
|
|
289
|
+
"""Error response from the server."""
|
|
290
|
+
|
|
291
|
+
error_code: int = Field(alias="E", default=0)
|
|
292
|
+
error_message: str | None = Field(alias="EM", default=None)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class Position(BaseModel):
|
|
296
|
+
"""A position on the game map."""
|
|
297
|
+
|
|
298
|
+
x: int = Field(alias="X")
|
|
299
|
+
y: int = Field(alias="Y")
|
|
300
|
+
kingdom: int = Field(alias="KID", default=0)
|
|
301
|
+
|
|
302
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class ResourceAmount(BaseModel):
|
|
306
|
+
"""Resource amounts."""
|
|
307
|
+
|
|
308
|
+
wood: int = Field(alias="W", default=0)
|
|
309
|
+
stone: int = Field(alias="S", default=0)
|
|
310
|
+
food: int = Field(alias="F", default=0)
|
|
311
|
+
coins: int = Field(alias="C", default=0)
|
|
312
|
+
rubies: int = Field(alias="R", default=0)
|
|
313
|
+
|
|
314
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class UnitCount(BaseModel):
|
|
318
|
+
"""A unit type and count pair."""
|
|
319
|
+
|
|
320
|
+
unit_id: int = Field(alias="UID")
|
|
321
|
+
count: int = Field(alias="C")
|
|
322
|
+
|
|
323
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class PlayerInfo(BaseModel):
|
|
327
|
+
"""Basic player information."""
|
|
328
|
+
|
|
329
|
+
player_id: int = Field(alias="PID")
|
|
330
|
+
player_name: str = Field(alias="PN")
|
|
331
|
+
alliance_id: int | None = Field(alias="AID", default=None)
|
|
332
|
+
alliance_name: str | None = Field(alias="AN", default=None)
|
|
333
|
+
|
|
334
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# Text encoding/decoding utilities for chat messages
|
|
338
|
+
def encode_chat_text(text: str) -> str:
|
|
339
|
+
"""
|
|
340
|
+
Encode text for sending in chat messages.
|
|
341
|
+
|
|
342
|
+
Converts special characters to their encoded forms:
|
|
343
|
+
- % -> %
|
|
344
|
+
- " -> "
|
|
345
|
+
- ' -> &145;
|
|
346
|
+
- \n -> <br />
|
|
347
|
+
- backslash -> %5C
|
|
348
|
+
"""
|
|
349
|
+
result = text
|
|
350
|
+
result = result.replace("\\", "%5C")
|
|
351
|
+
result = result.replace("%", "%")
|
|
352
|
+
result = result.replace('"', """)
|
|
353
|
+
result = result.replace("'", "&145;")
|
|
354
|
+
result = result.replace("\n", "<br />")
|
|
355
|
+
return result
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def decode_chat_text(text: str) -> str:
|
|
359
|
+
"""
|
|
360
|
+
Decode text received in chat messages.
|
|
361
|
+
|
|
362
|
+
Converts encoded forms back to special characters:
|
|
363
|
+
- % -> %
|
|
364
|
+
- " -> "
|
|
365
|
+
- &145; -> '
|
|
366
|
+
- <br /> or <br> -> \n
|
|
367
|
+
- %5C -> backslash
|
|
368
|
+
"""
|
|
369
|
+
result = text
|
|
370
|
+
result = result.replace("<br />", "\n")
|
|
371
|
+
result = result.replace("<br>", "\n")
|
|
372
|
+
result = result.replace("%", "%")
|
|
373
|
+
result = result.replace(""", '"')
|
|
374
|
+
result = result.replace("&145;", "'")
|
|
375
|
+
result = result.replace("%5C", "\\")
|
|
376
|
+
return result
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
__all__ = [
|
|
380
|
+
# Command registry
|
|
381
|
+
"GGECommand",
|
|
382
|
+
# Constants
|
|
383
|
+
"DEFAULT_ZONE",
|
|
384
|
+
# Enums
|
|
385
|
+
"HelpType",
|
|
386
|
+
"ProductionListType",
|
|
387
|
+
# Base classes
|
|
388
|
+
"BasePayload",
|
|
389
|
+
"BaseRequest",
|
|
390
|
+
"BaseResponse",
|
|
391
|
+
"ErrorResponse",
|
|
392
|
+
# Common types
|
|
393
|
+
"Position",
|
|
394
|
+
"ResourceAmount",
|
|
395
|
+
"UnitCount",
|
|
396
|
+
"PlayerInfo",
|
|
397
|
+
# Utilities
|
|
398
|
+
"encode_chat_text",
|
|
399
|
+
"decode_chat_text",
|
|
400
|
+
# Response registry
|
|
401
|
+
"get_response_model",
|
|
402
|
+
"parse_response",
|
|
403
|
+
]
|