Save endgame progress, team presets, technique support and camera controls

This commit is contained in:
CAtlas0784 committed 2026-10-17 23:44:36 +07:00
1 parent a681affdb3
commit 639cef7d19
52 files changed
+11940 -766

No files matched your search

+55 -6
View File
@@ -12,7 +12,11 @@ import (
// PearlSR 005d2c1 protocol/src/protocol.pb.zig: MonsterBattleType.
// The Go schema retains obfuscated enum labels; zero is NONE, not TRIGGER.
const monsterBattleTrigger goproto.MonsterBattleType = 1
const (
monsterBattleTrigger goproto.MonsterBattleType = 1
// PearlSR protocol.pb.zig: MONSTER_BATTLE_TYPE_NO_BATTLE.
monsterBattleNoBattle goproto.MonsterBattleType = 4
)
func init() {
handle("StartCocoonStage", func(s *Session, req *goproto.StartCocoonStageCsReq, rsp *goproto.StartCocoonStageScRsp) error {
@@ -109,7 +113,11 @@ func init() {
})
handle("PVEBattleResult", func(s *Session, req *goproto.PVEBattleResultCsReq, rsp *goproto.PVEBattleResultScRsp) error {
CURRENT_BATTLE.Delete(s.Token)
current, active := CURRENT_BATTLE.Load(s.Token)
active = active && current.StageId == req.StageId
if active {
CURRENT_BATTLE.Delete(s.Token)
}
rsp.EndStatus = req.EndStatus
rsp.BattleId = req.BattleId
rsp.StageId = req.StageId
@@ -130,7 +138,7 @@ func init() {
req.BattleId, report.TeamTotalDamage, report.RoundCnt, report.TotalBattleTurns, statusStr))
}
}
return nil
return finishChallengeBattle(s, req, rsp, active)
})
// Consume every target carrier in the current schema. Only entities sent
@@ -163,6 +171,9 @@ func init() {
req.AssistMonsterEntityIdList, req.AssistMonsterEntityInfo, s.MonsterEntities, targets)
if len(targets) == 0 {
if s.ChallengeStageID != 0 {
SceneDebugLog("[CHALLENGE] attack without monster target request=%s", req.String())
}
// ไม่มี monster จริงถูกตี — ไม่เข้า (นี่คือ fix หลัก)
SceneDebugLog("SCENE_CAST_SKILL cast_entity=%d caster=%d skill_index=%d -> no real monster target (battle skipped)",
req.CastEntityId, req.CasterId, req.SkillIndex)
@@ -173,6 +184,25 @@ func init() {
SceneDebugLog("SCENE_CAST_SKILL cast_entity=%d caster=%d -> with %d monster(s)",
req.CastEntityId, req.CasterId, len(targets))
// PearlSR getBattleType acknowledges non-attacking techniques without
// starting a battle. The monster still needs a NO_BATTLE result.
if req.SkillIndex == 1 {
rules, err := common.LoadTechniqueRules()
if err != nil {
return err
}
avatarID := s.techniqueAvatar(req.CasterId)
for _, id := range rules.NonAttackTechniques {
if avatarID == id {
for _, target := range targets {
rsp.MonsterBattleInfo = append(rsp.MonsterBattleInfo, &goproto.HitMonsterBattleInfo{TargetMonsterEntityId: target, MonsterBattleType: monsterBattleNoBattle})
}
SceneDebugLog("[TECHNIQUE] non-battle avatar=%d targets=%v", avatarID, targets)
return nil
}
}
}
battleInfo, err := createBattleInfo(s, req.CasterId, req.SkillIndex)
if err != nil {
return err
@@ -189,11 +219,12 @@ func init() {
return nil
})
// ตอบ MP cost ให้ client (ตอบ cast_entity_id กลับ, ไม่ตัด MP จริง)
// Cost and recovery use the same session MP as lineup and consumables.
handle("SceneCastSkillCostMp", func(s *Session, req *goproto.SceneCastSkillCostMpCsReq, rsp *goproto.SceneCastSkillCostMpScRsp) error {
rsp.Retcode = 0
rsp.CastEntityId = req.CastEntityId
return nil
code, err := s.payTechnique(req.CastEntityId, req.SkillIndex)
rsp.Retcode = code
return err
})
handle("GetCurBattleInfo", func(s *Session, req *goproto.GetCurBattleInfoCsReq, rsp *goproto.GetCurBattleInfoScRsp) error {
@@ -215,6 +246,24 @@ func createBattleInfo(s *Session, casterID, skillIndex uint32) (*goproto.SceneBa
if player == nil {
return nil, fmt.Errorf("battle: player data is not loaded")
}
if s.ChallengeBattleConfig != nil {
// Keep challenge waves and team local to this session. External
// Freesr reloads must not replace the stage the player just entered.
battlePlayer := *player
battlePlayer.BattleConfig = *s.ChallengeBattleConfig
player = &battlePlayer
} else if len(player.TeamPresets) > 0 {
// A selected native preset is the party the player sees in the scene.
// Keep the imported stage/waves while taking members from that party.
battlePlayer := *player
battlePlayer.BattleConfig.CustomBattleLineup = map[uint32]uint32{}
for slot, id := range player.Lineups {
if id != 0 {
battlePlayer.BattleConfig.CustomBattleLineup[slot] = id
}
}
player = &battlePlayer
}
rules, err := loadBattleRules()
if err != nil {