NahidaImpact/NahidaImpact.Gameserver/Game/Scene/SceneManager.cs
2024-01-05 18:14:13 +03:00

268 lines
9.2 KiB
C#

using System.Numerics;
using NahidaImpact.Common.Constants;
using NahidaImpact.Common.Data.Binout;
using NahidaImpact.Gameserver.Controllers;
using NahidaImpact.Gameserver.Game.Avatar;
using NahidaImpact.Gameserver.Game.Entity;
using NahidaImpact.Gameserver.Game.Entity.Factory;
using NahidaImpact.Gameserver.Network.Session;
using NahidaImpact.Protocol;
namespace NahidaImpact.Gameserver.Game.Scene;
internal class SceneManager(NetSession session, Player player, EntityManager entityManager, EntityFactory entityFactory, BinDataCollection binData)
{
public uint EnterToken { get; private set; }
public uint CurrentSceneId => _sceneId;
private readonly BinDataCollection _binData = binData;
private readonly NetSession _session = session;
private readonly Player _player = player;
private readonly EntityManager _entityManager = entityManager;
private readonly EntityFactory _entityFactory = entityFactory;
private readonly List<AvatarEntity> _teamAvatars = [];
private uint _enterTokenSeed;
private uint _sceneId;
private ulong _beginTime;
private SceneEnterState _enterState;
public async ValueTask OnEnterStateChanged(SceneEnterState changedToState)
{
if (_enterState is SceneEnterState.None or SceneEnterState.Complete)
throw new InvalidOperationException($"SceneManager::OnEnterStateChanged called when enter state is {_enterState}!");
if (_enterState > changedToState)
throw new ArgumentException($"SceneManager::OnEnterStateChanged - requested state is less than current! (curr={_enterState}, req={changedToState})");
if (_enterState + 1 != changedToState)
throw new ArgumentException($"SceneManager::OnEnterStateChanged - trying to skip enter state! (curr={_enterState}, req={changedToState})");
_enterState = changedToState;
switch (_enterState)
{
case SceneEnterState.ReadyToEnter:
await OnReadyToEnterScene();
break;
case SceneEnterState.InitFinished:
await OnSceneInitFinished();
break;
case SceneEnterState.EnterDone:
await OnEnterDone();
break;
case SceneEnterState.PostEnter:
await OnPostEnter();
break;
}
if (_enterState == SceneEnterState.PostEnter)
_enterState = SceneEnterState.Complete;
}
public async ValueTask TeleportTo(float x, float y, float z)
{
SceneEntity? entity = _entityManager.GetEntityById(_player.CurAvatarEntityId);
if (entity == null) return;
entity.MotionInfo.Pos = new Vector
{
X = x,
Y = y,
Z = z
};
_player.LastMainWorldPos = entity.MotionInfo.Pos;
await ReEnterCurScene(EnterType.Jump);
}
public async ValueTask ResetAllCoolDownsForAvatar(uint entityId)
{
await _entityManager.ChangeAvatarFightPropAsync(entityId, FightProp.FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO, 1);
await _entityManager.ChangeAvatarFightPropAsync(entityId, FightProp.FIGHT_PROP_SKILL_CD_MINUS_RATIO, 1);
}
public ValueTask MotionChanged(uint entityId, MotionInfo motion)
{
SceneEntity? entity = _entityManager.GetEntityById(entityId);
if (entity == null) return ValueTask.CompletedTask;
entity.MotionInfo = motion;
// need to notify peers, but it's single player anyway (for now)
return ValueTask.CompletedTask;
}
public async ValueTask ReplaceCurrentAvatarAsync(ulong replaceToGuid)
{
// TODO: add logic checks.
SceneEntity previousEntity = _entityManager.GetEntityById(_player.CurAvatarEntityId)!;
AvatarEntity avatar = _teamAvatars.Find(a => a.GameAvatar.Guid == replaceToGuid)
?? throw new ArgumentException($"ReplaceCurrentAvatar: avatar with guid {replaceToGuid} not in team!");
avatar.MotionInfo = previousEntity.MotionInfo; // maybe make a better way to do this?
_player.CurAvatarEntityId = avatar.EntityId;
await _entityManager.SpawnEntityAsync(avatar, VisionType.Replace);
}
public async ValueTask ChangeTeamAvatarsAsync(ulong[] guidList)
{
_teamAvatars.Clear();
SceneEntity previousEntity = _entityManager.GetEntityById(_player.CurAvatarEntityId)!;
Vector pos = previousEntity.MotionInfo.Pos;
foreach (ulong guid in guidList)
{
GameAvatar gameAvatar = _player.Avatars.Find(avatar => avatar.Guid == guid)!;
AvatarEntity avatarEntity = _entityFactory.CreateAvatar(gameAvatar, _player.Uid);
avatarEntity.SetPosition(pos.X, pos.Y, pos.Z);
_teamAvatars.Add(avatarEntity);
}
await SendSceneTeamUpdate();
_player.CurAvatarEntityId = _teamAvatars[0].EntityId;
await _entityManager.SpawnEntityAsync(_teamAvatars[0], VisionType.Born);
}
private async ValueTask OnEnterDone()
{
_player.CurAvatarEntityId = _teamAvatars[0].EntityId;
await _entityManager.SpawnEntityAsync(_teamAvatars[0], VisionType.Born);
}
private async ValueTask OnSceneInitFinished()
{
GameAvatarTeam avatarTeam = _player.GetCurrentTeam();
foreach (ulong guid in avatarTeam.AvatarGuidList)
{
GameAvatar gameAvatar = _player.Avatars.Find(avatar => avatar.Guid == guid)!;
AvatarEntity avatarEntity = _entityFactory.CreateAvatar(gameAvatar, _player.Uid);
Vector initialPos = _player.LastMainWorldPos;
avatarEntity.SetPosition(initialPos.X, initialPos.Y, initialPos.Z);
_teamAvatars.Add(avatarEntity);
}
await SendEnterSceneInfo();
await SendSceneTeamUpdate();
}
private async ValueTask OnReadyToEnterScene()
{
await _session.NotifyAsync(CmdType.EnterScenePeerNotify, new EnterScenePeerNotify
{
DestSceneId = _sceneId,
EnterSceneToken = EnterToken,
HostPeerId = 1, // TODO: Scene peers
PeerId = 1
});
}
private ValueTask OnPostEnter()
{
return ValueTask.CompletedTask;
}
public async ValueTask ReEnterCurScene(EnterType enterType)
{
await EnterSceneAsync(_sceneId, enterType);
}
public async ValueTask EnterSceneAsync(uint sceneId, EnterType enterType = EnterType.Self)
{
if (_beginTime != 0) ResetState();
_beginTime = (ulong)DateTimeOffset.Now.ToUnixTimeSeconds();
_sceneId = sceneId;
EnterToken = ++_enterTokenSeed;
_enterState = SceneEnterState.EnterRequested;
await _session.NotifyAsync(CmdType.PlayerEnterSceneNotify, new PlayerEnterSceneNotify
{
SceneBeginTime = _beginTime,
SceneId = _sceneId,
SceneTransaction = CreateTransaction(_sceneId, _player.Uid, _beginTime),
Pos = _player.LastMainWorldPos,
TargetUid = _player.Uid,
EnterSceneToken = EnterToken,
PrevPos = new(),
Type = enterType
});
}
private async ValueTask SendSceneTeamUpdate()
{
SceneTeamUpdateNotify sceneTeamUpdate = new();
foreach (AvatarEntity avatar in _teamAvatars)
{
sceneTeamUpdate.SceneTeamAvatarList.Add(new SceneTeamAvatar
{
SceneEntityInfo = avatar.AsInfo(),
WeaponEntityId = avatar.WeaponEntityId,
PlayerUid = _player.Uid,
WeaponGuid = avatar.GameAvatar.WeaponGuid,
EntityId = avatar.EntityId,
AvatarGuid = avatar.GameAvatar.Guid,
AbilityControlBlock = avatar.BuildAbilityControlBlock(_binData),
SceneId = _sceneId
});
}
await _session.NotifyAsync(CmdType.SceneTeamUpdateNotify, sceneTeamUpdate);
}
private async ValueTask SendEnterSceneInfo()
{
PlayerEnterSceneInfoNotify enterSceneInfo = new()
{
CurAvatarEntityId = _teamAvatars[0].EntityId,
EnterSceneToken = EnterToken,
MpLevelEntityInfo = new MPLevelEntityInfo
{
EntityId = 184549377,
AbilityInfo = new AbilitySyncStateInfo(),
AuthorityPeerId = 1
},
TeamEnterInfo = new TeamEnterSceneInfo
{
TeamEntityId = 150994946,
AbilityControlBlock = new AbilityControlBlock(),
TeamAbilityInfo = new AbilitySyncStateInfo()
}
};
foreach (AvatarEntity avatar in _teamAvatars)
{
enterSceneInfo.AvatarEnterInfo.Add(new AvatarEnterSceneInfo
{
AvatarGuid = avatar.GameAvatar.Guid,
AvatarEntityId = avatar.EntityId,
WeaponEntityId = avatar.WeaponEntityId,
WeaponGuid = avatar.GameAvatar.WeaponGuid
});
}
await _session.NotifyAsync(CmdType.PlayerEnterSceneInfoNotify, enterSceneInfo);
}
private void ResetState()
{
_teamAvatars.Clear();
_entityManager.Reset();
}
private static string CreateTransaction(uint sceneId, uint playerUid, ulong beginTime)
=> string.Format("{0}-{1}-{2}-13830", sceneId, playerUid, beginTime);
}