Manual/Gameplay Systems/Military, hero & battle
v1.0.364 notes · build 2026-11-07 12:10 UTC

Military, hero & battle

server-authoritative core · ~20 self-reporting exceptions · report channels
#battle #protocol

Authority split

Server-authoritative for marches, arena, world combat: the client sends identity refs only (hero uuids, formation ids), never stats or outcomes; the server returns the full protobuf combat log. Example StartArenaBattleMessage.OnCreate: target (UtfString), type (Int), power (Long, display only), heroes (SFSArray of {index, uuid} — identity only).

~20 self-reporting PVE exceptions static (Net.Msgs.Battle.* renders locally): lw.save.pve.record (isWin), user.finish.pve.level (isSuccess), lw.save.sky.battle.stage, lw.save.plane.stage, lw.save.pve.feature, lw.save.towerup.record, lw.save.count.record, lw.save.monopoly.record, end.detect.event.pve, parkour.fight.end, ghost.parkour.fight.end, user.finish.pve.trigger, sync.pve.resource. Surfing / Ghost Parkour upload frame logs over HTTPS outside the game socket; several modes have server-side run validation.

Combat report delivery — two channels

  1. Inline (arena, PVE monster fights): base64 battleContent on the SFS response, chunked into battleContentArr if oversized; decode base64 → raw protobuf → protobuf.BattleReport.
  2. Out-of-band (world combat, city sieges): response carries only uuid; fetch GET https://lastwar-fight-report.akamaized.net/report/{uuid}.bin — check Content-Encoding: zstd (BestHTTP doesn't auto-decompress); decode as protobuf.LwBattleReport (pairing inferred by convention, not confirmed static).

BattleReport.proto

  • ArmyUnitInfo{soldiers=1, heroes=2, name=3}
  • FightReport{uuid=1(int64), selfInfo=2, otherInfo=3 (ReportPlayerInfo), selfArmyResult=4, otherArmyResult=5 (ArmyResult), reward=6 (ReportReward), fightResult=7(int32), otherBattleEffectGroups=8}
  • BattleReport{battleResult=1, startTime=2(int64), startRound=3, battlePointInfo=4, fightReports=5, fightLost=6, selfBattleEffectGroups=7}

LwBattleReport.proto (per-skill-cast, per-target action log → deterministic server-side simulation)

  • TargetHit{index=1, miss=2, crit=3, damage=4}
  • FightAction{id=1, order=2, time=3, casterIndex=4, phase=5, skillId=6, targets=7 (repeated TargetHit), skillLevel=9}
  • LwBattleReport{uuid=1(int64), type=2, battleTime=3(int64), fightResult=5, player=6 (repeated LwBattlePlayerStat), reward=10, detail=12 (LwBattleDetail)}

Core commands

army.add (add trained soldiers to garrison); save.normal.formation.info (hero→squad slot); world.march.formation.new (dispatch march to target tile); world.march.change (redirect in-flight march — recall = MarchTargetType.BACK_HOME = 3); find.resource (server-side nearest search, no coordinates sent); find.monster (same but also sends pointId with criteria); user.arena.fight / arena.battle; hero.exp / hero.star.up / upgrade.hero.rank.

Duplicate-cmd gotcha static

MsgDefines.HeroRankUpgrade and MsgDefines.UpgradeHeroRank both resolve to wire string "upgrade.hero.rank"; MsgMap is a plain table literal so the later assignment in file order wins; command_catalog.json keeps both (mechanical dump) — trust the handler class appearing last in MsgMap.lua. SFSNetwork.GetMsgType uses that class both directions, so in 1.0.364 upgrade.hero.rank always goes out as {uuid: Long, exchange: Int}.

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