Skip to content

Commit

Permalink
Implement avatar swap out animations
Browse files Browse the repository at this point in the history
  • Loading branch information
Crypto137 committed Jun 21, 2024
1 parent 79ba6aa commit 8426347
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 141 deletions.
10 changes: 9 additions & 1 deletion src/MHServerEmu.Games/Entities/Avatars/Avatar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
using MHServerEmu.DatabaseAccess.Models;
using MHServerEmu.Games.Common;
using MHServerEmu.Games.Entities.Inventories;
using MHServerEmu.Games.Entities.PowerCollections;
using MHServerEmu.Games.Entities.Locomotion;
using MHServerEmu.Games.Entities.PowerCollections;
using MHServerEmu.Games.Events;
using MHServerEmu.Games.GameData;
using MHServerEmu.Games.GameData.Calligraphy;
using MHServerEmu.Games.GameData.Prototypes;
Expand All @@ -21,6 +22,8 @@ public class Avatar : Agent
{
private static readonly Logger Logger = LogManager.CreateLogger();

private EventPointer<TEMP_SendActivatePowerMessageEvent> _swapInPowerEvent = new();

private ReplicatedVariable<string> _playerName = new(0, string.Empty);
private ulong _ownerPlayerDbId;
private List<AbilityKeyMapping> _abilityKeyMappingList = new();
Expand Down Expand Up @@ -434,6 +437,11 @@ public override void OnLocomotionStateChanged(LocomotionState oldState, Locomoti
if (SkipAI) base.OnLocomotionStateChanged(oldState, newState);
}

public void ScheduleSwapInPower()
{
ScheduleEntityEvent(_swapInPowerEvent, TimeSpan.FromMilliseconds(700), GameDatabase.GlobalsPrototype.AvatarSwapInPower);
}

protected override void BuildString(StringBuilder sb)
{
base.BuildString(sb);
Expand Down
63 changes: 40 additions & 23 deletions src/MHServerEmu.Games/Entities/Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public class Entity : ISerialize

private EventGroup _pendingEvents = new();

public EventPointer<ScheduledLifespanEvent> ScheduledLifespanEvent = new();
public EventPointer<ScheduledLifespanEvent> ScheduledLifespanEvent { get; } = new();

public Entity(Game game)
{
Expand Down Expand Up @@ -424,28 +424,29 @@ public virtual void OnPropertyChange()
if (Properties.HasProperty(PropertyEnum.AIMasterAvatar)) _flags |= EntityFlags.AIMasterAvatar;
}

public void OnSelfAddedToOtherInventory()
public virtual void OnSelfAddedToOtherInventory()
{
}

public void OnSelfRemovedFromOtherInventory(InventoryLocation prevInvLoc)
public virtual void OnSelfRemovedFromOtherInventory(InventoryLocation prevInvLoc)
{
}

public void OnOtherEntityAddedToMyInventory(Entity entity, InventoryLocation invLoc, bool unpackedArchivedEntity)
public virtual void OnOtherEntityAddedToMyInventory(Entity entity, InventoryLocation invLoc, bool unpackedArchivedEntity)
{
}

public void OnOtherEntityRemovedFromMyInventory(Entity entity, InventoryLocation invLoc)
public virtual void OnOtherEntityRemovedFromMyInventory(Entity entity, InventoryLocation invLoc)
{
}

public void OnDetachedFromDestroyedContainer()
public virtual void OnDetachedFromDestroyedContainer()
{
}

public virtual void OnDeallocate()
{
Game.GameEventScheduler.CancelAllEvents(_pendingEvents);
}

public virtual void OnChangePlayerAOI(Player player, InterestTrackOperation operation,
Expand All @@ -461,6 +462,11 @@ public virtual void OnPostAOIAddOrRemove(Player player, InterestTrackOperation o

}

public void OnLifespanExpired()
{
Destroy();
}

#endregion

#region Inventory Management
Expand Down Expand Up @@ -783,36 +789,47 @@ public void ResetLifespan(TimeSpan lifespan)
TotalLifespan = lifespan;
}

public void ScheduleEntityEvent<T>(EventPointer<T> eventPointer, TimeSpan lifespan)
where T : ScheduledEvent, IEventTarget, new()
public void ScheduleEntityEvent<TEvent>(EventPointer<TEvent> eventPointer, TimeSpan timeOffset)
where TEvent : CallMethodEvent<Entity>, new()
{
var scheduler = Game?.GameEventScheduler;
if (scheduler == null) return;
scheduler.ScheduleEvent(eventPointer, lifespan, _pendingEvents);
eventPointer.Get().EventTarget = this;
scheduler.ScheduleEvent(eventPointer, timeOffset, _pendingEvents);
eventPointer.Get().Initialize(this);
}

public void OnLifespanExpired()
public void ScheduleEntityEvent<TEvent, TParam1>(EventPointer<TEvent> eventPointer, TimeSpan timeOffset, TParam1 param1)
where TEvent : CallMethodEventParam1<Entity, TParam1>, new()
{
Destroy();
var scheduler = Game?.GameEventScheduler;
if (scheduler == null) return;
scheduler.ScheduleEvent(eventPointer, timeOffset, _pendingEvents);
eventPointer.Get().Initialize(this, param1);
}

#endregion
}
public void ScheduleEntityEvent<TEvent, TParam1, TParam2>(EventPointer<TEvent> eventPointer, TimeSpan timeOffset, TParam1 param1, TParam2 param2)
where TEvent : CallMethodEventParam2<Entity, TParam1, TParam2>, new()
{
var scheduler = Game?.GameEventScheduler;
if (scheduler == null) return;
scheduler.ScheduleEvent(eventPointer, timeOffset, _pendingEvents);
eventPointer.Get().Initialize(this, param1, param2);
}

public class ScheduledLifespanEvent : TargetedScheduledEvent<Entity> , IEventTarget
{
public Entity EventTarget { get => _eventTarget; set => _eventTarget = value; }
public override bool OnTriggered()
public void ScheduleEntityEvent<TEvent, TParam1, TParam2, TParam3>(EventPointer<TEvent> eventPointer, TimeSpan lifespan, TParam1 param1, TParam2 param2, TParam3 param3)
where TEvent : CallMethodEventParam3<Entity, TParam1, TParam2, TParam3>, new()
{
if (_eventTarget == null) return false;
_eventTarget.OnLifespanExpired();
return true;
var scheduler = Game?.GameEventScheduler;
if (scheduler == null) return;
scheduler.ScheduleEvent(eventPointer, lifespan, _pendingEvents);
eventPointer.Get().Initialize(this, param1, param2, param3);
}

#endregion
}

public interface IEventTarget
public class ScheduledLifespanEvent : CallMethodEvent<Entity>
{
Entity EventTarget { get; set; }
protected override CallbackDelegate GetCallback() => (t) => t.OnLifespanExpired();
}
}
97 changes: 76 additions & 21 deletions src/MHServerEmu.Games/Entities/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
using MHServerEmu.Games.Entities.Inventories;
using MHServerEmu.Games.Entities.Items;
using MHServerEmu.Games.Entities.Options;
using MHServerEmu.Games.Events;
using MHServerEmu.Games.Events.LegacyImplementations;
using MHServerEmu.Games.Events.Templates;
using MHServerEmu.Games.GameData;
using MHServerEmu.Games.GameData.LiveTuning;
using MHServerEmu.Games.GameData.Prototypes;
Expand Down Expand Up @@ -54,6 +57,8 @@ public class Player : Entity, IMissionManagerOwner
{
private static readonly Logger Logger = LogManager.CreateLogger();

private readonly EventPointer<SwitchAvatarEvent> _switchAvatarEvent = new();

private MissionManager _missionManager = new();
private ReplicatedPropertyCollection _avatarProperties;
private ulong _shardId;
Expand Down Expand Up @@ -635,6 +640,16 @@ public bool RevealInventory(PrototypeId inventoryProtoRef)
return true;
}

