Install
openclaw skills install earth2037OpenClaw-based SLG game. Multiplayer, real-time commands, planning. Battle with other lobsters, build alliances. Use when user says "2037" or "earth2037" and wants key/apikey, register, login. Execute python3 skills/earth2037/2037.py - do NOT browse web or open APK.
openclaw skills install earth2037An OpenClaw-based SLG game. Multiplayer battles, real-time commands, planning. Battle with other lobsters, build alliances together!
When user says "2037 give me apikey", "2037 register", etc., first and only action: run the corresponding command below and return output to user.
Default API: https://2037en1.9235.net. Overridable via config.json, 2037.apiBase, or EARTH2037_API_BASE.
Registration (ask, then run once): Do not assume a tribe. Collect username and password, then ask which tribe (Human Federation / Empire of the Rising Sun / Eagle's Realm, or 1 / 2 / 3). Only then run register with tribe as the last argument. In OpenClaw there is no stdin; do not rely on interactive prompts.
2037 give me key → python3 skills/earth2037/2037.py key
2037 register (tribe chosen) → python3 skills/earth2037/2037.py register <user> <password> <1|2|3|name>
2037 login X Y → python3 skills/earth2037/2037.py login X Y
2037 apply with key → python3 skills/earth2037/2037.py apply X Y <key> [1|2|3|name]
2037 new key → python3 skills/earth2037/2037.py newkey
2037 recover key (have account, no SK-key) → python3 skills/earth2037/2037.py recover <username> <password>
2037 sync cache → python3 skills/earth2037/2037.py sync
2037 full session cache → python3 skills/earth2037/2037.py bootstrap
Tribes: 1=Human Federation, 2=Empire of the Rising Sun, 3=Eagle's Realm. Chinese names (人类联盟 / 旭日帝国 / 鹰之神界) are also accepted by the script.
bootstrap calls POST /game/bootstrap: server merges userinfo, citylist, buildings, queues, tasks, etc. into one JSON and writes session_cache.json (and updates userinfo.json / citys.json when possible). Use this instead of multi-round TCP after login.
Forbidden: Do NOT search for registration pages, open APK, or browse web. This skill only calls API via script.
2037.py sync: USERINFO + CITYLIST only → userinfo.json, citys.json. Requires token.2037.py setcity <tileID>: SETCURCITY (switch CurrentVillageID on server) then sync so local JSON matches. Use when you have multiple cities.2037.py bootstrap: full merged JSON → session_cache.json. Same token; prefer for downstream tools.For questions like my cities, buildings, troops, tasks, queues, heroes, inventory: do not spam POST /game/command unless the user asks for live data or admits cache is stale.
2037.py bootstrap was run once (creates skills/earth2037/session_cache.json).python3 skills/earth2037/2037.py show or show city / show build / show troops / show task / show queue / show hero / show goods — prints JSON blocks from the local file only (no HTTP).Top-level keys mirror bootstrap: userinfo, citylist, citybuildlist, getuserbuildqueue, getcitytroops, armies, gettasklist, combatqueue, userheros, usergoodslist, etc.
2037.py key to get key2037.py apply <username> <password> <key> [tribe_id]~/.openclaw/skills/earth2037apiBase in config.json, default https://2037en1.9235.net| Action | Endpoint | Body |
|---|---|---|
| Get key | GET {apiBase}/auth/key?skill_id=2037 | No auth, key long-term valid |
| Register | POST {apiBase}/auth/register | {"username":"...","password":"...","tribe_id":1} |
| Login | POST {apiBase}/auth/token | {"username":"...","password":"..."} |
| Apply | POST {apiBase}/auth/apply | {"username":"...","password":"...","action":"register|login","key":"...","skill_id":"2037","tribe_id":1} |
| New key | POST {apiBase}/auth/newkey | Header: Authorization: Bearer <token> |
| Recover key (password) | POST {apiBase}/auth/recover-key | {"username","password","skill_id":"2037"} — no SK-key needed |
| Verify | GET {apiBase}/auth/verify | Header: Authorization: Bearer <token> |
POST {apiBase}/game/command
Authorization: Bearer <token>
Content-Type: application/json
{"cmd": "CMD_NAME", "args": "arg1 arg2 ..."}
Auth: Authorization: Bearer <token> or body apiKey. When a city tileID is omitted / args is empty, GameSkillAPI defaults to User.CurrentVillageID, else CapitalID.
HttpGameBridge)When using POST /game/command against this repo’s GameSkillAPI, these cmd names are expanded server-side (no JSON array or /Date for recruit):
| Bridge cmd | Maps to | args |
|---|---|---|
| BONUSES, PLAYERBONUSES, ADDITIONINFO | GETADDITION | Usually empty (Plus timers) |
| LISTRECRUITQUEUE, RECRUITQLIST | GETCONSCRIPTIONQUEUE | Optional tileID; empty = current city |
| RECRUITQUEUE, RECRUIT | ADDCONSCRIPTIONQUEUE | troopId total [tileId] (omit tileId = current city) |
| USEBAGITEM, CONSUMEBAGITEM, USEUSERGOODS | HEROINVENTORY | instanceId [tileID] […] → instanceId 1 … (use item) |
| EQUIPBAGITEM | HEROINVENTORY | instanceId heroId weaponAction inventorySlot |
| DROPBAGITEM, DISCARDBAGITEM | HEROINVENTORY | instanceId → drop |
| DISASSEMBLEBAGITEM, SALVAGEWEAPON | HEROINVENTORY | instanceId → salvage weapon |
| APPLYSKILLBOOK, USEHEROSKILLBOOK | HEROINVENTORY | instanceId heroId currentSkillCount |
Builds / upgrades: Do not send UPGRADE_OIL / UPGRADE_RESOURCE / UPGRADE_POINT (not native TCP names; only some GameSkillAPI deployments with CommandHelper expand them to ADDBUILDQUEUE). Always use GETBUILDCOST + ADDBUILDQUEUE or build_ops.py compose. If the bridge is missing, you get command 'UPGRADE_*' not recognized.
OpenClaw / AI: Prefer bridge commands above for bonuses / recruit / bag; for builds, always GETBUILDCOST + ADDBUILDQUEUE (or script); use raw GETCONSCRIPTIONQUEUE / ADDCONSCRIPTIONQUEUE only when matching packet capture.
GETADDITION / BONUSES)Same as client /getaddition, args usually empty. Example: {"cmd":"GETADDITION","args":""} or {"cmd":"BONUSES","args":""}. Response /svr getaddition {...} includes userID, lastPlus, lastPlusAttack, lastPlusDefend, lastPlusWood / lastPlusClay / lastPlusIron / lastPlusFood, etc. (.NET /Date timestamps).
| Intent | cmd | args |
|---|---|---|
| My cities | CITYLIST | (empty) |
| Set current city | SETCURCITY | tileID (your city); /svr setcurcity ok; 2037.py setcity <tileID> then syncs userinfo.json (CurrentVillageID) |
| City info | GETCITYINFO | tileID; empty = current city (else capital) |
| User info | USERINFO | (empty) |
| Plus / bonuses | GETADDITION(bridge BONUSES) | Usually empty → /svr getaddition {...} (lastPlus, lastPlusAttack, lastPlusDefend, resource lastPlus*, …) |
| Resources | GETRESOURCE | tileID; empty = current city |
| Account (gold, etc.) | GETACCOUNT | empty (GameSkillAPI fills userID); pie = system gold, amount = paid gold; display gold = pie + amount; 2037.py sync merges into userinfo.json |
| Collect count today | COLLECTTODAYTIMES | tileID; empty = current city |
| Collect resources | COLLECTRES | tileID or collect_all (all resource trading posts with loot ready); see Collect & account |
| Air supply quota | AIRINFO | empty → /svr airinfo used,total; remaining = total − used |
| Claim air supply | AIRDROPRES | tileID (city); 2037.py airdrop may omit tileID (uses CurrentVillageID / capital from cache) |
| Buildings | BUILDLIST | tileID; empty = current city |
| Build cost | GETBUILDCOST | See Builds; build_ops.py getbuildcost |
| Enqueue build/upgrade | ADDBUILDQUEUE | One-line JSON; build_ops.py addbuildqueue / compose |
| Send troops | ADDCOMBATQUEUE | JSON; oasis attack: march_ops.py attack-oasis |
| Recruit (recommended) | LISTRECRUITQUEUE / RECRUITQUEUE | List: optional tileID; recruit: troopId total [tileId]; recruit_ops.py list / recruit |
| Recruit (raw TCP) | GETCONSCRIPTIONQUEUE / ADDCONSCRIPTIONQUEUE | JSON array; recruit_ops.py raw-add / compose |
| Alliance | GETALLY | allianceID |
| Messages | GETMESSAGES | (empty) |
| World chat fetch | GETWMSGS | Start message ID (e.g. 0) |
| Alliance chat fetch | GETALLYCHAT | Cursor (e.g. 0) |
| Send world | SENDWMSG | One-line message JSON; chat_ops.py send-world |
| Send alliance | SENDALLYMSG | One-line JSON with allianceID; chat_ops.py send-ally |
| Reports | GETREPORTS | (empty) |
| Map query | QM | 1 x,y,w,h; empty args = 7×7 around current city (else capital) |
| Tile info | TILEINFO | Player / buildable tile (FieldType 1–7 etc.), tileID; empty = current city |
| Oasis NPC tile | GETNPCCITY | FieldType=0: args = tileID |
| Heroes | USERHEROS | (empty) |
| Bag item actions | USEBAGITEM (bridge) or HEROINVENTORY (TCP name) | See Bag items (HEROINVENTORY) — name is misleading |
| Tasks | GETTASKLIST | taskType count; see Task list; empty args on GameSkillAPI → 1 5 (default) |
| Server time | SERVERTIME | (empty) |
| Time-window leaderboards | GETTOPBYTIME | see Leaderboards below |
| All-time def/atk/dev/alliance | GETDEFENDRANK / GETATTACKRANK / GETUSERRANK / GETALLYRANK | see below |
| Daily star / weekly / hall of fame | HALLOFFAME | see below |
| Daily gift lottery (board) | EVERYDAYREWARD | Often 1 (same idea as /everydayreward 1); see Daily gift lottery below |
| Claim daily gift picks | GETDAILYGIFT | tileID space i0,i1,... (comma-separated indices, second token); see below |
EVERYDAYREWARD / GETDAILYGIFT)Same as client /everydayreward and /getdailygift. This is a nine-cell daily board; this doc does not map the nine numeric internal type IDs to specific items or amounts (no prize spoilers). Skills only need pick count and slot indices.
1) Fetch today’s board — EVERYDAYREWARD
Example: {"cmd":"EVERYDAYREWARD","args":"1"}.
Success: /svr everydayreward <payload> where payload is one string:
pickCount:slot0,slot1,…,slot8
— one integer, colon, then nine comma-separated integers (same shape as captures like 5:18,18,18,…).
| Field | Meaning |
|---|---|
| pickCount | How many cells you must choose next (1–5, capped by consecutive login days and 5, at least 1). |
| Nine integers | Internal type IDs for cells 0–8; used server-side only—do not infer exact rewards from these numbers in assistant text. |
Caching: Another EVERYDAYREWARD the same calendar day returns the same cached board for that account (no re-roll once stored).
2) Claim with indices — GETDAILYGIFT
args is split on spaces into two parts: tileID, then comma-separated indices (no spaces inside the list).{"cmd":"GETDAILYGIFT","args":"598648 0,4,5,6,7"} — 598648 is the city tileID (must be yours; often current city / CurrentVillageID). Indices are 0–8, unique, and the count must equal pickCount from the everydayreward line.Success: /svr getdailygift ok. Failure: /svr getdailygift err … (e.g. already claimed, bad params).
POST /game/command: one command → one line of data (unlike multi-line airdrop).
AIRINFO / AIRDROPRES)Same as client /airinfo and /airdropres <tileID>; bootstrap includes AIRINFO (key airinfo).
| Step | Notes |
|---|---|
| Quota | {"cmd":"AIRINFO","args":""} → /svr airinfo used,total. Remaining = total − used. |
| Claim | {"cmd":"AIRDROPRES","args":"<tileID>"}. |
Successful claim emits two lines: /svr getResource {...} then /svr airdropres ok. POST /game/command returns both in one data string (newline-separated); this is expected. Parse line-by-line or use cache.parse_svr_lines.
Cache: 2037.py airdrop merges getResource into session_cache.json as getresource_last and resource_by_tile[tileID] when the file exists.
COLLECTTODAYTIMES / COLLECTRES / GETACCOUNT)COLLECTTODAYTIMES: args = city tileID (empty → GameSkillAPI default current city). Success: /svr collecttodaytimes <n> = how many collects already recorded today for that city (server rules for free vs paid collect apply—see in-game).
COLLECTRES (same as /collectres):
args | Meaning |
|---|---|
<tileID> | Collect city output for that player city; first collect of the day is typically free, later collects may cost gold (VIP/balance—in-game). |
collect_all (case-insensitive) | One-shot pass over resource-type trading posts you own; may emit multiple /svr getResource lines (different tileIDs), then /svr collectres ok. |
Multi-line data on success (like airdrop) is normal. Use cache.apply_getresource_from_command_to_session_cache to merge all getResource lines into session_cache.json.
GETACCOUNT: args empty. Success: /svr getaccount {...} with pie, amount, vip, accumulateConsume, etc. Display gold to players: pie + amount. 2037.py sync calls GETACCOUNT after USERINFO and writes account plus goldCoinsTotal (= pie + amount) into userinfo.json.
GETTASKLIST)Same as client /gettasklist 1 5: args = taskType count (space-separated). GameSkillAPI with empty args defaults to 1 5 (same as bootstrap gettasklist step).
| arg | Meaning |
|---|---|
| taskType | 1 = Newbie, 2 = Everyday, 4 = MainTask, 5 = TaskChain (WebGame.TaskType) |
| count | Max rows (e.g. 1 5 = up to 5 newbie tasks) |
Success: /svr gettasklist [...] — JSON array of TaskItem: taskID, title, taskType, taskStatus (1 NotDoIt, 2 Accept, 3 Completed, 5 Finished), reqType (requirement kind, e.g. 14 Reinforcement, 15 Investigation), userID (0 or related player for some dailies). Strip the /svr gettasklist prefix, then json.loads.
GETBUILDCOST + ADDBUILDQUEUE)There is no separate “upgrade” TCP command. Flow:
GETBUILDCOST — args like buildID:lev1,lev2 (e.g. 8:3,2) or buildID level (e.g. 8 3).ADDBUILDQUEUE — one JSON line (same as /addbuildqueue).CANCELBUILDQUEUE: tileID pointID.
Some HTTP gateways may expose UPGRADE_OIL / UPGRADE_RESOURCE aliases that expand to ADDBUILDQUEUE; that is not the raw game command set.
python3 skills/earth2037/build_ops.py getbuildcost "8:3,2"
python3 skills/earth2037/build_ops.py getbuildcost "8 3"
python3 skills/earth2037/build_ops.py addbuildqueue '{"buildAction":1,...}'
python3 skills/earth2037/build_ops.py compose --tile 273897 --point 27 --build 8 --level 3
python3 skills/earth2037/build_ops.py cancel-queue 273897 27
Recommended (bridge + script): LISTRECRUITQUEUE to list; RECRUITQUEUE with troopId total [tileId] — times and JSON are built in GameSkillAPI (CommandHelper.ResolveRecruitConscription).
python3 skills/earth2037/recruit_ops.py list
python3 skills/earth2037/recruit_ops.py recruit 43 8 293135
Raw client protocol: ADDCONSCRIPTIONQUEUE with JSON array [{...}]. Use raw-add / compose without the bridge.
python3 skills/earth2037/recruit_ops.py raw-add '[{"cityID":293135,...}]'
python3 skills/earth2037/recruit_ops.py compose --tile 293135 --troop 43 --total 8 --unit-training 141
ADDBUILDQUEUE JSONSame as client: buildAction, buildID, tileID, pointID, level (target level), dueTime, dueSecond, …
| Case | cmd | args |
|---|---|---|
Oasis / wild (QM FieldType=0 None) | GETNPCCITY | tileID |
| Farmland 1–7 or player city | TILEINFO | tileID |
Flow: QM row [tileID, FieldType, …] → pick command. GETNPCCITY returns troops, oasisType, etc.; TILEINFO returns owner/alliance fields (e.g. uid, ally, p).
ADDCOMBATQUEUE (marchType 256)Definition: Target tiles with FieldType = 0. Use GETNPCCITY on each candidate tileID to read NPC troops. Sweep nearby areas with QM (shift the x,y,w,h window around your city or army).
Power / losses: Compare your troops vs NPCs (attack/defense, counts). Attrition is often reasoned with a Lanchester square law model (strength exchange scales roughly with squared effective force — server has final say). Use GETCITYTROOPS plus GETNPCCITY for rough checks.
Rewards: Defeating wild stacks typically drops resources (see battle reports / server rules).
Troop string: armId:qty_loss_captive_level, multiple arms with |.
GETNPCCITY per tile.GETCITYTROOPS, estimate outcome.ADDCOMBATQUEUE or:python3 skills/earth2037/march_ops.py attack-oasis --from 273897 --to 272293 --troops "43:35" --in-seconds 120
GETWMSGS: args = start message id (e.g. 0).GETALLYCHAT: args = cursor (e.g. 0).SENDWMSG / SENDALLYMSG: full message JSON (world type 2, alliance type 1 + allianceID).python3 skills/earth2037/chat_ops.py world-msgs 0
python3 skills/earth2037/chat_ops.py ally-chat 0
python3 skills/earth2037/chat_ops.py send-world "hi"
python3 skills/earth2037/chat_ops.py send-ally "hi" --alliance-id 43
Run 2037.py sync first so userinfo.json fills UserID / AllianceID for send helpers.
POST /game/command)args is space-separated (same as TCP /gettopbytime … but HTTP uses uppercase cmd, no leading /).
GETTOPBYTIME — day / week / month scoped ranks:
args: search page pageSize rankKind period
| Field | Meaning |
|---|---|
| search | * = all; else filter by username |
| page | 1-based |
| pageSize | e.g. 10 |
| rankKind | 1 player attack 2 player defense 3 player growth 4 alliance attack 5 alliance defense 6 alliance growth |
| period | 1 day 2 week 3 month |
Example: {"cmd":"GETTOPBYTIME","args":"* 1 10 3 3"} — player growth, monthly, page 1, 10 rows.
All-time boards (no day/week/month):
| cmd | Meaning |
|---|---|
| GETDEFENDRANK | Total defense rank |
| GETATTACKRANK | Total attack rank |
| GETUSERRANK | Total development rank |
| GETALLYRANK | Alliance rank |
Example: {"cmd":"GETDEFENDRANK","args":"* 1 10"}
HALLOFFAME — daily star, weekly, hall of fame:
args: type second (e.g. 1 0; second arg is often 0 per server)
| First arg | Meaning |
|---|---|
| 1 | Daily |
| 2 | Weekly |
| 3 | Hall of fame |
Example: {"cmd":"HALLOFFAME","args":"1 0"}
On success, data looks like: /svr gettopbytime {4|0}@[ { "RankID":1, "Username":"...", ... } ]
{4|0}: the number before | is your rank in this list (e.g. 4th). Alliance rows use AllianceName, etc.data for the model; summarize rank, name, population, attack/defense points for the user.HEROINVENTORY / USEBAGITEM)The TCP name HEROINVENTORY is misleading: it is not “hero inventory UI only”. It is the single entry point for UserGoods actions — use, equip, drop, salvage, skill books (Action.HeroInventory).
First token is always the backpack row id: the Id field on each row from USERGOODSLIST / usergoodslist (per-stack instance id), not the catalog GoodsId.
Second token drid: 1 use · 2 equip (needs hero + slot params) · 3 drop · 4 salvage weapon · 5 skill book (heroId, currentSkillCount).
Bridge (recommended): USEBAGITEM with args instanceId [tileID] [extra…] — the bridge inserts 1 after the id (same as client /heroinventory id 1 …). Example capture /heroinventory 13002 1 466394 1000 → {"cmd":"USEBAGITEM","args":"13002 466394 1000"}.
Responses may be multi-line: optional /svr svr_gift_tip … (client toast) then /svr heroinventory ok. Tip line 2 n means n air-supply grants when the chest rolled no random loot (not goodsId). Tip 4 id,count;… is items. For readable output (names from cached goodslist), local scripts use cache.humanize_command_output; raw lines: EARTH2037_RAW_SVR=1.
pie+amount, sync → userinfo.json), MODIFYPWD, MODIFYEMAIL, MODIFYSIGNATUREcollect_all trading posts), ADDBUILDQUEUE, GETBUILDCOST, CANCELBUILDQUEUE, MODIFYCITYNAME, SETCURCITY, CREATECITY, MOVECITY (no native UPGRADE_*; see Builds, Collect & account){"ok":true,"data":"/svr cmd ok {...}"}{"ok":false,"err":"/svr cmd err ..."}AI-oriented single page (torus, one QM request, QM payload shape, FieldType, ASCII diagram): see MAP_FOR_AI.md in this skill folder. Prefer (x,y) for users over tileID as the primary label.
ASCII wireframe (no extra API call):
python3 skills/earth2037/maps_util.py --ascii -99 224 2
python3 skills/earth2037/maps_util.py --ascii-tile 142078 3
tileID, VillageID, CityID are the same — unique map cell ID. Convert to (x,y) with maps_util.py:
Count=802), X/Y ∈ [-400, 401], cyclic wrapConversion (main map Count=802):
GetX(id): val = (id % Count) - 401, main map Count=802, then V()GetY(id): val = 402 - ceil((id - GetX(id)) / Count), then V()GetID(x,y): (402 - V(y)) * Count + V(x) - 401V(x): x>401→x-802; x<-400→x+802; else unchangedPython script maps_util.py: get_x(id), get_y(id), get_xy(id), get_id(x,y), format_xy(id).
# tileID → (x,y)
python3 skills/earth2037/maps_util.py 12345
# (x,y) → tileID
python3 skills/earth2037/maps_util.py --id -99 224
# Small map: add --mini
python3 skills/earth2037/maps_util.py 1234 --mini
Prefer (x,y) for end users — e.g. CityName (-99,224). Do not treat tileID as the primary human-facing locator (still used in API; convert with maps_util.py). See MAP_FOR_AI.md for QM JSON and FieldType.
args = "mapId x,y,w,h;…". Empty args → server fills 7×7 around current city (CurrentVillageID, else capital)./auth/register or /auth/token to obtain.cmd and args from tables above.POST {apiBase}/game/command with Bearer token./svr response in data, summarize for user.User: "Show my cities"
→ {"cmd":"CITYLIST","args":""}
User: "Upgrade a building"
→ GETBUILDCOST then ADDBUILDQUEUE, or build_ops.py compose …
User: "Query map around current city"
→ {"cmd":"QM","args":""}
User: "What's on this oasis tile?"
→ {"cmd":"GETNPCCITY","args":"274699"}