Military, hero & battle
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
- Inline (arena, PVE monster fights): base64
battleContenton the SFS response, chunked intobattleContentArrif oversized; decode base64 → raw protobuf →protobuf.BattleReport. - Out-of-band (world combat, city sieges): response carries only
uuid; fetchGET https://lastwar-fight-report.akamaized.net/report/{uuid}.bin— checkContent-Encoding: zstd(BestHTTP doesn't auto-decompress); decode asprotobuf.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}.