public override void OnOtherEntityAddedToMyInventory(Entity entity, InventoryLocation invLoc, bool unpackedArchivedEntity)
{
base.OnOtherEntityAddedToMyInventory(entity, invLoc, unpackedArchivedEntity);

if (invLoc.InventoryConvenienceLabel == InventoryConvenienceLabel.AvatarInPlay && entity is Avatar avatar && invLoc.Slot == 0)
{
CurrentAvatar = avatar;
}
}

public bool TrashItem(Item item)
{
// See CPlayer::RequestItemTrash for reference
Expand Down Expand Up @@ -746,23 +761,6 @@ public Avatar GetAvatar(PrototypeId avatarProtoRef, AvatarMode avatarMode = Avat

return null;
}

public bool SwitchAvatar(PrototypeId avatarProtoRef, out Avatar prevAvatar)
{
Inventory avatarLibrary = GetInventory(InventoryConvenienceLabel.AvatarLibrary);
Inventory avatarInPlay = GetInventory(InventoryConvenienceLabel.AvatarInPlay);

prevAvatar = CurrentAvatar;

Avatar avatar = avatarLibrary.GetMatchingEntity(avatarProtoRef) as Avatar;
if (avatar == null)
Logger.WarnReturn(false, $"SwitchAvatar(): Failed to find avatar entity for avatarProtoRef {GameDatabase.GetPrototypeName(avatarProtoRef)}");

avatar.ChangeInventoryLocation(avatarInPlay, 0);
CurrentAvatar = avatar;

return true;
}

public IEnumerable<Avatar> IterateAvatars()
{
Expand All @@ -783,8 +781,62 @@ public Agent GetTeamUpAgent(PrototypeId teamUpProtoRef)
return teamUpInv.GetMatchingEntity(teamUpProtoRef) as Agent;
}

#endregion
public bool BeginSwitchAvatar(PrototypeId avatarProtoRef)
{
if (_switchAvatarEvent.IsValid) return false;

// Activate swap out power for the current avatar
// TODO: Replace this with regular power activation
CurrentAvatar.TEMP_ScheduleSendActivatePowerMessage(GameDatabase.GlobalsPrototype.AvatarSwapOutPower, TimeSpan.Zero);

// Schedule avatar switch
ScheduleEntityEvent(_switchAvatarEvent, TimeSpan.FromMilliseconds(1066), avatarProtoRef);

return true;
}

public bool SwitchAvatar(PrototypeId avatarProtoRef)
{
Inventory avatarLibrary = GetInventory(InventoryConvenienceLabel.AvatarLibrary);
Inventory avatarInPlay = GetInventory(InventoryConvenienceLabel.AvatarInPlay);

if (avatarLibrary.GetMatchingEntity(avatarProtoRef) is not Avatar avatar)
return Logger.WarnReturn(false, $"SwitchAvatar(): Failed to find avatar entity for avatarProtoRef {GameDatabase.GetPrototypeName(avatarProtoRef)}");

InventoryResult result = avatar.ChangeInventoryLocation(avatarInPlay, 0);

if (result != InventoryResult.Success)
return Logger.WarnReturn(false, $"SwitchAvatar(): Failed to change library avatar's inventory location ({result})");

EnableCurrentAvatar(true);
return true;
}

public bool EnableCurrentAvatar(bool withSwapInPower)
{
// TODO: Use this for teleportation within region as well

if (CurrentAvatar == null)
return Logger.WarnReturn(false, "EnableCurrentAvatar(): CurrentAvatar == null");

if (CurrentAvatar.IsInWorld)
return Logger.WarnReturn(false, "EnableCurrentAvatar(): Current avatar is already active");

Logger.Info($"EnableCurrentAvatar(): {CurrentAvatar} entering world");

// Disable initial visibility and schedule swap-in power if requested
EntitySettings settings = null;
if (withSwapInPower)
{
settings = new() { OptionFlags = EntitySettingsOptionFlags.IsClientEntityHidden };
CurrentAvatar.ScheduleSwapInPower();
}

// Add new avatar to the world
return CurrentAvatar.EnterWorld(PlayerConnection.AOI.Region, PlayerConnection.LastPosition, PlayerConnection.LastOrientation, settings);
}

#endregion

#region Messages

Expand Down Expand Up @@ -917,16 +969,19 @@ public bool IsTargetable(AlliancePrototype allianceProto)
return true;
}

