2024-02-17 13:49:28 +00:00
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using GameServer.Controllers.Attributes;
|
|
|
|
|
using GameServer.Network;
|
|
|
|
|
using GameServer.Systems.Entity;
|
|
|
|
|
using GameServer.Systems.Entity.Component;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Protocol;
|
|
|
|
|
|
|
|
|
|
namespace GameServer.Controllers.Combat;
|
|
|
|
|
internal class CombatManager
|
|
|
|
|
{
|
|
|
|
|
private delegate Task<CombatResponseData> CombatRequestHandler(CombatManager combatManager, CombatRequestContext context);
|
|
|
|
|
private static readonly ImmutableDictionary<CombatRequestData.MessageOneofCase, CombatRequestHandler> s_requestHandlers;
|
|
|
|
|
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
private readonly EntitySystem _entitySystem;
|
|
|
|
|
private readonly PlayerSession _session;
|
|
|
|
|
private readonly CreatureController _creatureController;
|
|
|
|
|
|
|
|
|
|
static CombatManager()
|
|
|
|
|
{
|
|
|
|
|
s_requestHandlers = MapRequestHandlers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public CombatManager(ILogger<CombatManager> logger, EntitySystem entitySystem, PlayerSession session, CreatureController creatureController)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_entitySystem = entitySystem;
|
|
|
|
|
_session = session;
|
|
|
|
|
_creatureController = creatureController;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-24 12:23:00 +00:00
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.RTimeStopRequest)]
|
|
|
|
|
public CombatResponseData OnRTimeStopRequest(CombatRequestContext context) => new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RTimeStopResponse = new()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.ActivateBuffRequest)]
|
|
|
|
|
public CombatResponseData OnActivateBuffRequest(CombatRequestContext context) => new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
ActivateBuffResponse = new()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.UseSkillRequest)]
|
|
|
|
|
public CombatResponseData OnUseSkillRequest(CombatRequestContext context) => new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
UseSkillResponse = new()
|
|
|
|
|
{
|
|
|
|
|
SkillSingleId = context.Request.UseSkillRequest.SkillSingleId,
|
|
|
|
|
UseSkillInfo = context.Request.UseSkillRequest.UseSkillInfo
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.ApplyGameplayEffectRequest)]
|
|
|
|
|
public CombatResponseData OnApplyGameplayEffectRequest(CombatRequestContext context) => new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
ApplyGameplayEffectResponse = new ApplyGameplayEffectResponse()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.RemoveGameplayEffectRequest)]
|
|
|
|
|
public CombatResponseData OnRemoveGameplayEffectRequest(CombatRequestContext context) => new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RemoveGameplayEffectResponse = new RemoveGameplayEffectResponse()
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-23 12:50:16 +00:00
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.CreateBulletRequest)]
|
|
|
|
|
public CombatResponseData OnCreateBulletRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
return new CombatResponseData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
CreateBulletResponse = new()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 13:49:28 +00:00
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.DamageExecuteRequest)]
|
|
|
|
|
public async Task<CombatResponseData> OnDamageExecuteRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
DamageExecuteRequest request = context.Request.DamageExecuteRequest;
|
|
|
|
|
|
|
|
|
|
EntityBase? entity = _entitySystem.Get<EntityBase>(request.TargetEntityId);
|
|
|
|
|
if (entity == null)
|
|
|
|
|
{
|
|
|
|
|
return new CombatResponseData
|
|
|
|
|
{
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
DamageExecuteResponse = new()
|
|
|
|
|
{
|
|
|
|
|
ErrorCode = (int)ErrorCode.ErrThrowDamageReqEntityIsAlreadyDead
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EntityAttributeComponent attr = entity.ComponentSystem.Get<EntityAttributeComponent>();
|
|
|
|
|
attr.SetAttribute(EAttributeType.Life, (int)request.DamageId); // Pakchunk patch! (cur hp instead of damageid)
|
|
|
|
|
|
|
|
|
|
if (request.DamageId <= 0 && entity.Type != EEntityType.Player) // Player death not implemented
|
|
|
|
|
{
|
|
|
|
|
_entitySystem.Destroy(entity);
|
|
|
|
|
await _session.Push(MessageId.EntityRemoveNotify, new EntityRemoveNotify
|
|
|
|
|
{
|
|
|
|
|
IsRemove = true,
|
|
|
|
|
RemoveInfos =
|
|
|
|
|
{
|
|
|
|
|
new EntityRemoveInfo
|
|
|
|
|
{
|
|
|
|
|
EntityId = entity.Id,
|
|
|
|
|
Type = (int)ERemoveEntityType.RemoveTypeNormal
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new CombatResponseData
|
|
|
|
|
{
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
DamageExecuteResponse = new()
|
|
|
|
|
{
|
|
|
|
|
AttackerEntityId = request.AttackerEntityId,
|
|
|
|
|
TargetEntityId = request.TargetEntityId,
|
|
|
|
|
ShieldCoverDamage = 0,
|
|
|
|
|
Damage = (int)request.DamageId,
|
|
|
|
|
PartId = request.PartId
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.HitRequest)]
|
|
|
|
|
public CombatResponseData OnHitRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
return new CombatResponseData
|
|
|
|
|
{
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
HitResponse = new()
|
|
|
|
|
{
|
|
|
|
|
HitInfo = context.Request.HitRequest.HitInfo
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.LogicStateInitRequest)]
|
|
|
|
|
public CombatResponseData OnLogicStateInitRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
CombatResponseData rsp = new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
LogicStateInitResponse = new()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EntityBase? entity = _entitySystem.Get<EntityBase>(context.Request.CombatCommon.EntityId);
|
|
|
|
|
if (entity == null) return rsp;
|
|
|
|
|
|
|
|
|
|
EntityLogicStateComponent logicStateComponent = entity.ComponentSystem.Get<EntityLogicStateComponent>();
|
|
|
|
|
logicStateComponent.States = [.. context.Request.LogicStateInitRequest.InitData.States];
|
|
|
|
|
|
|
|
|
|
context.Notifies.Add(new CombatNotifyData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
LogicStateInitNotify = new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
EntityId = entity.Id,
|
|
|
|
|
InitData = logicStateComponent.Pb.LogicStateComponentPb
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return rsp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.SwitchLogicStateRequest)]
|
|
|
|
|
public CombatResponseData OnSwitchLogicStateRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
CombatResponseData rsp = new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
SwitchLogicStateResponse = new()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EntityBase? entity = _entitySystem.Get<EntityBase>(context.Request.CombatCommon.EntityId);
|
|
|
|
|
if (entity == null)
|
|
|
|
|
{
|
|
|
|
|
rsp.SwitchLogicStateResponse.ErrorCode = (int)ErrorCode.ErrActionEntityNoExist;
|
|
|
|
|
return rsp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EntityLogicStateComponent logicStateComponent = entity.ComponentSystem.Get<EntityLogicStateComponent>();
|
|
|
|
|
logicStateComponent.States = [.. context.Request.SwitchLogicStateRequest.States];
|
|
|
|
|
|
|
|
|
|
context.Notifies.Add(new CombatNotifyData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
SwitchLogicStateNotify = new()
|
|
|
|
|
{
|
|
|
|
|
States = { logicStateComponent.States }
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return rsp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.BattleStateChangeRequest)]
|
|
|
|
|
public CombatResponseData OnBattleStateChangeRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
return new CombatResponseData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
BattleStateChangeResponse = new()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.ChangeStateConfirmRequest)]
|
|
|
|
|
public CombatResponseData OnChangeStateConfirmRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
CombatResponseData rsp = new()
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
ChangeStateConfirmResponse = new()
|
|
|
|
|
{
|
|
|
|
|
Error = new()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EntityBase? entity = _entitySystem.Get<EntityBase>(context.Request.CombatCommon.EntityId);
|
|
|
|
|
if (entity == null)
|
|
|
|
|
{
|
|
|
|
|
rsp.ChangeStateConfirmResponse.Error.ErrorCode = (int)ErrorCode.ErrEntityIsNotAlive;
|
|
|
|
|
return rsp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChangeStateConfirmRequest request = context.Request.ChangeStateConfirmRequest;
|
|
|
|
|
if (entity.ComponentSystem.TryGet(out EntityFsmComponent? fsmComponent))
|
|
|
|
|
{
|
|
|
|
|
DFsm? dfsm = fsmComponent.Fsms.FirstOrDefault(fsm => fsm.FsmId == request.FsmId);
|
2024-02-24 12:23:00 +00:00
|
|
|
|
|
|
|
|
|
if (dfsm == null)
|
2024-02-17 13:49:28 +00:00
|
|
|
|
{
|
2024-02-24 12:23:00 +00:00
|
|
|
|
dfsm = new DFsm
|
|
|
|
|
{
|
|
|
|
|
FsmId = request.FsmId,
|
|
|
|
|
Status = 1,
|
|
|
|
|
Flag = (int)EFsmStateFlag.Confirmed
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fsmComponent.Fsms.Add(dfsm);
|
|
|
|
|
}
|
2024-02-17 13:49:28 +00:00
|
|
|
|
|
|
|
|
|
dfsm.CurrentState = request.State;
|
|
|
|
|
context.Notifies.Add(new CombatNotifyData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
ChangeStateConfirmNotify = new ChangeStateConfirmNotify
|
|
|
|
|
{
|
|
|
|
|
State = request.State,
|
|
|
|
|
FsmId = request.FsmId
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rsp.ChangeStateConfirmResponse.State = context.Request.ChangeStateConfirmRequest.State;
|
|
|
|
|
rsp.ChangeStateConfirmResponse.FsmId = context.Request.ChangeStateConfirmRequest.FsmId;
|
|
|
|
|
|
|
|
|
|
return rsp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.FsmConditionPassRequest)]
|
|
|
|
|
public CombatResponseData OnFsmConditionPassRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
return new CombatResponseData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
FsmConditionPassResponse = new()
|
|
|
|
|
{
|
|
|
|
|
FsmId = context.Request.FsmConditionPassRequest.FsmId,
|
|
|
|
|
Error = new()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[CombatRequest(CombatRequestData.MessageOneofCase.AiInformationRequest)]
|
|
|
|
|
public CombatResponseData OnAiInformationRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
// Currently like this, TODO!
|
|
|
|
|
context.Notifies.Add(new CombatNotifyData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = new CombatCommon
|
|
|
|
|
{
|
|
|
|
|
EntityId = context.Request.CombatCommon.EntityId, // id of monster
|
|
|
|
|
},
|
|
|
|
|
AiHateNotify = new AiHateNotify
|
|
|
|
|
{
|
|
|
|
|
HateList =
|
|
|
|
|
{
|
|
|
|
|
new AiHateEntity
|
|
|
|
|
{
|
|
|
|
|
EntityId = _creatureController.GetPlayerEntity()!.Id, // id of hated entity (the player)
|
|
|
|
|
HatredValue = 99999
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return new CombatResponseData
|
|
|
|
|
{
|
|
|
|
|
CombatCommon = context.Request.CombatCommon,
|
|
|
|
|
RequestId = context.Request.RequestId,
|
|
|
|
|
AiInformationResponse = new() // ?? contains nothing
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<CombatResponseData?> HandleRequest(CombatRequestContext context)
|
|
|
|
|
{
|
|
|
|
|
if (s_requestHandlers.TryGetValue(context.Request.MessageCase, out CombatRequestHandler? handler))
|
|
|
|
|
return handler(this, context)!;
|
|
|
|
|
|
|
|
|
|
_logger.LogWarning("Combat request not handled: {type}", context.Request.MessageCase);
|
|
|
|
|
return Task.FromResult<CombatResponseData?>(null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ImmutableDictionary<CombatRequestData.MessageOneofCase, CombatRequestHandler> MapRequestHandlers()
|
|
|
|
|
{
|
|
|
|
|
var builder = ImmutableDictionary.CreateBuilder<CombatRequestData.MessageOneofCase, CombatRequestHandler>();
|
|
|
|
|
|
|
|
|
|
MethodInfo taskFromResultMethod = typeof(Task).GetMethod(nameof(Task.FromResult))!.MakeGenericMethod(typeof(CombatResponseData));
|
|
|
|
|
|
|
|
|
|
foreach (MethodInfo method in typeof(CombatManager).GetMethods())
|
|
|
|
|
{
|
|
|
|
|
CombatRequestAttribute? attribute = method.GetCustomAttribute<CombatRequestAttribute>();
|
|
|
|
|
if (attribute == null) continue;
|
|
|
|
|
|
|
|
|
|
ParameterExpression combatManagerParam = Expression.Parameter(typeof(CombatManager));
|
|
|
|
|
ParameterExpression contextParam = Expression.Parameter(typeof(CombatRequestContext));
|
|
|
|
|
|
|
|
|
|
Expression call = Expression.Call(combatManagerParam, method, contextParam);
|
|
|
|
|
if (method.ReturnType == typeof(CombatResponseData))
|
|
|
|
|
call = Expression.Call(taskFromResultMethod, call);
|
|
|
|
|
|
|
|
|
|
Expression<CombatRequestHandler> lambda = Expression.Lambda<CombatRequestHandler>(call, combatManagerParam, contextParam);
|
|
|
|
|
builder.Add(attribute.MessageCase, lambda.Compile());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return builder.ToImmutable();
|
|
|
|
|
}
|
|
|
|
|
}
|