Drops now land on the ground as gadgets and are picked up with F instead of appearing in the bag out of nowhere. Chests, ore, crystals and plants all spawn and can be interacted with. Monsters load by scene group rather than by a radius around each one, so a camp shows up as a camp and nothing pops into existence mid fight. The group anchors come out of the scene Lua, which also killed four invented tuning constants. The gateway was creating a whole new session for every handshake datagram, and the client retransmits those until it gets an answer. That is where the piles of duplicated monsters and the chests coming back from the dead were coming from. Also: monster level scales to the party now, path queries return an actual two point path instead of an empty one, NpcTalk closes properly, and the ability and combat invocations are no longer echoed back to the client that sent them.
287 lines
10 KiB
C#
287 lines
10 KiB
C#
using Yuanshen.Gameserver.Network.Session;
|
|
using Yuanshen.Protocol;
|
|
|
|
namespace Yuanshen.Gameserver.Game;
|
|
|
|
internal sealed class SceneManager
|
|
{
|
|
private const int LevelSyncAttempts = 6;
|
|
|
|
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);
|
|
await SyncMonsterLevelAsync(monster);
|
|
}
|
|
|
|
public async ValueTask ClearGadgetsAsync()
|
|
{
|
|
if (_player.Gadgets.Count == 0)
|
|
return;
|
|
|
|
SceneEntityDisappearNotify notify = new() { DisappearType = VisionType.VisionRemove };
|
|
foreach (GameGadget gadget in _player.Gadgets)
|
|
notify.EntityList.Add(gadget.EntityId);
|
|
|
|
_player.Gadgets.Clear();
|
|
await _session.NotifyAsync(CmdType.SceneEntityDisappearNotify, 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 }
|
|
});
|
|
}
|
|
}
|