---@class BP_MonsterSpawner_C:AActor ---@field Box UBoxComponent ---@field Type TEnumAsByte ---@field SpawnerID int32 --Edit Below-- local BP_MonsterSpawner = { CurPosIndex = 0, NumberOfPoints = 500, ScalarParam = 3.0, ResourceGrade = 1, DropPositions = {}, SpawnedMonsters = {}, SpawnNum = 10, TargetPlayerKey = -100, InheritMonster = { ['盔'] = false, ['甲'] = false }; IsActivate = false, SpawnMonsterIndex = 1, CheckNumInterval = 0.8, --检查剩余怪物数量的间隔时间 ElapsedTime = 0.0, SpawnWaitingNum = 0, --等待生成的怪物数量 SpawnWaitingBossNum = 0, }; function BP_MonsterSpawner:ReceiveBeginPlay() BP_MonsterSpawner.SuperClass.ReceiveBeginPlay(self); if self:HasAuthority() then self:GenerateDropPosition() end end function BP_MonsterSpawner:ReceiveTick(DeltaTime) BP_MonsterSpawner.SuperClass.ReceiveTick(self, DeltaTime); if self.IsActivate == false then return end if self:HasAuthority() then if self.Type == EMonsterSpawnerType.HangUpRoom then if self.SpawnWaitingNum > 0 then self:SpawnSingleHangupRoomMonster() self.SpawnWaitingNum = self.SpawnWaitingNum - 1 end self.ElapsedTime = self.ElapsedTime + DeltaTime if self.ElapsedTime >= self.CheckNumInterval then self.ElapsedTime = 0.0 self.CheckNumInterval = math.random(1, 5) / 10.0 local RemainedMonsterNum = table.getCount(self.SpawnedMonsters) if RemainedMonsterNum < self.SpawnNum then self.SpawnWaitingNum = math.clamp(self.SpawnWaitingNum + (self.SpawnNum - RemainedMonsterNum), 0, 10) end end else if self.SpawnWaitingNum > 0 then self:SpawnSingleAttackMonster() self.SpawnWaitingNum = self.SpawnWaitingNum - 1 end if self.SpawnWaitingBossNum > 0 then self:SpawnSingleAttackBoss() self.SpawnWaitingBossNum = self.SpawnWaitingBossNum - 1 end end end end function BP_MonsterSpawner:GetSpawnerType() return self.Type end function BP_MonsterSpawner:GetSpawnerID() return self.SpawnerID end function BP_MonsterSpawner:BindPlayerKey(InPlayerKey) self.TargetPlayerKey = InPlayerKey UE.Log("[BP_MonsterSpawner][BindPlayerKey] %s Bind PlayerKey: %d", KismetSystemLibrary.GetObjectName(self), self.TargetPlayerKey) end function BP_MonsterSpawner:StartSpawnHangupMonster(InSpawnNum) if self.Type ~= EMonsterSpawnerType.HangUpRoom then return end self.IsActivate = true UE.Log("[BP_MonsterSpawner][StartSpawnHangupMonster] %s: TargetPlayerKey[%s]", KismetSystemLibrary.GetObjectName(self), tostring(self.TargetPlayerKey)) self.SpawnWaitingNum = InSpawnNum end function BP_MonsterSpawner:SpawnSingleHangupRoomMonster() if table.getCount(self.SpawnedMonsters) >= 10 then return end local BindedPC = UGCGameSystem.GetPlayerControllerByPlayerKey(self.TargetPlayerKey) if BindedPC == nil or UE.IsValid(BindedPC) == false then UE.Log("[BP_MonsterSpawner][SpawnSingleHangupRoomMonster] Can't Find BindedPC") return end local MonsterClass = GameDataManager.GetRandHangupMonsterClass() if MonsterClass then local Location = self:GetSpawnLocation() local Rotation = self:GetSpawnRotation() local Scale = VectorHelper.ScaleOne() local Monster = UGCGameSystem.SpawnActor(self, MonsterClass, Location, Rotation, Scale, nil) if not Monster then return end Monster:SetOwner(self) local MonsterInitSuccess = Monster:InitMonsterByLevel(BindedPC.ResourceGrade) -- if MonsterInitSuccess == false then -- UE.Log("[BP_MonsterSpawner][SpawnSingleHangupRoomMonster] Monster Init Failed: MonsterName[%s]", KismetSystemLibrary.GetObjectName(Monster)) -- return -- end Monster:SetTargetPlayerKey(self.TargetPlayerKey) Monster:SetSpawnIndex(self.SpawnMonsterIndex) self.SpawnedMonsters[self.SpawnMonsterIndex] = Monster -- UE.Log("[BP_MonsterSpawner][SpawnSingleHangupRoomMonster] Success: MonsterName[%s], SpawnIndex[%d]", KismetSystemLibrary.GetObjectName(Monster), self.SpawnMonsterIndex) self.SpawnMonsterIndex = self.SpawnMonsterIndex + 1 end end function BP_MonsterSpawner:StartSpawnAttackWave(NormalMonsterNum, BossMonsterNum) if self.Type ~= EMonsterSpawnerType.AttackWave then return end self.IsActivate = true UGCGameSystem.GameState:UpdateIsInAttackWave(NormalMonsterNum + BossMonsterNum > 0) self.SpawnWaitingNum = NormalMonsterNum self.SpawnWaitingBossNum = BossMonsterNum end function BP_MonsterSpawner:SpawnSingleAttackMonster() local MonsterClass = GameDataManager.GetAttackWaveCommonMonsterClass() MonsterClass = UE.LoadClass(UGCGameSystem.GetUGCResourcesFullPath('Asset/Blueprint/Monster/BP_BossCharm.BP_BossCharm_C')) if MonsterClass then local Location = self:GetSpawnLocation() local Rotation = self:GetSpawnRotation() local Scale = VectorHelper.Scale(1.25) local Monster = UGCGameSystem.SpawnActor(self, MonsterClass, Location, Rotation, Scale, nil) if Monster then table.insert(self.SpawnedMonsters, Monster) Monster:SetOwner(self) Monster:InitMonsterByLevel(UGCGameSystem.GameState.CurAttackWave) Monster.OnDeath:Add(BP_MonsterSpawner.OnSpawnedMonsterDeath, self) end end end function BP_MonsterSpawner:SpawnSingleAttackBoss() local MonsterClass = GameDataManager.GetRandBossMonsterClass() if MonsterClass then local Location = self:GetSpawnLocation() local Rotation = self:GetSpawnRotation() local Scale = VectorHelper.Scale(1.5) local Monster = UGCGameSystem.SpawnActor(self, MonsterClass, Location, Rotation, Scale, nil) if Monster then table.insert(self.SpawnedMonsters, Monster) Monster:SetOwner(self) Monster:InitMonsterByLevel(GameDataManager.BossIndex) Monster.OnDeath:Add(BP_MonsterSpawner.OnSpawnedMonsterDeath, self) end end end function BP_MonsterSpawner:StartSpawnBreachMonster(RewardWeaponID, InLevel) if self.IsActivate == false or self.Type ~= EMonsterSpawnerType.HangUpRoom then return end if RewardWeaponID == nil or RewardWeaponID <= 0 then return end self.RewardWeaponID = RewardWeaponID local MonsterClass = GameDataManager.GetRandBreachMonsterClass() if MonsterClass then local Location = self:GetSpawnLocation() local Rotation = self:GetSpawnRotation() local Scale = VectorHelper.ScaleOne() local Monster = UGCGameSystem.SpawnActor(self, MonsterClass, Location, Rotation, Scale, nil) if not Monster then return end Monster:SetTargetPlayerKey(self.TargetPlayerKey) Monster:SetOwner(self) Monster.OnDeath:Add(BP_MonsterSpawner.OnSpawnedBreachMonsterDeath, self) local TargetPlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(self.TargetPlayerKey) Monster:InitMonsterByLevel(TargetPlayerState.BreachSuccessTime + 1) TargetPlayerState.BreachInfo.NeedBreach = true TargetPlayerState.BreachInfo.InBreachProgress = true UnrealNetwork.RepLazyProperty(TargetPlayerState, "BreachInfo") if self.BreachLifeSpanHandle ~= nil then EventSystem.StopTimer(self.BreachLifeSpanHandle) end --设置计时器,检查是否突破成功,若未成功,则继续显示突破按钮 self.BreachLifeSpanHandle = EventSystem.SetTimer(self, function() local PlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(self.TargetPlayerKey) if UE.IsValid(PlayerState) then if UE.IsValid(Monster) and Monster:IsAlive() then PlayerState.BreachInfo.NeedBreach = true PlayerState.BreachInfo.InBreachProgress = false UnrealNetwork.RepLazyProperty(PlayerState, "BreachInfo") Monster:K2_DestroyActor() NoticeTipsTools.ServerGeneralNoticeTips(self.TargetPlayerKey, "突破失败,请重新尝试,否则无法继续提升等级", true) end end EventSystem.StopTimer(self.BreachLifeSpanHandle) self.BreachLifeSpanHandle = nil end, 60) end end function BP_MonsterSpawner:StartSpawnChallengeMonster(InChallengeMonsterType, Level) if self.IsActivate == false or self.Type ~= EMonsterSpawnerType.HangUpRoom then return end if InChallengeMonsterType < EMonsterType.ChallengeGold or InChallengeMonsterType > EMonsterType.ChallengeWeaponRecasting then UE.Log("[BP_MonsterSpawner:StartSpawnChallengeMonster] Error: invalid ChallengeMonsterType") return end local MonsterClass = GameDataManager.GetRandMonsterClassByType(InChallengeMonsterType) if MonsterClass then local Location = self:GetSpawnLocation() local Rotation = self:GetSpawnRotation() local Scale = VectorHelper.Scale(1.2) local Monster = UGCGameSystem.SpawnActor(self, MonsterClass, Location, Rotation, Scale, nil) if not Monster then return end Monster:InitMonsterByLevel(Level) Monster:SetTargetPlayerKey(self.TargetPlayerKey) Monster:SetOwner(self) Monster.OnDeath:Add(BP_MonsterSpawner.OnSpawnedChallengeMonsterDeath, self) end end function BP_MonsterSpawner:StartSpawnSeal(InPlayerController) if self.IsActivate == false or self.Type ~= EMonsterSpawnerType.HangUpRoom then return end local MonsterClass = GameDataManager.GetRandMonsterClassByType(EMonsterType.Unseal) if MonsterClass then local Location = self:GetSpawnLocation() local Rotation = self:GetSpawnRotation() local Scale = VectorHelper.ScaleOne() local Monster = UGCGameSystem.SpawnActor( self, MonsterClass, Location, Rotation, Scale, nil) if not Monster then return end Monster:SetTargetPlayerKey(self.TargetPlayerKey) Monster:SetLifeSpan(60) Monster:SetGrowUpLevel(InPlayerController.SealInfo.Times) Monster:SetOwner(self) Monster.OnDeath:Add(BP_MonsterSpawner.OnSealMonsterDeath, self) InPlayerController.SealInfo.IsMonsterAlive = true EventSystem.SetTimer(self, function() local PlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(self.TargetPlayerKey) if UE.IsValid(PlayerState) then local PlayerController = UGCGameSystem.GetPlayerControllerByPlayerKey(self.TargetPlayerKey) if UE.IsValid(Monster) then PlayerController.SealInfo.IsMonsterAlive = false PlayerController.SealInfo.KillCount = 0 NoticeTipsTools.ServerGeneralNoticeTips(self.TargetPlayerKey, "封印失败,请重新尝试,否则无法获取封印奖励", true) UnrealNetwork.CallUnrealRPC(PlayerController, PlayerController, "ClientRPC_SetSealButtonVisible", true) end end end, 61) end end function BP_MonsterSpawner:StartSpawnInherit(InPlayerController, InText, InOldNum) if self.IsActivate == false or self.Type ~= EMonsterSpawnerType.HangUpRoom then return end local MonsterId = 0 if InText == '盔' then MonsterId = 1001 elseif InText == '甲' then MonsterId = 1002 end local Func = function(InNum) return InNum // 10 % 10 end local MonsterClass = UE.LoadClass(Tables.MonsterBaseConfig[MonsterId].Path) if MonsterClass then local Location = self:GetSpawnLocation() local Rotation = self:GetSpawnRotation() local Scale = VectorHelper.ScaleOne() local Monster = UGCGameSystem.SpawnActor( self, MonsterClass, Location, Rotation, Scale, nil) if Monster == nil then return end Monster:SetTargetPlayerKey(self.TargetPlayerKey) Monster:SetLifeSpan(60) Monster:UpdateMonsterLevel(Monster.MonsterName, Func(InOldNum)) Monster:SetOwner(self) Monster.OnDeath:Add(BP_MonsterSpawner.OnInheritDeath, self) self.InheritMonster[InText] = true EventSystem.SetTimer(self, function() local PlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(self.TargetPlayerKey) if UE.IsValid(PlayerState) and UE.IsValid(Monster) then NoticeTipsTools.ServerGeneralNoticeTips(self.TargetPlayerKey, "进阶失败,请重新尝试,否则无法获取奖励", true) UnrealNetwork.CallUnrealRPC(self, self, "ClientRPC_UpdateInherit", self.PlayerKey, 2) end end, 61) end end function BP_MonsterSpawner:GenerateDropPosition() self.DropPositions = {} local OriginPos = self:K2_GetActorLocation() local BoxExtent = self.Box:GetScaledBoxExtent() local NumOfPoints = self.NumberOfPoints local ScalarParam = self.ScalarParam local ActualWidth = math.max(100.0, BoxExtent.X) local GoldenAngle = 1.618 * 2 * 3.141 local LGRad = ActualWidth * 0.45 local SMArea = LGRad * LGRad * 3.141 / NumOfPoints for i = 1, NumOfPoints, 1 do local Angle = i * GoldenAngle local CumArea = i * SMArea local SpiralRad = math.sqrt(CumArea / 3.141) local X = math.cos(Angle) * SpiralRad * ScalarParam local Y = math.sin(Angle) * SpiralRad * ScalarParam local RelativePos = Vector.New(X, Y, 0.0) local TempPos = VectorHelper.Add(OriginPos, RelativePos) table.insert(self.DropPositions, TempPos) end end function BP_MonsterSpawner:GetSpawnLocation() if #self.DropPositions <= 1 then self:GenerateDropPosition() end return table.remove(self.DropPositions, math.random(1, #self.DropPositions)) end function BP_MonsterSpawner:GetSpawnRotation() return Rotator.New(0.0, math.random(-180.0, 180.0), 0.0) end function BP_MonsterSpawner:OnSpawnedMonsterDeath(DeadMonster, KillerController, DamageCauseActor, HitResult, HitImpulseDirection, DamageTypeID, bHeadShotDamage) if self:HasAuthority() then if self:GetSpawnerType() == EMonsterSpawnerType.AttackWave then table.removeValue(self.SpawnedMonsters, DeadMonster) if table.getCount(self.SpawnedMonsters) <= 0 then UGCGameSystem.GameState:UpdateIsInAttackWave(false) end end DeadMonster.OnDeath:Remove(BP_MonsterSpawner.OnSpawnedMonsterDeath, self) end end function BP_MonsterSpawner:OnSpawnedChallengeMonsterDeath(DeadMonster, KillerController, DamageCauseActor, HitResult, HitImpulseDirection, DamageTypeID, bHeadShotDamage) if self:HasAuthority() then local Level = DeadMonster.BossLevel local MonsterType = GameDataManager.GetMonsterTypeByID(DeadMonster.ID) local ChallengeIndex = GameDataManager.GetChallengeInfoIndex(MonsterType) Tables.ChallengeInfo[ChallengeIndex].IncomeFun(KillerController.PlayerKey, Level) DeadMonster.OnDeath:Remove(BP_MonsterSpawner.OnSpawnedChallengeMonsterDeath, self) end end function BP_MonsterSpawner:OnSpawnedBreachMonsterDeath(DeadMonster, KillerController, DamageCauseActor, HitResult, HitImpulseDirection, DamageTypeID, bHeadShotDamage) if self:HasAuthority() then if self.RewardWeaponID == nil or self.RewardWeaponID <= 0 then return end local KillerPlayerKey = KillerController.PlayerKey local KillerPlayerPawn = UGCGameSystem.GetPlayerPawnByPlayerKey(KillerPlayerKey) local KillerPlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(KillerPlayerKey) ---检测击杀者是否是自己(防止别人帮你击杀) if KillerPlayerKey ~= DeadMonster.TargetPlayerKey then local TargetPlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(DeadMonster.TargetPlayerKey) TargetPlayerState.BreachInfo.NeedBreach = true TargetPlayerState.BreachInfo.InBreachProgress = false UnrealNetwork.RepLazyProperty(TargetPlayerState, "BreachInfo") NoticeTipsTools.ServerGeneralNoticeTips(DeadMonster.TargetPlayerKey, "突破失败,请重新尝试,否则无法继续提升等级", true) return end KillerPlayerState.BreachSuccessTime = KillerPlayerState.BreachSuccessTime + 1 KillerPlayerState.BreachInfo.NeedBreach = false KillerPlayerState.BreachInfo.InBreachProgress = false UnrealNetwork.RepLazyProperty(KillerPlayerState, "BreachInfo") KillerPlayerState:OnChangedLevel() if KillerPlayerState.BreachSuccessTime == 1 then local DropPos = VectorHelper.Sub(DeadMonster:K2_GetActorLocation(), {X = 0, Y = 0, Z = DeadMonster.Capsule.CapsuleHalfHeight}) local DropRot = DeadMonster:K2_GetActorRotation() if KillerPlayerState:GetNeedTriggerForceGuide() then local RayItemData = { ItemID = 21240, ItemType = EItemType.SkillBook, Quality = 4, Count = 1, } EventSystem:SendEvent(EventType.SpawnDropItem, DropPos, DropRot, KillerController.PlayerKey, RayItemData) EventSystem:SendEvent(EventType.SpawnDropItem, DropPos, DropRot, KillerController.PlayerKey, GenerateSkillBookData(ESkillType.Active)) else EventSystem:SendEvent(EventType.SpawnDropItem, DropPos, DropRot, KillerController.PlayerKey, GenerateSkillBookData()) EventSystem:SendEvent(EventType.SpawnDropItem, DropPos, DropRot, KillerController.PlayerKey, GenerateSkillBookData()) end end KillerController:ServerRPC_AddWeapon(self.RewardWeaponID, KillerPlayerKey) UnrealNetwork.CallUnrealRPC(KillerController, KillerController, "ClientRPC_TriggerGuide", 19) UnrealNetwork.CallUnrealRPC_Multicast(KillerPlayerPawn, "Client_MulticastRPC_PlayEffect", GameDataManager.GetEffectInstanceId(), 36, KillerPlayerPawn, nil, 0, EEffectSpawnLocationType.Attach) NoticeTipsTools.ServerGeneralNoticeTips(KillerPlayerKey, "已消灭突破怪,等级已解锁,已获得突破武器") DeadMonster.OnDeath:Remove(BP_MonsterSpawner.OnSpawnedBreachMonsterDeath, self) if self.BreachLifeSpanHandle ~= nil then EventSystem.StopTimer(self.BreachLifeSpanHandle) end end end function BP_MonsterSpawner:OnSealMonsterDeath(DeadMonster, KillerController, DamageCauseActor, HitResult, HitImpulseDirection, DamageTypeID, bHeadShotDamage) if self:HasAuthority() then --重置参数 KillerController.SealInfo.IsMonsterAlive = false KillerController.SealInfo.KillCount = 0 local FinalItems = self:RandomSealRewards(KillerController.SealInfo.Times) KillerController.SealInfo.Times = KillerController.SealInfo.Times + 1 UnrealNetwork.CallUnrealRPC(KillerController, KillerController, "ClientRPC_SetSealRewards", FinalItems) end end function BP_MonsterSpawner:RandomSealRewards(InTimes) -- 随机两个 local Items = {} local FinalItems = {} local RewardCount = table.getCount(SealTables.Rewards) for i = 1, RewardCount do table.insert(Items, i) end local Func = function(InIndex) local TheIndex = 1 for i, v in pairs(SealTables.Rewards) do if TheIndex == InIndex then return i end TheIndex = TheIndex + 1 end end for _ = 1, 2 do local FirstIndex = math.random(1, #Items) table.remove(Items, FirstIndex) local Type = Func(Items[FirstIndex]) local Value = InTimes * SealTables.Rewards[Type] if Type == '配件' or Type == '技能书' then Value = InTimes // 5 + 1 end table.insert(FinalItems, { Type = Type, Value = Value }) end return FinalItems end function BP_MonsterSpawner:OnInheritDeath(DeadMonster, KillerController, DamageCauseActor, HitResult, HitImpulseDirection, DamageTypeID, bHeadShotDamage) local OldId local SelectText if DeadMonster.ID == 1001 then SelectText = '盔' elseif DeadMonster.ID == 1002 then SelectText = '甲' end OldId = KillerController.PlayerState.InheritItems[SelectText] -- 给予奖励 local PlayerState = UGCGameSystem.GetPlayerStateByPlayerKey(DeadMonster.TargetPlayerKey) local NextId = InheritTable.GetNextId(OldId) local Benefit = InheritTable.Items[OldId].Benefit for i = 1, #Benefit do PlayerState:AddAttribute(Benefit[i].Type, Benefit[i].Value, true) end PlayerState.InheritItems[SelectText] = NextId ArchiveTable.Funcs[ArchiveTable.ArchiveType.EasterEggs](self, 14) local PC = UGCGameSystem.GetPlayerControllerByPlayerKey(PlayerState.PlayerKey) UnrealNetwork.CallUnrealRPC(PC, PC, "ClientRPC_UpdateInherit", DeadMonster.TargetPlayerKey, 0) end function BP_MonsterSpawner:UpdateHangupRoomMonsterLevel(Level) local HangupRoomMonsterClass = UE.LoadClass(BPClassPath.HangupRoomMonster) for _, Monster in pairs(self.SpawnedMonsters) do if UE.IsA(Monster, HangupRoomMonsterClass) then Monster:InitMonsterByLevel(Level) end end end function BP_MonsterSpawner:CleanMonsterRefByIndex(SpawnIndex) if self.Type ~= EMonsterSpawnerType.HangUpRoom or SpawnIndex == nil or SpawnIndex <= 0 then return end self.SpawnedMonsters[SpawnIndex] = nil end function BP_MonsterSpawner:CheckSpecialDropForMonster(MonsterType, DropPos, DropRot, KillerPlayerKey) local GameDifficulty = UGCGameSystem.GameState.GameDifficulty if GameDifficulty < 3 or GameDifficulty > 10 then return end if self.RandDropRayDeadCount == nil then self.RandDropRayDeadCount = math.random(1, 3) end if self.HangupRoomMonsterDeadCount == nil then self.HangupRoomMonsterDeadCount = 0 end if self.HasDroppedRay == nil then self.HasDroppedRay = false end if self.HasDroppedRay == true then return end if MonsterType == EMonsterType.HangupRoom then self.HangupRoomMonsterDeadCount = self.HangupRoomMonsterDeadCount + 1 if self.HangupRoomMonsterDeadCount == self.RandDropRayDeadCount then UE.Log("[BP_MonsterSpawner][CheckSpecialDropForMonster] HangupRoom Drop") local RayItemData = {ItemID = 21240, ItemType = EItemType.SkillBook, Quality = 4, Count = 1,} EventSystem:SendEvent(EventType.SpawnDropItem, DropPos, DropRot, KillerPlayerKey, RayItemData) self.HasDroppedRay = true end end end return BP_MonsterSpawner;