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.
Files changed (67) hide show
  1. empire_core/__init__.py +36 -0
  2. empire_core/_archive/actions.py +511 -0
  3. empire_core/_archive/automation/__init__.py +24 -0
  4. empire_core/_archive/automation/alliance_tools.py +266 -0
  5. empire_core/_archive/automation/battle_reports.py +196 -0
  6. empire_core/_archive/automation/building_queue.py +242 -0
  7. empire_core/_archive/automation/defense_manager.py +124 -0
  8. empire_core/_archive/automation/map_scanner.py +370 -0
  9. empire_core/_archive/automation/multi_account.py +296 -0
  10. empire_core/_archive/automation/quest_automation.py +94 -0
  11. empire_core/_archive/automation/resource_manager.py +380 -0
  12. empire_core/_archive/automation/target_finder.py +153 -0
  13. empire_core/_archive/automation/tasks.py +224 -0
  14. empire_core/_archive/automation/unit_production.py +719 -0
  15. empire_core/_archive/cli.py +68 -0
  16. empire_core/_archive/client_async.py +469 -0
  17. empire_core/_archive/commands.py +201 -0
  18. empire_core/_archive/connection_async.py +228 -0
  19. empire_core/_archive/defense.py +156 -0
  20. empire_core/_archive/events/__init__.py +35 -0
  21. empire_core/_archive/events/base.py +153 -0
  22. empire_core/_archive/events/manager.py +85 -0
  23. empire_core/accounts.py +190 -0
  24. empire_core/client/__init__.py +0 -0
  25. empire_core/client/client.py +459 -0
  26. empire_core/config.py +87 -0
  27. empire_core/exceptions.py +42 -0
  28. empire_core/network/__init__.py +0 -0
  29. empire_core/network/connection.py +378 -0
  30. empire_core/protocol/__init__.py +0 -0
  31. empire_core/protocol/models/__init__.py +339 -0
  32. empire_core/protocol/models/alliance.py +186 -0
  33. empire_core/protocol/models/army.py +444 -0
  34. empire_core/protocol/models/attack.py +229 -0
  35. empire_core/protocol/models/auth.py +216 -0
  36. empire_core/protocol/models/base.py +403 -0
  37. empire_core/protocol/models/building.py +455 -0
  38. empire_core/protocol/models/castle.py +317 -0
  39. empire_core/protocol/models/chat.py +150 -0
  40. empire_core/protocol/models/defense.py +300 -0
  41. empire_core/protocol/models/map.py +269 -0
  42. empire_core/protocol/packet.py +104 -0
  43. empire_core/services/__init__.py +31 -0
  44. empire_core/services/alliance.py +222 -0
  45. empire_core/services/base.py +107 -0
  46. empire_core/services/castle.py +221 -0
  47. empire_core/state/__init__.py +0 -0
  48. empire_core/state/manager.py +398 -0
  49. empire_core/state/models.py +215 -0
  50. empire_core/state/quest_models.py +60 -0
  51. empire_core/state/report_models.py +115 -0
  52. empire_core/state/unit_models.py +75 -0
  53. empire_core/state/world_models.py +269 -0
  54. empire_core/storage/__init__.py +1 -0
  55. empire_core/storage/database.py +237 -0
  56. empire_core/utils/__init__.py +0 -0
  57. empire_core/utils/battle_sim.py +172 -0
  58. empire_core/utils/calculations.py +170 -0
  59. empire_core/utils/crypto.py +8 -0
  60. empire_core/utils/decorators.py +69 -0
  61. empire_core/utils/enums.py +111 -0
  62. empire_core/utils/helpers.py +252 -0
  63. empire_core/utils/response_awaiter.py +153 -0
  64. empire_core/utils/troops.py +93 -0
  65. empire_core-0.7.3.dist-info/METADATA +197 -0
  66. empire_core-0.7.3.dist-info/RECORD +67 -0
  67. 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
+ - % -> &percnt;
344
+ - " -> &quot;
345
+ - ' -> &145;
346
+ - \n -> <br />
347
+ - backslash -> %5C
348
+ """
349
+ result = text
350
+ result = result.replace("\\", "%5C")
351
+ result = result.replace("%", "&percnt;")
352
+ result = result.replace('"', "&quot;")
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
+ - &percnt; -> %
364
+ - &quot; -> "
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("&percnt;", "%")
373
+ result = result.replace("&quot;", '"')
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
+ ]