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
empire_core/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EmpireCore - Python library for Goodgame Empire automation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
|
|
7
|
+
from empire_core.client.client import EmpireClient
|
|
8
|
+
from empire_core.config import EmpireConfig
|
|
9
|
+
from empire_core.state.models import Alliance, Building, Castle, Player, Resources
|
|
10
|
+
from empire_core.state.unit_models import UNIT_IDS, Army, UnitStats
|
|
11
|
+
from empire_core.state.world_models import MapObject, Movement, MovementResources
|
|
12
|
+
from empire_core.utils.enums import KingdomType, MapObjectType, MovementType
|
|
13
|
+
|
|
14
|
+
__version__ = version(__package__)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"EmpireClient",
|
|
18
|
+
"EmpireConfig",
|
|
19
|
+
# Models
|
|
20
|
+
"Player",
|
|
21
|
+
"Castle",
|
|
22
|
+
"Resources",
|
|
23
|
+
"Building",
|
|
24
|
+
"Alliance",
|
|
25
|
+
"Movement",
|
|
26
|
+
"MovementResources",
|
|
27
|
+
"MapObject",
|
|
28
|
+
"Army",
|
|
29
|
+
"UnitStats",
|
|
30
|
+
# Enums
|
|
31
|
+
"MovementType",
|
|
32
|
+
"MapObjectType",
|
|
33
|
+
"KingdomType",
|
|
34
|
+
# Constants
|
|
35
|
+
"UNIT_IDS",
|
|
36
|
+
]
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Action commands for performing game actions (attack, transport, build, etc.)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from empire_core.config import ResourceType, TroopActionType
|
|
9
|
+
from empire_core.exceptions import ActionError
|
|
10
|
+
from empire_core.protocol.packet import Packet
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from empire_core.client.client import EmpireClient
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GameActionsMixin:
|
|
19
|
+
"""Mixin for game action commands."""
|
|
20
|
+
|
|
21
|
+
async def _send_command_action(
|
|
22
|
+
self,
|
|
23
|
+
command: str,
|
|
24
|
+
payload: Dict[str, Any],
|
|
25
|
+
action_name: str,
|
|
26
|
+
wait_for_response: bool = False,
|
|
27
|
+
timeout: float = 5.0,
|
|
28
|
+
) -> Any:
|
|
29
|
+
"""
|
|
30
|
+
Send a command packet and optionally wait for response.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
command: Command ID (e.g., 'att', 'tra', 'bui')
|
|
34
|
+
payload: Command payload dictionary
|
|
35
|
+
action_name: Human-readable action name for logging
|
|
36
|
+
wait_for_response: Whether to wait for server response
|
|
37
|
+
timeout: Response timeout in seconds
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Response payload if wait_for_response, else True
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ActionError: If command fails
|
|
44
|
+
"""
|
|
45
|
+
# self.client is now self (EmpireClient)
|
|
46
|
+
client: "EmpireClient" = self # type: ignore
|
|
47
|
+
|
|
48
|
+
if wait_for_response:
|
|
49
|
+
client.response_awaiter.create_waiter(command)
|
|
50
|
+
|
|
51
|
+
packet = Packet.build_xt(client.config.default_zone, command, payload)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
await client.connection.send(packet)
|
|
55
|
+
logger.info(f"{action_name} command sent successfully")
|
|
56
|
+
|
|
57
|
+
if wait_for_response:
|
|
58
|
+
logger.debug(f"Waiting for {command} response...")
|
|
59
|
+
response = await client.response_awaiter.wait_for(command, timeout)
|
|
60
|
+
logger.info(f"{action_name} response received: {response}")
|
|
61
|
+
return response
|
|
62
|
+
|
|
63
|
+
return True
|
|
64
|
+
except Exception as e:
|
|
65
|
+
if wait_for_response:
|
|
66
|
+
client.response_awaiter.cancel_command(command)
|
|
67
|
+
logger.error(f"Failed to {action_name.lower()}: {e}")
|
|
68
|
+
raise ActionError(f"{action_name} failed: {e}")
|
|
69
|
+
|
|
70
|
+
def _parse_action_response(self, response: Any, action_name: str) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Parse a standard action response from server.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
response: Server response
|
|
76
|
+
action_name: Action name for error messages
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if successful
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ActionError: If server rejected the action
|
|
83
|
+
"""
|
|
84
|
+
if isinstance(response, dict):
|
|
85
|
+
if response.get("success") or response.get("MID"):
|
|
86
|
+
return True
|
|
87
|
+
if response.get("error"):
|
|
88
|
+
raise ActionError(f"Server rejected {action_name}: {response.get('error')}")
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
def _build_unit_or_tool_payload(self, items: Optional[Dict[int, int]]) -> List[List[int]]:
|
|
92
|
+
"""Helper to build unit or tool list for cra payload."""
|
|
93
|
+
if not items:
|
|
94
|
+
return [[-1, 0]] * 6 # Fill with dummy values if empty, matches khan.py pattern
|
|
95
|
+
|
|
96
|
+
payload = []
|
|
97
|
+
for item_id, count in items.items():
|
|
98
|
+
payload.append([item_id, count])
|
|
99
|
+
|
|
100
|
+
# Pad with [-1, 0] if less than 6 items to match common packet structure
|
|
101
|
+
while len(payload) < 6:
|
|
102
|
+
payload.append([-1, 0])
|
|
103
|
+
|
|
104
|
+
return payload[:6] # Ensure max 6 items
|
|
105
|
+
|
|
106
|
+
def _build_flank_payload(self, units: Optional[Dict[int, int]], tools: Optional[Dict[int, int]]) -> Dict[str, Any]:
|
|
107
|
+
"""Helper to build flank payload for cra command."""
|
|
108
|
+
return {"T": self._build_unit_or_tool_payload(tools), "U": self._build_unit_or_tool_payload(units)}
|
|
109
|
+
|
|
110
|
+
async def send_attack(
|
|
111
|
+
self,
|
|
112
|
+
origin_x: int,
|
|
113
|
+
origin_y: int,
|
|
114
|
+
target_x: int,
|
|
115
|
+
target_y: int,
|
|
116
|
+
target_area_id: int, # LID
|
|
117
|
+
kingdom_id: int = 0,
|
|
118
|
+
attack_type: int = 0, # ATT
|
|
119
|
+
world_type: int = 0, # WT
|
|
120
|
+
middle_units: Optional[Dict[int, int]] = None,
|
|
121
|
+
left_units: Optional[Dict[int, int]] = None,
|
|
122
|
+
right_units: Optional[Dict[int, int]] = None,
|
|
123
|
+
middle_tools: Optional[Dict[int, int]] = None,
|
|
124
|
+
left_tools: Optional[Dict[int, int]] = None,
|
|
125
|
+
right_tools: Optional[Dict[int, int]] = None,
|
|
126
|
+
wait_for_response: bool = False,
|
|
127
|
+
timeout: float = 5.0,
|
|
128
|
+
) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Send a detailed attack using the 'cra' command, allowing flank deployment.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
origin_x: Source castle X coordinate.
|
|
134
|
+
origin_y: Source castle Y coordinate.
|
|
135
|
+
target_x: Target X coordinate.
|
|
136
|
+
target_y: Target Y coordinate.
|
|
137
|
+
target_area_id: Target Location ID (LID).
|
|
138
|
+
kingdom_id: Kingdom ID (KID, default 0).
|
|
139
|
+
attack_type: Type of attack (ATT, default 0 for regular attack).
|
|
140
|
+
world_type: World type (WT, default 0).
|
|
141
|
+
middle_units: Units for the middle flank ({unit_id: count}).
|
|
142
|
+
left_units: Units for the left flank.
|
|
143
|
+
right_units: Units for the right flank.
|
|
144
|
+
middle_tools: Tools for the middle flank.
|
|
145
|
+
left_tools: Tools for the left flank.
|
|
146
|
+
right_tools: Tools for the right flank.
|
|
147
|
+
wait_for_response: Whether to wait for server confirmation.
|
|
148
|
+
timeout: Response timeout in seconds.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
bool: True if attack sent successfully.
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
ActionError: If attack fails or no units/tools are specified.
|
|
155
|
+
"""
|
|
156
|
+
logger.info(
|
|
157
|
+
f"Sending detailed attack from ({origin_x},{origin_y}) to ({target_x},{target_y}) LID:{target_area_id}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Ensure at least some units or tools are being sent
|
|
161
|
+
if not any([middle_units, left_units, right_units, middle_tools, left_tools, right_tools]):
|
|
162
|
+
raise ActionError("Must specify at least one unit or tool for attack.")
|
|
163
|
+
|
|
164
|
+
# Construct the 'A' (Army) payload
|
|
165
|
+
army_payload = {
|
|
166
|
+
"L": self._build_flank_payload(left_units, left_tools),
|
|
167
|
+
"R": self._build_flank_payload(right_units, right_tools),
|
|
168
|
+
"M": self._build_flank_payload(middle_units, middle_tools),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# The 'A' field can be a list of these, representing waves. For now, a single wave.
|
|
172
|
+
full_army_payload = [army_payload]
|
|
173
|
+
|
|
174
|
+
payload = {
|
|
175
|
+
"SX": origin_x,
|
|
176
|
+
"SY": origin_y,
|
|
177
|
+
"TX": target_x,
|
|
178
|
+
"TY": target_y,
|
|
179
|
+
"KID": kingdom_id,
|
|
180
|
+
"LID": target_area_id,
|
|
181
|
+
"WT": world_type,
|
|
182
|
+
"ATT": attack_type,
|
|
183
|
+
"A": full_army_payload,
|
|
184
|
+
# Other fields from khan.py example can be added as needed or set to default/0
|
|
185
|
+
"HBW": 0,
|
|
186
|
+
"BPC": 0,
|
|
187
|
+
"AV": 0,
|
|
188
|
+
"LP": 1,
|
|
189
|
+
"FC": 0,
|
|
190
|
+
"PTT": 0,
|
|
191
|
+
"SD": 0,
|
|
192
|
+
"ICA": 0,
|
|
193
|
+
"CD": 99,
|
|
194
|
+
"BKS": [],
|
|
195
|
+
"AST": [-1, -1, -1],
|
|
196
|
+
"RW": [[-1, 0], [-1, 0], [-1, 0], [-1, 0], [-1, 0], [-1, 0], [-1, 0], [-1, 0]],
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
response = await self._send_command_action("cra", payload, "Detailed Attack", wait_for_response, timeout)
|
|
200
|
+
|
|
201
|
+
if wait_for_response:
|
|
202
|
+
return self._parse_action_response(response, "detailed attack")
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
async def send_transport(
|
|
206
|
+
self,
|
|
207
|
+
origin_castle_id: int,
|
|
208
|
+
target_area_id: int,
|
|
209
|
+
wood: int = 0,
|
|
210
|
+
stone: int = 0,
|
|
211
|
+
food: int = 0,
|
|
212
|
+
wait_for_response: bool = False,
|
|
213
|
+
timeout: float = 5.0,
|
|
214
|
+
) -> bool:
|
|
215
|
+
"""
|
|
216
|
+
Send resources from one castle to another.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
origin_castle_id: ID of sending castle
|
|
220
|
+
target_area_id: ID of receiving area
|
|
221
|
+
wood: Amount of wood
|
|
222
|
+
stone: Amount of stone
|
|
223
|
+
food: Amount of food
|
|
224
|
+
wait_for_response: Wait for server confirmation (default False)
|
|
225
|
+
timeout: Response timeout in seconds (default 5.0)
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
bool: True if transport sent successfully
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
ActionError: If transport fails
|
|
232
|
+
"""
|
|
233
|
+
logger.info(f"Sending transport from {origin_castle_id} to {target_area_id}")
|
|
234
|
+
|
|
235
|
+
if wood <= 0 and stone <= 0 and food <= 0:
|
|
236
|
+
raise ActionError("Must send at least one resource")
|
|
237
|
+
|
|
238
|
+
payload = {
|
|
239
|
+
"OID": origin_castle_id,
|
|
240
|
+
"TID": target_area_id,
|
|
241
|
+
"RES": {
|
|
242
|
+
ResourceType.WOOD: wood,
|
|
243
|
+
ResourceType.STONE: stone,
|
|
244
|
+
ResourceType.FOOD: food,
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
response = await self._send_command_action("tra", payload, "Transport", wait_for_response, timeout)
|
|
249
|
+
|
|
250
|
+
if wait_for_response:
|
|
251
|
+
return self._parse_action_response(response, "transport")
|
|
252
|
+
return True
|
|
253
|
+
|
|
254
|
+
async def send_spy(
|
|
255
|
+
self,
|
|
256
|
+
origin_castle_id: int,
|
|
257
|
+
target_area_id: int,
|
|
258
|
+
units: Dict[int, int],
|
|
259
|
+
kingdom_id: int = 0,
|
|
260
|
+
wait_for_response: bool = False,
|
|
261
|
+
timeout: float = 5.0,
|
|
262
|
+
) -> bool:
|
|
263
|
+
"""
|
|
264
|
+
Send spies to a target.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
origin_castle_id: ID of attacking castle
|
|
268
|
+
target_area_id: ID of target area
|
|
269
|
+
units: Dictionary of {unit_id: count} (e.g., spies)
|
|
270
|
+
kingdom_id: Kingdom ID (default 0)
|
|
271
|
+
wait_for_response: Wait for server confirmation (default False)
|
|
272
|
+
timeout: Response timeout in seconds (default 5.0)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
bool: True if spies sent successfully
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
ActionError: If spy action fails
|
|
279
|
+
"""
|
|
280
|
+
logger.info(f"Sending spies from {origin_castle_id} to {target_area_id}")
|
|
281
|
+
|
|
282
|
+
if not units or all(count <= 0 for count in units.values()):
|
|
283
|
+
raise ActionError("Must specify at least one unit")
|
|
284
|
+
|
|
285
|
+
payload = {
|
|
286
|
+
"OID": origin_castle_id,
|
|
287
|
+
"TID": target_area_id,
|
|
288
|
+
"UN": units,
|
|
289
|
+
"TT": TroopActionType.SPY,
|
|
290
|
+
"KID": kingdom_id,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
response = await self._send_command_action("scl", payload, "Spy", wait_for_response, timeout)
|
|
294
|
+
|
|
295
|
+
if wait_for_response:
|
|
296
|
+
return self._parse_action_response(response, "spy")
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
async def collect_taxes(
|
|
300
|
+
self,
|
|
301
|
+
castle_id: int,
|
|
302
|
+
wait_for_response: bool = False,
|
|
303
|
+
timeout: float = 5.0,
|
|
304
|
+
) -> bool:
|
|
305
|
+
"""
|
|
306
|
+
Collect taxes/harvest resources from castle.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
castle_id: ID of castle
|
|
310
|
+
wait_for_response: Wait for server confirmation (default False)
|
|
311
|
+
timeout: Response timeout in seconds (default 5.0)
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
bool: True if collection successful
|
|
315
|
+
"""
|
|
316
|
+
logger.info(f"Collecting taxes from castle {castle_id}")
|
|
317
|
+
|
|
318
|
+
payload = {"AID": castle_id}
|
|
319
|
+
|
|
320
|
+
response = await self._send_command_action("har", payload, "Harvest", wait_for_response, timeout)
|
|
321
|
+
|
|
322
|
+
if wait_for_response:
|
|
323
|
+
return self._parse_action_response(response, "harvest")
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
async def use_item(
|
|
327
|
+
self,
|
|
328
|
+
castle_id: int,
|
|
329
|
+
item_id: int,
|
|
330
|
+
count: int = 1,
|
|
331
|
+
target_id: int = 0,
|
|
332
|
+
wait_for_response: bool = False,
|
|
333
|
+
timeout: float = 5.0,
|
|
334
|
+
) -> bool:
|
|
335
|
+
"""
|
|
336
|
+
Use a consumable item.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
castle_id: ID of castle context
|
|
340
|
+
item_id: ID of item to use
|
|
341
|
+
count: Number of items to use
|
|
342
|
+
target_id: Optional target ID (e.g. for specific building or unit boost)
|
|
343
|
+
wait_for_response: Wait for server confirmation
|
|
344
|
+
timeout: Response timeout
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
bool: True if item used successfully
|
|
348
|
+
"""
|
|
349
|
+
logger.info(f"Using item {item_id} (x{count}) in castle {castle_id}")
|
|
350
|
+
|
|
351
|
+
payload = {
|
|
352
|
+
"AID": castle_id,
|
|
353
|
+
"IID": item_id,
|
|
354
|
+
"C": count,
|
|
355
|
+
}
|
|
356
|
+
if target_id:
|
|
357
|
+
payload["TID"] = target_id
|
|
358
|
+
|
|
359
|
+
response = await self._send_command_action("itu", payload, "Use Item", wait_for_response, timeout)
|
|
360
|
+
|
|
361
|
+
if wait_for_response:
|
|
362
|
+
return self._parse_action_response(response, "use_item")
|
|
363
|
+
return True
|
|
364
|
+
|
|
365
|
+
async def upgrade_building(self, castle_id: int, building_id: int, building_type: Optional[int] = None) -> bool:
|
|
366
|
+
"""
|
|
367
|
+
Upgrade or build a building in a castle.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
castle_id: ID of castle
|
|
371
|
+
building_id: ID of building to upgrade
|
|
372
|
+
building_type: Type of building (if constructing new)
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
bool: True if upgrade started successfully
|
|
376
|
+
|
|
377
|
+
Raises:
|
|
378
|
+
ActionError: If upgrade fails
|
|
379
|
+
"""
|
|
380
|
+
logger.info(f"Upgrading building {building_id} in castle {castle_id}")
|
|
381
|
+
|
|
382
|
+
payload = {"AID": castle_id, "BID": building_id}
|
|
383
|
+
if building_type is not None:
|
|
384
|
+
payload["BTYP"] = building_type
|
|
385
|
+
|
|
386
|
+
await self._send_command_action("bui", payload, "Building upgrade")
|
|
387
|
+
return True
|
|
388
|
+
|
|
389
|
+
async def recruit_units(self, castle_id: int, unit_id: int, count: int) -> bool:
|
|
390
|
+
"""
|
|
391
|
+
Recruit/train units in a castle.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
castle_id: ID of castle
|
|
395
|
+
unit_id: ID of unit type to recruit
|
|
396
|
+
count: Number of units to recruit
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
bool: True if recruitment started successfully
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
ActionError: If recruitment fails
|
|
403
|
+
"""
|
|
404
|
+
logger.info(f"Recruiting {count}x unit {unit_id} in castle {castle_id}")
|
|
405
|
+
|
|
406
|
+
if count <= 0:
|
|
407
|
+
raise ActionError("Must recruit at least one unit")
|
|
408
|
+
|
|
409
|
+
payload = {"AID": castle_id, "UID": unit_id, "C": count}
|
|
410
|
+
|
|
411
|
+
await self._send_command_action("tru", payload, "Unit recruitment")
|
|
412
|
+
return True
|
|
413
|
+
|
|
414
|
+
async def send_support(
|
|
415
|
+
self,
|
|
416
|
+
origin_castle_id: int,
|
|
417
|
+
target_x: int,
|
|
418
|
+
target_y: int,
|
|
419
|
+
kingdom_id: int,
|
|
420
|
+
units: List[List[int]],
|
|
421
|
+
target_location_id: int = -14,
|
|
422
|
+
world_type: int = 12,
|
|
423
|
+
wait_for_response: bool = False,
|
|
424
|
+
timeout: float = 5.0,
|
|
425
|
+
) -> bool:
|
|
426
|
+
"""
|
|
427
|
+
Send troops as support to a location (used for birding troops).
|
|
428
|
+
|
|
429
|
+
This uses the 'cds' command which sends troops to a map location
|
|
430
|
+
for support/defense. Commonly used for sending troops to bird
|
|
431
|
+
protection bookmarks.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
origin_castle_id: ID of sending castle (SID)
|
|
435
|
+
target_x: Target X coordinate
|
|
436
|
+
target_y: Target Y coordinate
|
|
437
|
+
kingdom_id: Kingdom ID (0=Green, 2=Ice, 1=Sands, 3=Storm, 4=Fire)
|
|
438
|
+
units: List of [unit_id, count] pairs, max 10 per request
|
|
439
|
+
target_location_id: Target location ID (default -14 for bird)
|
|
440
|
+
world_type: World type (default 12)
|
|
441
|
+
wait_for_response: Wait for server confirmation
|
|
442
|
+
timeout: Response timeout in seconds
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
bool: True if support sent successfully
|
|
446
|
+
|
|
447
|
+
Raises:
|
|
448
|
+
ActionError: If support fails
|
|
449
|
+
|
|
450
|
+
Example:
|
|
451
|
+
# Send 100 soldiers (unit 1) and 50 archers (unit 2) to bird
|
|
452
|
+
await client.send_support(
|
|
453
|
+
origin_castle_id=12345,
|
|
454
|
+
target_x=100,
|
|
455
|
+
target_y=200,
|
|
456
|
+
kingdom_id=0,
|
|
457
|
+
units=[[1, 100], [2, 50]]
|
|
458
|
+
)
|
|
459
|
+
"""
|
|
460
|
+
logger.info(f"Sending support from castle {origin_castle_id} to ({target_x},{target_y})")
|
|
461
|
+
|
|
462
|
+
if not units or len(units) == 0:
|
|
463
|
+
raise ActionError("Must specify at least one unit for support")
|
|
464
|
+
|
|
465
|
+
# Limit to 10 units per request (game limitation)
|
|
466
|
+
if len(units) > 10:
|
|
467
|
+
logger.warning(f"Truncating units list from {len(units)} to 10 (max per request)")
|
|
468
|
+
units = units[:10]
|
|
469
|
+
|
|
470
|
+
payload = {
|
|
471
|
+
"SID": origin_castle_id,
|
|
472
|
+
"TX": target_x,
|
|
473
|
+
"TY": target_y,
|
|
474
|
+
"LID": target_location_id,
|
|
475
|
+
"WT": world_type,
|
|
476
|
+
"HBW": -1,
|
|
477
|
+
"BPC": 1,
|
|
478
|
+
"PTT": 1,
|
|
479
|
+
"SD": 0,
|
|
480
|
+
"A": units,
|
|
481
|
+
"KID": kingdom_id,
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
response = await self._send_command_action("cds", payload, "Support", wait_for_response, timeout)
|
|
485
|
+
|
|
486
|
+
if wait_for_response:
|
|
487
|
+
return self._parse_action_response(response, "support")
|
|
488
|
+
return True
|
|
489
|
+
|
|
490
|
+
async def get_bookmarks(
|
|
491
|
+
self,
|
|
492
|
+
wait_for_response: bool = True,
|
|
493
|
+
timeout: float = 5.0,
|
|
494
|
+
) -> Any:
|
|
495
|
+
"""
|
|
496
|
+
Get alliance bookmarks.
|
|
497
|
+
|
|
498
|
+
This uses the 'gbl' command to retrieve alliance bookmarks,
|
|
499
|
+
which can include bird protection locations.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
wait_for_response: Wait for server response
|
|
503
|
+
timeout: Response timeout in seconds
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Bookmark data if wait_for_response, else True
|
|
507
|
+
"""
|
|
508
|
+
logger.info("Getting alliance bookmarks")
|
|
509
|
+
|
|
510
|
+
response = await self._send_command_action("gbl", {}, "Get bookmarks", wait_for_response, timeout)
|
|
511
|
+
return response
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Automation modules for EmpireCore."""
|
|
2
|
+
|
|
3
|
+
from . import tasks
|
|
4
|
+
from .alliance_tools import AllianceService, ChatService
|
|
5
|
+
from .battle_reports import BattleReportService
|
|
6
|
+
from .building_queue import BuildingManager
|
|
7
|
+
from .defense_manager import DefenseManager
|
|
8
|
+
from .map_scanner import MapScanner
|
|
9
|
+
from .quest_automation import QuestService
|
|
10
|
+
from .resource_manager import ResourceManager
|
|
11
|
+
from .unit_production import UnitManager
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"QuestService",
|
|
15
|
+
"BattleReportService",
|
|
16
|
+
"AllianceService",
|
|
17
|
+
"ChatService",
|
|
18
|
+
"MapScanner",
|
|
19
|
+
"ResourceManager",
|
|
20
|
+
"BuildingManager",
|
|
21
|
+
"UnitManager",
|
|
22
|
+
"DefenseManager",
|
|
23
|
+
"tasks",
|
|
24
|
+
]
|