internal WorldEntity GetDialogTarget(bool validateTarget = false)
public WorldEntity GetDialogTarget(bool validateTarget = false)
{
throw new NotImplementedException();
}

internal bool CanAcquireCurrencyItem(WorldEntity localInteractee)
public bool CanAcquireCurrencyItem(WorldEntity localInteractee)
{
throw new NotImplementedException();
}


private class SwitchAvatarEvent : CallMethodEventParam1<Entity, PrototypeId>
{
protected override CallbackDelegate GetCallback() => (t, p1) => ((Player)t).SwitchAvatar(p1);
}
}
}
41 changes: 41 additions & 0 deletions src/MHServerEmu.Games/Entities/WorldEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
using MHServerEmu.Games.Entities.Locomotion;
using MHServerEmu.Games.Entities.Physics;
using MHServerEmu.Games.Entities.PowerCollections;
using MHServerEmu.Games.Events;
using MHServerEmu.Games.Events.Templates;
using MHServerEmu.Games.GameData;
using MHServerEmu.Games.GameData.Prototypes;
using MHServerEmu.Games.Generators;
Expand Down Expand Up @@ -57,6 +59,8 @@ public class WorldEntity : Entity
{
private static readonly Logger Logger = LogManager.CreateLogger();

private EventPointer<TEMP_SendActivatePowerMessageEvent> _sendActivatePowerMessageEvent = new();

private AlliancePrototype _allianceProto;

protected EntityTrackingContextMap _trackingContextMap;
Expand Down Expand Up @@ -1290,6 +1294,43 @@ public bool IsThrowableBy(WorldEntity thrower)
}

