88 lines
4.6 KiB
Go
88 lines
4.6 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// Party-camp furniture the server places itself — 露营的烤炉 / 帐篷 / 篝火 / 野餐垫 / 灯 / 野餐车.
|
||
// WHY (2026-09-26, round 44). 用户站在露营点 2.8 m 处实测「家具全无」:烤炉、帐篷一件都没有,
|
||
// 而我们**确实**把营地那一格(32,59)通过 grid-AOI 下发过了。原因和健身房器材一模一样:
|
||
// 这批 `Gadgets_Party_*` 只活在客户端的关卡 blob(LX6.Item.GadgetBlob)里,而
|
||
// SyncGadgetGridAOIDecrease(68956797) 这条下发**不驱动**
|
||
// GadgetBlob.OnAoiAddBlobCfgByGridIndex ⇒ 烘焙里的道具一件都不会自己出现。
|
||
// 用户那句「是不是和健身器材一样」是对的。见 TickGymEquipmentAsync。
|
||
// ★ 判据(回答「是不是开派对才刷」):**不是**。家具烘焙在 raid 23300888 = 主城 free-roam,
|
||
// 而露营/舞会的 PartyConfig.RaidId 也是 23300888 ⇒ 派对就地举行、不切副本 ⇒ 场地家具只能是
|
||
// 预先常驻的世界装点。旁证:同一批 prefab 还被当作天台夜市(-378.28,130.50,-497.87)的
|
||
// 常驻装点摆着;营地 400 m 圈内全是改车店/公厕/时装店/象棋摊等主城常驻机关。
|
||
// ⇒ 所以这里是**常驻摆放**(玩家走进场地半径就摆),而不是「开派对时才摆」。
|
||
// ⛔ PackedGadgetInfo.iScale 必须是 0(100 ⇒ transform scale (0,0,1) 零尺寸空壳)。
|
||
// Rollback: world.content.partyCamp.enabled = false.
|
||
//
|
||
//
|
||
// ★★★ 2026-09-30:**就地开局** —— 把指定 partyId 的整片场地搬到玩家脚下。
|
||
//
|
||
//
|
||
// 官方把舞会 13030003 摆在 y ≈ 80 的悬空层(那层没有地板,实机够不到),
|
||
// 所以开派对时由 GameRouter 的 StartSingleParty 分支调本方法,
|
||
// 把家具整片平移到玩家坐标;下一次 grid tick 就会按新坐标摆出来。
|
||
// ⛑⛑ 必须同时把场地从 Placed 里摘掉:
|
||
// Placed 是「每会话每场地只摆一次」的去重 HashSet,而一次误判
|
||
// (玩家临时站在矩形外 ⇒ INDOOR-GATE 全挡)就会把场地永久毒死
|
||
// (2026-09-30 踩过,见 2026-09-30.md §45.2)。
|
||
// 同理,同一会话重开派对时也必须解掉,否则第二场一件家具都没有。
|
||
// 传入的 必须是地面基准(玩家上报 y − 眼高 ≈ 1.07 m),
|
||
// 不是玩家上报值 —— 角色上报的是胶囊中心,直接用会整体浮空 1 m。
|
||
// 是开派对那一刻玩家的朝向(度)。partyVenuePropFacePlayer
|
||
// 打开时,家具整片会按它旋转,让最远那件(舞会 = 灯光控制台)落在玩家正前方;
|
||
// 传 0(或开关关闭)则保持官方的世界方位。
|
||
//
|
||
func GameRouter_PartyCamp_SetPartyVenueAtPlayer(ctx context.Context, session *GameSession, partyId uint32, x float32, y float32, z float32, facingDeg float32) any {
|
||
// Router implementation for GameRouter_PartyCamp_SetPartyVenueAtPlayer
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_PartyCamp_PartyCampStateOf(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_PartyCamp_PartyCampStateOf
|
||
var zero any; return zero
|
||
}
|
||
|
||
// Reconnect / scene switch: forget what was stocked so the next visit re-places it.
|
||
func GameRouter_PartyCamp_ResetPartyCamp(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_PartyCamp_ResetPartyCamp
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// Called from the grid-AOI tick (which already knows the player position). Places every camp
|
||
// the player has walked into, once per camp per session.
|
||
//
|
||
func GameRouter_PartyCamp_TickPartyCampAsync(ctx context.Context, session *GameSession, state any, px float32, pz float32) any {
|
||
// Router implementation for GameRouter_PartyCamp_TickPartyCampAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
type PartyCampSessionState struct {
|
||
SyncRoot any `json:"syncRoot"`
|
||
// Camp sites already stocked this session. One shot per site, like the gym.
|
||
Placed []string `json:"placed"`
|
||
PlacedCount int32 `json:"placedCount"`
|
||
WarnedEmpty bool `json:"warnedEmpty"`
|
||
// ★ 2026-09-30 就地开局:把整片场地临时搬到玩家脚下(0 = 未启用,走配表锚点)。
|
||
VenueAtPlayerPartyId uint32 `json:"venueAtPlayerPartyId"`
|
||
VenueAtPlayerX float32 `json:"venueAtPlayerX"`
|
||
VenueAtPlayerY float32 `json:"venueAtPlayerY"`
|
||
VenueAtPlayerZ float32 `json:"venueAtPlayerZ"`
|
||
//
|
||
// 就地开局那一刻玩家的朝向(度)。家具整片靠它旋转到玩家正前方
|
||
// (见 SetPartyVenueAtPlayer)。0 = 保持官方世界方位。
|
||
//
|
||
VenueAtPlayerFacingDeg float32 `json:"venueAtPlayerFacingDeg"`
|
||
}
|
||
|