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,222 @@
1
+ """
2
+ Alliance service for EmpireCore.
3
+
4
+ Provides high-level APIs for:
5
+ - Alliance chat (send messages, get history)
6
+ - Alliance help (help members, help all, request help)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import TYPE_CHECKING, Callable
12
+
13
+ from empire_core.protocol.models import (
14
+ AllianceChatLogRequest,
15
+ AllianceChatLogResponse,
16
+ AllianceChatMessageRequest,
17
+ AllianceChatMessageResponse,
18
+ AskHelpRequest,
19
+ ChatLogEntry,
20
+ HelpAllRequest,
21
+ HelpAllResponse,
22
+ HelpMemberRequest,
23
+ )
24
+
25
+ from .base import BaseService, register_service
26
+
27
+ if TYPE_CHECKING:
28
+ pass
29
+
30
+
31
+ @register_service("alliance")
32
+ class AllianceService(BaseService):
33
+ """
34
+ Service for alliance operations.
35
+
36
+ Accessible via client.alliance after auto-registration.
37
+
38
+ Usage:
39
+ client = EmpireClient(...)
40
+ client.login()
41
+
42
+ # Send chat message
43
+ client.alliance.send_chat("Hello alliance!")
44
+
45
+ # Help all members
46
+ client.alliance.help_all()
47
+
48
+ # Subscribe to incoming messages
49
+ def on_message(response: AllianceChatMessageResponse):
50
+ print(f"{response.player_name}: {response.decoded_text}")
51
+
52
+ client.alliance.on_chat_message(on_message)
53
+ """
54
+
55
+ def __init__(self, client) -> None:
56
+ super().__init__(client)
57
+ self._chat_callbacks: list[Callable[[AllianceChatMessageResponse], None]] = []
58
+
59
+ # Register internal handler for chat messages
60
+ self.on_response("acm", self._handle_chat_message)
61
+
62
+ # =========================================================================
63
+ # Chat Operations
64
+ # =========================================================================
65
+
66
+ def send_chat(self, message: str) -> None:
67
+ """
68
+ Send a message to alliance chat.
69
+
70
+ Args:
71
+ message: The message text to send (will be auto-encoded)
72
+
73
+ Example:
74
+ client.alliance.send_chat("Hello alliance!")
75
+ client.alliance.send_chat("Special chars work: 100% safe!")
76
+ """
77
+ request = AllianceChatMessageRequest.create(message)
78
+ self.send(request)
79
+
80
+ def get_chat_log(self, timeout: float = 5.0) -> list[ChatLogEntry]:
81
+ """
82
+ Get alliance chat history.
83
+
84
+ Args:
85
+ timeout: Timeout in seconds to wait for response
86
+
87
+ Returns:
88
+ List of ChatLogEntry objects
89
+
90
+ Example:
91
+ history = client.alliance.get_chat_log()
92
+ for entry in history:
93
+ print(f"{entry.player_name}: {entry.decoded_text}")
94
+ """
95
+ request = AllianceChatLogRequest()
96
+ response = self.send(request, wait=True, timeout=timeout)
97
+
98
+ if isinstance(response, AllianceChatLogResponse):
99
+ return response.chat_log
100
+
101
+ return []
102
+
103
+ def on_chat_message(self, callback: Callable[[AllianceChatMessageResponse], None]) -> None:
104
+ """
105
+ Register a callback for incoming alliance chat messages.
106
+
107
+ The callback will be called whenever a chat message is received,
108
+ including messages from other players and confirmations of your own.
109
+
110
+ Args:
111
+ callback: Function that receives AllianceChatMessageResponse
112
+
113
+ Example:
114
+ def on_message(msg: AllianceChatMessageResponse):
115
+ print(f"[{msg.player_name}] {msg.decoded_text}")
116
+
117
+ client.alliance.on_chat_message(on_message)
118
+ """
119
+ self._chat_callbacks.append(callback)
120
+
121
+ def _handle_chat_message(self, response) -> None:
122
+ """Internal handler for chat message responses."""
123
+ if isinstance(response, AllianceChatMessageResponse):
124
+ for callback in self._chat_callbacks:
125
+ try:
126
+ callback(response)
127
+ except Exception:
128
+ pass # Silently ignore callback errors
129
+
130
+ # =========================================================================
131
+ # Help Operations
132
+ # =========================================================================
133
+
134
+ def help_all(self) -> HelpAllResponse | None:
135
+ """
136
+ Help all alliance members who need help.
137
+
138
+ Sends a single request that helps all pending help requests
139
+ (heal, repair, recruit).
140
+
141
+ Returns:
142
+ HelpAllResponse with helped_count, or None on failure
143
+
144
+ Example:
145
+ response = client.alliance.help_all()
146
+ if response:
147
+ print(f"Helped {response.helped_count} members")
148
+ """
149
+ request = HelpAllRequest()
150
+ response = self.send(request, wait=True, timeout=5.0)
151
+
152
+ if isinstance(response, HelpAllResponse):
153
+ return response
154
+
155
+ return None
156
+
157
+ def help_member_heal(self, player_id: int, castle_id: int) -> None:
158
+ """
159
+ Help heal a specific member's wounded soldiers.
160
+
161
+ Args:
162
+ player_id: The player's ID
163
+ castle_id: The castle ID with wounded soldiers
164
+ """
165
+ request = HelpMemberRequest.heal(player_id, castle_id)
166
+ self.send(request)
167
+
168
+ def help_member_repair(self, player_id: int, castle_id: int) -> None:
169
+ """
170
+ Help repair a specific member's building.
171
+
172
+ Args:
173
+ player_id: The player's ID
174
+ castle_id: The castle ID with damaged building
175
+ """
176
+ request = HelpMemberRequest.repair(player_id, castle_id)
177
+ self.send(request)
178
+
179
+ def help_member_recruit(self, player_id: int, castle_id: int) -> None:
180
+ """
181
+ Help a specific member with soldier recruitment.
182
+
183
+ Args:
184
+ player_id: The player's ID
185
+ castle_id: The castle ID recruiting soldiers
186
+ """
187
+ request = HelpMemberRequest.recruit(player_id, castle_id)
188
+ self.send(request)
189
+
190
+ def request_heal_help(self, castle_id: int) -> None:
191
+ """
192
+ Request heal help from alliance for a castle.
193
+
194
+ Args:
195
+ castle_id: The castle ID with wounded soldiers
196
+ """
197
+ request = AskHelpRequest.heal(castle_id)
198
+ self.send(request)
199
+
200
+ def request_repair_help(self, castle_id: int, building_id: int) -> None:
201
+ """
202
+ Request repair help from alliance for a building.
203
+
204
+ Args:
205
+ castle_id: The castle ID
206
+ building_id: The building ID that needs repair
207
+ """
208
+ request = AskHelpRequest.repair(castle_id, building_id)
209
+ self.send(request)
210
+
211
+ def request_recruit_help(self, castle_id: int) -> None:
212
+ """
213
+ Request recruit help from alliance for a castle.
214
+
215
+ Args:
216
+ castle_id: The castle ID recruiting soldiers
217
+ """
218
+ request = AskHelpRequest.recruit(castle_id)
219
+ self.send(request)
220
+
221
+
222
+ __all__ = ["AllianceService"]
@@ -0,0 +1,107 @@
1
+ """
2
+ Base service class and registration decorator.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING, Callable, Type, TypeVar
8
+
9
+ from empire_core.protocol.models import BaseRequest, BaseResponse, parse_response
10
+
11
+ if TYPE_CHECKING:
12
+ from empire_core.client.client import EmpireClient
13
+
14
+ # Registry of service classes
15
+ _service_registry: dict[str, Type["BaseService"]] = {}
16
+
17
+ T = TypeVar("T", bound="BaseService")
18
+
19
+
20
+ def register_service(name: str) -> Callable[[Type[T]], Type[T]]:
21
+ """
22
+ Decorator to register a service class.
23
+
24
+ Usage:
25
+ @register_service("alliance")
26
+ class AllianceService(BaseService):
27
+ ...
28
+
29
+ The service will be accessible as client.alliance
30
+ """
31
+
32
+ def decorator(cls: Type[T]) -> Type[T]:
33
+ _service_registry[name] = cls
34
+ cls._service_name = name
35
+ return cls
36
+
37
+ return decorator
38
+
39
+
40
+ def get_registered_services() -> dict[str, Type["BaseService"]]:
41
+ """Get all registered service classes."""
42
+ return _service_registry.copy()
43
+
44
+
45
+ class BaseService:
46
+ """
47
+ Base class for all services.
48
+
49
+ Services provide high-level APIs for game domains and use
50
+ protocol models for type-safe request/response handling.
51
+ """
52
+
53
+ _service_name: str = ""
54
+
55
+ def __init__(self, client: "EmpireClient") -> None:
56
+ self.client = client
57
+
58
+ @property
59
+ def zone(self) -> str:
60
+ """Get the game zone from client config."""
61
+ return self.client.config.default_zone
62
+
63
+ def send(self, request: BaseRequest, wait: bool = False, timeout: float = 5.0) -> BaseResponse | None:
64
+ """
65
+ Send a request to the server.
66
+
67
+ Args:
68
+ request: The request model to send
69
+ wait: Whether to wait for a response
70
+ timeout: Timeout in seconds when waiting
71
+
72
+ Returns:
73
+ The parsed response if wait=True, otherwise None
74
+ """
75
+ packet = request.to_packet(zone=self.zone)
76
+ self.client.connection.send(packet)
77
+
78
+ if wait:
79
+ command = request.get_command()
80
+ try:
81
+ response_packet = self.client.connection.wait_for(command, timeout=timeout)
82
+ if response_packet and isinstance(response_packet.payload, dict):
83
+ return parse_response(command, response_packet.payload)
84
+ except Exception:
85
+ return None
86
+
87
+ return None
88
+
89
+ def on_response(self, command: str, handler: Callable[[BaseResponse], None]) -> None:
90
+ """
91
+ Register a handler for a specific response type.
92
+
93
+ Handlers are registered with the client for efficient routing.
94
+ Only commands with registered handlers will be parsed.
95
+
96
+ Args:
97
+ command: The command code to handle (e.g., "acm")
98
+ handler: Callback function that receives the parsed response
99
+ """
100
+ self.client._register_handler(command, handler)
101
+
102
+
103
+ __all__ = [
104
+ "BaseService",
105
+ "register_service",
106
+ "get_registered_services",
107
+ ]
@@ -0,0 +1,221 @@
1
+ """
2
+ Castle service for EmpireCore.
3
+
4
+ Provides high-level APIs for:
5
+ - Castle management (list, select, rename, relocate)
6
+ - Resource information
7
+ - Production rates
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from empire_core.protocol.models import (
13
+ CastleInfo,
14
+ DetailedCastleInfo,
15
+ GetCastlesRequest,
16
+ GetCastlesResponse,
17
+ GetDetailedCastleRequest,
18
+ GetDetailedCastleResponse,
19
+ GetProductionRequest,
20
+ GetProductionResponse,
21
+ GetResourcesRequest,
22
+ GetResourcesResponse,
23
+ ProductionRates,
24
+ RenameCastleRequest,
25
+ RenameCastleResponse,
26
+ ResourceAmount,
27
+ SelectCastleRequest,
28
+ SelectCastleResponse,
29
+ )
30
+
31
+ from .base import BaseService, register_service
32
+
33
+
34
+ @register_service("castle")
35
+ class CastleService(BaseService):
36
+ """
37
+ Service for castle operations.
38
+
39
+ Accessible via client.castle after auto-registration.
40
+
41
+ Usage:
42
+ client = EmpireClient(...)
43
+ client.login()
44
+
45
+ # Get all castles
46
+ castles = client.castle.get_all()
47
+ for c in castles:
48
+ print(f"{c.castle_name} at ({c.x}, {c.y})")
49
+
50
+ # Select a castle
51
+ client.castle.select(castle_id=12345)
52
+
53
+ # Get resources
54
+ resources = client.castle.get_resources(castle_id=12345)
55
+ """
56
+
57
+ # =========================================================================
58
+ # Castle List Operations
59
+ # =========================================================================
60
+
61
+ def get_all(self, timeout: float = 5.0) -> list[CastleInfo]:
62
+ """
63
+ Get list of all player's castles.
64
+
65
+ Args:
66
+ timeout: Timeout in seconds to wait for response
67
+
68
+ Returns:
69
+ List of CastleInfo objects
70
+
71
+ Example:
72
+ castles = client.castle.get_all()
73
+ for c in castles:
74
+ print(f"{c.castle_name} (ID: {c.castle_id}) at ({c.x}, {c.y})")
75
+ """
76
+ request = GetCastlesRequest()
77
+ response = self.send(request, wait=True, timeout=timeout)
78
+
79
+ if isinstance(response, GetCastlesResponse):
80
+ return response.castles
81
+
82
+ return []
83
+
84
+ def get_details(self, castle_id: int, timeout: float = 5.0) -> DetailedCastleInfo | None:
85
+ """
86
+ Get detailed information about a specific castle.
87
+
88
+ Args:
89
+ castle_id: The castle ID
90
+ timeout: Timeout in seconds
91
+
92
+ Returns:
93
+ DetailedCastleInfo with buildings, resources, etc., or None
94
+
95
+ Example:
96
+ details = client.castle.get_details(12345)
97
+ if details:
98
+ print(f"Buildings: {len(details.buildings)}")
99
+ print(f"Population: {details.population}/{details.max_population}")
100
+ """
101
+ request = GetDetailedCastleRequest(CID=castle_id)
102
+ response = self.send(request, wait=True, timeout=timeout)
103
+
104
+ if isinstance(response, GetDetailedCastleResponse):
105
+ return response.castle
106
+
107
+ return None
108
+
109
+ # =========================================================================
110
+ # Castle Selection
111
+ # =========================================================================
112
+
113
+ def select(self, castle_id: int, timeout: float = 5.0) -> bool:
114
+ """
115
+ Select/jump to a castle (makes it the active castle).
116
+
117
+ Args:
118
+ castle_id: The castle ID to select
119
+ timeout: Timeout in seconds
120
+
121
+ Returns:
122
+ True if successful, False otherwise
123
+
124
+ Example:
125
+ if client.castle.select(12345):
126
+ print("Castle selected!")
127
+ """
128
+ request = SelectCastleRequest(CID=castle_id)
129
+ response = self.send(request, wait=True, timeout=timeout)
130
+
131
+ if isinstance(response, SelectCastleResponse):
132
+ return response.error_code == 0
133
+
134
+ return False
135
+
136
+ # =========================================================================
137
+ # Castle Modification
138
+ # =========================================================================
139
+
140
+ def rename(self, castle_id: int, new_name: str, timeout: float = 5.0) -> bool:
141
+ """
142
+ Rename a castle.
143
+
144
+ Args:
145
+ castle_id: The castle ID
146
+ new_name: The new castle name
147
+ timeout: Timeout in seconds
148
+
149
+ Returns:
150
+ True if successful, False otherwise
151
+
152
+ Example:
153
+ if client.castle.rename(12345, "My Fortress"):
154
+ print("Castle renamed!")
155
+ """
156
+ request = RenameCastleRequest(CID=castle_id, CN=new_name)
157
+ response = self.send(request, wait=True, timeout=timeout)
158
+
159
+ if isinstance(response, RenameCastleResponse):
160
+ return response.error_code == 0
161
+
162
+ return False
163
+
164
+ # =========================================================================
165
+ # Resource Operations
166
+ # =========================================================================
167
+
168
+ def get_resources(self, castle_id: int, timeout: float = 5.0) -> ResourceAmount | None:
169
+ """
170
+ Get current resources for a castle.
171
+
172
+ Args:
173
+ castle_id: The castle ID
174
+ timeout: Timeout in seconds
175
+
176
+ Returns:
177
+ ResourceAmount with wood, stone, food, coins, or None
178
+
179
+ Example:
180
+ resources = client.castle.get_resources(12345)
181
+ if resources:
182
+ print(f"Wood: {resources.wood}")
183
+ print(f"Stone: {resources.stone}")
184
+ """
185
+ request = GetResourcesRequest(CID=castle_id)
186
+ response = self.send(request, wait=True, timeout=timeout)
187
+
188
+ if isinstance(response, GetResourcesResponse):
189
+ return response.resources
190
+
191
+ return None
192
+
193
+ def get_production(
194
+ self, castle_id: int, timeout: float = 5.0
195
+ ) -> tuple[ProductionRates | None, ProductionRates | None]:
196
+ """
197
+ Get production and consumption rates for a castle.
198
+
199
+ Args:
200
+ castle_id: The castle ID
201
+ timeout: Timeout in seconds
202
+
203
+ Returns:
204
+ Tuple of (production_rates, consumption_rates), either may be None
205
+
206
+ Example:
207
+ production, consumption = client.castle.get_production(12345)
208
+ if production:
209
+ print(f"Wood/hr: {production.wood}")
210
+ print(f"Food/hr: {production.food}")
211
+ """
212
+ request = GetProductionRequest(CID=castle_id)
213
+ response = self.send(request, wait=True, timeout=timeout)
214
+
215
+ if isinstance(response, GetProductionResponse):
216
+ return response.production, response.consumption
217
+
218
+ return None, None
219
+
220
+
221
+ __all__ = ["CastleService"]
File without changes