From 99603c5bfccd631d27b45edfa067973275a26f92 Mon Sep 17 00:00:00 2001 From: pluviolithic Date: Wed, 15 Nov 2023 09:34:48 -0500 Subject: [PATCH 1/6] Add animation handlers --- .../Combat/Enemies/ApplyEnemyAnimations.lua | 34 +++++++++++++++ .../Combat/Enemies/ApplyPlayerAnimations.lua | 35 ++++++++++++++++ src/server/Combat/Enemies/init.lua | 1 + src/shared/Utils/AnimationUtils.lua | 41 +++++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 src/server/Combat/Enemies/ApplyEnemyAnimations.lua create mode 100644 src/server/Combat/Enemies/ApplyPlayerAnimations.lua create mode 100644 src/server/Combat/Enemies/init.lua create mode 100644 src/shared/Utils/AnimationUtils.lua diff --git a/src/server/Combat/Enemies/ApplyEnemyAnimations.lua b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua new file mode 100644 index 0000000..4dfffd1 --- /dev/null +++ b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua @@ -0,0 +1,34 @@ +local ReplicatedStorage = game:GetService "ReplicatedStorage" +local CollectionService = game:GetService "CollectionService" + +local bossAttackSpeed = ReplicatedStorage.Config.Combat.BossAttackSpeed.Value +local enemyAttackSpeed = ReplicatedStorage.Config.Combat.EnemyAttackSpeed.Value + +local animationUtilities = require(ReplicatedStorage.Common.Utils.AnimationUtils) + +return function(enemy, janitor) + local attackDelay = if CollectionService:HasTag(enemy, "Boss") then bossAttackSpeed else enemyAttackSpeed + local runAnimations = true + + local animationInstances = + animationUtilities.filterAndSortAnimationInstances(enemy.Configuration.AttackAnims:GetChildren()) + local currentIndex, animationTrack = 0, nil + + while runAnimations do + currentIndex, animationTrack = + animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) + animationTrack.Priority = Enum.AnimationPriority.Action + + animationTrack:Play() + animationTrack.Stopped:Wait() + animationTrack:Destroy() + task.wait(attackDelay) + end + + janitor:Add(function() + runAnimations = false + if animationTrack.IsPlaying then + animationTrack:Stop() + end + end, true) +end diff --git a/src/server/Combat/Enemies/ApplyPlayerAnimations.lua b/src/server/Combat/Enemies/ApplyPlayerAnimations.lua new file mode 100644 index 0000000..5bdcf83 --- /dev/null +++ b/src/server/Combat/Enemies/ApplyPlayerAnimations.lua @@ -0,0 +1,35 @@ +local ReplicatedStorage = game:GetService "ReplicatedStorage" +local ServerScriptService = game:GetService "ServerScriptService" + +local combatAnimations = ReplicatedStorage.CombatAnimations + +local store = require(ServerScriptService.Server.State.Store) +local selectors = require(ReplicatedStorage.Common.State.selectors) +local animationUtilities = require(ReplicatedStorage.Common.Utils.AnimationUtils) + +return function(player, janitor) + local runAnimations = true + + local currentIndex, animationTrack = 0, nil + local animationInstances = animationUtilities.filterAndSortAnimationInstances( + combatAnimations[selectors.getEquippedWeapon(store:getState(), player.Name)]:GetChildren() + ) + + while runAnimations do + currentIndex, animationTrack = + animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) + animationTrack.Priority = Enum.AnimationPriority.Action + + animationTrack:Play() + animationTrack.Stopped:Wait() + animationTrack:Destroy() + task.wait(animationUtilities.getPlayerAttackSpeed(player)) + end + + janitor:Add(function() + runAnimations = false + if animationTrack.IsPlaying then + animationTrack:Stop() + end + end, true) +end diff --git a/src/server/Combat/Enemies/init.lua b/src/server/Combat/Enemies/init.lua new file mode 100644 index 0000000..61c7ebf --- /dev/null +++ b/src/server/Combat/Enemies/init.lua @@ -0,0 +1 @@ +return 0 diff --git a/src/shared/Utils/AnimationUtils.lua b/src/shared/Utils/AnimationUtils.lua new file mode 100644 index 0000000..e8dd021 --- /dev/null +++ b/src/shared/Utils/AnimationUtils.lua @@ -0,0 +1,41 @@ +local ReplicatedStorage = game:GetService "ReplicatedStorage" +local ServerScriptService = game:GetService "ServerScriptService" + +local store = require(ServerScriptService.Server.State.Store) +local selectors = require(ReplicatedStorage.Common.State.selectors) + +local playerAttackSpeed = ReplicatedStorage.Config.Combat.PlayerAttackSpeed.Value +local doubleAttackSpeedID = tostring(ReplicatedStorage.Config.GamepassData.IDs["2xAttackSpeed"].Value) + +local animationUtilities +animationUtilities = { + removeIdleFromAnimationList = function(animationInstances) + for i, animationInstance in animationInstances do + if animationInstance.Name == "Idle" then + table.remove(animationInstances, i) + break + end + end + end, + sortAnimationListByName = function(animationInstances) + table.sort(animationInstances, function(a, b) + return tonumber(a.Name:match "%d+") < tonumber(b.Name:match "%d+") + end) + end, + filterAndSortAnimationInstances = function(animationInstances) + animationUtilities.removeIdleFromAnimationList(animationInstances) + animationUtilities.sortAnimationListByName(animationInstances) + return animationInstances + end, + getNextIndexAndAnimationTrack = function(animationInstances, currentIndex) + currentIndex = (currentIndex % #animationInstances) + 1 + return currentIndex, animationInstances[currentIndex]:Clone() + end, + getPlayerAttackSpeed = function(player) + return if selectors.hasGamepass(store:getState(), player.Name, doubleAttackSpeedID) + then playerAttackSpeed / 2 + else playerAttackSpeed + end, +} + +return animationUtilities From 26bcdd1234b2bedbf0ede2323d3d75f56ff2d310 Mon Sep 17 00:00:00 2001 From: pluviolithic Date: Wed, 15 Nov 2023 11:46:55 -0500 Subject: [PATCH 2/6] Add further structure for refactor --- .../Combat/Enemies/ApplyDamageToEnemy.lua | 38 +++++++++++ .../Combat/Enemies/ApplyDamageToPlayers.lua | 38 +++++++++++ .../Combat/Enemies/ApplyEnemyAnimations.lua | 20 +++--- .../Combat/Enemies/ApplyPlayerAnimations.lua | 20 +++--- src/server/Combat/Enemies/init.lua | 67 +++++++++++++++++++ src/shared/Utils/AnimationUtils.lua | 14 +++- 6 files changed, 176 insertions(+), 21 deletions(-) create mode 100644 src/server/Combat/Enemies/ApplyDamageToEnemy.lua create mode 100644 src/server/Combat/Enemies/ApplyDamageToPlayers.lua diff --git a/src/server/Combat/Enemies/ApplyDamageToEnemy.lua b/src/server/Combat/Enemies/ApplyDamageToEnemy.lua new file mode 100644 index 0000000..b2c8a49 --- /dev/null +++ b/src/server/Combat/Enemies/ApplyDamageToEnemy.lua @@ -0,0 +1,38 @@ +local ReplicatedStorage = game:GetService "ReplicatedStorage" +local CollectionService = game:GetService "CollectionService" +local ServerScriptService = game:GetService "ServerScriptService" + +local store = require(ServerScriptService.Server.State.Store) +local actions = require(ServerScriptService.Server.State.Actions) +local selectors = require(ReplicatedStorage.Common.State.selectors) +local animationUtilities = require(ReplicatedStorage.Common.Utils.AnimationUtils) + +local weapons = ReplicatedStorage.Weapons + +local function canAttack(player, enemy, info) + local fightRange = enemy.Configuration.FightRange.Value + local enemyRootPosition = enemy.RootPart.Position + + return info.HealthValue.Value > 0 + and selectors.isPlayerLoaded(store:getState(), player.Name) + and selectors.getCurrentTarget(store:getState(), player.Name) == enemy + and player:DistanceFromCharacter(enemyRootPosition) <= fightRange + 10 +end + +return function(player, enemy, info, janitor) + local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) + local damageMultiplier = if weaponName == "Fists" then 1 else weapons[weaponName].Damage.Value + task.spawn(function() + while canAttack(player, enemy, info) do + local damageToDeal = math.clamp( + selectors.getStat(store:getState(), player.Name, "Strength") * damageMultiplier, + 0, + info.HealthValue.Value + ) + info.DamageDealtByPlayer[player] = (info.DamageDealtByPlayer[player] or 0) + damageToDeal + info.HealthValue.Value -= damageToDeal + task.wait(animationUtilities.getPlayerAttackSpeed(player)) + end + end) + janitor:Add(function() end, true) +end diff --git a/src/server/Combat/Enemies/ApplyDamageToPlayers.lua b/src/server/Combat/Enemies/ApplyDamageToPlayers.lua new file mode 100644 index 0000000..86ae327 --- /dev/null +++ b/src/server/Combat/Enemies/ApplyDamageToPlayers.lua @@ -0,0 +1,38 @@ +local ReplicatedStorage = game:GetService "ReplicatedStorage" +local CollectionService = game:GetService "CollectionService" +local ServerScriptService = game:GetService "ServerScriptService" + +local bossAttackSpeed = ReplicatedStorage.Config.Combat.BossAttackSpeed.Value +local enemyAttackSpeed = ReplicatedStorage.Config.Combat.EnemyAttackSpeed.Value + +local store = require(ServerScriptService.Server.State.Store) +local actions = require(ServerScriptService.Server.State.Actions) +local selectors = require(ReplicatedStorage.Common.State.selectors) + +return function(enemy, engagedPlayers, janitor) + local attackDelay = if CollectionService:HasTag(enemy, "Boss") then bossAttackSpeed else enemyAttackSpeed + local damagePlayers = true + + task.spawn(function() + while damagePlayers do + for _, player in engagedPlayers do + local fearMeterGoal = math.min( + selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + + enemy.Configuration.Damage.Value, + selectors.getStat(store:getState(), player.Name, "MaxFearMeter") + ) + local fearMeterAddendum = fearMeterGoal + - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + + if fearMeterAddendum ~= 0 then + store:dispatch(actions.incrementPlayerStat(player.Name, "CurrentFearMeter", fearMeterAddendum)) + end + end + task.wait(attackDelay) + end + end) + + janitor:Add(function() + damagePlayers = false + end, true) +end diff --git a/src/server/Combat/Enemies/ApplyEnemyAnimations.lua b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua index 4dfffd1..93d9131 100644 --- a/src/server/Combat/Enemies/ApplyEnemyAnimations.lua +++ b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua @@ -14,16 +14,18 @@ return function(enemy, janitor) animationUtilities.filterAndSortAnimationInstances(enemy.Configuration.AttackAnims:GetChildren()) local currentIndex, animationTrack = 0, nil - while runAnimations do - currentIndex, animationTrack = - animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) - animationTrack.Priority = Enum.AnimationPriority.Action + task.spawn(function() + while runAnimations do + currentIndex, animationTrack = + animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) + animationTrack.Priority = Enum.AnimationPriority.Action - animationTrack:Play() - animationTrack.Stopped:Wait() - animationTrack:Destroy() - task.wait(attackDelay) - end + animationTrack:Play() + animationTrack.Stopped:Wait() + animationTrack:Destroy() + task.wait(attackDelay) + end + end) janitor:Add(function() runAnimations = false diff --git a/src/server/Combat/Enemies/ApplyPlayerAnimations.lua b/src/server/Combat/Enemies/ApplyPlayerAnimations.lua index 5bdcf83..8bc25d6 100644 --- a/src/server/Combat/Enemies/ApplyPlayerAnimations.lua +++ b/src/server/Combat/Enemies/ApplyPlayerAnimations.lua @@ -15,16 +15,18 @@ return function(player, janitor) combatAnimations[selectors.getEquippedWeapon(store:getState(), player.Name)]:GetChildren() ) - while runAnimations do - currentIndex, animationTrack = - animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) - animationTrack.Priority = Enum.AnimationPriority.Action + task.spawn(function() + while runAnimations do + currentIndex, animationTrack = + animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) + animationTrack.Priority = Enum.AnimationPriority.Action - animationTrack:Play() - animationTrack.Stopped:Wait() - animationTrack:Destroy() - task.wait(animationUtilities.getPlayerAttackSpeed(player)) - end + animationTrack:Play() + animationTrack.Stopped:Wait() + animationTrack:Destroy() + task.wait(animationUtilities.getPlayerAttackSpeed(player)) + end + end) janitor:Add(function() runAnimations = false diff --git a/src/server/Combat/Enemies/init.lua b/src/server/Combat/Enemies/init.lua index 61c7ebf..9e287e1 100644 --- a/src/server/Combat/Enemies/init.lua +++ b/src/server/Combat/Enemies/init.lua @@ -1 +1,68 @@ +local CollectionService = game:GetService "CollectionService" +local ReplicatedStorage = game:GetService "ReplicatedStorage" +local ServerScriptService = game:GetService "ServerScriptService" + +local applyDamageToEnemy = require(script.ApplyDamageToEnemy) +local store = require(ServerScriptService.Server.State.Store) +local Janitor = require(ReplicatedStorage.Common.lib.Janitor) +local actions = require(ServerScriptService.Server.State.Actions) +local applyDamageToPlayers = require(script.ApplyDamageToPlayers) +local applyEnemyAnimations = require(script.ApplyEnemyAnimations) +local applyPlayerAnimations = require(script.ApplyPlayerAnimations) +local selectors = require(ReplicatedStorage.Common.State.selectors) + +local function handleEnemy(enemy) + local info = { + HealthValue = enemy.Configuration.FearHealth, + MaxHealth = enemy.Configuration.FearHealth.Value, + DamageDealtByPlayer = {}, + } + local debounces = {} + local enemyJanitor = Janitor.new() + + enemyJanitor:Add(enemy) + + enemy.Hitbox.ClickDetector.MouseClick:Connect(function(player) + local humanoid = player.Character and player.Character:FindFirstChild "Humanoid" + if debounces[player] or not humanoid then + return + end + + debounces[player] = true + task.delay(1, function() + debounces[player] = nil + end) + + if + selectors.getCurrentTarget(store:getState(), player.Name) == enemy + or CollectionService:HasTag(selectors.getCurrentTarget(store:getState(), player.Name), "PunchingBag") + then + return + end + + local playerJanitor = Janitor.new() + store:dispatch(actions.switchPlayerEnemy(player.Name, enemy)) + + playerJanitor:Add(store.changed:connect(function(newState) + if selectors.getCurrentTarget(newState, player.Name) ~= enemy then + playerJanitor:Destroy() + end + end, "disconnect")) + end) +end + +for _, enemy in CollectionService:GetTagged "Enemy" do + local success, error = pcall(handleEnemy, enemy) + if not success then + warn(error) + end +end + +CollectionService:GetInstanceAddedSignal("Enemy"):Connect(function(enemy) + local success, error = pcall(handleEnemy, enemy) + if not success then + warn(error) + end +end) + return 0 diff --git a/src/shared/Utils/AnimationUtils.lua b/src/shared/Utils/AnimationUtils.lua index e8dd021..10613b2 100644 --- a/src/shared/Utils/AnimationUtils.lua +++ b/src/shared/Utils/AnimationUtils.lua @@ -32,9 +32,17 @@ animationUtilities = { return currentIndex, animationInstances[currentIndex]:Clone() end, getPlayerAttackSpeed = function(player) - return if selectors.hasGamepass(store:getState(), player.Name, doubleAttackSpeedID) - then playerAttackSpeed / 2 - else playerAttackSpeed + local multiplier = 1 + if selectors.hasGamepass(store:getState(), player.Name, doubleAttackSpeedID) then + multiplier /= 2 + end + if + selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + == selectors.getStat(store:getState(), player.Name, "MaxFearMeter") + then + multiplier *= 2 + end + return playerAttackSpeed * multiplier end, } From e3784135e40eea07b3e6694130dd14b7c7d1a74f Mon Sep 17 00:00:00 2001 From: pluviolithic Date: Wed, 15 Nov 2023 13:24:49 -0500 Subject: [PATCH 3/6] Complete initial refactor version --- .../Soundscape/PrimaryRegionSelector.lua | 10 +- .../Soundscape/SoundInstanceHandler.lua | 6 +- .../Combat/Enemies/ApplyDamageToEnemy.lua | 13 ++- .../Combat/Enemies/ApplyDamageToPlayers.lua | 7 +- .../Combat/Enemies/ApplyEnemyAnimations.lua | 7 +- src/server/Combat/Enemies/init.lua | 97 +++++++++++++++++++ .../AddWeaponAccessoryMiddleware.lua | 51 +++++----- 7 files changed, 160 insertions(+), 31 deletions(-) diff --git a/src/client/GameAtmosphere/Soundscape/PrimaryRegionSelector.lua b/src/client/GameAtmosphere/Soundscape/PrimaryRegionSelector.lua index 08bbf6d..3a3f275 100644 --- a/src/client/GameAtmosphere/Soundscape/PrimaryRegionSelector.lua +++ b/src/client/GameAtmosphere/Soundscape/PrimaryRegionSelector.lua @@ -24,9 +24,13 @@ store.changed:connect(function(newState, oldState) -- verify whether this actually does anything if - Sift.Array.equals( - Sift.Dictionary.keys(oldSoundRegions.OccupiedSoundRegions), - Sift.Dictionary.keys(newSoundRegions.OccupiedSoundRegions) + not newSoundRegions + or ( + oldSoundRegions + and Sift.Array.equals( + Sift.Dictionary.keys(oldSoundRegions.OccupiedSoundRegions), + Sift.Dictionary.keys(newSoundRegions.OccupiedSoundRegions) + ) ) then return diff --git a/src/client/GameAtmosphere/Soundscape/SoundInstanceHandler.lua b/src/client/GameAtmosphere/Soundscape/SoundInstanceHandler.lua index e15b9b5..23e6e25 100644 --- a/src/client/GameAtmosphere/Soundscape/SoundInstanceHandler.lua +++ b/src/client/GameAtmosphere/Soundscape/SoundInstanceHandler.lua @@ -46,7 +46,11 @@ for _, playlistFolder in playlists:GetChildren() do volumeKnobs.on[playlistFolder.Name] = createKnob(nextAudioInstance, true) volumeKnobs.off[playlistFolder.Name] = createKnob(nextAudioInstance, false) - if selectors.getAudioData(store:getState(), player.Name).PrimarySoundRegion ~= playlistFolder.Name then + if + not selectors.isPlayerLoaded(store:getState(), player.Name) + or selectors.getAudioData(store:getState(), player.Name).PrimarySoundRegion + ~= playlistFolder.Name + then nextAudioInstance.Volume = 0 end diff --git a/src/server/Combat/Enemies/ApplyDamageToEnemy.lua b/src/server/Combat/Enemies/ApplyDamageToEnemy.lua index b2c8a49..031732f 100644 --- a/src/server/Combat/Enemies/ApplyDamageToEnemy.lua +++ b/src/server/Combat/Enemies/ApplyDamageToEnemy.lua @@ -1,5 +1,4 @@ local ReplicatedStorage = game:GetService "ReplicatedStorage" -local CollectionService = game:GetService "CollectionService" local ServerScriptService = game:GetService "ServerScriptService" local store = require(ServerScriptService.Server.State.Store) @@ -22,6 +21,14 @@ end return function(player, enemy, info, janitor) local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) local damageMultiplier = if weaponName == "Fists" then 1 else weapons[weaponName].Damage.Value + + store:dispatch(actions.combatBegan(player.Name)) + + if weaponName ~= "Fists" then + local weaponAccessory = weapons[weaponName]:Clone() + player.Character.Humanoid:AddAccessory(weaponAccessory) + end + task.spawn(function() while canAttack(player, enemy, info) do local damageToDeal = math.clamp( @@ -34,5 +41,7 @@ return function(player, enemy, info, janitor) task.wait(animationUtilities.getPlayerAttackSpeed(player)) end end) - janitor:Add(function() end, true) + janitor:Add(function() + info.EngagedPlayers[player] = nil + end, true) end diff --git a/src/server/Combat/Enemies/ApplyDamageToPlayers.lua b/src/server/Combat/Enemies/ApplyDamageToPlayers.lua index 86ae327..16c647b 100644 --- a/src/server/Combat/Enemies/ApplyDamageToPlayers.lua +++ b/src/server/Combat/Enemies/ApplyDamageToPlayers.lua @@ -9,7 +9,12 @@ local store = require(ServerScriptService.Server.State.Store) local actions = require(ServerScriptService.Server.State.Actions) local selectors = require(ReplicatedStorage.Common.State.selectors) -return function(enemy, engagedPlayers, janitor) +return function(enemy, engagedPlayers, info, janitor) + if info.Active then + return + end + info.Active = true + local attackDelay = if CollectionService:HasTag(enemy, "Boss") then bossAttackSpeed else enemyAttackSpeed local damagePlayers = true diff --git a/src/server/Combat/Enemies/ApplyEnemyAnimations.lua b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua index 93d9131..44a3802 100644 --- a/src/server/Combat/Enemies/ApplyEnemyAnimations.lua +++ b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua @@ -6,7 +6,12 @@ local enemyAttackSpeed = ReplicatedStorage.Config.Combat.EnemyAttackSpeed.Value local animationUtilities = require(ReplicatedStorage.Common.Utils.AnimationUtils) -return function(enemy, janitor) +return function(enemy, info, janitor) + if info.Active then + return + end + info.Active = true + local attackDelay = if CollectionService:HasTag(enemy, "Boss") then bossAttackSpeed else enemyAttackSpeed local runAnimations = true diff --git a/src/server/Combat/Enemies/init.lua b/src/server/Combat/Enemies/init.lua index 9e287e1..e24315d 100644 --- a/src/server/Combat/Enemies/init.lua +++ b/src/server/Combat/Enemies/init.lua @@ -1,3 +1,4 @@ +local Players = game:GetService "Players" local CollectionService = game:GetService "CollectionService" local ReplicatedStorage = game:GetService "ReplicatedStorage" local ServerScriptService = game:GetService "ServerScriptService" @@ -10,17 +11,80 @@ local applyDamageToPlayers = require(script.ApplyDamageToPlayers) local applyEnemyAnimations = require(script.ApplyEnemyAnimations) local applyPlayerAnimations = require(script.ApplyPlayerAnimations) local selectors = require(ReplicatedStorage.Common.State.selectors) +local HealthBar = require(ReplicatedStorage.Common.Utils.HealthBar) + +local bossRespawnRate = ReplicatedStorage.Config.Combat.BossRespawnRate.Value +local enemyRespawnRate = ReplicatedStorage.Config.Combat.EnemyRespawnRate.Value + +local function addHealthBar(enemy, info) + local NPCUI = enemy:FindFirstChild("NPCUI", true) + HealthBar.new(NPCUI.Frame.Background.Frame):connect(enemy) + NPCUI:FindFirstChild("NPCName", true).Text = enemy.Name + info.HealthValue.Value = info.MaxHealth + NPCUI.Enabled = true +end + +local function orientPlayer(player, rootPart) + local enemyDirection = rootPart.Position * Vector3.new(1, 0, 1) + local playerRootPart = player.Character and player.Character:FindFirstChild "HumanoidRootPart" + local playerPosition = if playerRootPart then playerRootPart.Position else Vector3.new(0, 0, 0) + + if player.Character then + player.Character:PivotTo( + CFrame.lookAt(playerPosition, enemyDirection + playerPosition.Y * Vector3.new(0, 1, 0)) + ) + end +end + +local function orientEnemy(rootPart, playerPosition) + rootPart.CFrame = CFrame.lookAt( + rootPart.Position, + playerPosition * Vector3.new(1, 0, 1) + rootPart.Position.Y * Vector3.new(0, 1, 0) + ) +end local function handleEnemy(enemy) local info = { + Active = false, HealthValue = enemy.Configuration.FearHealth, MaxHealth = enemy.Configuration.FearHealth.Value, DamageDealtByPlayer = {}, + EngagedPlayers = {}, } + + local isBoss = CollectionService:HasTag(enemy, "Boss") + local fightRange = enemy.Configuration.FightRange.Value + local respawnRate = if isBoss then bossRespawnRate else enemyRespawnRate + local rootPart = if enemy.Humanoid.RootPart then enemy.Humanoid.RootPart else enemy:FindFirstChild "RootPart" + local debounces = {} + local enemyClone = enemy:Clone() local enemyJanitor = Janitor.new() enemyJanitor:Add(enemy) + enemyJanitor:Add(info.HealthValue.Value:GetPropertyChangedSignal("Value"):Connect(function() + if info.HealthValue <= 0 then + enemyJanitor:Destroy() + for player, damage in info.DamageDealtByPlayer do + if not Players:FindFirstChild(player.Name) then + continue + end + + store:dispatch(actions.incrementPlayerStat(player.Name, "Fear", damage, enemy.Name)) + store:dispatch(actions.incrementPlayerStat(player.Name, "Kills")) + store:dispatch(actions.logKilledEnemyType(player.Name, enemy.Name)) + + if damage >= info.MaxHealth * 0.3 then + store:dispatch( + actions.incrementPlayerStat(player.Name, "Gems", enemy.Configuration.Gems.Value, enemy.Name) + ) + end + end + task.wait(respawnRate) + enemyClone.Parent = workspace + end + end)) + addHealthBar(enemy, info) enemy.Hitbox.ClickDetector.MouseClick:Connect(function(player) local humanoid = player.Character and player.Character:FindFirstChild "Humanoid" @@ -48,6 +112,39 @@ local function handleEnemy(enemy) playerJanitor:Destroy() end end, "disconnect")) + + repeat + task.wait(0.1) + until player:DistanceFromCharacter(rootPart.Position) <= fightRange + 5 + or not Janitor.Is(playerJanitor) + or not selectors.isPlayerLoaded(store:getState(), player.Name) + + if + not Janitor.Is(playerJanitor) + or (player:DistanceFromCharacter(rootPart.Position) > fightRange + 10) + or not selectors.isPlayerLoaded(store:getState(), player.Name) + then + return + end + + orientPlayer(player, rootPart) + table.insert(info.EngagedPlayers, player) + + if #info.EngagedPlayers == 1 and not isBoss then + orientEnemy(rootPart, player.Character.HumanoidRootPart.Position) + end + + playerJanitor:Add(function() + table.remove(info.EngagedPlayers, table.find(info.EngagedPlayers, player)) + if #info.EngagedPlayers == 1 and not isBoss then + orientEnemy(rootPart, player.Character.HumanoidRootPart.Position) + end + end, true) + + applyEnemyAnimations(enemy, info, enemyJanitor) + applyPlayerAnimations(player, playerJanitor) + applyDamageToPlayers(enemy, info, enemyJanitor) + applyDamageToEnemy(player, enemy, info, playerJanitor) end) end diff --git a/src/server/State/Middleware/AddWeaponAccessoryMiddleware.lua b/src/server/State/Middleware/AddWeaponAccessoryMiddleware.lua index 49da638..1c4b89e 100644 --- a/src/server/State/Middleware/AddWeaponAccessoryMiddleware.lua +++ b/src/server/State/Middleware/AddWeaponAccessoryMiddleware.lua @@ -24,6 +24,33 @@ local function findFirstChildWithTag(parent: Instance?, tag: string, recursive: return nil end +local function modifyAccessories(player, action, equippedWeaponAccessory) + local character = player.Character or player.CharacterAdded:Wait() + local humanoid = character:WaitForChild "Humanoid" + if action.type == "combatBegan" or action.type == "unequipWeapon" then + local oldEquippedWeaponAccessory = findFirstChildWithTag(player.Character, "WeaponAccessory") + if oldEquippedWeaponAccessory then + oldEquippedWeaponAccessory:Destroy() + end + elseif action.type == "switchPlayerEnemy" then + if not findFirstChildWithTag(player.Character, "WeaponAccessory") then + humanoid:AddAccessory(equippedWeaponAccessory:Clone()) + end + elseif action.type == "equipWeapon" then + local oldEquippedWeaponAccessory = findFirstChildWithTag(player.Character, "WeaponAccessory") + if oldEquippedWeaponAccessory then + oldEquippedWeaponAccessory:Destroy() + end + if equippedWeaponAccessory then + humanoid:AddAccessory(equippedWeaponAccessory:Clone()) + end + elseif action.type == "addPlayer" then + if action.profileData.WeaponData.EquippedWeapon ~= "Fists" then + humanoid:AddAccessory(equippedWeaponAccessory:Clone()) + end + end +end + return function(nextDispatch, store) return function(action) if @@ -36,7 +63,6 @@ return function(nextDispatch, store) end local player = Players[action.playerName] - local humanoid = player.Character and player.Character:FindFirstChild "Humanoid" local equippedWeapon = "Fists" if action.profileData then @@ -58,28 +84,7 @@ return function(nextDispatch, store) return end - if action.type == "combatBegan" or action.type == "unequipWeapon" then - local oldEquippedWeaponAccessory = findFirstChildWithTag(player.Character, "WeaponAccessory") - if oldEquippedWeaponAccessory then - oldEquippedWeaponAccessory:Destroy() - end - elseif action.type == "switchPlayerEnemy" then - if not findFirstChildWithTag(player.Character, "WeaponAccessory") then - humanoid:AddAccessory(equippedWeaponAccessory:Clone()) - end - elseif action.type == "equipWeapon" then - local oldEquippedWeaponAccessory = findFirstChildWithTag(player.Character, "WeaponAccessory") - if oldEquippedWeaponAccessory then - oldEquippedWeaponAccessory:Destroy() - end - if equippedWeaponAccessory then - humanoid:AddAccessory(equippedWeaponAccessory:Clone()) - end - elseif action.type == "addPlayer" then - if action.profileData.WeaponData.EquippedWeapon ~= "Fists" then - humanoid:AddAccessory(equippedWeaponAccessory:Clone()) - end - end + task.spawn(modifyAccessories, player, action, equippedWeaponAccessory) nextDispatch(action) end end From 41056f0d1c373d417de717f654293915f1c351b8 Mon Sep 17 00:00:00 2001 From: pluviolithic Date: Wed, 15 Nov 2023 13:25:24 -0500 Subject: [PATCH 4/6] Comment out old system for testing --- src/server/Combat/Enemy.lua | 830 ++++++++++++++++++------------------ 1 file changed, 415 insertions(+), 415 deletions(-) diff --git a/src/server/Combat/Enemy.lua b/src/server/Combat/Enemy.lua index 7f29b29..c238049 100644 --- a/src/server/Combat/Enemy.lua +++ b/src/server/Combat/Enemy.lua @@ -1,415 +1,415 @@ -local Players = game:GetService "Players" -local CollectionService = game:GetService "CollectionService" -local ReplicatedStorage = game:GetService "ReplicatedStorage" -local ServerScriptService = game:GetService "ServerScriptService" - -local server = ServerScriptService.Server -local animations = ReplicatedStorage.CombatAnimations -local gemRewardPercentage = 0.3 - -local store = require(server.State.Store) -local actions = require(server.State.Actions) -local HealthBar = require(ReplicatedStorage.Common.Utils.HealthBar) -local selectors = require(ReplicatedStorage.Common.State.selectors) - -local bossRespawnRate = ReplicatedStorage.Config.Combat.BossRespawnRate.Value -local enemyRespawnRate = ReplicatedStorage.Config.Combat.EnemyRespawnRate.Value -local bossAttackSpeed = ReplicatedStorage.Config.Combat.BossAttackSpeed.Value -local enemyAttackSpeed = ReplicatedStorage.Config.Combat.EnemyAttackSpeed.Value -local playerAttackSpeed = ReplicatedStorage.Config.Combat.PlayerAttackSpeed.Value -local doubleAttackSpeedID = tostring(ReplicatedStorage.Config.GamepassData.IDs["2xAttackSpeed"].Value) - -local weapons = ReplicatedStorage.Weapons - -local function getSortedAnimationInstances(animationInstances) - for i, animationInstance in animationInstances do - if animationInstance.Name == "Idle" then - table.remove(animationInstances, i) - break - end - end - table.sort(animationInstances, function(a, b) - return tonumber(a.Name:match "%d+") < tonumber(b.Name:match "%d+") - end) - return animationInstances -end - -local function getPlayerAttackSpeed(player) - return if selectors.hasGamepass(store:getState(), player.Name, doubleAttackSpeedID) - then playerAttackSpeed / 2 - else playerAttackSpeed -end - -local function handleEnemy(enemy) - local clickDetector = enemy.Hitbox.ClickDetector - local goalPosition = enemy.Hitbox.Position - local enemyHumanoid = enemy.Humanoid - local maxHealth = enemy.Configuration.FearHealth.Value - - local NPCUI = enemy:FindFirstChild("NPCUI", true) - local healthValue = enemy.Configuration.FearHealth - local damageValue = enemy.Configuration.Damage - local fightRange = enemy.Configuration.FightRange.Value - local gemAmountToDrop = enemy.Configuration.Gems.Value - local idleAnimationInstance = if enemy.Configuration:FindFirstChild "IdleAnim" - then enemy.Configuration.IdleAnim.Anim - else nil - - local runEnemyAnimations = false - local attackAnimations = getSortedAnimationInstances(enemy.Configuration.AttackAnims:GetChildren()) - local currentAttackAnimation = attackAnimations[math.random(#attackAnimations)]:Clone() - local attackTrack = enemyHumanoid:LoadAnimation(currentAttackAnimation) - - local debounceTable = {} - local engagedPlayers = {} - local damageDealtByPlayer = {} - - local lastInCombat = -1 - local targetPlayer = nil - local resetBegan = false - local totalDamageDealt = 0 - local enemyClone = enemy:Clone() - local isBoss = CollectionService:HasTag(enemy, "Boss") - local respawnRate = if isBoss then bossRespawnRate else enemyRespawnRate - local rootPart = if enemyHumanoid.RootPart then enemyHumanoid.RootPart else enemy:FindFirstChild "RootPart" - - if not rootPart then - error "Failed to find a root part for the provided enemy." - return - end - - HealthBar.new(NPCUI.Frame.Background.Frame):connect(enemy) - NPCUI:FindFirstChild("NPCName", true).Text = enemy.Name - healthValue.Value = maxHealth - NPCUI.Enabled = true - - task.spawn(function() - while enemy:IsDescendantOf(game) do - if os.time() - lastInCombat < 5 then - task.wait(1) - continue - end - local foundPlayerInRange = false - for _, player in Players:GetPlayers() do - if player:DistanceFromCharacter(goalPosition) <= fightRange + 5 then - foundPlayerInRange = true - break - end - end - if not foundPlayerInRange then - totalDamageDealt = 0 - healthValue.Value = maxHealth - table.clear(damageDealtByPlayer) - end - task.wait(5) - end - end) - - local function startEnemyAnimations(): () - local t = if isBoss then bossAttackSpeed else enemyAttackSpeed - runEnemyAnimations = true - repeat - attackTrack.Priority = Enum.AnimationPriority.Action - attackTrack:Play() - attackTrack.Stopped:Wait() - attackTrack:Destroy() - currentAttackAnimation = attackAnimations[math.random(#attackAnimations)]:Clone() - attackTrack = enemyHumanoid:LoadAnimation(currentAttackAnimation) - - for _, player in engagedPlayers do - local fearMeterGoal = math.min( - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + damageValue.Value, - selectors.getStat(store:getState(), player.Name, "MaxFearMeter") - ) - local fearMeterAddendum = fearMeterGoal - - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") - - if fearMeterAddendum ~= 0 then - store:dispatch(actions.incrementPlayerStat(player.Name, "CurrentFearMeter", fearMeterAddendum)) - end - end - task.wait(t) - until not runEnemyAnimations - end - - local function endEnemyAnimations(): () - runEnemyAnimations = false - attackTrack:Stop() - end - - local function removePlayer(player: Player): () - local playerIndex = table.find(engagedPlayers, player) - if not playerIndex then - return - end - - table.remove(engagedPlayers, playerIndex) - - if #engagedPlayers == 0 then - lastInCombat = os.time() - endEnemyAnimations() - elseif targetPlayer == player then - if not isBoss then - for _, engagedPlayer in engagedPlayers do - if not engagedPlayer.Character or not engagedPlayer.Character:FindFirstChildOfClass "Humanoid" then - continue - end - local humanoidRootPart = engagedPlayer.Character.HumanoidRootPart - local lookAt = humanoidRootPart.Position * Vector3.new(1, 0, 1) - rootPart.CFrame = - CFrame.lookAt(rootPart.Position, lookAt + rootPart.Position.Y * Vector3.new(0, 1, 0)) - targetPlayer = engagedPlayer - break - end - end - end - end - - -- set up idle animations - if idleAnimationInstance then - local idleTrack = enemyHumanoid:LoadAnimation(idleAnimationInstance) - idleTrack.Priority = Enum.AnimationPriority.Idle - idleTrack:Play() - end - - clickDetector.MouseClick:Connect(function(player: Player) - local humanoid = if player.Character then player.Character:FindFirstChildOfClass "Humanoid" else nil - if debounceTable[player.UserId] or not humanoid or (os.time() - lastInCombat) < 1 then - return - end - - debounceTable[player.UserId] = true - task.delay(1, function() - debounceTable[player.UserId] = nil - end) - - if - selectors.getCurrentTarget(store:getState(), player.Name) == enemy - or CollectionService:HasTag(selectors.getCurrentTarget(store:getState(), player.Name), "PunchingBag") - then - return - else - store:dispatch(actions.switchPlayerEnemy(player.Name, enemy)) - end - - local connections = {} - local cleanedUp = false - local runAnimations = true - - local currentAnimation, currentTrack = nil, nil - local animationInstances = getSortedAnimationInstances( - animations[selectors.getEquippedWeapon(store:getState(), player.Name)]:GetChildren() - ) - - local function cleanUpPlayer(skipPlayerRemoval: boolean?): () - if cleanedUp then - return - end - cleanedUp = true - - for _, connection in connections do - if typeof(connection) == "RBXScriptSignal" then - connection:Disconnect() - else - connection:disconnect() - end - end - - if not selectors.isPlayerLoaded(store:getState(), player.Name) then - return - end - - local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) - local wepaon = if player.Character then player.Character:FindFirstChild(weaponName) else nil - if wepaon then - wepaon:Destroy() - end - - runAnimations = false - if currentTrack then - currentTrack:Stop() - end - - if selectors.getCurrentTarget(store:getState(), player.Name) == enemy then - store:dispatch(actions.switchPlayerEnemy(player.Name, nil)) - end - if not skipPlayerRemoval then - removePlayer(player) - end - end - - table.insert(connections, humanoid.Died:Connect(cleanUpPlayer)) - - humanoid:MoveTo(goalPosition + (humanoid.RootPart.Position - goalPosition).Unit * fightRange) - - table.insert(connections, humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(cleanUpPlayer)) - - table.insert( - connections, - store.changed:connect(function(newState) - if selectors.getCurrentTarget(newState, player.Name) ~= enemy then - cleanUpPlayer() - end - end) - ) - - repeat - task.wait(0.1) - until player:DistanceFromCharacter(rootPart.Position) <= fightRange + 5 - or cleanedUp - or not selectors.isPlayerLoaded(store:getState(), player.Name) - - if cleanedUp or (player:DistanceFromCharacter(rootPart.Position) > fightRange + 5) then - return - end - - -- rotate the player to face the enemy - local enemyDirection = rootPart.Position * Vector3.new(1, 0, 1) - local playerRootPart = player.Character and player.Character:FindFirstChild "HumanoidRootPart" - local playerPosition = if playerRootPart then playerRootPart.Position else Vector3.new(0, 0, 0) - - if player.Character then - player.Character:PivotTo( - CFrame.lookAt(playerPosition, enemyDirection + playerPosition.Y * Vector3.new(0, 1, 0)) - ) - end - - store:dispatch(actions.combatBegan(player.Name)) - -- attach currently equipped weapon to player's hand - local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) - if weaponName ~= "Fists" then - local weaponAccessory = weapons[weaponName]:Clone() - humanoid:AddAccessory(weaponAccessory) - end - - local currentIndex, maxIndex = 0, #animationInstances - task.spawn(function() - repeat - currentIndex = (currentIndex % maxIndex) + 1 - currentAnimation = animationInstances[currentIndex]:Clone() - currentTrack = humanoid:LoadAnimation(currentAnimation) - currentTrack:Play() - currentTrack.Stopped:Wait() - currentTrack:Destroy() - if - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") - == selectors.getStat(store:getState(), player.Name, "MaxFearMeter") - then - task.wait(getPlayerAttackSpeed(player) * 2) - else - task.wait(getPlayerAttackSpeed(player)) - end - until not runAnimations - end) - - table.insert(engagedPlayers, player) - if #engagedPlayers == 1 then - -- rotate enemy to face player if not boss and it is not already facing a player - if not isBoss then - local playerDirection = playerPosition * Vector3.new(1, 0, 1) - rootPart.CFrame = - CFrame.lookAt(rootPart.Position, playerDirection + rootPart.Position.Y * Vector3.new(0, 1, 0)) - end - - targetPlayer = player - - task.spawn(startEnemyAnimations) - end - - task.wait(0.5) - - local damageMultiplier = if weaponName == "Fists" then 1 else weapons[weaponName].Damage.Value - local counter = 0 - - while - runAnimations - --and humanoid.Health > 0 - and totalDamageDealt < maxHealth - and humanoid:IsDescendantOf(game) - and selectors.isPlayerLoaded(store:getState(), player.Name) - and selectors.getCurrentTarget(store:getState(), player.Name) == enemy - and player:DistanceFromCharacter(rootPart.Position) <= fightRange + 10 - do - if - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") - ~= selectors.getStat(store:getState(), player.Name, "MaxFearMeter") - or counter % 2 == 0 - then - local damageToDeal = math.clamp( - selectors.getStat(store:getState(), player.Name, "Strength") * damageMultiplier, - 0, - maxHealth - totalDamageDealt - ) - totalDamageDealt += damageToDeal - damageDealtByPlayer[player] = (damageDealtByPlayer[player] or 0) + damageToDeal - healthValue.Value = maxHealth - totalDamageDealt - end - - -- need to quick exit for this one - if totalDamageDealt >= maxHealth then - break - end - - counter += 1 - if - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") - == selectors.getStat(store:getState(), player.Name, "MaxFearMeter") - then - task.wait(getPlayerAttackSpeed(player) * 2) - else - task.wait(getPlayerAttackSpeed(player)) - end - end - - if totalDamageDealt >= maxHealth then - cleanUpPlayer(true) - - if resetBegan then - return - end - resetBegan = true - - for otherPlayer, damage in damageDealtByPlayer do - if not Players:FindFirstChild(otherPlayer.Name) then - continue - end - - store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Fear", damage, enemy.Name)) - store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Kills")) - store:dispatch(actions.logKilledEnemyType(otherPlayer.Name, enemy.Name)) - - if damage >= maxHealth * gemRewardPercentage then - store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Gems", gemAmountToDrop, enemy.Name)) - end - end - - enemy:Destroy() - - task.delay(respawnRate, function() - enemyClone.Parent = workspace - --I think instanceadded handles this? - --handleEnemy(enemyClone) - end) - - return - else - cleanUpPlayer() - end - end) -end - -for _, enemy in CollectionService:GetTagged "Enemy" do - local success, error = pcall(handleEnemy, enemy) - if not success then - warn(error) - end -end - -CollectionService:GetInstanceAddedSignal("Enemy"):Connect(function(enemy) - local success, error = pcall(handleEnemy, enemy) - if not success then - warn(error) - end -end) - -return 0 +-- local Players = game:GetService "Players" +-- local CollectionService = game:GetService "CollectionService" +-- local ReplicatedStorage = game:GetService "ReplicatedStorage" +-- local ServerScriptService = game:GetService "ServerScriptService" + +-- local server = ServerScriptService.Server +-- local animations = ReplicatedStorage.CombatAnimations +-- local gemRewardPercentage = 0.3 + +-- local store = require(server.State.Store) +-- local actions = require(server.State.Actions) +-- local HealthBar = require(ReplicatedStorage.Common.Utils.HealthBar) +-- local selectors = require(ReplicatedStorage.Common.State.selectors) + +-- local bossRespawnRate = ReplicatedStorage.Config.Combat.BossRespawnRate.Value +-- local enemyRespawnRate = ReplicatedStorage.Config.Combat.EnemyRespawnRate.Value +-- local bossAttackSpeed = ReplicatedStorage.Config.Combat.BossAttackSpeed.Value +-- local enemyAttackSpeed = ReplicatedStorage.Config.Combat.EnemyAttackSpeed.Value +-- local playerAttackSpeed = ReplicatedStorage.Config.Combat.PlayerAttackSpeed.Value +-- local doubleAttackSpeedID = tostring(ReplicatedStorage.Config.GamepassData.IDs["2xAttackSpeed"].Value) + +-- local weapons = ReplicatedStorage.Weapons + +-- local function getSortedAnimationInstances(animationInstances) +-- for i, animationInstance in animationInstances do +-- if animationInstance.Name == "Idle" then +-- table.remove(animationInstances, i) +-- break +-- end +-- end +-- table.sort(animationInstances, function(a, b) +-- return tonumber(a.Name:match "%d+") < tonumber(b.Name:match "%d+") +-- end) +-- return animationInstances +-- end + +-- local function getPlayerAttackSpeed(player) +-- return if selectors.hasGamepass(store:getState(), player.Name, doubleAttackSpeedID) +-- then playerAttackSpeed / 2 +-- else playerAttackSpeed +-- end + +-- local function handleEnemy(enemy) +-- local clickDetector = enemy.Hitbox.ClickDetector +-- local goalPosition = enemy.Hitbox.Position +-- local enemyHumanoid = enemy.Humanoid +-- local maxHealth = enemy.Configuration.FearHealth.Value + +-- local NPCUI = enemy:FindFirstChild("NPCUI", true) +-- local healthValue = enemy.Configuration.FearHealth +-- local damageValue = enemy.Configuration.Damage +-- local fightRange = enemy.Configuration.FightRange.Value +-- local gemAmountToDrop = enemy.Configuration.Gems.Value +-- local idleAnimationInstance = if enemy.Configuration:FindFirstChild "IdleAnim" +-- then enemy.Configuration.IdleAnim.Anim +-- else nil + +-- local runEnemyAnimations = false +-- local attackAnimations = getSortedAnimationInstances(enemy.Configuration.AttackAnims:GetChildren()) +-- local currentAttackAnimation = attackAnimations[math.random(#attackAnimations)]:Clone() +-- local attackTrack = enemyHumanoid:LoadAnimation(currentAttackAnimation) + +-- local debounceTable = {} +-- local engagedPlayers = {} +-- local damageDealtByPlayer = {} + +-- local lastInCombat = -1 +-- local targetPlayer = nil +-- local resetBegan = false +-- local totalDamageDealt = 0 +-- local enemyClone = enemy:Clone() +-- local isBoss = CollectionService:HasTag(enemy, "Boss") +-- local respawnRate = if isBoss then bossRespawnRate else enemyRespawnRate +-- local rootPart = if enemyHumanoid.RootPart then enemyHumanoid.RootPart else enemy:FindFirstChild "RootPart" + +-- if not rootPart then +-- error "Failed to find a root part for the provided enemy." +-- return +-- end + +-- HealthBar.new(NPCUI.Frame.Background.Frame):connect(enemy) +-- NPCUI:FindFirstChild("NPCName", true).Text = enemy.Name +-- healthValue.Value = maxHealth +-- NPCUI.Enabled = true + +-- task.spawn(function() +-- while enemy:IsDescendantOf(game) do +-- if os.time() - lastInCombat < 5 then +-- task.wait(1) +-- continue +-- end +-- local foundPlayerInRange = false +-- for _, player in Players:GetPlayers() do +-- if player:DistanceFromCharacter(goalPosition) <= fightRange + 5 then +-- foundPlayerInRange = true +-- break +-- end +-- end +-- if not foundPlayerInRange then +-- totalDamageDealt = 0 +-- healthValue.Value = maxHealth +-- table.clear(damageDealtByPlayer) +-- end +-- task.wait(5) +-- end +-- end) + +-- local function startEnemyAnimations(): () +-- local t = if isBoss then bossAttackSpeed else enemyAttackSpeed +-- runEnemyAnimations = true +-- repeat +-- attackTrack.Priority = Enum.AnimationPriority.Action +-- attackTrack:Play() +-- attackTrack.Stopped:Wait() +-- attackTrack:Destroy() +-- currentAttackAnimation = attackAnimations[math.random(#attackAnimations)]:Clone() +-- attackTrack = enemyHumanoid:LoadAnimation(currentAttackAnimation) + +-- for _, player in engagedPlayers do +-- local fearMeterGoal = math.min( +-- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + damageValue.Value, +-- selectors.getStat(store:getState(), player.Name, "MaxFearMeter") +-- ) +-- local fearMeterAddendum = fearMeterGoal +-- - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + +-- if fearMeterAddendum ~= 0 then +-- store:dispatch(actions.incrementPlayerStat(player.Name, "CurrentFearMeter", fearMeterAddendum)) +-- end +-- end +-- task.wait(t) +-- until not runEnemyAnimations +-- end + +-- local function endEnemyAnimations(): () +-- runEnemyAnimations = false +-- attackTrack:Stop() +-- end + +-- local function removePlayer(player: Player): () +-- local playerIndex = table.find(engagedPlayers, player) +-- if not playerIndex then +-- return +-- end + +-- table.remove(engagedPlayers, playerIndex) + +-- if #engagedPlayers == 0 then +-- lastInCombat = os.time() +-- endEnemyAnimations() +-- elseif targetPlayer == player then +-- if not isBoss then +-- for _, engagedPlayer in engagedPlayers do +-- if not engagedPlayer.Character or not engagedPlayer.Character:FindFirstChildOfClass "Humanoid" then +-- continue +-- end +-- local humanoidRootPart = engagedPlayer.Character.HumanoidRootPart +-- local lookAt = humanoidRootPart.Position * Vector3.new(1, 0, 1) +-- rootPart.CFrame = +-- CFrame.lookAt(rootPart.Position, lookAt + rootPart.Position.Y * Vector3.new(0, 1, 0)) +-- targetPlayer = engagedPlayer +-- break +-- end +-- end +-- end +-- end + +-- -- set up idle animations +-- if idleAnimationInstance then +-- local idleTrack = enemyHumanoid:LoadAnimation(idleAnimationInstance) +-- idleTrack.Priority = Enum.AnimationPriority.Idle +-- idleTrack:Play() +-- end + +-- clickDetector.MouseClick:Connect(function(player: Player) +-- local humanoid = if player.Character then player.Character:FindFirstChildOfClass "Humanoid" else nil +-- if debounceTable[player.UserId] or not humanoid or (os.time() - lastInCombat) < 1 then +-- return +-- end + +-- debounceTable[player.UserId] = true +-- task.delay(1, function() +-- debounceTable[player.UserId] = nil +-- end) + +-- if +-- selectors.getCurrentTarget(store:getState(), player.Name) == enemy +-- or CollectionService:HasTag(selectors.getCurrentTarget(store:getState(), player.Name), "PunchingBag") +-- then +-- return +-- else +-- store:dispatch(actions.switchPlayerEnemy(player.Name, enemy)) +-- end + +-- local connections = {} +-- local cleanedUp = false +-- local runAnimations = true + +-- local currentAnimation, currentTrack = nil, nil +-- local animationInstances = getSortedAnimationInstances( +-- animations[selectors.getEquippedWeapon(store:getState(), player.Name)]:GetChildren() +-- ) + +-- local function cleanUpPlayer(skipPlayerRemoval: boolean?): () +-- if cleanedUp then +-- return +-- end +-- cleanedUp = true + +-- for _, connection in connections do +-- if typeof(connection) == "RBXScriptSignal" then +-- connection:Disconnect() +-- else +-- connection:disconnect() +-- end +-- end + +-- if not selectors.isPlayerLoaded(store:getState(), player.Name) then +-- return +-- end + +-- local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) +-- local wepaon = if player.Character then player.Character:FindFirstChild(weaponName) else nil +-- if wepaon then +-- wepaon:Destroy() +-- end + +-- runAnimations = false +-- if currentTrack then +-- currentTrack:Stop() +-- end + +-- if selectors.getCurrentTarget(store:getState(), player.Name) == enemy then +-- store:dispatch(actions.switchPlayerEnemy(player.Name, nil)) +-- end +-- if not skipPlayerRemoval then +-- removePlayer(player) +-- end +-- end + +-- table.insert(connections, humanoid.Died:Connect(cleanUpPlayer)) + +-- humanoid:MoveTo(goalPosition + (humanoid.RootPart.Position - goalPosition).Unit * fightRange) + +-- table.insert(connections, humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(cleanUpPlayer)) + +-- table.insert( +-- connections, +-- store.changed:connect(function(newState) +-- if selectors.getCurrentTarget(newState, player.Name) ~= enemy then +-- cleanUpPlayer() +-- end +-- end) +-- ) + +-- repeat +-- task.wait(0.1) +-- until player:DistanceFromCharacter(rootPart.Position) <= fightRange + 5 +-- or cleanedUp +-- or not selectors.isPlayerLoaded(store:getState(), player.Name) + +-- if cleanedUp or (player:DistanceFromCharacter(rootPart.Position) > fightRange + 5) then +-- return +-- end + +-- -- rotate the player to face the enemy +-- local enemyDirection = rootPart.Position * Vector3.new(1, 0, 1) +-- local playerRootPart = player.Character and player.Character:FindFirstChild "HumanoidRootPart" +-- local playerPosition = if playerRootPart then playerRootPart.Position else Vector3.new(0, 0, 0) + +-- if player.Character then +-- player.Character:PivotTo( +-- CFrame.lookAt(playerPosition, enemyDirection + playerPosition.Y * Vector3.new(0, 1, 0)) +-- ) +-- end + +-- store:dispatch(actions.combatBegan(player.Name)) +-- -- attach currently equipped weapon to player's hand +-- local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) +-- if weaponName ~= "Fists" then +-- local weaponAccessory = weapons[weaponName]:Clone() +-- humanoid:AddAccessory(weaponAccessory) +-- end + +-- local currentIndex, maxIndex = 0, #animationInstances +-- task.spawn(function() +-- repeat +-- currentIndex = (currentIndex % maxIndex) + 1 +-- currentAnimation = animationInstances[currentIndex]:Clone() +-- currentTrack = humanoid:LoadAnimation(currentAnimation) +-- currentTrack:Play() +-- currentTrack.Stopped:Wait() +-- currentTrack:Destroy() +-- if +-- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") +-- == selectors.getStat(store:getState(), player.Name, "MaxFearMeter") +-- then +-- task.wait(getPlayerAttackSpeed(player) * 2) +-- else +-- task.wait(getPlayerAttackSpeed(player)) +-- end +-- until not runAnimations +-- end) + +-- table.insert(engagedPlayers, player) +-- if #engagedPlayers == 1 then +-- -- rotate enemy to face player if not boss and it is not already facing a player +-- if not isBoss then +-- local playerDirection = playerPosition * Vector3.new(1, 0, 1) +-- rootPart.CFrame = +-- CFrame.lookAt(rootPart.Position, playerDirection + rootPart.Position.Y * Vector3.new(0, 1, 0)) +-- end + +-- targetPlayer = player + +-- task.spawn(startEnemyAnimations) +-- end + +-- task.wait(0.5) + +-- local damageMultiplier = if weaponName == "Fists" then 1 else weapons[weaponName].Damage.Value +-- local counter = 0 + +-- while +-- runAnimations +-- --and humanoid.Health > 0 +-- and totalDamageDealt < maxHealth +-- and humanoid:IsDescendantOf(game) +-- and selectors.isPlayerLoaded(store:getState(), player.Name) +-- and selectors.getCurrentTarget(store:getState(), player.Name) == enemy +-- and player:DistanceFromCharacter(rootPart.Position) <= fightRange + 10 +-- do +-- if +-- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") +-- ~= selectors.getStat(store:getState(), player.Name, "MaxFearMeter") +-- or counter % 2 == 0 +-- then +-- local damageToDeal = math.clamp( +-- selectors.getStat(store:getState(), player.Name, "Strength") * damageMultiplier, +-- 0, +-- maxHealth - totalDamageDealt +-- ) +-- totalDamageDealt += damageToDeal +-- damageDealtByPlayer[player] = (damageDealtByPlayer[player] or 0) + damageToDeal +-- healthValue.Value = maxHealth - totalDamageDealt +-- end + +-- -- need to quick exit for this one +-- if totalDamageDealt >= maxHealth then +-- break +-- end + +-- counter += 1 +-- if +-- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") +-- == selectors.getStat(store:getState(), player.Name, "MaxFearMeter") +-- then +-- task.wait(getPlayerAttackSpeed(player) * 2) +-- else +-- task.wait(getPlayerAttackSpeed(player)) +-- end +-- end + +-- if totalDamageDealt >= maxHealth then +-- cleanUpPlayer(true) + +-- if resetBegan then +-- return +-- end +-- resetBegan = true + +-- for otherPlayer, damage in damageDealtByPlayer do +-- if not Players:FindFirstChild(otherPlayer.Name) then +-- continue +-- end + +-- store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Fear", damage, enemy.Name)) +-- store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Kills")) +-- store:dispatch(actions.logKilledEnemyType(otherPlayer.Name, enemy.Name)) + +-- if damage >= maxHealth * gemRewardPercentage then +-- store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Gems", gemAmountToDrop, enemy.Name)) +-- end +-- end + +-- enemy:Destroy() + +-- task.delay(respawnRate, function() +-- enemyClone.Parent = workspace +-- --I think instanceadded handles this? +-- --handleEnemy(enemyClone) +-- end) + +-- return +-- else +-- cleanUpPlayer() +-- end +-- end) +-- end + +-- for _, enemy in CollectionService:GetTagged "Enemy" do +-- local success, error = pcall(handleEnemy, enemy) +-- if not success then +-- warn(error) +-- end +-- end + +-- CollectionService:GetInstanceAddedSignal("Enemy"):Connect(function(enemy) +-- local success, error = pcall(handleEnemy, enemy) +-- if not success then +-- warn(error) +-- end +-- end) + +-- return 0 From 35295c20950e7e2b1fc25a77ccb496586c8e08db Mon Sep 17 00:00:00 2001 From: pluviolithic Date: Wed, 15 Nov 2023 15:42:36 -0500 Subject: [PATCH 5/6] Add some setup help for new machines --- CONTRIBUTING.md | 38 +++++++++++++++++++++++++++++++ Makefile | 6 +++++ README.md | 27 +++++++++++++++++++++- default.project.json | 54 ++++++++++++++++++++++---------------------- 4 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 Makefile diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2202eee --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing to Horror Simulator +Thanks for contributing to Horror Simulator! This guide has a few tips and guidelines to make contributing to the project as easy as possible. + +## Bug Reports +Any bugs (or things that look like bugs) can be reported on the [GitHub issue tracker](https://github.com/pluviolithic/horror-simulator/issues) + +Make sure you check to see if someone has already reported your bug first! + +## Working on Horror Simulator +To get started working on Horror Simulator, you'll need: +* Git +* [Wally](https://github.com/UpliftGames/wally) +* [StyLua](https://github.com/JohnnyMorganz/StyLua) +* [Selene](https://github.com/Kampfkarren/selene) + +The `setup` make target will automatically install and configure all of them via aftman. +```sh +git clone https://github.com/pluviolithic/horror-simulator/ +cd horror-simulator +make setup +``` + +### Pull Requests +Before starting a pull request, open an issue about the feature or bug. This helps us prevent duplicated and wasted effort. These issues are a great place to ask for help if you run into problems! + +Before you submit a new pull request, check: +* Code Quality: Run [Selene](https://github.com/Kampfkarren/selene) on your code, no warnings allowed! +* Code Style: Run [StyLua](https://github.com/JohnnyMorganz/StyLua) on your code so it's formatted to follow the Roblox Lua Style Guide + +### Code Style +Try to match the existing code style! In short: + +* Tabs for indentation +* Double quotes +* One statement per line + +Use StyLua to automatically format your code to comply with the Roblox Lua Style Guide. +You can run this tool manually from the commandline (`stylua -c src/`), or use one of StyLua's editor integrations. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c114e8 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +setup: + curl https://sh.rustup.rs -sSf | sh -s -- -y + cargo install aftman + aftman self-install + aftman install --no-trust-check + wally install diff --git a/README.md b/README.md index da4028b..67a69e0 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# horror-simulator \ No newline at end of file +# horror-simulator +Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.3.0. + +## Getting Started +To install the necessary tooling, use: +```sh +make setup +``` + +To build the place from scratch, use: + +```sh +rojo build -o "horror-simulator.rbxlx" +``` + +Next, open `horror-simulator.rbxlx` in Roblox Studio and start the Rojo server: + +```sh +rojo serve +``` + +For more help, check out [the Rojo documentation](https://rojo.space/docs). + +## Contributing + +See [the contribution guide](CONTRIBUTING.md) before contributing! diff --git a/default.project.json b/default.project.json index 5ada809..6577e68 100644 --- a/default.project.json +++ b/default.project.json @@ -1,31 +1,31 @@ { - "name": "horror-simulator", - "tree": { - "$className": "DataModel", - "ReplicatedStorage": { - "Common": { - "$path": "src/shared", - "lib": { - "$className": "Folder", - "$path": "./Packages" - } - } - }, - "ServerScriptService": { - "Server": { - "$path": "src/server", - "lib": { - "$className": "Folder", - "$path": "./ServerPackages" - } - } - }, - "StarterPlayer": { - "StarterPlayerScripts": { - "Client": { - "$path": "src/client" - } - } + "name": "horror-simulator", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "Common": { + "$path": "src/shared", + "lib": { + "$className": "Folder", + "$path": "./Packages" } + } + }, + "ServerScriptService": { + "Server": { + "$path": "src/server", + "lib": { + "$className": "Folder", + "$path": "./ServerPackages" + } + } + }, + "StarterPlayer": { + "StarterPlayerScripts": { + "Client": { + "$path": "src/client" + } + } } + } } From c83f588f3bd156fbf68d8213da7b01f5cbfe4522 Mon Sep 17 00:00:00 2001 From: Pluviolithic <94190216+Pluviolithic@users.noreply.github.com> Date: Wed, 15 Nov 2023 23:51:52 -0500 Subject: [PATCH 6/6] Apply finishing touches/fixes --- .../Combat/Enemies/ApplyDamageToEnemy.lua | 12 +- .../Combat/Enemies/ApplyDamageToPlayers.lua | 9 +- .../Combat/Enemies/ApplyEnemyAnimations.lua | 11 +- .../Combat/Enemies/ApplyPlayerAnimations.lua | 6 +- src/server/Combat/Enemies/init.lua | 87 +++- src/server/Combat/Enemy.lua | 415 ------------------ 6 files changed, 91 insertions(+), 449 deletions(-) delete mode 100644 src/server/Combat/Enemy.lua diff --git a/src/server/Combat/Enemies/ApplyDamageToEnemy.lua b/src/server/Combat/Enemies/ApplyDamageToEnemy.lua index 031732f..e484c39 100644 --- a/src/server/Combat/Enemies/ApplyDamageToEnemy.lua +++ b/src/server/Combat/Enemies/ApplyDamageToEnemy.lua @@ -9,13 +9,17 @@ local animationUtilities = require(ReplicatedStorage.Common.Utils.AnimationUtils local weapons = ReplicatedStorage.Weapons local function canAttack(player, enemy, info) + if not enemy:IsDescendantOf(game) then + return false + end + + local rootPart = if enemy.Humanoid.RootPart then enemy.Humanoid.RootPart else enemy:FindFirstChild "RootPart" local fightRange = enemy.Configuration.FightRange.Value - local enemyRootPosition = enemy.RootPart.Position return info.HealthValue.Value > 0 and selectors.isPlayerLoaded(store:getState(), player.Name) and selectors.getCurrentTarget(store:getState(), player.Name) == enemy - and player:DistanceFromCharacter(enemyRootPosition) <= fightRange + 10 + and player:DistanceFromCharacter(rootPart.Position) <= fightRange + 10 end return function(player, enemy, info, janitor) @@ -27,6 +31,7 @@ return function(player, enemy, info, janitor) if weaponName ~= "Fists" then local weaponAccessory = weapons[weaponName]:Clone() player.Character.Humanoid:AddAccessory(weaponAccessory) + janitor:Add(weaponAccessory) end task.spawn(function() @@ -41,7 +46,4 @@ return function(player, enemy, info, janitor) task.wait(animationUtilities.getPlayerAttackSpeed(player)) end end) - janitor:Add(function() - info.EngagedPlayers[player] = nil - end, true) end diff --git a/src/server/Combat/Enemies/ApplyDamageToPlayers.lua b/src/server/Combat/Enemies/ApplyDamageToPlayers.lua index 16c647b..6e639a8 100644 --- a/src/server/Combat/Enemies/ApplyDamageToPlayers.lua +++ b/src/server/Combat/Enemies/ApplyDamageToPlayers.lua @@ -9,18 +9,18 @@ local store = require(ServerScriptService.Server.State.Store) local actions = require(ServerScriptService.Server.State.Actions) local selectors = require(ReplicatedStorage.Common.State.selectors) -return function(enemy, engagedPlayers, info, janitor) - if info.Active then +return function(enemy, info, janitor) + if info.DamageActive then return end - info.Active = true + info.DamageActive = true local attackDelay = if CollectionService:HasTag(enemy, "Boss") then bossAttackSpeed else enemyAttackSpeed local damagePlayers = true task.spawn(function() while damagePlayers do - for _, player in engagedPlayers do + for _, player in info.EngagedPlayers do local fearMeterGoal = math.min( selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + enemy.Configuration.Damage.Value, @@ -38,6 +38,7 @@ return function(enemy, engagedPlayers, info, janitor) end) janitor:Add(function() + info.DamageActive = nil damagePlayers = false end, true) end diff --git a/src/server/Combat/Enemies/ApplyEnemyAnimations.lua b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua index 44a3802..844b4ed 100644 --- a/src/server/Combat/Enemies/ApplyEnemyAnimations.lua +++ b/src/server/Combat/Enemies/ApplyEnemyAnimations.lua @@ -7,22 +7,22 @@ local enemyAttackSpeed = ReplicatedStorage.Config.Combat.EnemyAttackSpeed.Value local animationUtilities = require(ReplicatedStorage.Common.Utils.AnimationUtils) return function(enemy, info, janitor) - if info.Active then + if info.AnimationsActive then return end - info.Active = true + info.AnimationsActive = true local attackDelay = if CollectionService:HasTag(enemy, "Boss") then bossAttackSpeed else enemyAttackSpeed local runAnimations = true local animationInstances = animationUtilities.filterAndSortAnimationInstances(enemy.Configuration.AttackAnims:GetChildren()) - local currentIndex, animationTrack = 0, nil + local currentIndex, animationTrack, animation = 0, nil, nil task.spawn(function() while runAnimations do - currentIndex, animationTrack = - animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) + currentIndex, animation = animationUtilities.getNextIndexAndAnimationTrack(animationInstances, currentIndex) + animationTrack = enemy.Humanoid:LoadAnimation(animation) animationTrack.Priority = Enum.AnimationPriority.Action animationTrack:Play() @@ -34,6 +34,7 @@ return function(enemy, info, janitor) janitor:Add(function() runAnimations = false + info.AnimationsActive = nil if animationTrack.IsPlaying then animationTrack:Stop() end diff --git a/src/server/Combat/Enemies/ApplyPlayerAnimations.lua b/src/server/Combat/Enemies/ApplyPlayerAnimations.lua index 8bc25d6..b1f20d3 100644 --- a/src/server/Combat/Enemies/ApplyPlayerAnimations.lua +++ b/src/server/Combat/Enemies/ApplyPlayerAnimations.lua @@ -10,15 +10,15 @@ local animationUtilities = require(ReplicatedStorage.Common.Utils.AnimationUtils return function(player, janitor) local runAnimations = true - local currentIndex, animationTrack = 0, nil + local currentIndex, animationTrack, animation = 0, nil, nil local animationInstances = animationUtilities.filterAndSortAnimationInstances( combatAnimations[selectors.getEquippedWeapon(store:getState(), player.Name)]:GetChildren() ) task.spawn(function() while runAnimations do - currentIndex, animationTrack = - animationUtilities.getNextAnimationTrackAndIndex(animationInstances, currentIndex) + currentIndex, animation = animationUtilities.getNextIndexAndAnimationTrack(animationInstances, currentIndex) + animationTrack = player.Character.Humanoid:LoadAnimation(animation) animationTrack.Priority = Enum.AnimationPriority.Action animationTrack:Play() diff --git a/src/server/Combat/Enemies/init.lua b/src/server/Combat/Enemies/init.lua index e24315d..da6d21f 100644 --- a/src/server/Combat/Enemies/init.lua +++ b/src/server/Combat/Enemies/init.lua @@ -45,27 +45,62 @@ end local function handleEnemy(enemy) local info = { - Active = false, HealthValue = enemy.Configuration.FearHealth, MaxHealth = enemy.Configuration.FearHealth.Value, DamageDealtByPlayer = {}, EngagedPlayers = {}, + Janitors = {}, } local isBoss = CollectionService:HasTag(enemy, "Boss") local fightRange = enemy.Configuration.FightRange.Value local respawnRate = if isBoss then bossRespawnRate else enemyRespawnRate local rootPart = if enemy.Humanoid.RootPart then enemy.Humanoid.RootPart else enemy:FindFirstChild "RootPart" + local idleAnimationInstance = if enemy.Configuration:FindFirstChild "IdleAnim" + then enemy.Configuration.IdleAnim.Anim + else nil local debounces = {} + local lastInCombat = -1 local enemyClone = enemy:Clone() local enemyJanitor = Janitor.new() + local enemyAnimationJanitor = Janitor.new() + + if idleAnimationInstance then + local idleTrack = enemy.Humanoid:LoadAnimation(idleAnimationInstance) + idleTrack.Priority = Enum.AnimationPriority.Idle + idleTrack:Play() + end + + task.spawn(function() + while enemy:IsDescendantOf(game) do + if os.time() - lastInCombat < 5 or #info.EngagedPlayers > 0 then + task.wait(1) + continue + end + local foundPlayerInRange = false + for _, player in Players:GetPlayers() do + if player:DistanceFromCharacter(rootPart.Position) <= fightRange + 5 then + foundPlayerInRange = true + break + end + end + if not foundPlayerInRange then + info.HealthValue.Value = info.MaxHealth + table.clear(info.DamageDealtByPlayer) + end + task.wait(5) + end + end) enemyJanitor:Add(enemy) - enemyJanitor:Add(info.HealthValue.Value:GetPropertyChangedSignal("Value"):Connect(function() - if info.HealthValue <= 0 then - enemyJanitor:Destroy() + enemyJanitor:Add(enemyAnimationJanitor) + enemyJanitor:Add(info.HealthValue:GetPropertyChangedSignal("Value"):Connect(function() + if info.HealthValue.Value <= 0 then for player, damage in info.DamageDealtByPlayer do + if Janitor.Is(info.Janitors[player]) then + info.Janitors[player]:Destroy() + end if not Players:FindFirstChild(player.Name) then continue end @@ -80,6 +115,7 @@ local function handleEnemy(enemy) ) end end + enemyJanitor:Destroy() task.wait(respawnRate) enemyClone.Parent = workspace end @@ -105,13 +141,37 @@ local function handleEnemy(enemy) end local playerJanitor = Janitor.new() + info.Janitors[player] = playerJanitor store:dispatch(actions.switchPlayerEnemy(player.Name, enemy)) - playerJanitor:Add(store.changed:connect(function(newState) - if selectors.getCurrentTarget(newState, player.Name) ~= enemy then - playerJanitor:Destroy() + humanoid:MoveTo(enemy.Hitbox.Position + (humanoid.RootPart.Position - enemy.Hitbox.Position).Unit * fightRange) + + playerJanitor:Add( + store.changed:connect(function(newState) + if selectors.getCurrentTarget(newState, player.Name) ~= enemy then + playerJanitor:Destroy() + end + end), + "disconnect" + ) + playerJanitor:Add(humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(function() + playerJanitor:Destroy() + end)) + playerJanitor:Add(function() + local playerIndex = table.find(info.EngagedPlayers, player) + if selectors.getCurrentTarget(store:getState(), player.Name) == enemy then + store:dispatch(actions.switchPlayerEnemy(player.Name, nil)) + end + if playerIndex then + table.remove(info.EngagedPlayers, playerIndex) + end + if #info.EngagedPlayers == 1 and not isBoss then + orientEnemy(rootPart, info.EngagedPlayers[1].Character.HumanoidRootPart.Position) + elseif #info.EngagedPlayers == 0 then + lastInCombat = os.time() + enemyAnimationJanitor:Cleanup() end - end, "disconnect")) + end, true) repeat task.wait(0.1) @@ -134,16 +194,9 @@ local function handleEnemy(enemy) orientEnemy(rootPart, player.Character.HumanoidRootPart.Position) end - playerJanitor:Add(function() - table.remove(info.EngagedPlayers, table.find(info.EngagedPlayers, player)) - if #info.EngagedPlayers == 1 and not isBoss then - orientEnemy(rootPart, player.Character.HumanoidRootPart.Position) - end - end, true) - - applyEnemyAnimations(enemy, info, enemyJanitor) + applyEnemyAnimations(enemy, info, enemyAnimationJanitor) applyPlayerAnimations(player, playerJanitor) - applyDamageToPlayers(enemy, info, enemyJanitor) + applyDamageToPlayers(enemy, info, enemyAnimationJanitor) applyDamageToEnemy(player, enemy, info, playerJanitor) end) end diff --git a/src/server/Combat/Enemy.lua b/src/server/Combat/Enemy.lua deleted file mode 100644 index c238049..0000000 --- a/src/server/Combat/Enemy.lua +++ /dev/null @@ -1,415 +0,0 @@ --- local Players = game:GetService "Players" --- local CollectionService = game:GetService "CollectionService" --- local ReplicatedStorage = game:GetService "ReplicatedStorage" --- local ServerScriptService = game:GetService "ServerScriptService" - --- local server = ServerScriptService.Server --- local animations = ReplicatedStorage.CombatAnimations --- local gemRewardPercentage = 0.3 - --- local store = require(server.State.Store) --- local actions = require(server.State.Actions) --- local HealthBar = require(ReplicatedStorage.Common.Utils.HealthBar) --- local selectors = require(ReplicatedStorage.Common.State.selectors) - --- local bossRespawnRate = ReplicatedStorage.Config.Combat.BossRespawnRate.Value --- local enemyRespawnRate = ReplicatedStorage.Config.Combat.EnemyRespawnRate.Value --- local bossAttackSpeed = ReplicatedStorage.Config.Combat.BossAttackSpeed.Value --- local enemyAttackSpeed = ReplicatedStorage.Config.Combat.EnemyAttackSpeed.Value --- local playerAttackSpeed = ReplicatedStorage.Config.Combat.PlayerAttackSpeed.Value --- local doubleAttackSpeedID = tostring(ReplicatedStorage.Config.GamepassData.IDs["2xAttackSpeed"].Value) - --- local weapons = ReplicatedStorage.Weapons - --- local function getSortedAnimationInstances(animationInstances) --- for i, animationInstance in animationInstances do --- if animationInstance.Name == "Idle" then --- table.remove(animationInstances, i) --- break --- end --- end --- table.sort(animationInstances, function(a, b) --- return tonumber(a.Name:match "%d+") < tonumber(b.Name:match "%d+") --- end) --- return animationInstances --- end - --- local function getPlayerAttackSpeed(player) --- return if selectors.hasGamepass(store:getState(), player.Name, doubleAttackSpeedID) --- then playerAttackSpeed / 2 --- else playerAttackSpeed --- end - --- local function handleEnemy(enemy) --- local clickDetector = enemy.Hitbox.ClickDetector --- local goalPosition = enemy.Hitbox.Position --- local enemyHumanoid = enemy.Humanoid --- local maxHealth = enemy.Configuration.FearHealth.Value - --- local NPCUI = enemy:FindFirstChild("NPCUI", true) --- local healthValue = enemy.Configuration.FearHealth --- local damageValue = enemy.Configuration.Damage --- local fightRange = enemy.Configuration.FightRange.Value --- local gemAmountToDrop = enemy.Configuration.Gems.Value --- local idleAnimationInstance = if enemy.Configuration:FindFirstChild "IdleAnim" --- then enemy.Configuration.IdleAnim.Anim --- else nil - --- local runEnemyAnimations = false --- local attackAnimations = getSortedAnimationInstances(enemy.Configuration.AttackAnims:GetChildren()) --- local currentAttackAnimation = attackAnimations[math.random(#attackAnimations)]:Clone() --- local attackTrack = enemyHumanoid:LoadAnimation(currentAttackAnimation) - --- local debounceTable = {} --- local engagedPlayers = {} --- local damageDealtByPlayer = {} - --- local lastInCombat = -1 --- local targetPlayer = nil --- local resetBegan = false --- local totalDamageDealt = 0 --- local enemyClone = enemy:Clone() --- local isBoss = CollectionService:HasTag(enemy, "Boss") --- local respawnRate = if isBoss then bossRespawnRate else enemyRespawnRate --- local rootPart = if enemyHumanoid.RootPart then enemyHumanoid.RootPart else enemy:FindFirstChild "RootPart" - --- if not rootPart then --- error "Failed to find a root part for the provided enemy." --- return --- end - --- HealthBar.new(NPCUI.Frame.Background.Frame):connect(enemy) --- NPCUI:FindFirstChild("NPCName", true).Text = enemy.Name --- healthValue.Value = maxHealth --- NPCUI.Enabled = true - --- task.spawn(function() --- while enemy:IsDescendantOf(game) do --- if os.time() - lastInCombat < 5 then --- task.wait(1) --- continue --- end --- local foundPlayerInRange = false --- for _, player in Players:GetPlayers() do --- if player:DistanceFromCharacter(goalPosition) <= fightRange + 5 then --- foundPlayerInRange = true --- break --- end --- end --- if not foundPlayerInRange then --- totalDamageDealt = 0 --- healthValue.Value = maxHealth --- table.clear(damageDealtByPlayer) --- end --- task.wait(5) --- end --- end) - --- local function startEnemyAnimations(): () --- local t = if isBoss then bossAttackSpeed else enemyAttackSpeed --- runEnemyAnimations = true --- repeat --- attackTrack.Priority = Enum.AnimationPriority.Action --- attackTrack:Play() --- attackTrack.Stopped:Wait() --- attackTrack:Destroy() --- currentAttackAnimation = attackAnimations[math.random(#attackAnimations)]:Clone() --- attackTrack = enemyHumanoid:LoadAnimation(currentAttackAnimation) - --- for _, player in engagedPlayers do --- local fearMeterGoal = math.min( --- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") + damageValue.Value, --- selectors.getStat(store:getState(), player.Name, "MaxFearMeter") --- ) --- local fearMeterAddendum = fearMeterGoal --- - selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") - --- if fearMeterAddendum ~= 0 then --- store:dispatch(actions.incrementPlayerStat(player.Name, "CurrentFearMeter", fearMeterAddendum)) --- end --- end --- task.wait(t) --- until not runEnemyAnimations --- end - --- local function endEnemyAnimations(): () --- runEnemyAnimations = false --- attackTrack:Stop() --- end - --- local function removePlayer(player: Player): () --- local playerIndex = table.find(engagedPlayers, player) --- if not playerIndex then --- return --- end - --- table.remove(engagedPlayers, playerIndex) - --- if #engagedPlayers == 0 then --- lastInCombat = os.time() --- endEnemyAnimations() --- elseif targetPlayer == player then --- if not isBoss then --- for _, engagedPlayer in engagedPlayers do --- if not engagedPlayer.Character or not engagedPlayer.Character:FindFirstChildOfClass "Humanoid" then --- continue --- end --- local humanoidRootPart = engagedPlayer.Character.HumanoidRootPart --- local lookAt = humanoidRootPart.Position * Vector3.new(1, 0, 1) --- rootPart.CFrame = --- CFrame.lookAt(rootPart.Position, lookAt + rootPart.Position.Y * Vector3.new(0, 1, 0)) --- targetPlayer = engagedPlayer --- break --- end --- end --- end --- end - --- -- set up idle animations --- if idleAnimationInstance then --- local idleTrack = enemyHumanoid:LoadAnimation(idleAnimationInstance) --- idleTrack.Priority = Enum.AnimationPriority.Idle --- idleTrack:Play() --- end - --- clickDetector.MouseClick:Connect(function(player: Player) --- local humanoid = if player.Character then player.Character:FindFirstChildOfClass "Humanoid" else nil --- if debounceTable[player.UserId] or not humanoid or (os.time() - lastInCombat) < 1 then --- return --- end - --- debounceTable[player.UserId] = true --- task.delay(1, function() --- debounceTable[player.UserId] = nil --- end) - --- if --- selectors.getCurrentTarget(store:getState(), player.Name) == enemy --- or CollectionService:HasTag(selectors.getCurrentTarget(store:getState(), player.Name), "PunchingBag") --- then --- return --- else --- store:dispatch(actions.switchPlayerEnemy(player.Name, enemy)) --- end - --- local connections = {} --- local cleanedUp = false --- local runAnimations = true - --- local currentAnimation, currentTrack = nil, nil --- local animationInstances = getSortedAnimationInstances( --- animations[selectors.getEquippedWeapon(store:getState(), player.Name)]:GetChildren() --- ) - --- local function cleanUpPlayer(skipPlayerRemoval: boolean?): () --- if cleanedUp then --- return --- end --- cleanedUp = true - --- for _, connection in connections do --- if typeof(connection) == "RBXScriptSignal" then --- connection:Disconnect() --- else --- connection:disconnect() --- end --- end - --- if not selectors.isPlayerLoaded(store:getState(), player.Name) then --- return --- end - --- local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) --- local wepaon = if player.Character then player.Character:FindFirstChild(weaponName) else nil --- if wepaon then --- wepaon:Destroy() --- end - --- runAnimations = false --- if currentTrack then --- currentTrack:Stop() --- end - --- if selectors.getCurrentTarget(store:getState(), player.Name) == enemy then --- store:dispatch(actions.switchPlayerEnemy(player.Name, nil)) --- end --- if not skipPlayerRemoval then --- removePlayer(player) --- end --- end - --- table.insert(connections, humanoid.Died:Connect(cleanUpPlayer)) - --- humanoid:MoveTo(goalPosition + (humanoid.RootPart.Position - goalPosition).Unit * fightRange) - --- table.insert(connections, humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(cleanUpPlayer)) - --- table.insert( --- connections, --- store.changed:connect(function(newState) --- if selectors.getCurrentTarget(newState, player.Name) ~= enemy then --- cleanUpPlayer() --- end --- end) --- ) - --- repeat --- task.wait(0.1) --- until player:DistanceFromCharacter(rootPart.Position) <= fightRange + 5 --- or cleanedUp --- or not selectors.isPlayerLoaded(store:getState(), player.Name) - --- if cleanedUp or (player:DistanceFromCharacter(rootPart.Position) > fightRange + 5) then --- return --- end - --- -- rotate the player to face the enemy --- local enemyDirection = rootPart.Position * Vector3.new(1, 0, 1) --- local playerRootPart = player.Character and player.Character:FindFirstChild "HumanoidRootPart" --- local playerPosition = if playerRootPart then playerRootPart.Position else Vector3.new(0, 0, 0) - --- if player.Character then --- player.Character:PivotTo( --- CFrame.lookAt(playerPosition, enemyDirection + playerPosition.Y * Vector3.new(0, 1, 0)) --- ) --- end - --- store:dispatch(actions.combatBegan(player.Name)) --- -- attach currently equipped weapon to player's hand --- local weaponName = selectors.getEquippedWeapon(store:getState(), player.Name) --- if weaponName ~= "Fists" then --- local weaponAccessory = weapons[weaponName]:Clone() --- humanoid:AddAccessory(weaponAccessory) --- end - --- local currentIndex, maxIndex = 0, #animationInstances --- task.spawn(function() --- repeat --- currentIndex = (currentIndex % maxIndex) + 1 --- currentAnimation = animationInstances[currentIndex]:Clone() --- currentTrack = humanoid:LoadAnimation(currentAnimation) --- currentTrack:Play() --- currentTrack.Stopped:Wait() --- currentTrack:Destroy() --- if --- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") --- == selectors.getStat(store:getState(), player.Name, "MaxFearMeter") --- then --- task.wait(getPlayerAttackSpeed(player) * 2) --- else --- task.wait(getPlayerAttackSpeed(player)) --- end --- until not runAnimations --- end) - --- table.insert(engagedPlayers, player) --- if #engagedPlayers == 1 then --- -- rotate enemy to face player if not boss and it is not already facing a player --- if not isBoss then --- local playerDirection = playerPosition * Vector3.new(1, 0, 1) --- rootPart.CFrame = --- CFrame.lookAt(rootPart.Position, playerDirection + rootPart.Position.Y * Vector3.new(0, 1, 0)) --- end - --- targetPlayer = player - --- task.spawn(startEnemyAnimations) --- end - --- task.wait(0.5) - --- local damageMultiplier = if weaponName == "Fists" then 1 else weapons[weaponName].Damage.Value --- local counter = 0 - --- while --- runAnimations --- --and humanoid.Health > 0 --- and totalDamageDealt < maxHealth --- and humanoid:IsDescendantOf(game) --- and selectors.isPlayerLoaded(store:getState(), player.Name) --- and selectors.getCurrentTarget(store:getState(), player.Name) == enemy --- and player:DistanceFromCharacter(rootPart.Position) <= fightRange + 10 --- do --- if --- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") --- ~= selectors.getStat(store:getState(), player.Name, "MaxFearMeter") --- or counter % 2 == 0 --- then --- local damageToDeal = math.clamp( --- selectors.getStat(store:getState(), player.Name, "Strength") * damageMultiplier, --- 0, --- maxHealth - totalDamageDealt --- ) --- totalDamageDealt += damageToDeal --- damageDealtByPlayer[player] = (damageDealtByPlayer[player] or 0) + damageToDeal --- healthValue.Value = maxHealth - totalDamageDealt --- end - --- -- need to quick exit for this one --- if totalDamageDealt >= maxHealth then --- break --- end - --- counter += 1 --- if --- selectors.getStat(store:getState(), player.Name, "CurrentFearMeter") --- == selectors.getStat(store:getState(), player.Name, "MaxFearMeter") --- then --- task.wait(getPlayerAttackSpeed(player) * 2) --- else --- task.wait(getPlayerAttackSpeed(player)) --- end --- end - --- if totalDamageDealt >= maxHealth then --- cleanUpPlayer(true) - --- if resetBegan then --- return --- end --- resetBegan = true - --- for otherPlayer, damage in damageDealtByPlayer do --- if not Players:FindFirstChild(otherPlayer.Name) then --- continue --- end - --- store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Fear", damage, enemy.Name)) --- store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Kills")) --- store:dispatch(actions.logKilledEnemyType(otherPlayer.Name, enemy.Name)) - --- if damage >= maxHealth * gemRewardPercentage then --- store:dispatch(actions.incrementPlayerStat(otherPlayer.Name, "Gems", gemAmountToDrop, enemy.Name)) --- end --- end - --- enemy:Destroy() - --- task.delay(respawnRate, function() --- enemyClone.Parent = workspace --- --I think instanceadded handles this? --- --handleEnemy(enemyClone) --- end) - --- return --- else --- cleanUpPlayer() --- end --- end) --- end - --- for _, enemy in CollectionService:GetTagged "Enemy" do --- local success, error = pcall(handleEnemy, enemy) --- if not success then --- warn(error) --- end --- end - --- CollectionService:GetInstanceAddedSignal("Enemy"):Connect(function(enemy) --- local success, error = pcall(handleEnemy, enemy) --- if not success then --- warn(error) --- end --- end) - --- return 0