Gadgets are streamed alongside monsters now, so chests and ore nodes show up in the world and can be interacted with. The two flows differ: for a chest the client sends the finish request itself and the loot is granted on that one, for ore it sends nothing more so the server has to close everything on the first request. Artifacts used to be silently dropped on the floor because the item ids that actually roll were not in the reliquary table at all. Added the missing rows and gave them real main stat and substat rolls instead of the fixed four. Dropped items survive a relogin. The inventory is still rebuilt from the tables on every login, so the trick is to remember where the default set ends and only persist what was found in the world. Gacha and shop weapons were disappearing the same way and are fixed by the same change.
273 lines
9.7 KiB
C#
273 lines
9.7 KiB
C#
using Yuanshen.Gameserver.Network.Session;
|
|
using Yuanshen.Protocol;
|
|
|
|
namespace Yuanshen.Gameserver.Game;
|
|
|
|
internal sealed class SceneManager
|
|
{
|
|
private const int LevelSyncAttempts = 3;
|
|
|
|
private readonly NetSession _session;
|
|
private readonly Player _player;
|
|
private readonly GameState _state;
|
|
|
|
public SceneManager(NetSession session, Player player, GameState state)
|
|
{
|
|
_session = session;
|
|
_player = player;
|
|
_state = state;
|
|
}
|
|
|
|
public SceneTeamUpdateNotify BuildSceneTeamUpdate()
|
|
{
|
|
SceneTeamUpdateNotify notify = new();
|
|
|
|
foreach (GameAvatar avatar in _player.TeamAvatars)
|
|
{
|
|
GameWeapon weapon = _player.FindWeapon(avatar.WeaponGuid)!;
|
|
|
|
notify.SceneTeamAvatarList.Add(new SceneTeamAvatar
|
|
{
|
|
PlayerId = _player.Uid,
|
|
AvatarGuid = avatar.Guid,
|
|
SceneId = GameState.SceneId,
|
|
EntityId = avatar.EntityId,
|
|
WeaponGuid = avatar.WeaponGuid,
|
|
WeaponEntityId = avatar.WeaponEntityId,
|
|
IsPlayerCurAvatar = avatar.Guid == _player.CurrentAvatarGuid,
|
|
IsOnScene = avatar.Guid == _player.CurrentAvatarGuid,
|
|
AbilityControlBlock = avatar.BuildAbilityControlBlock(),
|
|
AvatarAbilityInfo = new AbilitySyncStateInfo { IsInited = true },
|
|
WeaponAbilityInfo = new AbilitySyncStateInfo { IsInited = true },
|
|
SceneEntityInfo = avatar.AsSceneEntity(_state.CurrentPos, weapon, EquippedReliquaries(avatar))
|
|
});
|
|
}
|
|
|
|
return notify;
|
|
}
|
|
|
|
public PlayerEnterSceneInfoNotify BuildEnterSceneInfo()
|
|
{
|
|
GameAvatar active = _player.ActiveAvatar!;
|
|
|
|
PlayerEnterSceneInfoNotify notify = new()
|
|
{
|
|
CurAvatarEntityId = active.EntityId,
|
|
EnterSceneToken = _state.EnterSceneToken,
|
|
MpLevelEntityInfo = new MPLevelEntityInfo
|
|
{
|
|
EntityId = Player.MpLevelEntityId,
|
|
AuthorityPeerId = Player.PeerId,
|
|
AbilityInfo = new AbilitySyncStateInfo { IsInited = true }
|
|
},
|
|
TeamEnterInfo = new TeamEnterSceneInfo
|
|
{
|
|
TeamEntityId = Player.TeamEntityId,
|
|
TeamAbilityInfo = new AbilitySyncStateInfo { IsInited = true }
|
|
}
|
|
};
|
|
|
|
foreach (GameAvatar avatar in _player.TeamAvatars)
|
|
{
|
|
notify.AvatarEnterInfo.Add(new AvatarEnterSceneInfo
|
|
{
|
|
AvatarGuid = avatar.Guid,
|
|
AvatarEntityId = avatar.EntityId,
|
|
WeaponGuid = avatar.WeaponGuid,
|
|
WeaponEntityId = avatar.WeaponEntityId,
|
|
AvatarAbilityInfo = new AbilitySyncStateInfo { IsInited = true },
|
|
WeaponAbilityInfo = new AbilitySyncStateInfo { IsInited = true }
|
|
});
|
|
}
|
|
|
|
return notify;
|
|
}
|
|
|
|
public async ValueTask SpawnActiveAvatarAsync(VisionType visionType)
|
|
{
|
|
GameAvatar active = _player.ActiveAvatar!;
|
|
GameWeapon weapon = _player.FindWeapon(active.WeaponGuid)!;
|
|
|
|
await _session.NotifyAsync(CmdType.SceneEntityAppearNotify, new SceneEntityAppearNotify
|
|
{
|
|
AppearType = visionType,
|
|
EntityList = { active.AsSceneEntity(_state.CurrentPos, weapon, EquippedReliquaries(active)) }
|
|
});
|
|
}
|
|
|
|
public async ValueTask DespawnMonsterAsync(GameMonster monster)
|
|
{
|
|
_player.Monsters.Remove(monster);
|
|
|
|
SceneEntityDisappearNotify notify = new() { DisappearType = VisionType.VisionRemove };
|
|
notify.EntityList.Add(monster.EntityId);
|
|
await _session.NotifyAsync(CmdType.SceneEntityDisappearNotify, notify);
|
|
}
|
|
|
|
public async ValueTask AppearMonsterAsync(GameMonster monster)
|
|
{
|
|
SceneEntityAppearNotify notify = new() { AppearType = VisionType.VisionBorn };
|
|
notify.EntityList.Add(monster.AsSceneEntity());
|
|
await _session.NotifyAsync(CmdType.SceneEntityAppearNotify, notify);
|
|
}
|
|
|
|
public async ValueTask AppearGadgetAsync(GameGadget gadget)
|
|
{
|
|
SceneEntityAppearNotify notify = new() { AppearType = VisionType.VisionBorn };
|
|
notify.EntityList.Add(gadget.AsSceneEntity());
|
|
await _session.NotifyAsync(CmdType.SceneEntityAppearNotify, notify);
|
|
}
|
|
|
|
public async ValueTask DespawnGadgetAsync(GameGadget gadget, VisionType visionType)
|
|
{
|
|
_player.Gadgets.Remove(gadget);
|
|
|
|
SceneEntityDisappearNotify notify = new() { DisappearType = visionType };
|
|
notify.EntityList.Add(gadget.EntityId);
|
|
await _session.NotifyAsync(CmdType.SceneEntityDisappearNotify, notify);
|
|
}
|
|
|
|
public async ValueTask SetGadgetStateAsync(GameGadget gadget, uint state, bool enableInteract)
|
|
{
|
|
await _session.NotifyAsync(CmdType.GadgetStateNotify, new GadgetStateNotify
|
|
{
|
|
GadgetEntityId = gadget.EntityId,
|
|
GadgetState = state,
|
|
IsEnableInteract = enableInteract
|
|
});
|
|
}
|
|
|
|
public async ValueTask SyncMonsterLevelAsync(GameMonster monster)
|
|
{
|
|
await _session.NotifyAsync(CmdType.EntityPropNotify, new EntityPropNotify
|
|
{
|
|
EntityId = monster.EntityId,
|
|
PropMap = { { 4001u, new PropValue { Type = 4001, Ival = monster.Level, Val = monster.Level } } }
|
|
});
|
|
}
|
|
|
|
public async ValueTask SyncPendingMonsterLevelsAsync()
|
|
{
|
|
foreach (GameMonster monster in _player.Monsters.Where(m => m.IsAlive && m.LevelSyncCount < LevelSyncAttempts).ToList())
|
|
{
|
|
monster.LevelSyncCount++;
|
|
await SyncMonsterLevelAsync(monster);
|
|
}
|
|
}
|
|
|
|
public async ValueTask ClearMonstersAsync()
|
|
{
|
|
if (_player.Monsters.Count == 0)
|
|
return;
|
|
|
|
SceneEntityDisappearNotify notify = new() { DisappearType = VisionType.VisionRemove };
|
|
foreach (GameMonster monster in _player.Monsters)
|
|
notify.EntityList.Add(monster.EntityId);
|
|
|
|
_player.Monsters.Clear();
|
|
await _session.NotifyAsync(CmdType.SceneEntityDisappearNotify, notify);
|
|
}
|
|
|
|
public async ValueTask TeleportAsync(Vector pos)
|
|
{
|
|
_state.UpdatePosition(pos);
|
|
|
|
await _session.NotifyAsync(CmdType.PlayerEnterSceneNotify, new PlayerEnterSceneNotify
|
|
{
|
|
SceneId = GameState.SceneId,
|
|
Pos = pos,
|
|
SceneBeginTime = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
|
Type = EnterType.EnterGoto,
|
|
TargetUid = _player.Uid,
|
|
EnterSceneToken = _state.NextEnterSceneToken(),
|
|
WorldLevel = 0,
|
|
EnterReason = 1
|
|
});
|
|
}
|
|
|
|
public async ValueTask HealTeamAsync()
|
|
{
|
|
foreach (GameAvatar avatar in _player.TeamAvatars)
|
|
await _session.NotifyAsync(CmdType.EntityFightPropUpdateNotify, new EntityFightPropUpdateNotify
|
|
{
|
|
EntityId = avatar.EntityId,
|
|
FightPropMap = { { 1010u, 999999f }, { 2000u, 999999f } }
|
|
});
|
|
}
|
|
|
|
public async ValueTask RefreshAvatarDataAsync()
|
|
{
|
|
AvatarDataNotify data = new()
|
|
{
|
|
CurAvatarTeamId = _player.CurrentTeamId,
|
|
ChooseAvatarGuid = _player.CurrentAvatarGuid
|
|
};
|
|
|
|
foreach (uint flycloak in Player.Flycloaks)
|
|
data.OwnedFlycloakList.Add(flycloak);
|
|
|
|
foreach (GameAvatar avatar in _player.Avatars)
|
|
data.AvatarList.Add(avatar.AsAvatarInfo());
|
|
|
|
AvatarTeam team = new() { TeamName = "Team " + _player.CurrentTeamId };
|
|
team.AvatarGuidList.AddRange(_player.TeamAvatarGuids);
|
|
data.AvatarTeamMap.Add(_player.CurrentTeamId, team);
|
|
|
|
await _session.NotifyAsync(CmdType.AvatarDataNotify, data);
|
|
}
|
|
|
|
public async ValueTask RefreshTeamAsync()
|
|
=> await _session.NotifyAsync(CmdType.SceneTeamUpdateNotify, BuildSceneTeamUpdate());
|
|
|
|
public async ValueTask SetupTeamAsync(uint teamId, IReadOnlyList<ulong> guids, ulong curAvatarGuid)
|
|
{
|
|
ulong previousActiveGuid = _player.CurrentAvatarGuid;
|
|
|
|
_player.CurrentTeamId = teamId;
|
|
_player.TeamAvatarGuids.Clear();
|
|
_player.TeamAvatarGuids.AddRange(guids);
|
|
_player.CurrentAvatarGuid = curAvatarGuid != 0 ? curAvatarGuid : guids[0];
|
|
|
|
AvatarTeam team = new() { TeamName = "Team " + teamId };
|
|
team.AvatarGuidList.AddRange(guids);
|
|
|
|
await _session.NotifyAsync(CmdType.AvatarTeamUpdateNotify, new AvatarTeamUpdateNotify
|
|
{
|
|
AvatarTeamMap = { { teamId, team } }
|
|
});
|
|
|
|
if (previousActiveGuid != _player.CurrentAvatarGuid)
|
|
await DisappearAsync(previousActiveGuid, VisionType.VisionReplace);
|
|
|
|
await _session.NotifyAsync(CmdType.SceneTeamUpdateNotify, BuildSceneTeamUpdate());
|
|
|
|
if (previousActiveGuid != _player.CurrentAvatarGuid)
|
|
await SpawnActiveAvatarAsync(VisionType.VisionReplace);
|
|
}
|
|
|
|
public async ValueTask ChangeAvatarAsync(ulong newGuid)
|
|
{
|
|
if (_player.CurrentAvatarGuid == newGuid)
|
|
return;
|
|
|
|
await DisappearAsync(_player.CurrentAvatarGuid, VisionType.VisionReplace);
|
|
_player.CurrentAvatarGuid = newGuid;
|
|
await SpawnActiveAvatarAsync(VisionType.VisionReplace);
|
|
}
|
|
|
|
private IEnumerable<GameReliquary> EquippedReliquaries(GameAvatar avatar)
|
|
=> avatar.ReliquaryGuids.Values.Select(_player.FindReliquary).OfType<GameReliquary>();
|
|
|
|
private async ValueTask DisappearAsync(ulong avatarGuid, VisionType visionType)
|
|
{
|
|
GameAvatar? avatar = _player.FindAvatar(avatarGuid);
|
|
if (avatar is null)
|
|
return;
|
|
|
|
await _session.NotifyAsync(CmdType.SceneEntityDisappearNotify, new SceneEntityDisappearNotify
|
|
{
|
|
DisappearType = visionType,
|
|
EntityList = { avatar.EntityId }
|
|
});
|
|
}
|
|
}
|