---@class BP_MonsterBase_C:STExtraSimpleCharacter ---@field ParticleSystem_Stun UParticleSystemComponent ---@field MonsterAnimList UUAEMonsterAnimListComponent ---@field Capsule UCapsuleComponent ---@field HurtEffect UParticleSystem ---@field TargetPlayerKey int64 --Edit Below-- local BP_MonsterBase = { ID = 0; DeadExp = 0; DeadCoinPoint = 0; DeadKillPoint = 1; ModifyDamageDelegation = {}; -- 修改收到伤害的委托 SkillCount = 0; MonsterName = ""; -- ID = 0; bIsDead = false; bIsStun = false; --是否眩晕 bHasInitedByLevel = false; bHasTriggeredReceiveOnDeath = false; AttackValue = 0; MagicValue = 0; BossLevel = 0; PhysicalDefense = 0; MagicDefense = 0; Gold = 0; KillPoint = 0; exp = 0; -- 附加攻击力 AdditionalAttack = 0; -- 附加防御力 AdditionalDefense = 0; --攻击力效率 AttackScale = 1; --防御力效率 DefenseScale = 1; -- 应用附加攻击力 ApplyAdditionalAttack = 1; -- 应用附加防御力 ApplyAdditionalDefense = 1; -- 回血效率 BloodReturnEfficiency = 1; -- 伤害减免百分比 DamageReductionPercentage = 0; -- 追踪玩家范围 TrackPlayerRange = 1000; bSetWeaponSkill = false; }; ---------------------------------属性同步--------------------------------- function BP_MonsterBase:GetReplicatedProperties() return "TargetPlayerKey", "bIsStun" end function BP_MonsterBase:OnRep_bIsStun() if self.bIsStun then self.ParticleSystem_Stun:SetVisibility(true) else self.ParticleSystem_Stun:SetVisibility(false) end end function BP_MonsterBase:OnRep_TargetPlayerKey() print(string.format('[BP_MonsterBase:OnRep_TargetPlayerKey] 同步 PlayerKey = %d', self.TargetPlayerKey)) end ---------------------------------初始化流程--------------------------------- function BP_MonsterBase:ReceiveBeginPlay() BP_MonsterBase.SuperClass.ReceiveBeginPlay(self) if self:HasAuthority() then self.OnDeath:Add(BP_MonsterBase.ServerOnDeath, self) else self.OnSimpleCharacterHpChange:Add(BP_MonsterBase.ClientOnHealthChanged, self) end self:ReceiveBeginPlayEx() end function BP_MonsterBase:ReceiveTick(DeltaTime) BP_MonsterBase.SuperClass.ReceiveTick(self, DeltaTime); self:ReceiveTickEx(DeltaTime) end function BP_MonsterBase:ReceiveEndPlay() self:ReceiveOnDeath() if self:HasAuthority() then self.OnDeath:Remove(BP_MonsterBase.ServerOnDeath, self) self.bIsStun = false if self.StunTimerHandle ~= nil then EventSystem.StopTimer(self.StunTimerHandle) end else self.ParticleSystem_Stun:SetVisibility(false) end self:ReceiveEndPlayEx() BP_MonsterBase.SuperClass.ReceiveEndPlay(self) end ---额外初始化项的接口 ---子类中实现 function BP_MonsterBase:ReceiveBeginPlayEx() end ---额外Tick操作接口 ---子类中实现 function BP_MonsterBase:ReceiveTickEx(DeltaTime) end ---额外销毁项的接口 ---子类中实现 function BP_MonsterBase:ReceiveEndPlayEx() end ---客户端血量变化回调 function BP_MonsterBase:ClientOnHealthChanged(CurrentHP, MaxHP) self:ClientOnHealthChangedEx(CurrentHP, MaxHP) end ---客户端血量变化回调 ---子类中实现 function BP_MonsterBase:ClientOnHealthChangedEx(CurrentHP, MaxHP) end ----------------------------------------死亡回调函数---------------------------------------- function BP_MonsterBase:ServerOnDeath(DeadMonster, KillerController, DamageCauser, KillingHitInfo, KillingHitImpulseDir, KillingHitDamageTypeID, DamageTypeClass, IsHeadShotDamage) if DeadMonster == self then local PlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(KillerController.PlayerKey) PlayerState:AddExp(self:GetDeadExp()) PlayerState:AddCoinPoint(self:GetDeadCoinPoint()) PlayerState:AddKillPoint(self:GetDeadKillPoint()) PlayerState:AddKillCount() KillerController:AddSealKillCount() local Rand = math.random(1, 100) local DropItemAddition = PlayerState:GetDroppingRate() if Rand < (self:GetDropRate() * (1 + DropItemAddition)) then local DropPos = VectorHelper.Sub(self:K2_GetActorLocation(), {X = 0, Y = 0, Z = self.Capsule.CapsuleHalfHeight}) EventSystem:SendEvent(EventType.SpawnDropItem, DropPos, self:K2_GetActorRotation(), KillerController.PlayerKey, nil) end self.bIsDead = true self.bIsStun = false if self.StunTimerHandle ~= nil then EventSystem.StopTimer(self.StunTimerHandle) end self:ReceiveOnDeath() self:ServerOnDeathEx(DeadMonster, KillerController, DamageCauser, KillingHitInfo, KillingHitImpulseDir, KillingHitDamageTypeID, DamageTypeClass, IsHeadShotDamage) end end function BP_MonsterBase:ReceiveOnDeath() if self.bHasTriggeredReceiveOnDeath then return end self.bHasTriggeredReceiveOnDeath = true self:ReceiveOnMonsterDeath() if self:HasAuthority() then UnrealNetwork.CallUnrealRPC_Multicast(self, "ReceiveOnMonsterDeath") UnrealNetwork.CallUnrealRPC_Multicast(self, "Client_MulticastRPC_RemoveAllSkillEffect", self) end end ---当收到死亡回调后的处理项接口(服务器和客户端都会运行) ---子类中实现 function BP_MonsterBase:ReceiveOnMonsterDeath() end ---死亡回调额外处理项接口 ---子类中实现 function BP_MonsterBase:ServerOnDeathEx(DeadMonster, KillerController, DamageCauser, KillingHitInfo, KillingHitImpulseDir, KillingHitDamageTypeID, DamageTypeClass, IsHeadShotDamage) end -- 处理飘字 function BP_MonsterBase:UGC_GetDamageNumberConfigIndex(Damage, IsHeadShot, InstigatorController, DamageCauser, DamageType) if not self:HasAuthority() then GameplayStatics.SpawnEmitterAttached(self.HurtEffect, self.Mesh, "spine_01", VectorHelper.VectorZero(), VectorHelper.RotZero(), VectorHelper.Scale(2.0), EAttachLocation.SnapToTarget, true) end if (not InstigatorController) or (not DamageCauser:IsLocallyControlled()) then return -1 end UE.Log("[BP_MonsterBase:UGC_GetDamageNumberConfigIndex] 0") return 0 end ---------------------------------初始化属性流程--------------------------------- function BP_MonsterBase:InitMonsterByLevel(InLevel) self.bHasInitedByLevel = false return self:UpdateMonsterLevel(self.MonsterName, InLevel) end ---更新怪物等级 function BP_MonsterBase:UpdateMonsterLevel(MonsterName, newLevel) if self.bHasInitedByLevel then return true end self.bHasInitedByLevel = true if self.HealthCheckHandle ~= nil then EventSystem.StopTimer(self.HealthCheckHandle) self.HealthCheckHandle = nil end if MonsterName == self.MonsterName and self.BossLevel == newLevel then UE.Log("[BP_MonsterBase][UpdateMonsterLevel] %s: Has Inited", KismetSystemLibrary.GetObjectName(self)) return true end self.bSetWeaponSkill = false local GameState = UGCGameSystem.GameState if UE.IsValid(GameState) == false then UE.Log("[BP_MonsterBase][UpdateMonsterLevel] invalid gamestate") return false end local MultiplierBoss = GameState.BossHealthMultiplier local MultiplierMonster = GameState.MonsterHealthAndAttackValueMultiplier if ((type(newLevel) == "number") and MonsterParam[MonsterName]) then self.BossLevel = newLevel local MonsterType = self:GetMonsterType() local HealthMax = MonsterParam[MonsterName].GradeParam[self.BossLevel].Health * MultiplierMonster["Health"] if MonsterType == EMonsterType.Boss then HealthMax = HealthMax * MultiplierBoss end UGCSimpleCharacterSystem.SetHealthMax(self, HealthMax) UGCSimpleCharacterSystem.SetHealth(self, HealthMax) self.HealthCheckHandle = EventSystem.SetTimerLoop(self, function() local HealthMax = UGCSimpleCharacterSystem.GetHealthMax(self) local Health = UGCSimpleCharacterSystem.GetHealth(self) if math.isNearlyEqual(Health, HealthMax, 10000) == false then UGCSimpleCharacterSystem.SetHealth(self, HealthMax) local Now_HealthMax = UGCSimpleCharacterSystem.GetHealthMax(self) local Now_Health = UGCSimpleCharacterSystem.GetHealth(self) local LogStr = string.format("[BP_MonsterBase][CheckMonsterHealth] %s: Reset Health: HealthMax = %.2f, Health = %.2f; ", KismetSystemLibrary.GetObjectName(self), Now_HealthMax, Now_Health) if math.isNearlyEqual(Now_HealthMax, Now_Health, 10000) then LogStr = LogStr.." Stop Timer !" EventSystem.StopTimer(self.HealthCheckHandle) end UE.Log(LogStr) else UE.Log("[BP_MonsterBase][CheckMonsterHealth] %s: Valid Health: HealthMax = %.2f, Health = %.2f", KismetSystemLibrary.GetObjectName(self), HealthMax, Health) EventSystem.StopTimer(self.HealthCheckHandle) end end, 0.5) self.AttackValue = MonsterParam[MonsterName].GradeParam[self.BossLevel].AttackValue * MultiplierMonster["Attack"] self.PhysicalDefense = MonsterParam[MonsterName].GradeParam[self.BossLevel].PhysicalDefense local MaxWalkSpeed = MonsterParam[MonsterName].GradeParam[self.BossLevel].MovementSpeed UGCSimpleCharacterSystem.SetSpeedScale(self, MaxWalkSpeed / 600) self.DeadCoinPoint = MonsterParam[MonsterName].GradeParam[self.BossLevel].Gold self.DeadExp = MonsterParam[MonsterName].GradeParam[self.BossLevel].exp self.DeadKillPoint = MonsterParam[MonsterName].GradeParam[self.BossLevel].KillPoint self.BaseHealthMax = HealthMax self.BaseAttackValue = self.AttackValue self.BasePhysicalDefense = self.PhysicalDefense self.BaseMaxWalkSpeed = MaxWalkSpeed if MonsterType ~= EMonsterType.Breach then local LifeTime = MonsterParam[MonsterName].GradeParam[self.BossLevel].LifeTime if LifeTime and LifeTime > 0 then self:SetLifeSpan(LifeTime) end end self:UpdateMonsterLevelEx(MonsterName, newLevel) else UE.Log("[BP_MonsterBase][UpdateMonsterLevel] Failure: invalid level or monstername") return false end return true end ---更新怪物等级其他项 ---子类实现 function BP_MonsterBase:UpdateMonsterLevelEx(MonsterName, newLevel) end function BP_MonsterBase:UpdateSkillCount() self.SkillCount = self.SkillCount + 1 end ---怪物应用伤害 function BP_MonsterBase:MonsterApplyDamage(TargetPawn, DamageValue) if self.bIsDead then return end if TargetPawn == nil then return end local juryReduction = 0 local PlayerState = TargetPawn.PlayerState if PlayerState and PlayerState.InjuryReduction then juryReduction = PlayerState:InjuryReduction(self) end DamageValue = DamageValue - juryReduction if DamageValue < 0 then DamageValue = 0 end print("BP_MonsterBase_Fun_" .. "MonsterApplyDamage_ " .. DamageValue) UGCGameSystem.ApplyDamage(TargetPawn, DamageValue, self:GetController(), self, EDamageType.ShootDamage) end ---------------------------------Get&Set--------------------------------- -- virtual function BP_MonsterBase:GetDeadExp() return self.DeadExp end function BP_MonsterBase:GetDeadCoinPoint() return self.DeadCoinPoint end function BP_MonsterBase:GetDeadKillPoint() return self.DeadKillPoint end function BP_MonsterBase:GetDropRate() return 10 end function BP_MonsterBase:GetMonsterType() return GameDataManager.GetMonsterTypeByID(self.ID) end ---Server function BP_MonsterBase:SetTargetPlayerKey(InPlayerKey) self.TargetPlayerKey = InPlayerKey -- UE.Log("[BP_MonsterBase][SetTargetPlayerKey] [%s]: TargetPlayerKey[%s]", KismetSystemLibrary.GetObjectName(self), tostring(self.TargetPlayerKey)) end ---Server function BP_MonsterBase:SetIsStun(flag, Duration) if not self:IsAlive() then return end if flag then if Duration > 0 then if self.StunTimerHandle ~= nil then EventSystem.StopTimer(self.StunTimerHandle) end self.StunTimerHandle = EventSystem.SetTimer(self, function() self.bIsStun = false end, Duration) else return end end self.bIsStun = flag end function BP_MonsterBase:GetCanDamagePlayer() return GameplayStatics.GetAllActorsOfClass(self, self:GetPawnClass(), {}) end ---设置血量恢复效率 ---@field NewBloodReturnEfficiency float function BP_MonsterBase:SetBloodReturnEfficiency(NewBloodReturnEfficiency) if NewBloodReturnEfficiency < 0 then return false end self.BloodReturnEfficiency = NewBloodReturnEfficiency return true end ---恢复怪物的血量,自动控制在血量上限 ---@field TargetAddHealth float function BP_MonsterBase:AddHealth(TargetAddHealth) TargetAddHealth = self.BloodReturnEfficiency * TargetAddHealth local TargetHealth = UGCSimpleCharacterSystem.GetHealth(self) + TargetAddHealth if TargetHealth > UGCSimpleCharacterSystem.GetHealthMax(self) then TargetHealth = UGCSimpleCharacterSystem.GetHealthMax(self) end UGCSimpleCharacterSystem.SetHealth(self, TargetHealth) end ---获取攻击力 ---@return float function BP_MonsterBase:GetAttack() return self.AttackScale * (self.AttackValue + (self.AdditionalAttack * self.ApplyAdditionalAttack)) end ---获取防御力 ---@return float function BP_MonsterBase:GetDefense() return self.DefenseScale * (self.PhysicalDefense + (self.AdditionalDefense * self.ApplyAdditionalDefense)) end ---设置攻击缩放 ---@field NewAttackScale float ---@return bool function BP_MonsterBase:SetAttackScale(NewAttackScale) if NewAttackScale < 0 then return false end self.AttackScale = NewAttackScale return true end ---设置防御缩放 ---@field NewDefenseScale float ---@return bool function BP_MonsterBase:SetDefenseScale(NewDefenseScale) if NewDefenseScale < 0 then return false end self.DefenseScale = NewDefenseScale return true end ---设置应用攻击附加 ---@field NewApplyAdditionalAttack bool function BP_MonsterBase:SetApplyAdditionalAttack(NewApplyAdditionalAttack) if NewApplyAdditionalAttack then self.ApplyAdditionalAttack = 1 else self.ApplyAdditionalAttack = 0 end end ---设置设置应用防御附加 ---@field NewApplyAdditionalDefense bool function BP_MonsterBase:SetApplyAdditionalDefense(NewApplyAdditionalDefense) if NewApplyAdditionalDefense then self.ApplyAdditionalDefense = 1 else self.ApplyAdditionalDefense = 0 end end ---添加攻击力附加 function BP_MonsterBase:AddAdditionalAttack(Val) self.AdditionalAttack = Val + self.AdditionalAttack end ---添加防御力附加 function BP_MonsterBase:AddAdditionalDefense(Val) self.AdditionalDefense = Val + self.AdditionalDefense end ---重置额外攻击力 function BP_MonsterBase:ResetAdditionalAttack(Val) self.AdditionalAttack = Val end ---重置额外防御力 function BP_MonsterBase:ResetAdditionalDefense(Val) self.AdditionalDefense = Val end ---@field CenterPosition Vector ---@field radius float ---@field TargetClass UClass ---@return resActors table function BP_MonsterBase:GetSphereActors(CenterPosition, radius, TargetClass) local TargetActors = GameplayStatics.GetAllActorsOfClass(self, TargetClass, {}) local resActors = {} for k, TempActor in pairs(TargetActors) do if VectorHelper.GetDistance(TempActor:K2_GetActorLocation(), CenterPosition) <= radius then table.insert(resActors, TempActor) end end return resActors end function BP_MonsterBase:GetPawnClass() if not self.PawnClass then self.PawnClass = UE.LoadClass(BPClassPath.PlayerPawn) end return self.PawnClass end function BP_MonsterBase:GetMonsterClass() if not self.MonsterClass then self.MonsterClass = UE.LoadClass(UGCGameSystem.GetUGCResourcesFullPath('Asset/Blueprint/Monster/BP_MonsterBase.BP_MonsterBase_C')) end return self.MonsterClass end ---------------------------------伤害处理--------------------------------- ---伤害处理函数 function BP_MonsterBase:BP_CharacterModifyDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser) local BaseDamageAmount = DamageAmount DamageAmount = BP_MonsterBase.ReduceInjury(self, DamageCauser, DamageAmount) if self.ModifyDamageDelegation then for k, fun in pairs(self.ModifyDamageDelegation) do DamageAmount = fun(DamageAmount, DamageEvent, EventInstigator, DamageCauser) end end if DamageCauser.IsInTrueDamage then DamageAmount = BaseDamageAmount end if DamageAmount < 0 then DamageAmount = 0 end DamageAmount = self:DealWeapon(DamageCauser, DamageAmount) if EventInstigator then local ControlledCharacter = EventInstigator:GetPlayerCharacterSafety() ControlledCharacter:DealRealDamage(self, DamageAmount) end self:CollectPlayerDamage(DamageAmount, EventInstigator.PlayerKey) DamageAmount = self:BP_CharacterModifyDamageEx(DamageAmount) return DamageAmount end ---额外伤害处理接口 ---子类实现 function BP_MonsterBase:BP_CharacterModifyDamageEx(DamageAmount) return DamageAmount end --收到伤害时触发, Server调用, 顺序在ModifyDamage之后, DamageAmout应是计算过后的伤害 function BP_MonsterBase:BPReceiveDamage(DamageAmount, DamageType, EventInstigator, DamageCauser) self:BPReceiveDamageEx(DamageAmount, DamageType, EventInstigator, DamageCauser) end ---子类中实现 function BP_MonsterBase:BPReceiveDamageEx(DamageAmount, DamageType, EventInstigator, DamageCauser) end --[[ time: 20230312 name: LanTian describe: 添加一个ModifyDamageDelegation的委托table,作用于重载的伤害修改函数BP_CharacterModifyDamage,便利所有的修改委托函数后返回最终伤害值 AddModifyDamageDelegation : 添加一个委托 RemoveModifyDamageDelegation : 移除一个委托 ]]-- function BP_MonsterBase.ReduceInjury(Monster, Pawn, DamageAmount) local ReduceInjury = 0 if UE.IsValid(Monster) and UE.IsValid(Pawn) and UE.IsValid(Pawn.PlayerState) and Monster.GetDefense and Pawn.PlayerState.DefencePenetrate then ReduceInjury = (Monster:GetDefense() - Pawn.PlayerState:DefencePenetrate()) / ((Monster:GetDefense() - Pawn.PlayerState:DefencePenetrate()) + 1000) local OldDamage = DamageAmount DamageAmount = DamageAmount * (1 - ReduceInjury) * (1 - Monster.DamageReductionPercentage) if GameDataManager.GetMonsterTypeByID(Monster.ID) == EMonsterType.Boss then UE.Log("[BP_MonsterBoss:ReduceInjury] %s: OldDmg=%.2f, Result=%.2f", KismetSystemLibrary.GetObjectName(Monster), OldDamage, DamageAmount) end UE.Log("[BP_MonsterBase_ReduceInjury] %f", DamageAmount) end return DamageAmount end function BP_MonsterBase:DealWeapon(InDamageCauser, InDamageAmount) local WeaponType = GameDataManager.GetWeaponType(InDamageCauser.WeaponActor.CurrentWeaponId) if WeaponType == nil then UE.Log("[BP_MonsterBase:DealWeapon] Data is nil") return else if WeaponType == EWeaponClassType.WT_ShotGun then -- 霰弹枪 if self.ID == 10006 and self.bIsReplica then InDamageAmount = InDamageAmount * 4 UE.Log('[BP_MonsterBase:DealWeapon] 霰弹枪: 攻击分身伤害:%f ---SelfName=%s', InDamageAmount, KismetSystemLibrary.GetObjectName(self)) end elseif WeaponType == EWeaponClassType.WT_MachineGun then -- 机枪 --self. elseif WeaponType == EWeaponClassType.WT_SubmachineGun then -- 冲锋枪 InDamageCauser.bCanBeControlledByMonsters = true if GameDataManager.GetMonsterTypeByID(self.ID) == EMonsterType.Boss then UE.Log('[BP_MonsterBase:DealWeapon] 冲锋枪: 免疫控制 ---SelfName=%s', KismetSystemLibrary.GetObjectName(self)) end elseif WeaponType == EWeaponClassType.WT_ShooterRifle then -- 射手步枪 if GameDataManager.GetMonsterTypeByID(self.ID) == EMonsterType.Boss then if not self.bSetWeaponSkill then local SpeedScale = UGCSimpleCharacterSystem.GetSpeedScale(self) UGCSimpleCharacterSystem.SetSpeedScale(self, SpeedScale * 0.3) self.bSetWeaponSkill = true UE.Log('[BP_MonsterBase:DealWeapon] 射手步枪: 减速 ---SelfName=%s', KismetSystemLibrary.GetObjectName(self)) end end elseif WeaponType == EWeaponClassType.WT_AssaultRifle then -- 突击步枪 if GameDataManager.GetMonsterTypeByID(self.ID) == EMonsterType.Boss then self:SetBloodReturnEfficiency(0) UE.Log('[BP_MonsterBase:DealWeapon] 突击步枪: 抑制回复 ---SelfName=%s', KismetSystemLibrary.GetObjectName(self)) end elseif WeaponType == EWeaponClassType.WT_Sniper then -- 狙击枪 if GameDataManager.GetMonsterTypeByID(self.ID) == EMonsterType.Boss then self:SetApplyAdditionalDefense(false) UE.Log('[BP_MonsterBase:DealWeapon] 狙击枪: 无视防御 ---SelfName=%s', KismetSystemLibrary.GetObjectName(self)) end end end return InDamageAmount end function BP_MonsterBase:CollectPlayerDamage(DamageAmount, PlayerKey) if PlayerKey == nil then UE.Log("BP_MonsterBase_Fun_" .. "CollectPlayerDamage" .. "PlayerKey is nil") return end if self:GetMonsterType() == EMonsterType.Boss then if DamageAmount > UGCSimpleCharacterSystem.GetHealth(self) then DamageAmount = UGCSimpleCharacterSystem.GetHealth(self) end local GameState = UGCGameSystem.GameState GameState:UpdatePlayerDamage(PlayerKey, self.ID, DamageAmount) end end function BP_MonsterBase:RemoveModifyDamageDelegation(Key) if self.ModifyDamageDelegation[Key] then self.ModifyDamageDelegation[Key] = nil return true end return false end ---@param Key string ---@param DelegationFun fun(DamageAmount:float,DamageEvent:FDamageEvent,EventInstigator:AController,DamageCauser:AActor):float ---@return bool function BP_MonsterBase:AddModifyDamageDelegationFun(Key, DelegationFun) if self.ModifyDamageDelegation[Key] then return false else self.ModifyDamageDelegation[Key] = DelegationFun return true end end -- 获取封印 KillCount function BP_MonsterBase:GetSealKillCount() return 1 end -------------------------------RPCs------------------------------- function BP_MonsterBase:GetAvailableServerRPCs() return "ReceiveOnMonsterDeath" -- "Client_MulticastRPC_RemoveAllSkillEffect" end function BP_MonsterBase:Client_MulticastRPC_RemoveAllSkillEffect(CasterActor) local EffectSystemManager = require('Script.Manager.EffectSystemManager') EffectSystemManager.RemoveAllEffectOfActor(CasterActor) end return BP_MonsterBase;