合并本地领先源码:旧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,192 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/dnfenum"
|
||||
)
|
||||
|
||||
// Real-actor PoC for the volunteer squad.
|
||||
//
|
||||
// This path is deliberately switch-gated and OFF by default. It publishes each
|
||||
// volunteer as a real character actor (mode0 + mode1 + mode3, objectKey =
|
||||
// character id, non-zero party owner context) instead of the support-soldier
|
||||
// APC projection. It is the first milestone of the "real character enters the
|
||||
// dungeon" route; attaching the client AIC to these actors is the next client
|
||||
// patch step, so this path currently ships appearance/roster only.
|
||||
const (
|
||||
currentCompanionRealActorsEnv = "DNF_COMPANION_REAL_ACTORS"
|
||||
currentCompanionRealActorsFlagFile = "companion_real_actors.enabled"
|
||||
currentCompanionRealActorsMode0Kind = "real_party_actor"
|
||||
)
|
||||
|
||||
func currentCompanionRealActorsEnabled() bool {
|
||||
switch strings.ToLower(strings.TrimSpace(os.Getenv(currentCompanionRealActorsEnv))) {
|
||||
case "0", "off", "false", "no":
|
||||
return false
|
||||
}
|
||||
if _, err := os.Stat(currentCompanionRealActorsFlagFile); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// publishCurrentDungeonCompanionRealActors returns true only when every ready
|
||||
// volunteer was published as a real actor. A partial failure returns false so
|
||||
// the caller can keep the proven projection path instead of showing a broken
|
||||
// half-team.
|
||||
func (s *Service) publishCurrentDungeonCompanionRealActors(
|
||||
session *gameSession,
|
||||
runtime *runtimeDungeonState,
|
||||
) bool {
|
||||
if s == nil || session == nil || runtime == nil || runtime.Companion == nil ||
|
||||
!currentCompanionRealActorsEnabled() {
|
||||
return false
|
||||
}
|
||||
run := runtime.Companion
|
||||
if !run.Ready || len(run.Sources) == 0 {
|
||||
return false
|
||||
}
|
||||
ownerContext := currentPartySceneOwnerContext(session)
|
||||
if ownerContext == 0 && session.channel.ID > 0 && session.channel.ID <= 0xff {
|
||||
ownerContext = byte(session.channel.ID)
|
||||
}
|
||||
if ownerContext == 0 {
|
||||
s.logGameEvent(session, "game-companion-real-actors-skipped",
|
||||
"owner_character_id", session.selectedCharacterID,
|
||||
"reason", "party_owner_context_not_committed")
|
||||
return false
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), createWriteTimeout)
|
||||
defer cancel()
|
||||
type realRow struct {
|
||||
id uint16
|
||||
name string
|
||||
mode0 []byte
|
||||
mode1 []byte
|
||||
}
|
||||
rows := make([]realRow, 0, len(run.Sources))
|
||||
for _, entry := range run.Sources {
|
||||
if !entry.Ready || entry.SourceCharacterID == 0 {
|
||||
continue
|
||||
}
|
||||
state := entry.ProgressionSnapshot
|
||||
if state.Source.CharacterID == "" {
|
||||
return false
|
||||
}
|
||||
name := strings.TrimSpace(state.Source.Name)
|
||||
if name == "" {
|
||||
name = entry.SourceName
|
||||
}
|
||||
mode0, err := s.buildCurrentSceneObjectListBodyForSessionInContextStrict(
|
||||
ctx, session, entry.SourceCharacterID, name, state.Source, true, ownerContext)
|
||||
if err != nil || len(mode0) < 3 {
|
||||
s.logGameEvent(session, "game-companion-real-actor-failed",
|
||||
"source_character_id", entry.SourceCharacterID,
|
||||
"stage", "mode0", "error", err)
|
||||
return false
|
||||
}
|
||||
reader := csharpLegacyUserInfoReader{
|
||||
ctx: ctx,
|
||||
characterID: state.Source.CharacterID,
|
||||
service: s,
|
||||
session: session,
|
||||
suppressActorSkillResources: true,
|
||||
equipmentSnapshot: &state.Equipment,
|
||||
}
|
||||
mode1 := reader.buildCurrentUserInfoMode1WithEquipmentForOwner(
|
||||
state.Source, true, entry.SourceCharacterID, true, 0,
|
||||
currentSceneObjectRoute, ownerContext)
|
||||
if len(mode1) < 3 {
|
||||
s.logGameEvent(session, "game-companion-real-actor-failed",
|
||||
"source_character_id", entry.SourceCharacterID,
|
||||
"stage", "mode1")
|
||||
return false
|
||||
}
|
||||
rows = append(rows, realRow{id: entry.SourceCharacterID, name: name, mode0: mode0, mode1: mode1})
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return false
|
||||
}
|
||||
// The client's native multi-row reader creates scene actors for a party
|
||||
// prelude. Sending one row at a time produced global visuals that a dungeon
|
||||
// room never rendered; the roster must be one counted mode0/mode1 body.
|
||||
var multiMode0, multiMode1 packetWriter
|
||||
multiMode0.writeByte(0)
|
||||
multiMode0.writeUint16(uint16(len(rows)))
|
||||
multiMode1.writeByte(1)
|
||||
multiMode1.writeUint16(uint16(len(rows)))
|
||||
for _, row := range rows {
|
||||
multiMode0.writeBytes(row.mode0[3:])
|
||||
multiMode1.writeBytes(row.mode1[3:])
|
||||
}
|
||||
for _, body := range [][]byte{multiMode0.bytes(), multiMode1.bytes()} {
|
||||
if err := s.sendGameUpperRawClass(
|
||||
session, uint16(dnfenum.CmdPacketSetUDPIPPort), body, 0); err != nil {
|
||||
s.logGameEvent(session, "game-companion-real-actor-failed",
|
||||
"stage", "multi-row-send", "error", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
session.townMu.Lock()
|
||||
position := session.townPositionSnapshot
|
||||
session.townMu.Unlock()
|
||||
stateBits := currentActorDefaultUserStateBits
|
||||
if runtime.Character.Stats != nil {
|
||||
if value, exists := runtime.Character.Stats["user_state_bits"]; exists && value >= 0 && value <= 0xff {
|
||||
stateBits = byte(value)
|
||||
}
|
||||
}
|
||||
for index, row := range rows {
|
||||
x := int(position.PositionX) + index*40
|
||||
y := int(position.PositionY)
|
||||
if !position.PositionValid {
|
||||
x = int(numericCharacterStatValue(runtime.Character, "pos_x")) + index*40
|
||||
y = int(numericCharacterStatValue(runtime.Character, "pos_y"))
|
||||
}
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
if x > 0xffff {
|
||||
x = 0xffff
|
||||
}
|
||||
if y < 0 {
|
||||
y = 0
|
||||
}
|
||||
if y > 0xffff {
|
||||
y = 0xffff
|
||||
}
|
||||
areaBody := buildCurrentTownUserAreaNotificationBody(
|
||||
currentSceneActorObjectKey(row.id),
|
||||
position.TownID, 0xff, uint16(x), uint16(y),
|
||||
position.MovementCode, stateBits,
|
||||
)
|
||||
if err := s.sendGameUpperRawClass(
|
||||
session, currentTownUserAreaNotificationMsgID, areaBody, 0); err != nil {
|
||||
s.logGameEvent(session, "game-companion-real-actor-failed",
|
||||
"source_character_id", row.id,
|
||||
"stage", "op23", "error", err)
|
||||
return false
|
||||
}
|
||||
s.logGameEvent(session, "game-companion-real-actor-sent",
|
||||
"source_character_id", row.id,
|
||||
"name", row.name,
|
||||
"owner_context", ownerContext,
|
||||
"object_key", currentSceneActorObjectKey(row.id),
|
||||
"mode0_bytes", len(row.mode0),
|
||||
"mode1_bytes", len(row.mode1),
|
||||
"path", currentCompanionRealActorsMode0Kind)
|
||||
}
|
||||
sent := len(rows)
|
||||
if sent == 0 {
|
||||
return false
|
||||
}
|
||||
s.logGameEvent(session, "game-companion-real-actors-published",
|
||||
"owner_character_id", session.selectedCharacterID,
|
||||
"count", sent,
|
||||
"party_owner_context", ownerContext,
|
||||
"aic", "pending_client_attachment")
|
||||
return true
|
||||
}
|
||||
Reference in new issue
Block a user