合并本地领先源码:旧UI、GM疲劳燃烧、白金徽章孔、宠物修复、支援兵普攻开关
This commit is contained in:
1 parent
f2debaf9bf
commit
70b3c5a6dc
57 files changed
+4256
-30
No files matched your search
@@ -0,0 +1,306 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
dnfcharstat "longheng.io/server/internal/modules/dnf/charstat"
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
)
|
||||
|
||||
// Companion panel snapshot (Option B): the client captures the source
|
||||
// character's final character-info panel values and stores them in a settings
|
||||
// record; the support-soldier projection prefers this snapshot over the
|
||||
// PVF-derived base vector so the companion can mirror the source panel.
|
||||
const (
|
||||
companionPanelSnapshotKeyPrefix = "character:"
|
||||
companionPanelSnapshotKeySuffix = ":companion-panel-v1"
|
||||
)
|
||||
|
||||
var errCompanionPanelCharacterID = errors.New("companion panel: character_id is required")
|
||||
|
||||
type companionPanelSnapshot struct {
|
||||
HPMax int64
|
||||
MPMax int64
|
||||
Strength int64
|
||||
Intelligence int64
|
||||
Vitality int64
|
||||
Spirit int64
|
||||
PhysicalAttack int64
|
||||
MagicalAttack int64
|
||||
IndependentAttack int64
|
||||
PhysicalDefense int64
|
||||
MagicalDefense int64
|
||||
FireResistance int64
|
||||
WaterResistance int64
|
||||
DarkResistance int64
|
||||
LightResistance int64
|
||||
MoveSpeed int64
|
||||
AttackSpeed int64
|
||||
CastSpeed int64
|
||||
HitRecovery int64
|
||||
JumpPower int64
|
||||
Weight int64
|
||||
InventoryLimit int64
|
||||
HPRegenSpeed int64
|
||||
MPRegenSpeed int64
|
||||
SourceClient string
|
||||
|
||||
// The following values are shown on the character-info panel but are not
|
||||
// part of the 92-byte companion mode1 blob; they are stored so the GM panel
|
||||
// can mirror the full panel and future projections can consume them.
|
||||
// Percentages are stored scaled by 10 (70.0% -> 700); the three speed fields
|
||||
// above use the blob's scale of 100 (60.50% -> 6050).
|
||||
PhysicalCritical int64
|
||||
MagicalCritical int64
|
||||
MagicResistance int64
|
||||
HitRate int64
|
||||
DodgeRate int64
|
||||
Stiffness int64
|
||||
FireDamage int64
|
||||
WaterDamage int64
|
||||
LightDamage int64
|
||||
DarkDamage int64
|
||||
}
|
||||
|
||||
func companionPanelSnapshotScope(characterID string) string {
|
||||
return companionPanelSnapshotKeyPrefix + strings.TrimSpace(characterID) +
|
||||
companionPanelSnapshotKeySuffix
|
||||
}
|
||||
|
||||
func companionPanelSnapshotInt(values map[string]string, names ...string) int64 {
|
||||
for _, name := range names {
|
||||
raw, ok := values[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
parsed, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
|
||||
if err == nil {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func loadCompanionPanelSnapshot(
|
||||
ctx context.Context,
|
||||
repos dnfrepo.Group,
|
||||
characterID string,
|
||||
) (companionPanelSnapshot, bool) {
|
||||
if repos.Settings == nil || strings.TrimSpace(characterID) == "" {
|
||||
return companionPanelSnapshot{}, false
|
||||
}
|
||||
record, found, err := repos.Settings.Load(
|
||||
ctx, companionPanelSnapshotScope(characterID))
|
||||
if err != nil || !found {
|
||||
return companionPanelSnapshot{}, false
|
||||
}
|
||||
values := record.Values
|
||||
snapshot := companionPanelSnapshot{
|
||||
HPMax: companionPanelSnapshotInt(values, "hp_max"),
|
||||
MPMax: companionPanelSnapshotInt(values, "mp_max"),
|
||||
Strength: companionPanelSnapshotInt(values, "strength"),
|
||||
Intelligence: companionPanelSnapshotInt(values, "intelligence"),
|
||||
Vitality: companionPanelSnapshotInt(values, "vitality"),
|
||||
Spirit: companionPanelSnapshotInt(values, "spirit"),
|
||||
PhysicalAttack: companionPanelSnapshotInt(values, "physical_attack"),
|
||||
MagicalAttack: companionPanelSnapshotInt(values, "magical_attack"),
|
||||
IndependentAttack: companionPanelSnapshotInt(values, "independent_attack"),
|
||||
PhysicalDefense: companionPanelSnapshotInt(values, "physical_defense"),
|
||||
MagicalDefense: companionPanelSnapshotInt(values, "magical_defense"),
|
||||
FireResistance: companionPanelSnapshotInt(values, "fire_resistance"),
|
||||
WaterResistance: companionPanelSnapshotInt(values, "water_resistance"),
|
||||
DarkResistance: companionPanelSnapshotInt(values, "dark_resistance"),
|
||||
LightResistance: companionPanelSnapshotInt(values, "light_resistance"),
|
||||
MoveSpeed: companionPanelSnapshotInt(values, "move_speed"),
|
||||
AttackSpeed: companionPanelSnapshotInt(values, "attack_speed"),
|
||||
CastSpeed: companionPanelSnapshotInt(values, "cast_speed"),
|
||||
HitRecovery: companionPanelSnapshotInt(values, "hit_recovery"),
|
||||
JumpPower: companionPanelSnapshotInt(values, "jump_power"),
|
||||
Weight: companionPanelSnapshotInt(values, "weight"),
|
||||
InventoryLimit: companionPanelSnapshotInt(values, "inventory_limit"),
|
||||
HPRegenSpeed: companionPanelSnapshotInt(values, "hp_regen"),
|
||||
MPRegenSpeed: companionPanelSnapshotInt(values, "mp_regen"),
|
||||
SourceClient: strings.TrimSpace(values["source_client"]),
|
||||
PhysicalCritical: companionPanelSnapshotInt(values, "physical_critical"),
|
||||
MagicalCritical: companionPanelSnapshotInt(values, "magical_critical"),
|
||||
MagicResistance: companionPanelSnapshotInt(values, "magic_resistance"),
|
||||
HitRate: companionPanelSnapshotInt(values, "hit_rate"),
|
||||
DodgeRate: companionPanelSnapshotInt(values, "dodge_rate"),
|
||||
Stiffness: companionPanelSnapshotInt(values, "stiffness"),
|
||||
FireDamage: companionPanelSnapshotInt(values, "fire_damage"),
|
||||
WaterDamage: companionPanelSnapshotInt(values, "water_damage"),
|
||||
LightDamage: companionPanelSnapshotInt(values, "light_damage"),
|
||||
DarkDamage: companionPanelSnapshotInt(values, "dark_damage"),
|
||||
}
|
||||
if snapshot.HPMax <= 0 || snapshot.MPMax <= 0 || snapshot.Strength <= 0 {
|
||||
return companionPanelSnapshot{}, false
|
||||
}
|
||||
return snapshot, true
|
||||
}
|
||||
|
||||
func (s companionPanelSnapshot) Vector() dnfcharstat.Vector {
|
||||
return dnfcharstat.Vector{
|
||||
HPMax: s.HPMax,
|
||||
MPMax: s.MPMax,
|
||||
Strength: s.Strength,
|
||||
Intelligence: s.Intelligence,
|
||||
Vitality: s.Vitality,
|
||||
Spirit: s.Spirit,
|
||||
PhysicalAttack: s.PhysicalAttack,
|
||||
PhysicalDefense: s.PhysicalDefense,
|
||||
MagicalAttack: s.MagicalAttack,
|
||||
MagicalDefense: s.MagicalDefense,
|
||||
IndependentAttack: s.IndependentAttack,
|
||||
FireResistance: s.FireResistance,
|
||||
WaterResistance: s.WaterResistance,
|
||||
DarkResistance: s.DarkResistance,
|
||||
LightResistance: s.LightResistance,
|
||||
InventoryLimit: s.InventoryLimit,
|
||||
HPRegenSpeed: s.HPRegenSpeed,
|
||||
MPRegenSpeed: s.MPRegenSpeed,
|
||||
MoveSpeed: s.MoveSpeed,
|
||||
AttackSpeed: s.AttackSpeed,
|
||||
CastSpeed: s.CastSpeed,
|
||||
HitRecovery: s.HitRecovery,
|
||||
JumpPower: s.JumpPower,
|
||||
Weight: s.Weight,
|
||||
}
|
||||
}
|
||||
|
||||
// CompanionPanelValues returns the raw stored panel snapshot values for the GM
|
||||
// editor. found is false when no snapshot has been saved yet.
|
||||
func (s *Service) CompanionPanelValues(ctx context.Context, characterID string) (map[string]string, bool, error) {
|
||||
repos, ok := s.repositoryGroup()
|
||||
if !ok {
|
||||
return nil, false, ErrGMRepositoryUnavailable
|
||||
}
|
||||
characterID = strings.TrimSpace(characterID)
|
||||
if characterID == "" {
|
||||
return nil, false, errCompanionPanelCharacterID
|
||||
}
|
||||
record, found, err := repos.Settings.Load(ctx, companionPanelSnapshotScope(characterID))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !found {
|
||||
return map[string]string{}, false, nil
|
||||
}
|
||||
values := make(map[string]string, len(record.Values))
|
||||
nonEmpty := false
|
||||
for k, v := range record.Values {
|
||||
values[k] = v
|
||||
if strings.TrimSpace(v) != "" {
|
||||
nonEmpty = true
|
||||
}
|
||||
}
|
||||
// A record whose values are all empty is a leftover of a blank GM save and
|
||||
// carries no usable panel; report it as not configured so the editor does
|
||||
// not show a misleading "已读取".
|
||||
if !nonEmpty {
|
||||
return map[string]string{}, false, nil
|
||||
}
|
||||
return values, true, nil
|
||||
}
|
||||
|
||||
// SaveCompanionPanelValues replaces the stored panel snapshot values.
|
||||
func (s *Service) SaveCompanionPanelValues(ctx context.Context, characterID string, values map[string]string) error {
|
||||
repos, ok := s.repositoryGroup()
|
||||
if !ok {
|
||||
return ErrGMRepositoryUnavailable
|
||||
}
|
||||
characterID = strings.TrimSpace(characterID)
|
||||
if characterID == "" {
|
||||
return errCompanionPanelCharacterID
|
||||
}
|
||||
cleaned := make(map[string]string, len(values))
|
||||
for k, v := range values {
|
||||
key := strings.TrimSpace(k)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
cleaned[key] = strings.TrimSpace(v)
|
||||
}
|
||||
return repos.Settings.Save(ctx, dnfrepo.SettingsRecord{
|
||||
Scope: companionPanelSnapshotScope(characterID),
|
||||
Values: cleaned,
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateCompanionPanelSnapshot builds a best-effort panel snapshot from the
|
||||
// server's PVF character growth plus the character's worn equipment PVF stats
|
||||
// (and weapon forging). It deliberately does not model enchant cards,
|
||||
// amplification, reinforcement, quality, set effects, titles, auras or pets:
|
||||
// the saved values are a BASE ESTIMATE that the GM page can correct.
|
||||
func (s *Service) GenerateCompanionPanelSnapshot(ctx context.Context, characterID string) (map[string]string, error) {
|
||||
repos, ok := s.repositoryGroup()
|
||||
if !ok {
|
||||
return nil, ErrGMRepositoryUnavailable
|
||||
}
|
||||
characterID = strings.TrimSpace(characterID)
|
||||
if characterID == "" {
|
||||
return nil, errCompanionPanelCharacterID
|
||||
}
|
||||
character, found, err := repos.Character.Load(ctx, characterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("character %s not found", characterID)
|
||||
}
|
||||
base, ok := s.characterPVFStatsForUserInfo(ctx, nil, character, true)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("character %s PVF stats unavailable", characterID)
|
||||
}
|
||||
stats := s.addEquipmentPVFStatsForUserInfo(ctx, nil, character, base)
|
||||
values := companionPanelValuesFromVector(stats, character)
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func companionPanelValuesFromVector(stats dnfcharstat.Vector, character dnfrepo.CharacterRecord) map[string]string {
|
||||
values := make(map[string]string, 24)
|
||||
put := func(key string, value int64) {
|
||||
values[key] = strconv.FormatInt(value, 10)
|
||||
}
|
||||
put("hp_max", stats.HPMax)
|
||||
put("mp_max", stats.MPMax)
|
||||
put("strength", stats.Strength)
|
||||
put("intelligence", stats.Intelligence)
|
||||
put("vitality", stats.Vitality)
|
||||
put("spirit", stats.Spirit)
|
||||
put("physical_attack", stats.PhysicalAttack)
|
||||
put("magical_attack", stats.MagicalAttack)
|
||||
put("independent_attack", stats.IndependentAttack)
|
||||
put("physical_defense", stats.PhysicalDefense)
|
||||
put("magical_defense", stats.MagicalDefense)
|
||||
put("fire_resistance", stats.FireResistance)
|
||||
put("water_resistance", stats.WaterResistance)
|
||||
put("dark_resistance", stats.DarkResistance)
|
||||
put("light_resistance", stats.LightResistance)
|
||||
put("move_speed", stats.MoveSpeed)
|
||||
put("attack_speed", stats.AttackSpeed)
|
||||
put("cast_speed", stats.CastSpeed)
|
||||
put("hit_recovery", stats.HitRecovery)
|
||||
jump := stats.JumpPower
|
||||
if jump == 0 {
|
||||
jump = character.Stats["stat_jump_power"]
|
||||
}
|
||||
put("jump_power", jump)
|
||||
weight := stats.Weight
|
||||
if weight == 0 {
|
||||
weight = character.Stats["stat_weight"]
|
||||
}
|
||||
put("weight", weight)
|
||||
inventory := stats.InventoryLimit
|
||||
if inventory == 0 {
|
||||
inventory = character.Stats["stat_inventory_limit"]
|
||||
}
|
||||
put("inventory_limit", inventory)
|
||||
put("hp_regen", stats.HPRegenSpeed)
|
||||
put("mp_regen", stats.MPRegenSpeed)
|
||||
values["source_client"] = "server-estimate"
|
||||
return values
|
||||
}
|
||||
Reference in new issue
Block a user