public virtual void OnDramaticEntranceEnd() { }

public bool TEMP_ScheduleSendActivatePowerMessage(PrototypeId powerProtoRef, TimeSpan timeOffset)
{
if (_sendActivatePowerMessageEvent.IsValid) return false;
ScheduleEntityEvent(_sendActivatePowerMessageEvent, timeOffset, powerProtoRef);
return true;
}

public bool TEMP_SendActivatePowerMessage(PrototypeId powerProtoRef)
{
if (IsInWorld == false) return false;

Logger.Trace($"Activating {GameDatabase.GetPrototypeName(powerProtoRef)} for {this}");

ActivatePowerArchive activatePower = new()
{
Flags = ActivatePowerMessageFlags.TargetIsUser | ActivatePowerMessageFlags.HasTargetPosition |
ActivatePowerMessageFlags.TargetPositionIsUserPosition | ActivatePowerMessageFlags.HasFXRandomSeed |
ActivatePowerMessageFlags.HasPowerRandomSeed,

PowerPrototypeRef = powerProtoRef,
UserEntityId = Id,
TargetPosition = RegionLocation.Position,
FXRandomSeed = (uint)Game.Random.Next(),
PowerRandomSeed = (uint)Game.Random.Next()
};

var activatePowerMessage = NetMessageActivatePower.CreateBuilder().SetArchiveData(activatePower.ToByteString()).Build();
Game.NetworkManager.SendMessageToInterested(activatePowerMessage, this, AOINetworkPolicyValues.AOIChannelProximity);

return true;
}

protected class TEMP_SendActivatePowerMessageEvent : CallMethodEventParam1<Entity, PrototypeId>
{
protected override CallbackDelegate GetCallback() => (t, p1) => ((WorldEntity)t).TEMP_SendActivatePowerMessage(p1);
}
}

}
Loading

0 comments on commit 8426347

Please sign in to comment.