artifacts-mmo 0.1.0__tar.gz

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 (62) hide show
  1. artifacts_mmo-0.1.0/.gitattributes +2 -0
  2. artifacts_mmo-0.1.0/PKG-INFO +12 -0
  3. artifacts_mmo-0.1.0/README.md +622 -0
  4. artifacts_mmo-0.1.0/examples/basic_usage.py +83 -0
  5. artifacts_mmo-0.1.0/examples/combat_loop_5chars.py +155 -0
  6. artifacts_mmo-0.1.0/pyproject.toml +24 -0
  7. artifacts_mmo-0.1.0/src/artifacts/__init__.py +76 -0
  8. artifacts_mmo-0.1.0/src/artifacts/api/__init__.py +1 -0
  9. artifacts_mmo-0.1.0/src/artifacts/api/accounts.py +79 -0
  10. artifacts_mmo-0.1.0/src/artifacts/api/achievements.py +36 -0
  11. artifacts_mmo-0.1.0/src/artifacts/api/badges.py +27 -0
  12. artifacts_mmo-0.1.0/src/artifacts/api/characters.py +46 -0
  13. artifacts_mmo-0.1.0/src/artifacts/api/effects.py +27 -0
  14. artifacts_mmo-0.1.0/src/artifacts/api/events.py +38 -0
  15. artifacts_mmo-0.1.0/src/artifacts/api/grand_exchange.py +60 -0
  16. artifacts_mmo-0.1.0/src/artifacts/api/items.py +46 -0
  17. artifacts_mmo-0.1.0/src/artifacts/api/leaderboard.py +49 -0
  18. artifacts_mmo-0.1.0/src/artifacts/api/maps.py +72 -0
  19. artifacts_mmo-0.1.0/src/artifacts/api/monsters.py +44 -0
  20. artifacts_mmo-0.1.0/src/artifacts/api/my_account.py +111 -0
  21. artifacts_mmo-0.1.0/src/artifacts/api/npcs.py +67 -0
  22. artifacts_mmo-0.1.0/src/artifacts/api/resources.py +46 -0
  23. artifacts_mmo-0.1.0/src/artifacts/api/sandbox.py +64 -0
  24. artifacts_mmo-0.1.0/src/artifacts/api/server.py +19 -0
  25. artifacts_mmo-0.1.0/src/artifacts/api/simulation.py +33 -0
  26. artifacts_mmo-0.1.0/src/artifacts/api/tasks.py +62 -0
  27. artifacts_mmo-0.1.0/src/artifacts/api/token.py +24 -0
  28. artifacts_mmo-0.1.0/src/artifacts/character.py +428 -0
  29. artifacts_mmo-0.1.0/src/artifacts/client.py +99 -0
  30. artifacts_mmo-0.1.0/src/artifacts/cooldown.py +26 -0
  31. artifacts_mmo-0.1.0/src/artifacts/errors.py +133 -0
  32. artifacts_mmo-0.1.0/src/artifacts/http.py +198 -0
  33. artifacts_mmo-0.1.0/src/artifacts/models/__init__.py +263 -0
  34. artifacts_mmo-0.1.0/src/artifacts/models/account.py +33 -0
  35. artifacts_mmo-0.1.0/src/artifacts/models/achievements.py +45 -0
  36. artifacts_mmo-0.1.0/src/artifacts/models/badges.py +35 -0
  37. artifacts_mmo-0.1.0/src/artifacts/models/bank.py +14 -0
  38. artifacts_mmo-0.1.0/src/artifacts/models/character.py +129 -0
  39. artifacts_mmo-0.1.0/src/artifacts/models/combat.py +59 -0
  40. artifacts_mmo-0.1.0/src/artifacts/models/common.py +68 -0
  41. artifacts_mmo-0.1.0/src/artifacts/models/effects.py +13 -0
  42. artifacts_mmo-0.1.0/src/artifacts/models/enums.py +298 -0
  43. artifacts_mmo-0.1.0/src/artifacts/models/errors.py +11 -0
  44. artifacts_mmo-0.1.0/src/artifacts/models/events.py +39 -0
  45. artifacts_mmo-0.1.0/src/artifacts/models/grand_exchange.py +44 -0
  46. artifacts_mmo-0.1.0/src/artifacts/models/items.py +28 -0
  47. artifacts_mmo-0.1.0/src/artifacts/models/leaderboard.py +42 -0
  48. artifacts_mmo-0.1.0/src/artifacts/models/logs.py +31 -0
  49. artifacts_mmo-0.1.0/src/artifacts/models/maps.py +42 -0
  50. artifacts_mmo-0.1.0/src/artifacts/models/monsters.py +37 -0
  51. artifacts_mmo-0.1.0/src/artifacts/models/npcs.py +30 -0
  52. artifacts_mmo-0.1.0/src/artifacts/models/pagination.py +17 -0
  53. artifacts_mmo-0.1.0/src/artifacts/models/resources.py +14 -0
  54. artifacts_mmo-0.1.0/src/artifacts/models/responses.py +194 -0
  55. artifacts_mmo-0.1.0/src/artifacts/models/sandbox.py +26 -0
  56. artifacts_mmo-0.1.0/src/artifacts/models/server.py +20 -0
  57. artifacts_mmo-0.1.0/src/artifacts/models/simulation.py +11 -0
  58. artifacts_mmo-0.1.0/src/artifacts/models/tasks.py +30 -0
  59. artifacts_mmo-0.1.0/tmpclaude-7cd1-cwd +1 -0
  60. artifacts_mmo-0.1.0/tmpclaude-81d2-cwd +1 -0
  61. artifacts_mmo-0.1.0/tmpclaude-a324-cwd +1 -0
  62. artifacts_mmo-0.1.0/tmpclaude-eb80-cwd +1 -0
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: artifacts-mmo
3
+ Version: 0.1.0
4
+ Summary: Official Async Python wrapper for the Artifacts MMO API
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: aiohttp<4,>=3.9
8
+ Requires-Dist: pydantic<3,>=2.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: aioresponses; extra == 'dev'
11
+ Requires-Dist: pytest; extra == 'dev'
12
+ Requires-Dist: pytest-asyncio; extra == 'dev'
@@ -0,0 +1,622 @@
1
+ # Artifacts MMO - Python Wrapper
2
+
3
+ Async Python wrapper for the [Artifacts MMO](https://artifactsmmo.com/) API. Control up to 5 characters simultaneously with full type safety and IDE autocompletion.
4
+
5
+ - **65 endpoints** covered (every single one)
6
+ - **Async** built on `aiohttp` -- run multiple characters in parallel
7
+ - **Typed** with Pydantic v2 models -- full IDE autocompletion
8
+ - **Simple** -- character-centric design, beginner friendly
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install -e .
14
+ ```
15
+
16
+ **Requirements:** Python 3.10+
17
+
18
+ ## Quick Start
19
+
20
+ ```python
21
+ import asyncio
22
+ from artifacts import ArtifactsClient, wait_for_cooldown
23
+
24
+ async def main():
25
+ async with ArtifactsClient(token="your_token_here") as client:
26
+ # Get a character controller
27
+ char = client.character("MyCharacter")
28
+
29
+ # Fetch character info
30
+ info = await char.get()
31
+ print(f"{info.name} lv{info.level} HP={info.hp}/{info.max_hp}")
32
+
33
+ # Fight a monster
34
+ result = await char.fight()
35
+ print(f"Result: {result.fight.result.value}")
36
+
37
+ # Wait for cooldown before next action
38
+ await wait_for_cooldown(result.cooldown)
39
+
40
+ asyncio.run(main())
41
+ ```
42
+
43
+ ## Getting a Token
44
+
45
+ You need a JWT token to authenticate. Generate one from your account credentials:
46
+
47
+ ```python
48
+ async with ArtifactsClient() as client:
49
+ token = await client.token.generate("your_username", "your_password")
50
+ print(token) # Use this token from now on
51
+ ```
52
+
53
+ Or use a token you already have from the Artifacts website.
54
+
55
+ ## Custom API URL
56
+
57
+ By default the wrapper connects to `https://api.artifactsmmo.com/`. To use a different server (e.g. sandbox):
58
+
59
+ ```python
60
+ client = ArtifactsClient(
61
+ token="your_token",
62
+ base_url="https://api.sandbox.artifactsmmo.com",
63
+ )
64
+ ```
65
+
66
+ ## Architecture Overview
67
+
68
+ ```
69
+ ArtifactsClient
70
+ ├── .character("name") -> Character (34 action methods)
71
+ ├── .server -> ServerAPI
72
+ ├── .token -> TokenAPI
73
+ ├── .accounts -> AccountsAPI
74
+ ├── .my_account -> MyAccountAPI
75
+ ├── .characters -> CharactersAPI
76
+ ├── .items -> ItemsAPI
77
+ ├── .monsters -> MonstersAPI
78
+ ├── .maps -> MapsAPI
79
+ ├── .resources -> ResourcesAPI
80
+ ├── .npcs -> NPCsAPI
81
+ ├── .events -> EventsAPI
82
+ ├── .achievements -> AchievementsAPI
83
+ ├── .badges -> BadgesAPI
84
+ ├── .effects -> EffectsAPI
85
+ ├── .grand_exchange -> GrandExchangeAPI
86
+ ├── .leaderboard -> LeaderboardAPI
87
+ ├── .tasks -> TasksAPI
88
+ ├── .simulation -> SimulationAPI
89
+ └── .sandbox -> SandboxAPI
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Fetching Game Data
95
+
96
+ All game data endpoints are read-only and return typed Pydantic models. Paginated results come wrapped in `DataPage[T]`.
97
+
98
+ ### Items
99
+
100
+ ```python
101
+ # Get a single item by code
102
+ item = await client.items.get("copper_ore")
103
+ print(f"{item.name} (lv{item.level}, type={item.type.value})")
104
+
105
+ # List items with filters
106
+ page = await client.items.get_all(min_level=1, max_level=10, type="resource", size=20)
107
+ for item in page.data:
108
+ print(f" {item.code}: {item.name}")
109
+ print(f"Page {page.page}/{page.pages} (total: {page.total})")
110
+ ```
111
+
112
+ ### Monsters
113
+
114
+ ```python
115
+ monster = await client.monsters.get("chicken")
116
+ print(f"{monster.name} lv{monster.level} HP={monster.hp}")
117
+ print(f"Drops: {[(d.code, d.rate) for d in monster.drops]}")
118
+
119
+ # List all monsters in a level range
120
+ page = await client.monsters.get_all(min_level=1, max_level=5)
121
+ ```
122
+
123
+ ### Maps
124
+
125
+ ```python
126
+ # Find maps containing a specific monster
127
+ maps = await client.maps.get_all(content_type="monster", content_code="chicken")
128
+ for m in maps.data:
129
+ print(f" ({m.x},{m.y}) layer={m.layer.value}")
130
+
131
+ # Get a specific map tile
132
+ tile = await client.maps.get_by_position("overworld", 0, 1)
133
+
134
+ # Get a map by its ID
135
+ tile = await client.maps.get_by_id(42)
136
+ ```
137
+
138
+ ### Resources
139
+
140
+ ```python
141
+ # All mining resources
142
+ page = await client.resources.get_all(skill="mining", min_level=1)
143
+ for r in page.data:
144
+ print(f" {r.code} (lv{r.level}) drops: {[d.code for d in r.drops]}")
145
+ ```
146
+
147
+ ### NPCs
148
+
149
+ ```python
150
+ # List all NPCs
151
+ npcs = await client.npcs.get_all()
152
+
153
+ # Get items sold by an NPC
154
+ items = await client.npcs.get_items("merchant_1")
155
+ for i in items.data:
156
+ print(f" {i.code} buy={i.buy_price} sell={i.sell_price}")
157
+ ```
158
+
159
+ ### Other game data
160
+
161
+ ```python
162
+ # Achievements
163
+ all_achievements = await client.achievements.get_all()
164
+ single = await client.achievements.get("first_kill")
165
+
166
+ # Badges
167
+ badges = await client.badges.get_all()
168
+
169
+ # Effects
170
+ effects = await client.effects.get_all()
171
+
172
+ # Events
173
+ active = await client.events.get_all_active()
174
+ all_events = await client.events.get_all()
175
+
176
+ # Tasks
177
+ tasks = await client.tasks.get_all(type="monsters", min_level=1)
178
+ rewards = await client.tasks.get_all_rewards()
179
+
180
+ # Leaderboard
181
+ top_chars = await client.leaderboard.get_characters(sort="combat")
182
+ top_accounts = await client.leaderboard.get_accounts(sort="gold")
183
+
184
+ # Grand Exchange
185
+ orders = await client.grand_exchange.get_orders(code="copper_ore")
186
+ history = await client.grand_exchange.get_history("copper_ore")
187
+ ```
188
+
189
+ ### Pagination
190
+
191
+ All list endpoints return a `DataPage[T]`:
192
+
193
+ ```python
194
+ page = await client.items.get_all(page=1, size=50)
195
+ page.data # list[ItemSchema] -- the items on this page
196
+ page.total # int -- total number of items across all pages
197
+ page.page # int -- current page number
198
+ page.pages # int -- total number of pages
199
+ page.size # int -- page size
200
+ ```
201
+
202
+ To fetch every item across all pages at once, use the HTTP client helper:
203
+
204
+ ```python
205
+ all_items = await client._http.get_all_pages("/items", ItemSchema, page_size=100)
206
+ # Returns a flat list[ItemSchema] with every item
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Account & Bank
212
+
213
+ ```python
214
+ # Account details
215
+ account = await client.my_account.get_details()
216
+ print(f"{account.username} -- gems={account.gems}")
217
+
218
+ # Bank info
219
+ bank = await client.my_account.get_bank()
220
+ print(f"Gold: {bank.gold}, Slots: {bank.slots}/{bank.slots}")
221
+
222
+ # Bank items
223
+ bank_items = await client.my_account.get_bank_items()
224
+ for item in bank_items.data:
225
+ print(f" {item.code} x{item.quantity}")
226
+
227
+ # Your characters
228
+ chars = await client.my_account.get_characters()
229
+
230
+ # Your GE orders and history
231
+ orders = await client.my_account.get_ge_orders()
232
+ history = await client.my_account.get_ge_history()
233
+
234
+ # Pending items (from achievements, events, etc.)
235
+ pending = await client.my_account.get_pending_items()
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Character Actions
241
+
242
+ Create a character controller, then call action methods. Every action returns a result containing a `cooldown` field.
243
+
244
+ ```python
245
+ char = client.character("MyCharacter")
246
+ ```
247
+
248
+ ### Movement
249
+
250
+ ```python
251
+ # Move by coordinates
252
+ result = await char.move(x=2, y=3)
253
+ await wait_for_cooldown(result.cooldown)
254
+
255
+ # Move by map ID
256
+ result = await char.move(map_id=42)
257
+ await wait_for_cooldown(result.cooldown)
258
+
259
+ # Layer transition (e.g. enter a building)
260
+ result = await char.transition()
261
+ await wait_for_cooldown(result.cooldown)
262
+ ```
263
+
264
+ ### Combat
265
+
266
+ ```python
267
+ # Solo fight
268
+ result = await char.fight()
269
+ fight = result.fight
270
+ print(f"{fight.result.value} in {fight.turns} turns")
271
+ for cr in fight.characters:
272
+ print(f" +{cr.xp}xp +{cr.gold}g, drops={[d.code for d in cr.drops]}")
273
+ await wait_for_cooldown(result.cooldown)
274
+
275
+ # Boss fight with other characters (up to 3 total)
276
+ result = await char.fight(participants=["Char2", "Char3"])
277
+
278
+ # Rest to recover HP
279
+ result = await char.rest()
280
+ print(f"Restored {result.hp_restored} HP")
281
+ await wait_for_cooldown(result.cooldown)
282
+ ```
283
+
284
+ ### Equipment
285
+
286
+ ```python
287
+ from artifacts.models import ItemSlot
288
+
289
+ # Equip
290
+ result = await char.equip(code="iron_sword", slot=ItemSlot.WEAPON)
291
+ await wait_for_cooldown(result.cooldown)
292
+
293
+ # Unequip
294
+ result = await char.unequip(slot=ItemSlot.WEAPON)
295
+ await wait_for_cooldown(result.cooldown)
296
+ ```
297
+
298
+ ### Gathering & Crafting
299
+
300
+ ```python
301
+ # Gather (character must be on a resource tile)
302
+ result = await char.gathering()
303
+ print(f"+{result.details.xp}xp, got: {[d.code for d in result.details.items]}")
304
+ await wait_for_cooldown(result.cooldown)
305
+
306
+ # Craft (character must be at a workshop)
307
+ result = await char.crafting(code="iron_sword", quantity=1)
308
+ await wait_for_cooldown(result.cooldown)
309
+
310
+ # Recycle equipment
311
+ result = await char.recycling(code="iron_sword", quantity=1)
312
+ await wait_for_cooldown(result.cooldown)
313
+ ```
314
+
315
+ ### Bank Operations
316
+
317
+ ```python
318
+ # Character must be on a bank tile
319
+
320
+ # Gold
321
+ result = await char.bank_deposit_gold(quantity=500)
322
+ result = await char.bank_withdraw_gold(quantity=200)
323
+
324
+ # Items
325
+ from artifacts.models import SimpleItemSchema
326
+ items = [SimpleItemSchema(code="copper_ore", quantity=50)]
327
+ result = await char.bank_deposit_items(items)
328
+ result = await char.bank_withdraw_items(items)
329
+
330
+ # Buy a bank expansion (+20 slots)
331
+ result = await char.bank_buy_expansion()
332
+ ```
333
+
334
+ ### NPC Trading
335
+
336
+ ```python
337
+ # Character must be on an NPC tile
338
+ result = await char.npc_buy(code="wooden_staff", quantity=1)
339
+ result = await char.npc_sell(code="wooden_staff", quantity=1)
340
+ ```
341
+
342
+ ### Grand Exchange
343
+
344
+ ```python
345
+ # Character must be on a GE tile
346
+
347
+ # Buy from an existing sell order
348
+ result = await char.ge_buy(id="order_id_here", quantity=10)
349
+
350
+ # Create a sell order
351
+ result = await char.ge_create_sell_order(code="copper_ore", quantity=100, price=5)
352
+
353
+ # Create a buy order (gold is locked upfront)
354
+ result = await char.ge_create_buy_order(code="copper_ore", quantity=100, price=5)
355
+
356
+ # Fill someone's buy order by selling to it
357
+ result = await char.ge_fill(id="order_id_here", quantity=50)
358
+
359
+ # Cancel your order
360
+ result = await char.ge_cancel_order(id="order_id_here")
361
+ ```
362
+
363
+ ### Tasks
364
+
365
+ ```python
366
+ # Accept a new task (character must be at a tasks master)
367
+ result = await char.task_new()
368
+ print(f"Task: {result.task.code} ({result.task.type.value}) x{result.task.total}")
369
+
370
+ # Trade items for the task
371
+ result = await char.task_trade(code="copper_ore", quantity=10)
372
+
373
+ # Complete the task
374
+ result = await char.task_complete()
375
+
376
+ # Exchange 6 task coins for a random reward
377
+ result = await char.task_exchange()
378
+
379
+ # Cancel current task (costs 1 task coin)
380
+ result = await char.task_cancel()
381
+ ```
382
+
383
+ ### Consumables & Items
384
+
385
+ ```python
386
+ # Use a consumable
387
+ result = await char.use(code="healing_potion", quantity=1)
388
+
389
+ # Delete an item from inventory
390
+ result = await char.delete_item(code="junk_item", quantity=5)
391
+ ```
392
+
393
+ ### Give Items/Gold to Another Character
394
+
395
+ ```python
396
+ # Characters must be on the same map tile
397
+
398
+ # Give gold
399
+ result = await char.give_gold(quantity=100, character="OtherChar")
400
+
401
+ # Give items
402
+ items = [SimpleItemSchema(code="copper_ore", quantity=20)]
403
+ result = await char.give_items(items=items, character="OtherChar")
404
+ ```
405
+
406
+ ### Other
407
+
408
+ ```python
409
+ # Claim a pending item
410
+ result = await char.claim_item(id=123)
411
+
412
+ # Change character skin
413
+ from artifacts.models import CharacterSkin
414
+ result = await char.change_skin(skin=CharacterSkin.MEN2)
415
+
416
+ # View action logs
417
+ logs = await char.get_logs(page=1, size=20)
418
+ for log in logs.data:
419
+ print(f" [{log.type.value}] {log.description}")
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Cooldown Handling
425
+
426
+ Every action returns a `cooldown` object. The wrapper **never** waits automatically -- you decide when to wait.
427
+
428
+ ```python
429
+ from artifacts import wait_for_cooldown, cooldown_seconds
430
+
431
+ result = await char.fight()
432
+
433
+ # Option 1: Helper that sleeps for the remaining duration
434
+ await wait_for_cooldown(result.cooldown)
435
+
436
+ # Option 2: Read the value and handle it yourself
437
+ seconds = cooldown_seconds(result.cooldown)
438
+ print(f"Cooldown: {seconds}s remaining")
439
+ await asyncio.sleep(seconds)
440
+
441
+ # Option 3: Access the raw CooldownSchema
442
+ cd = result.cooldown
443
+ print(f"Total: {cd.total_seconds}s")
444
+ print(f"Remaining: {cd.remaining_seconds}s")
445
+ print(f"Reason: {cd.reason.value}")
446
+ print(f"Expires at: {cd.expiration}")
447
+ ```
448
+
449
+ ---
450
+
451
+ ## Error Handling
452
+
453
+ The wrapper raises typed exceptions mapped to API error codes:
454
+
455
+ ```python
456
+ from artifacts.errors import (
457
+ ArtifactsAPIError, # Base class for all API errors
458
+ CooldownActiveError, # 499 -- character is in cooldown
459
+ ActionInProgressError, # 486 -- action already running
460
+ InventoryFullError, # 497 -- inventory full
461
+ InsufficientGoldError, # 492 -- not enough gold
462
+ NotFoundError, # 404 -- resource not found
463
+ ContentNotOnMapError, # 598 -- no monster/resource here
464
+ AlreadyAtDestinationError, # 490 -- already at target
465
+ SkillLevelTooLowError, # 493 -- skill level too low
466
+ EquipmentSlotError, # 491 -- equipment slot issue
467
+ MapBlockedError, # 596 -- map is blocked
468
+ NoPathError, # 595 -- no path to destination
469
+ MemberRequiredError, # 451 -- member/founder required
470
+ ConditionsNotMetError, # 496 -- conditions not met
471
+ TaskError, # 474-489 -- task-related errors
472
+ GrandExchangeError, # 433-438 -- GE errors
473
+ ValidationError, # 422 -- invalid request
474
+ )
475
+ ```
476
+
477
+ Example:
478
+
479
+ ```python
480
+ try:
481
+ result = await char.fight()
482
+ await wait_for_cooldown(result.cooldown)
483
+ except CooldownActiveError:
484
+ await asyncio.sleep(3)
485
+ except InventoryFullError:
486
+ print("Inventory full! Go deposit at the bank.")
487
+ except ContentNotOnMapError:
488
+ print("No monster on this tile.")
489
+ except ArtifactsAPIError as e:
490
+ print(f"API error [{e.code}]: {e.message}")
491
+ ```
492
+
493
+ ---
494
+
495
+ ## Running Multiple Characters in Parallel
496
+
497
+ Use `asyncio.gather()` to run multiple characters at the same time:
498
+
499
+ ```python
500
+ import asyncio
501
+ from artifacts import ArtifactsClient, wait_for_cooldown
502
+
503
+ async def combat_loop(char):
504
+ for _ in range(10):
505
+ info = await char.get()
506
+ if info.hp < info.max_hp * 0.3:
507
+ result = await char.rest()
508
+ await wait_for_cooldown(result.cooldown)
509
+ continue
510
+ result = await char.fight()
511
+ print(f"[{char.name}] {result.fight.result.value}")
512
+ await wait_for_cooldown(result.cooldown)
513
+
514
+ async def main():
515
+ async with ArtifactsClient(token="your_token") as client:
516
+ names = ["Char1", "Char2", "Char3", "Char4", "Char5"]
517
+ chars = [client.character(n) for n in names]
518
+ await asyncio.gather(*[combat_loop(c) for c in chars])
519
+
520
+ asyncio.run(main())
521
+ ```
522
+
523
+ See `examples/combat_loop_5chars.py` for a complete example with error handling, movement, and drop tracking.
524
+
525
+ ---
526
+
527
+ ## Character Management
528
+
529
+ ```python
530
+ from artifacts.models import CharacterSkin
531
+
532
+ # Create a character (max 5 per account)
533
+ new_char = await client.characters.create("NewHero", CharacterSkin.MEN1)
534
+
535
+ # Delete a character
536
+ deleted = await client.characters.delete("NewHero")
537
+
538
+ # List all active characters on the server
539
+ active = await client.characters.get_active()
540
+
541
+ # Get any character's public info
542
+ info = await client.characters.get("SomePlayer")
543
+ ```
544
+
545
+ ---
546
+
547
+ ## Simulation (Members Only)
548
+
549
+ ```python
550
+ from artifacts.models import FakeCharacterSchema
551
+
552
+ fake = FakeCharacterSchema(
553
+ level=20,
554
+ weapon_slot="iron_sword",
555
+ body_armor_slot="iron_armor",
556
+ )
557
+ result = await client.simulation.fight(
558
+ characters=[fake],
559
+ monster="ogre",
560
+ iterations=100,
561
+ )
562
+ print(f"Winrate: {result.winrate:.1%} ({result.wins}W / {result.losses}L)")
563
+ ```
564
+
565
+ ---
566
+
567
+ ## Sandbox (Sandbox Server Only)
568
+
569
+ When using the sandbox server (`base_url="https://api.sandbox.artifactsmmo.com"`):
570
+
571
+ ```python
572
+ await client.sandbox.give_gold("MyChar", 10000)
573
+ await client.sandbox.give_item("MyChar", "iron_sword", 5)
574
+ await client.sandbox.give_xp("MyChar", "combat", 5000)
575
+ await client.sandbox.spawn_event("event_code")
576
+ await client.sandbox.reset_account()
577
+ ```
578
+
579
+ ---
580
+
581
+ ## Complete API Reference
582
+
583
+ ### Client Sub-Accessors
584
+
585
+ | Accessor | Methods |
586
+ |---|---|
587
+ | `client.server` | `get_status()` |
588
+ | `client.token` | `generate(username, password)` |
589
+ | `client.accounts` | `create()`, `forgot_password()`, `reset_password()`, `get()`, `get_achievements()`, `get_characters()` |
590
+ | `client.my_account` | `get_details()`, `get_bank()`, `get_bank_items()`, `get_ge_orders()`, `get_ge_history()`, `get_pending_items()`, `change_password()`, `get_characters()`, `get_all_logs()` |
591
+ | `client.characters` | `create()`, `delete()`, `get_active()`, `get()` |
592
+ | `client.items` | `get_all()`, `get()` |
593
+ | `client.monsters` | `get_all()`, `get()` |
594
+ | `client.maps` | `get_all()`, `get_layer()`, `get_by_position()`, `get_by_id()` |
595
+ | `client.resources` | `get_all()`, `get()` |
596
+ | `client.npcs` | `get_all()`, `get()`, `get_all_items()`, `get_items()` |
597
+ | `client.events` | `get_all_active()`, `get_all()`, `spawn()` |
598
+ | `client.achievements` | `get_all()`, `get()` |
599
+ | `client.badges` | `get_all()`, `get()` |
600
+ | `client.effects` | `get_all()`, `get()` |
601
+ | `client.grand_exchange` | `get_history()`, `get_orders()`, `get_order()` |
602
+ | `client.leaderboard` | `get_characters()`, `get_accounts()` |
603
+ | `client.tasks` | `get_all()`, `get()`, `get_all_rewards()`, `get_reward()` |
604
+ | `client.simulation` | `fight()` |
605
+ | `client.sandbox` | `give_gold()`, `give_item()`, `give_xp()`, `spawn_event()`, `reset_account()` |
606
+
607
+ ### Character Methods
608
+
609
+ | Category | Methods |
610
+ |---|---|
611
+ | Info | `get()`, `get_logs()` |
612
+ | Movement | `move(x, y)`, `move(map_id)`, `transition()` |
613
+ | Combat | `fight()`, `rest()` |
614
+ | Equipment | `equip(code, slot)`, `unequip(slot)` |
615
+ | Skills | `gathering()`, `crafting(code, qty)`, `recycling(code, qty)` |
616
+ | Items | `use(code, qty)`, `delete_item(code, qty)` |
617
+ | Bank | `bank_deposit_gold(qty)`, `bank_withdraw_gold(qty)`, `bank_deposit_items(items)`, `bank_withdraw_items(items)`, `bank_buy_expansion()` |
618
+ | NPC | `npc_buy(code, qty)`, `npc_sell(code, qty)` |
619
+ | Grand Exchange | `ge_buy(id, qty)`, `ge_create_sell_order(code, qty, price)`, `ge_create_buy_order(code, qty, price)`, `ge_cancel_order(id)`, `ge_fill(id, qty)` |
620
+ | Tasks | `task_new()`, `task_complete()`, `task_exchange()`, `task_trade(code, qty)`, `task_cancel()` |
621
+ | Give | `give_gold(qty, character)`, `give_items(items, character)` |
622
+ | Misc | `claim_item(id)`, `change_skin(skin)` |