Manual/Gameplay Systems/Alliance, chat & mail
v1.0.364 notes · build 2026-11-07 12:10 UTC

Alliance, chat & mail

two chat channels · WS frame signing · mail groups · government
#alliance #chat #mail

Two chat channels

  • SFS socket (main) carries moderation (ban/lock), room admin, "share to chat" attachments/cards, player search — binary SFSObject, main-session auth.
  • Dedicated chat WebSocket carries message text, room join, history/pagination, read receipts — JSON text frames, auth via chatToken from the SFS init push + signed per-connection header. Sending text = cmd="chat.room" on the WebSocket; SFS al.msg/chat.country only share rich attachments, no free text.
  • Frame signing: sign = MD5(uid + json(sortedRecursively(params)) + sendTime + connectionSign); frame {cmd, params, sendTime, sign}.

Room ids (1.0.364)

alliance alliance_<sourceServerId>_<allianceId>; R4/R5 room alliance_manager_<sid>_<aid>; notice alliance_<sid>_<aid>_Notice. Joining gated by room_join_control DataConfig: default client-side room.joinMulti {rooms:[{id, group:"alliance"}]}; with the switch on, the client sends chat.permission.syn {action} (actions joinAlliance, leaveAlliance, rankChange, notCheck) on SFS; server replies push.room.synPermission {roomId, auth, group}. common.chat.room.id returns allRooms[{group, roomId}], only when ClientSwitch.CHAT_SERVER_ROOMLIST is on.

WebSocket transport [static — 1.0.364 Lua ChatNetManager/ChatManager2, C# ChatService]

  1. Discovery: HTTPS form POST to the first reachable of https://lastwar-chat-cf.lastwarapp.net/server/links, https://lastwar-chat-us-aws-ali.lastwargame.com/server/links, https://lastwar-chat-us-gcp-ali.lastwargame.com/server/links; with CheckChatNewLine on, posts to /gameservice/chat_server_links.php on lastwar-serverlist-* hosts. Form fields: t (server time s), s (MD5 sig), a (app id), u (uid), serverId, f=cn in China. Response {code, data:[{protocol, ip, port}], data_new}; ChatUseNewLine selects data_new. Retries every 3 s rotating URLs.
  2. Connect {protocol}://{ip}:{port} no path; headers APPID, TIME (ms), UID, SIGN (= connectionSign), CHATTOKEN.
  3. Keepalive: ping 3 s; close after 9 s silence; server sends literal heartbeat text frames.
  4. After login.success: user.setInfo, user.setSwitchFlag, room.getCustomRoomListV2, room.joinMulti, then history.roomsv2 per room.
  5. another.login = same uid elsewhere; client closes, no reconnect. Replies reuse the request cmd + result; pushes carry data. Red packets/gifts/rewards are claimed on the game socket; chat delivers ids only.

Alliance commands

  • Lifecycle al.create/al.dismiss/al.leave; join al.apply/al.acceptapply/al.refuseapply (open alliances auto-join); al.invite/al.acceptinvite (via mail); al.kick/al.setrank (kicks rate-limited); alliance.declare.war.create; rally-assist al.call.help/al.show.help.
  • al.help.all live — bulk-completes pending help; only wire field cmdBaseTime (Long, epoch-ms); no-op success when nothing pending.
  • Gifts alliance.reward.list / alliance.reward.allreceive live — claim-all per type (1 = Premium, 2 = Regular); Premium claim-all unlocked at alliance gift level ≥ alliance_gift config k5.
  • Tech: science.data.refresh live — full tech tree, no params; exactly one entry state=1 (the "Recommended" tech). al.science.donate live costs resource; al.science.refreshNum {scienceId} live → maxNum=30, refreshTimeBlock=1200000; errorCode=120471 ("Donate science CD time is not finish") = the charge pool is empty, not a per-donation CD. al.science.donate.gold costs gems, no charge limit (useGoldNum/maxGoldNum both 999999999); no batch field — "hold to donate" is client-side auto-repeat. al.science.recommend — officer marks/unmarks scienceId Recommended; read via state.

Mail

  • mail.read {uid: mailId, type, toUser: senderUid}.
  • chat.get.system.mails — paginated (clientseq/time/count/firstCmd/isAll in; more/lastUid/lastMailTime out); response is a separate push push.chat.get.system.mails live cold start (time:0, empty clientseq) paginated a 350-entry mailbox across 4 pages; push gained delActivityIds in 1.0.364.
  • mail.delete.batch {type:"auto", uids} sent at login; auto-delete removes >30-day mail once daily; answered via push.mail.delete.batch.
  • mail.read.status.betch live — marks read; needs only uids (comma-separated string GUIDs), global across categories, not gated on reward status. Skip types 112/134/135 (translation-rating rewards), saved mail (saveFlag != 0), ungrouped mail; hide-group mail (arena, truck battle reports) marked read, never claimed.
  • mail.reward/.batch live: uids are string GUIDs (not int64); batch claim scoped per mailbox category type.
  • mail.reward.batch type values (internal group ids, UtfString): 2 Report, 3 Alliance, 4 Activity, 5 System, 6 Daily, 7 Charge, 8 Favor (also saveFlag==1), 9 Season. Mapping via MailTypeToInternalGroup: 140 rows in MailEnum.lua 1.0.367 + mail_group rows (MailDataManager.lua:30-36); 144 types with table 39879. Claim All walks groups 2, 3, 4, 9, 5, 6, 7, 8 (MailShowGroup); ungrouped mail is never read/claimed; hide group 1 is marked read at login, never bulk-claimed. Sending raw mail type as Int is also accepted live ({success=true}).
  • mail.send (1:1 or alliance-wide); get.fight.report.detail.

Government ("Kingdom")

Cross-alliance elected leadership: choose.king, kingdom.position.appoint/.apply/.resign, cross.throne.* (14 commands, cross-server king-of-the-hill) — ordinary SFS commands, no separate channel.

notes build 2026-11-07 12:10 UTC · lw-notes@9c47e1b · sources: own capture/analysis cross-checked against the public dossier