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,172 @@
1
+ """
2
+ Battle simulation engine.
3
+ """
4
+
5
+ import logging
6
+ from dataclasses import dataclass
7
+ from typing import Dict
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ @dataclass
13
+ class UnitType:
14
+ """Unit type definition."""
15
+
16
+ id: int
17
+ name: str
18
+ attack: int
19
+ defense: int
20
+ health: int
21
+ speed: float
22
+ capacity: int
23
+ food_cost: int
24
+
25
+
26
+ # Common unit definitions (example values - adjust based on actual game)
27
+ UNIT_TYPES = {
28
+ 620: UnitType(620, "Militia", 10, 10, 50, 20, 10, 1),
29
+ 614: UnitType(614, "Swordsman", 30, 20, 100, 18, 20, 2),
30
+ 611: UnitType(611, "Bowman", 25, 15, 80, 20, 15, 1),
31
+ 629: UnitType(629, "Cavalry", 50, 30, 150, 30, 30, 3),
32
+ 626: UnitType(626, "Archer", 35, 20, 90, 20, 15, 2),
33
+ 637: UnitType(637, "Knight", 80, 50, 200, 25, 50, 5),
34
+ }
35
+
36
+
37
+ @dataclass
38
+ class BattleResult:
39
+ """Result of a battle simulation."""
40
+
41
+ attacker_wins: bool
42
+ attacker_losses: Dict[int, int]
43
+ defender_losses: Dict[int, int]
44
+ attacker_survivors: Dict[int, int]
45
+ defender_survivors: Dict[int, int]
46
+ loot: Dict[str, int]
47
+ rounds: int
48
+
49
+
50
+ class BattleSimulator:
51
+ """Simulate battles between armies."""
52
+
53
+ def __init__(self):
54
+ self.unit_types = UNIT_TYPES
55
+
56
+ def simulate(
57
+ self,
58
+ attacker_army: Dict[int, int],
59
+ defender_army: Dict[int, int],
60
+ attacker_bonus: float = 0.0,
61
+ defender_bonus: float = 0.0,
62
+ defender_wall_level: int = 0,
63
+ ) -> BattleResult:
64
+ """
65
+ Simulate a battle.
66
+
67
+ Args:
68
+ attacker_army: {unit_id: count}
69
+ defender_army: {unit_id: count}
70
+ attacker_bonus: Attack bonus % (0-100)
71
+ defender_bonus: Defense bonus % (0-100)
72
+ defender_wall_level: Wall level (adds defense)
73
+
74
+ Returns:
75
+ BattleResult
76
+ """
77
+ # Calculate total power
78
+ attacker_power = self._calculate_power(attacker_army, attacker_bonus)
79
+ defender_power = self._calculate_power(defender_army, defender_bonus)
80
+
81
+ # Add wall bonus
82
+ wall_bonus = defender_wall_level * 50 # 50 defense per level
83
+ defender_power += wall_bonus
84
+
85
+ # Determine winner
86
+ attacker_wins = attacker_power > defender_power
87
+
88
+ # Calculate losses (simplified)
89
+ if attacker_wins:
90
+ # Attacker wins - defender loses all, attacker loses some
91
+ power_ratio = defender_power / attacker_power if attacker_power > 0 else 0
92
+ attacker_losses = self._calculate_losses(attacker_army, power_ratio)
93
+ defender_losses = defender_army.copy()
94
+ else:
95
+ # Defender wins - attacker loses all, defender loses some
96
+ power_ratio = attacker_power / defender_power if defender_power > 0 else 0
97
+ attacker_losses = attacker_army.copy()
98
+ defender_losses = self._calculate_losses(defender_army, power_ratio)
99
+
100
+ # Calculate survivors
101
+ attacker_survivors = {uid: count - attacker_losses.get(uid, 0) for uid, count in attacker_army.items()}
102
+ defender_survivors = {uid: count - defender_losses.get(uid, 0) for uid, count in defender_army.items()}
103
+
104
+ # Calculate loot (if attacker wins)
105
+ loot = {}
106
+ if attacker_wins:
107
+ total_capacity = sum(
108
+ UNIT_TYPES.get(uid, UNIT_TYPES[620]).capacity * count for uid, count in attacker_survivors.items()
109
+ )
110
+ loot = {
111
+ "wood": int(total_capacity * 0.33),
112
+ "stone": int(total_capacity * 0.33),
113
+ "food": int(total_capacity * 0.34),
114
+ }
115
+
116
+ return BattleResult(
117
+ attacker_wins=attacker_wins,
118
+ attacker_losses=attacker_losses,
119
+ defender_losses=defender_losses,
120
+ attacker_survivors=attacker_survivors,
121
+ defender_survivors=defender_survivors,
122
+ loot=loot,
123
+ rounds=1, # Simplified - single round
124
+ )
125
+
126
+ def _calculate_power(self, army: Dict[int, int], bonus: float = 0.0) -> float:
127
+ """Calculate total army power."""
128
+ total = 0.0
129
+
130
+ for unit_id, count in army.items():
131
+ unit = self.unit_types.get(unit_id)
132
+ if not unit:
133
+ continue
134
+
135
+ unit_power = (unit.attack + unit.defense + unit.health) / 3
136
+ total += unit_power * count
137
+
138
+ # Apply bonus
139
+ total *= 1 + bonus / 100
140
+
141
+ return total
142
+
143
+ def _calculate_losses(self, army: Dict[int, int], loss_ratio: float) -> Dict[int, int]:
144
+ """Calculate unit losses."""
145
+ losses = {}
146
+
147
+ for unit_id, count in army.items():
148
+ lost = int(count * loss_ratio)
149
+ if lost > 0:
150
+ losses[unit_id] = lost
151
+
152
+ return losses
153
+
154
+ def estimate_outcome(self, attacker_army: Dict[int, int], defender_army: Dict[int, int]) -> str:
155
+ """Quick estimate of battle outcome."""
156
+ att_power = self._calculate_power(attacker_army)
157
+ def_power = self._calculate_power(defender_army)
158
+
159
+ ratio = att_power / def_power if def_power > 0 else 999
160
+
161
+ if ratio > 2.0:
162
+ return "Easy Win"
163
+ elif ratio > 1.5:
164
+ return "Likely Win"
165
+ elif ratio > 1.0:
166
+ return "Close Win"
167
+ elif ratio > 0.75:
168
+ return "Close Loss"
169
+ elif ratio > 0.5:
170
+ return "Likely Loss"
171
+ else:
172
+ return "Heavy Loss"
@@ -0,0 +1,170 @@
1
+ """
2
+ Game calculation utilities.
3
+ """
4
+
5
+ import math
6
+ from typing import Tuple
7
+
8
+
9
+ def calculate_distance(x1: int, y1: int, x2: int, y2: int) -> float:
10
+ """
11
+ Calculate distance between two coordinates.
12
+
13
+ Args:
14
+ x1, y1: First coordinate
15
+ x2, y2: Second coordinate
16
+
17
+ Returns:
18
+ float: Distance
19
+ """
20
+ return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
21
+
22
+
23
+ def calculate_travel_time(distance: float, speed: float = 20.0, speed_bonus: float = 0.0) -> int:
24
+ """
25
+ Calculate travel time for army movement.
26
+
27
+ Args:
28
+ distance: Distance to travel
29
+ speed: Base speed (default 20)
30
+ speed_bonus: Speed bonus percentage (0-100)
31
+
32
+ Returns:
33
+ int: Travel time in seconds
34
+ """
35
+ effective_speed = speed * (1 + speed_bonus / 100)
36
+ return int(distance / effective_speed * 60) # Convert to seconds
37
+
38
+
39
+ def calculate_resource_production(production_rate: float, hours: float) -> int:
40
+ """
41
+ Calculate resource production over time.
42
+
43
+ Args:
44
+ production_rate: Resources per hour
45
+ hours: Number of hours
46
+
47
+ Returns:
48
+ int: Total resources produced
49
+ """
50
+ return int(production_rate * hours)
51
+
52
+
53
+ def calculate_building_cost(base_cost: int, level: int, multiplier: float = 1.5) -> int:
54
+ """
55
+ Calculate building upgrade cost.
56
+
57
+ Args:
58
+ base_cost: Base cost at level 1
59
+ level: Target level
60
+ multiplier: Cost multiplier per level
61
+
62
+ Returns:
63
+ int: Total cost
64
+ """
65
+ return int(base_cost * (multiplier ** (level - 1)))
66
+
67
+
68
+ def calculate_unit_power(unit_stats: dict, count: int) -> int:
69
+ """
70
+ Calculate total unit power.
71
+
72
+ Args:
73
+ unit_stats: Dict with attack, defense, health
74
+ count: Number of units
75
+
76
+ Returns:
77
+ int: Total power
78
+ """
79
+ attack = unit_stats.get("attack", 0)
80
+ defense = unit_stats.get("defense", 0)
81
+ health = unit_stats.get("health", 0)
82
+
83
+ base_power = (attack + defense + health) / 3
84
+ return int(base_power * count)
85
+
86
+
87
+ def is_within_range(x1: int, y1: int, x2: int, y2: int, max_range: float) -> bool:
88
+ """
89
+ Check if coordinates are within range.
90
+
91
+ Args:
92
+ x1, y1: First coordinate
93
+ x2, y2: Second coordinate
94
+ max_range: Maximum range
95
+
96
+ Returns:
97
+ bool: True if within range
98
+ """
99
+ distance = calculate_distance(x1, y1, x2, y2)
100
+ return distance <= max_range
101
+
102
+
103
+ def format_time(seconds: int) -> str:
104
+ """
105
+ Format seconds to human readable time.
106
+
107
+ Args:
108
+ seconds: Time in seconds
109
+
110
+ Returns:
111
+ str: Formatted time (e.g., "1h 30m 45s")
112
+ """
113
+ if seconds < 0:
114
+ return "0s"
115
+
116
+ hours = seconds // 3600
117
+ minutes = (seconds % 3600) // 60
118
+ secs = seconds % 60
119
+
120
+ parts = []
121
+ if hours > 0:
122
+ parts.append(f"{hours}h")
123
+ if minutes > 0:
124
+ parts.append(f"{minutes}m")
125
+ if secs > 0 or not parts:
126
+ parts.append(f"{secs}s")
127
+
128
+ return " ".join(parts)
129
+
130
+
131
+ def calculate_coordinates_in_radius(center_x: int, center_y: int, radius: float) -> list[Tuple[int, int]]:
132
+ """
133
+ Get all coordinates within radius.
134
+
135
+ Args:
136
+ center_x: Center X coordinate
137
+ center_y: Center Y coordinate
138
+ radius: Radius
139
+
140
+ Returns:
141
+ list: List of (x, y) tuples
142
+ """
143
+ coords = []
144
+ r = int(math.ceil(radius))
145
+
146
+ for x in range(center_x - r, center_x + r + 1):
147
+ for y in range(center_y - r, center_y + r + 1):
148
+ if calculate_distance(center_x, center_y, x, y) <= radius:
149
+ coords.append((x, y))
150
+
151
+ return coords
152
+
153
+
154
+ def calculate_loot_capacity(unit_capacities: dict, unit_counts: dict) -> int:
155
+ """
156
+ Calculate total loot capacity.
157
+
158
+ Args:
159
+ unit_capacities: Dict of {unit_id: capacity}
160
+ unit_counts: Dict of {unit_id: count}
161
+
162
+ Returns:
163
+ int: Total capacity
164
+ """
165
+ total = 0
166
+ for unit_id, count in unit_counts.items():
167
+ capacity = unit_capacities.get(unit_id, 0)
168
+ total += capacity * count
169
+
170
+ return total
@@ -0,0 +1,8 @@
1
+ import hashlib
2
+
3
+
4
+ def hash_password(password: str) -> str:
5
+ """
6
+ Hashes the password using MD5, which is the standard for this SFS implementation.
7
+ """
8
+ return hashlib.md5(password.encode("utf-8")).hexdigest()
@@ -0,0 +1,69 @@
1
+ import asyncio
2
+ import functools
3
+ import logging
4
+ from typing import Callable, Optional, Tuple, Type
5
+
6
+
7
+ def handle_errors(
8
+ logger: Optional[logging.Logger] = None,
9
+ log_msg: Optional[str] = None,
10
+ re_raise: bool = True,
11
+ cleanup_method: Optional[str] = None,
12
+ ignore: Optional[Tuple[Type[BaseException], ...]] = None,
13
+ ):
14
+ """
15
+ Decorator to centralize error handling, logging, and cleanup.
16
+
17
+ Args:
18
+ logger: The logger instance to use. If None, tries to get 'logger' from instance or module.
19
+ log_msg: Custom message to prefix the error log with.
20
+ re_raise: Whether to re-raise the caught exception.
21
+ cleanup_method: Name of a method on 'self' to call if an exception occurs (for instance methods).
22
+ ignore: Tuple of exception types to ignore (not log as error, just debug/pass).
23
+ """
24
+
25
+ def decorator(func: Callable):
26
+ @functools.wraps(func)
27
+ async def wrapper(*args, **kwargs):
28
+ # Determine logger
29
+ _logger = logger
30
+ if _logger is None:
31
+ # Try to get 'self.logger' or module level logger
32
+ if args and hasattr(args[0], "logger"):
33
+ _logger = args[0].logger
34
+ else:
35
+ _logger = logging.getLogger(func.__module__)
36
+
37
+ try:
38
+ return await func(*args, **kwargs)
39
+ except asyncio.CancelledError as e:
40
+ # CancelledError is usually control flow, maybe just re-raise
41
+ if ignore and asyncio.CancelledError in ignore:
42
+ pass
43
+ else:
44
+ _logger.debug(f"{func.__name__} cancelled.")
45
+ raise e
46
+ except Exception as e:
47
+ if ignore and isinstance(e, ignore):
48
+ _logger.debug(f"Ignored error in {func.__name__}: {e}")
49
+ return
50
+
51
+ msg = log_msg or f"Error in {func.__name__}"
52
+ _logger.error(f"{msg}: {e}", exc_info=True)
53
+
54
+ # Cleanup logic
55
+ if cleanup_method and args:
56
+ self_obj = args[0]
57
+ if hasattr(self_obj, cleanup_method):
58
+ cleanup = getattr(self_obj, cleanup_method)
59
+ if asyncio.iscoroutinefunction(cleanup):
60
+ await cleanup()
61
+ else:
62
+ cleanup()
63
+
64
+ if re_raise:
65
+ raise e
66
+
67
+ return wrapper
68
+
69
+ return decorator
@@ -0,0 +1,111 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class MapObjectType(IntEnum):
5
+ EMPTY = 0
6
+ CASTLE = 1
7
+ DUNGEON = 2
8
+ CAPITAL = 3
9
+ OUTPOST = 4
10
+ TREASURE_DUNGEON = 7
11
+ TREASURE_CAMP = 8
12
+ SHADOW_AREA = 9
13
+ VILLAGE = 10
14
+ BOSS_DUNGEON = 11
15
+ KINGDOM_CASTLE = 12
16
+ EVENT_DUNGEON = 13
17
+ NO_LANDMARK = 14
18
+ FACTION_CAMP = 15
19
+ FACTION_VILLAGE = 16
20
+ FACTION_TOWER = 17
21
+ FACTION_CAPITAL = 18
22
+ PLAGUE_AREA = 19
23
+ TROOP_HOSTEL = 20
24
+ ALIEN_CAMP = 21
25
+ METRO = 22
26
+ KINGS_TOWER = 23
27
+ ISLE_RESOURCE = 24
28
+ ISLE_DUNGEON = 25
29
+ MONUMENT = 26
30
+ NOMAD_CAMP = 27
31
+ LABORATORY = 28
32
+ SAMURAI_CAMP = 29
33
+ FACTION_INVASION_CAMP = 30
34
+ DYNAMIC = 31
35
+ ROBBER_BARON_CASTLE = 32 # Added explicitly
36
+ SAMURAI_ALIEN_CAMP = 33
37
+ RED_ALIEN_CAMP = 34
38
+ ALLIANCE_NOMAD_CAMP = 35
39
+ DAIMYO_CASTLE = 37
40
+ DAIMYO_TOWNSHIP = 38
41
+ ABG_RESOURCE_TOWER = 40
42
+ ABG_TOWER = 41
43
+ WOLF_KING = 42
44
+ NO_OUTPOST = 99
45
+ UNKNOWN = -1
46
+
47
+ @property
48
+ def is_player(self) -> bool:
49
+ """Is this object a player-owned entity?"""
50
+ return self in (
51
+ MapObjectType.CASTLE,
52
+ MapObjectType.OUTPOST,
53
+ MapObjectType.CAPITAL,
54
+ MapObjectType.METRO,
55
+ )
56
+
57
+ @property
58
+ def is_npc(self) -> bool:
59
+ """Is this a permanent NPC/Robber Baron target?"""
60
+ return self in (
61
+ MapObjectType.DUNGEON,
62
+ MapObjectType.ROBBER_BARON_CASTLE,
63
+ MapObjectType.BOSS_DUNGEON,
64
+ )
65
+
66
+ @property
67
+ def is_event(self) -> bool:
68
+ """Is this a temporary event target (Nomad, Samurai, Alien)?"""
69
+ return self in (
70
+ MapObjectType.NOMAD_CAMP,
71
+ MapObjectType.SAMURAI_CAMP,
72
+ MapObjectType.ALIEN_CAMP,
73
+ MapObjectType.SAMURAI_ALIEN_CAMP,
74
+ MapObjectType.RED_ALIEN_CAMP,
75
+ MapObjectType.ALLIANCE_NOMAD_CAMP,
76
+ MapObjectType.EVENT_DUNGEON,
77
+ )
78
+
79
+ @property
80
+ def is_resource(self) -> bool:
81
+ """Is this a resource village or island?"""
82
+ return self in (
83
+ MapObjectType.VILLAGE,
84
+ MapObjectType.ISLE_RESOURCE,
85
+ MapObjectType.FACTION_VILLAGE,
86
+ )
87
+
88
+
89
+ class KingdomType(IntEnum):
90
+ GREEN = 0
91
+ SANDS = 1
92
+ ICE = 2
93
+ FIRE = 3
94
+ STORM = 4
95
+
96
+
97
+ class MovementType(IntEnum):
98
+ """Types of army movements."""
99
+
100
+ ATTACK = 1
101
+ SUPPORT = 2
102
+ TRANSPORT = 3
103
+ SPY = 4
104
+ RAID = 5
105
+ SETTLE = 6
106
+ CAMP = 7
107
+ TRADE = 8
108
+ ATTACK_CAMP = 9
109
+ RAID_CAMP = 10
110
+ RETURN = 11
111
+ UNKNOWN = -1