........... Posted July 9, 2020 Share Posted July 9, 2020 (edited) I've been attempting to design a mod for dst that lets maxwell toggle to ai of his minions. To do that, I use two keys to toggle two global variables: Spoiler GLOBAL.REVISED_MAXWELL_RUN = false GLOBAL.REVISED_MAXWELL_ATT = false function ToggleMaxwellAtt() if GLOBAL.REVISED_MAXWELL_ATT == false then GLOBAL.REVISED_MAXWELL_RUN = false GLOBAL.REVISED_MAXWELL_ATT = true else GLOBAL.REVISED_MAXWELL_ATT = false end end function ToggleMaxwellRun() if GLOBAL.REVISED_MAXWELL_RUN == false then GLOBAL.REVISED_MAXWELL_ATT = false GLOBAL.REVISED_MAXWELL_RUN = true else GLOBAL.REVISED_MAXWELL_RUN = false end end function sendToggleAtt() SendModRPCToServer(MOD_RPC[modname]["ToggleMaxwellAtt"]) end function sendToggleRun() SendModRPCToServer(MOD_RPC[modname]["ToggleMaxwellRun"]) end AddModRPCHandler(modname, "ToggleMaxwellAtt", ToggleMaxwellAtt) AddModRPCHandler(modname, "ToggleMaxwellRun", ToggleMaxwellRun) GLOBAL.TheInput:AddKeyDownHandler(118,sendToggleAtt) GLOBAL.TheInput:AddKeyDownHandler(98,sendToggleRun) AddPrefabPostInit("shadowwaxwellbrain", function() brain = require "brains/revised_maxwell_minions" end) print("finish") After doing this, I've been messing around with shadowmaxwellbrain.lua to make acquiring a target require a variable, and make kiting require a variable: Spoiler local function FindEntityToWorkAction(inst, action, addtltags) local leader = GetLeader(inst) if GLOBAL.REVISED_MAXWELL_RUN then return nil end if leader ~= nil then --Keep existing target? local target = inst.sg.statemem.target if target ~= nil and target:IsValid() and not (target:IsInLimbo() or target:HasTag("NOCLICK") or target:HasTag("event_trigger")) and target:IsOnValidGround() and target.components.workable ~= nil and target.components.workable:CanBeWorked() and target.components.workable:GetWorkAction() == action and not (target.components.burnable ~= nil and (target.components.burnable:IsBurning() or target.components.burnable:IsSmoldering())) and target.entity:IsVisible() and target:IsNear(leader, KEEP_WORKING_DIST) then if addtltags ~= nil then for i, v in ipairs(addtltags) do if target:HasTag(v) then return BufferedAction(inst, target, action) end end else return BufferedAction(inst, target, action) end end --Find new target target = FindEntity(leader, SEE_WORK_DIST, nil, { action.id.."_workable" }, TOWORK_CANT_TAGS, addtltags) return target ~= nil and BufferedAction(inst, target, action) or nil end end local function ShouldKite(target, inst) return inst.components.combat:TargetIs(target) and target.components.health ~= nil and not target.components.health:IsDead() and not GLOBAL.REVISED_MAXWELL_ATT --modification end However, this doesn't seem to do anything, so I decided to read through the code for behaviorTrees. My conclusions so far: Actions and if/for loops and such are all BehaviorNodes A function is passed to if Nodes which is evaluated every time When selecting a Node, it goes from the first to last, unless using nodes that specifically change that BehaviorTrees are loaded upon brain creation My questions: Do name values have a use? What is the use of PriorityNodes? Do BehaviorTrees loop back to the root node every time? What is _cstor? Why do brains return themselves? What are the numbers in the priority node construction? What wrong with my code? BehaviorTree.lua: Spoiler require("class") SUCCESS = "SUCCESS" FAILED = "FAILED" READY = "READY" RUNNING = "RUNNING" --------------------------------------------------------------------------------------- BT = Class(function(self, inst, root) self.inst = inst self.root = root end) function BT:ForceUpdate() self.forceupdate = true end function BT:Update() self.root:Visit() self.root:SaveStatus() self.root:Step() self.forceupdate = false end function BT:Reset() self.root:Reset() end function BT:Stop() self.root:Stop() end function BT:GetSleepTime() if self.forceupdate then return 0 end return self.root:GetTreeSleepTime() end function BT:__tostring() return self.root:GetTreeString() end --------------------------------------------------------------------------------------- BehaviourNode = Class(function (self, name, children) self.name = name or "" self.children = children self.status = READY self.lastresult = READY if children then for i,k in pairs(children) do k.parent = self end end end) function BehaviourNode:DoToParents(fn) if self.parent then fn(self.parent) return self.parent:DoToParents(fn) end end function BehaviourNode:GetTreeString(indent) indent = indent or "" local str = string.format("%s%s>%2.2f\n", indent, self:GetString(), self:GetTreeSleepTime() or 0) if self.children then for k, v in ipairs(self.children) do -- uncomment this to see only the "active" part of the tree. handy for pigbrain. --if v.status == RUNNING or v.status == SUCCESS or v.lastresult == RUNNING or v.lastresult == SUCCESS then str = str .. v:GetTreeString(indent .. " >") --end end end return str end function BehaviourNode:DBString() return "" end function BehaviourNode:Sleep(t) self.nextupdatetime = GetTime() + t end function BehaviourNode:GetSleepTime() if self.status == RUNNING and not self.children and not self:is_a(ConditionNode) then if self.nextupdatetime then local time_to = self.nextupdatetime - GetTime() if time_to < 0 then time_to = 0 end return time_to end return 0 end return nil end function BehaviourNode:GetTreeSleepTime() local sleeptime = nil if self.children then for k,v in ipairs(self.children) do if v.status == RUNNING then local t = v:GetTreeSleepTime() if t and (not sleeptime or sleeptime > t) then sleeptime = t end end end end local my_t = self:GetSleepTime() if my_t and (not sleeptime or sleeptime > my_t) then sleeptime = my_t end return sleeptime end function BehaviourNode:GetString() local str = "" if self.status == RUNNING then str = self:DBString() end return string.format([[%s - %s <%s> (%s)]], self.name, self.status or "UNKNOWN", self.lastresult or "?", str) end function BehaviourNode:Visit() self.status = FAILED end function BehaviourNode:SaveStatus() self.lastresult = self.status if self.children then for k,v in pairs(self.children) do v:SaveStatus() end end end function BehaviourNode:Step() if self.status ~= RUNNING then self:Reset() elseif self.children then for k, v in ipairs(self.children) do v:Step() end end end function BehaviourNode:Reset() if self.status ~= READY then self.status = READY if self.children then for idx, child in ipairs(self.children) do child:Reset() end end end end function BehaviourNode:Stop() if self.OnStop then self:OnStop() end if self.children then for idx, child in ipairs(self.children) do child:Stop() end end end --------------------------------------------------------------------------------------- DecoratorNode = Class(BehaviourNode, function(self, name, child) BehaviourNode._ctor(self, name or "Decorator", {child}) end) --------------------------------------------------------------------------------------- ConditionNode = Class(BehaviourNode, function(self, fn, name) BehaviourNode._ctor(self, name or "Condition") self.fn = fn end) function ConditionNode:Visit() if self.fn() then self.status = SUCCESS else self.status = FAILED end end --------------------------------------------------------------------------------------- ConditionWaitNode = Class(BehaviourNode, function(self, fn, name) BehaviourNode._ctor(self, name or "Wait") self.fn = fn end) function ConditionWaitNode:Visit() if self.fn() then self.status = SUCCESS else self.status = RUNNING end end --------------------------------------------------------------------------------------- ActionNode = Class(BehaviourNode, function(self, action, name) BehaviourNode._ctor(self, name or "ActionNode") self.action = action end) function ActionNode:Visit() self.action() self.status = SUCCESS end --------------------------------------------------------------------------------------- WaitNode = Class(BehaviourNode, function(self, time) BehaviourNode._ctor(self, "Wait") self.wait_time = time end) function WaitNode:DBString() local w = self.wake_time - GetTime() return string.format("%2.2f", w) end function WaitNode:Visit() local current_time = GetTime() if self.status ~= RUNNING then self.wake_time = current_time + (type(self.wait_time) == "function" and self.wait_time() or self.wait_time) self.status = RUNNING end if self.status == RUNNING then if current_time >= self.wake_time then self.status = SUCCESS else self:Sleep(current_time - self.wake_time) end end end --------------------------------------------------------------------------------------- SequenceNode = Class(BehaviourNode, function(self, children) BehaviourNode._ctor(self, "Sequence", children) self.idx = 1 end) function SequenceNode:DBString() return tostring(self.idx) end function SequenceNode:Reset() self._base.Reset(self) self.idx = 1 end function SequenceNode:Visit() if self.status ~= RUNNING then self.idx = 1 end local done = false while self.idx <= #self.children do local child = self.children[self.idx] child:Visit() if child.status == RUNNING or child.status == FAILED then self.status = child.status return end self.idx = self.idx + 1 end self.status = SUCCESS end --------------------------------------------------------------------------------------- SelectorNode = Class(BehaviourNode, function(self, children) BehaviourNode._ctor(self, "Selector", children) self.idx = 1 end) function SelectorNode:DBString() return tostring(self.idx) end function SelectorNode:Reset() self._base.Reset(self) self.idx = 1 end function SelectorNode:Visit() if self.status ~= RUNNING then self.idx = 1 end local done = false while self.idx <= #self.children do local child = self.children[self.idx] child:Visit() if child.status == RUNNING or child.status == SUCCESS then self.status = child.status return end self.idx = self.idx + 1 end self.status = FAILED end --------------------------------------------------------------------------------------- NotDecorator = Class(DecoratorNode, function(self, child) DecoratorNode._ctor(self, "Not", child) end) function NotDecorator:Visit() local child = self.children[1] child:Visit() if child.status == SUCCESS then self.status = FAILED elseif child.status == FAILED then self.status = SUCCESS else self.status = child.status end end --------------------------------------------------------------------------------------- FailIfRunningDecorator = Class(DecoratorNode, function(self, child) DecoratorNode._ctor(self, "FailIfRunning", child) end) function FailIfRunningDecorator:Visit() local child = self.children[1] child:Visit() if child.status == RUNNING then self.status = FAILED else self.status = child.status end end --------------------------------------------------------------------------------------- -- Useful to make a prioritynode move to the next element whether a child succeeds or fails FailIfSuccessDecorator = Class(DecoratorNode, function(self, child) DecoratorNode._ctor(self, "FailIfSuccess", child) end) function FailIfSuccessDecorator:Visit() local child = self.children[1] child:Visit() if child.status == SUCCESS then self.status = FAILED else self.status = child.status end end --------------------------------------------------------------------------------------- LoopNode = Class(BehaviourNode, function(self, children, maxreps) BehaviourNode._ctor(self, "Sequence", children) self.idx = 1 self.maxreps = maxreps self.rep = 0 end) function LoopNode:DBString() return tostring(self.idx) end function LoopNode:Reset() self._base.Reset(self) self.idx = 1 self.rep = 0 end function LoopNode:Visit() if self.status ~= RUNNING then self.idx = 1 self.rep = 0 end local done = false while self.idx <= #self.children do local child = self.children[self.idx] child:Visit() if child.status == RUNNING or child.status == FAILED then if child.status == FAILED then --print("EXIT LOOP ON FAIL") end self.status = child.status return end self.idx = self.idx + 1 end self.idx = 1 self.rep = self.rep + 1 if self.maxreps and self.rep >= self.maxreps then --print("DONE LOOP") self.status = SUCCESS else for k,v in ipairs(self.children) do v:Reset() end end end --------------------------------------------------------------------------------------- RandomNode = Class(BehaviourNode, function(self, children) BehaviourNode._ctor(self, "Random", children) end) function RandomNode:Reset() self._base.Reset(self) self.idx = nil end function RandomNode:Visit() local done = false if self.status == READY then --pick a new child self.idx = math.random(#self.children) local start = self.idx while true do local child = self.children[self.idx] child:Visit() if child.status ~= FAILED then self.status = child.status return end self.idx = self.idx + 1 if self.idx > #self.children then self.idx = 1 end if self.idx == start then self.status = FAILED return end end else local child = self.children[self.idx] child:Visit() self.status = child.status end end --------------------------------------------------------------------------------------- PriorityNode = Class(BehaviourNode, function(self, children, period, noscatter) BehaviourNode._ctor(self, "Priority", children) self.period = period or 1 if not noscatter then self.lasttime = self.period * 0.5 + (self.period * math.random()) end end) function PriorityNode:GetSleepTime() if self.status == RUNNING then if not self.period then return 0 end local time_to = 0 if self.lasttime then time_to = self.lasttime + self.period - GetTime() if time_to < 0 then time_to = 0 end end return time_to elseif self.status == READY then return 0 end return nil end function PriorityNode:DBString() local time_till = 0 if self.period then time_till = (self.lasttime or 0) + self.period - GetTime() end return string.format("execute %d, eval in %2.2f", self.idx or -1, time_till) end function PriorityNode:Reset() self._base.Reset(self) self.idx = nil end function PriorityNode:Visit() local time = GetTime() local do_eval = not self.lasttime or not self.period or self.lasttime + self.period < time local oldidx = self.idx if do_eval then local old_event = nil if self.idx and self.children[self.idx]:is_a(EventNode) then old_event = self.children[self.idx] end self.lasttime = time local found = false for idx, child in ipairs(self.children) do local should_test_anyway = old_event and child:is_a(EventNode) and old_event.priority <= child.priority if not found or should_test_anyway then if child.status == FAILED or child.status == SUCCESS then child:Reset() end child:Visit() local cs = child.status if cs == SUCCESS or cs == RUNNING then if should_test_anyway and self.idx ~= idx then self.children[self.idx]:Reset() end self.status = cs found = true self.idx = idx end else child:Reset() end end if not found then self.status = FAILED end else if self.idx then local child = self.children[self.idx] if child.status == RUNNING then child:Visit() self.status = child.status if self.status ~= RUNNING then self.lasttime = nil end end end end end --------------------------------------------------------------------------------------- ParallelNode = Class(BehaviourNode, function(self, children, name) BehaviourNode._ctor(self, name or "Parallel", children) end) function ParallelNode:Step() if self.status ~= RUNNING then self:Reset() elseif self.children then for k, v in ipairs(self.children) do if v.status == SUCCESS and v:is_a(ConditionNode) then v:Reset() end end end end function ParallelNode:Visit() local done = true local any_done = false for idx, child in ipairs(self.children) do if child:is_a(ConditionNode) then child:Reset() end if child.status ~= SUCCESS then child:Visit() if child.status == FAILED then self.status = FAILED return end end if child.status == RUNNING then done = false else any_done = true end end if done or (self.stoponanycomplete and any_done) then self.status = SUCCESS else self.status = RUNNING end end ParallelNodeAny = Class(ParallelNode, function(self, children) ParallelNode._ctor(self, children, "Parallel(Any)") self.stoponanycomplete = true end) --------------------------------------------------------------------------------------- EventNode = Class(BehaviourNode, function(self, inst, event, child, priority) BehaviourNode._ctor(self, "Event("..event..")", {child}) self.inst = inst self.event = event self.priority = priority or 0 self.eventfn = function(inst, data) self:OnEvent(data) end self.inst:ListenForEvent(self.event, self.eventfn) --print(self.inst, "EventNode()", self.event) end) function EventNode:OnStop() --print(self.inst, "EventNode:OnStop()", self.event) if self.eventfn then self.inst:RemoveEventCallback(self.event, self.eventfn) self.eventfn = nil end end function EventNode:OnEvent(data) --print(self.inst, "EventNode:OnEvent()", self.event) if self.status == RUNNING then self.children[1]:Reset() end self.triggered = true self.data = data if self.inst.brain then self.inst.brain:ForceUpdate() end self:DoToParents(function(node) if node:is_a(PriorityNode) then node.lasttime = nil end end) --wake the parent! end function EventNode:Step() self._base.Step(self) self.triggered = false end function EventNode:Reset() self.triggered = false self._base.Reset(self) end function EventNode:Visit() if self.status == READY and self.triggered then self.status = RUNNING end if self.status == RUNNING then if self.children and #self.children == 1 then local child = self.children[1] child:Visit() self.status = child.status else self.status = FAILED end end end --------------------------------------------------------------- function WhileNode(cond, name, node) return ParallelNode { ConditionNode(cond, name), node } end --------------------------------------------------------------- function IfNode(cond, name, node) return SequenceNode { ConditionNode(cond, name), node } end --------------------------------------------------------------- LatchNode = Class(BehaviourNode, function(self, inst, latchduration, child) BehaviourNode._ctor(self, "Latch ("..tostring(latchduration)..")", {child}) self.inst = inst self.latchduration = latchduration self.currentlatchduration = 0 self.lastlatchtime = -math.huge end) function LatchNode:Visit() if self.status == READY then if GetTime() > self.currentlatchduration + self.lastlatchtime then print("GONNA GO!", GetTime(), self.currentlatchduration, "----", GetTime()+self.currentlatchduration, ">", self.lastlatchtime) self.lastlatchtime = GetTime() self.currentlatchduration = type(self.latchduration) == "function" and self.latchduration(self.inst) or self.latchduration print("New vals:", self.currentlatchduration , self.lastlatchtime) self.status = RUNNING else self.status = FAILED end end if self.status == RUNNING then self.children[1]:Visit() self.status = self.children[1].status end end shadowmaxwellbrain.lua: Spoiler require "behaviours/wander" require "behaviours/faceentity" require "behaviours/chaseandattack" require "behaviours/panic" require "behaviours/follow" require "behaviours/attackwall" require "behaviours/standstill" require "behaviours/leash" require "behaviours/runaway" local ShadowWaxwellBrain = Class(Brain, function(self, inst) Brain._ctor(self, inst) end) --Images will help chop, mine and fight. local MIN_FOLLOW_DIST = 0 local TARGET_FOLLOW_DIST = 6 local MAX_FOLLOW_DIST = 8 local START_FACE_DIST = 6 local KEEP_FACE_DIST = 8 local KEEP_WORKING_DIST = 14 local SEE_WORK_DIST = 10 local KEEP_DANCING_DIST = 2 local KITING_DIST = 3 local STOP_KITING_DIST = 5 local RUN_AWAY_DIST = 5 local STOP_RUN_AWAY_DIST = 8 local AVOID_EXPLOSIVE_DIST = 5 local DIG_TAGS = { "stump", "grave" } local function GetLeader(inst) return inst.components.follower.leader end local function GetLeaderPos(inst) return inst.components.follower.leader:GetPosition() end local function GetFaceTargetFn(inst) local target = FindClosestPlayerToInst(inst, START_FACE_DIST, true) return target ~= nil and not target:HasTag("notarget") and target or nil end local function IsNearLeader(inst, dist) local leader = GetLeader(inst) return leader ~= nil and inst:IsNear(leader, dist) end local TOWORK_CANT_TAGS = { "fire", "smolder", "event_trigger", "INLIMBO", "NOCLICK" } local function FindEntityToWorkAction(inst, action, addtltags) local leader = GetLeader(inst) if leader ~= nil then --Keep existing target? local target = inst.sg.statemem.target if target ~= nil and target:IsValid() and not (target:IsInLimbo() or target:HasTag("NOCLICK") or target:HasTag("event_trigger")) and target:IsOnValidGround() and target.components.workable ~= nil and target.components.workable:CanBeWorked() and target.components.workable:GetWorkAction() == action and not (target.components.burnable ~= nil and (target.components.burnable:IsBurning() or target.components.burnable:IsSmoldering())) and target.entity:IsVisible() and target:IsNear(leader, KEEP_WORKING_DIST) then if addtltags ~= nil then for i, v in ipairs(addtltags) do if target:HasTag(v) then return BufferedAction(inst, target, action) end end else return BufferedAction(inst, target, action) end end --Find new target target = FindEntity(leader, SEE_WORK_DIST, nil, { action.id.."_workable" }, TOWORK_CANT_TAGS, addtltags) return target ~= nil and BufferedAction(inst, target, action) or nil end end local function KeepFaceTargetFn(inst, target) return not target:HasTag("notarget") and inst:IsNear(target, KEEP_FACE_DIST) end local function DanceParty(inst) inst:PushEvent("dance") end local function ShouldDanceParty(inst) local leader = GetLeader(inst) return leader ~= nil and leader.sg:HasStateTag("dancing") end local function ShouldAvoidExplosive(target) return target.components.explosive == nil or target.components.burnable == nil or target.components.burnable:IsBurning() end local function ShouldRunAway(target) return not (target.components.health ~= nil and target.components.health:IsDead()) and (not target:HasTag("shadowcreature") or (target.components.combat ~= nil and target.components.combat:HasTarget())) end local function ShouldKite(target, inst) return inst.components.combat:TargetIs(target) and target.components.health ~= nil and not target.components.health:IsDead() end local function ShouldWatchMinigame(inst) if inst.components.follower.leader ~= nil and inst.components.follower.leader.components.minigame_participator ~= nil then if inst.components.combat.target == nil or inst.components.combat.target.components.minigame_participator ~= nil then return true end end return false end local function WatchingMinigame(inst) return (inst.components.follower.leader ~= nil and inst.components.follower.leader.components.minigame_participator ~= nil) and inst.components.follower.leader.components.minigame_participator:GetMinigame() or nil end function ShadowWaxwellBrain:OnStart() local watch_game = WhileNode( function() return ShouldWatchMinigame(self.inst) end, "Watching Game", PriorityNode({ Follow(self.inst, WatchingMinigame, TUNING.MINIGAME_CROWD_DIST_MIN, TUNING.MINIGAME_CROWD_DIST_TARGET, TUNING.MINIGAME_CROWD_DIST_MAX), RunAway(self.inst, "minigame_participator", 5, 7), FaceEntity(self.inst, WatchingMinigame, WatchingMinigame), }, 0.25)) local root = PriorityNode( { watch_game, --#1 priority is dancing beside your leader. Obviously. WhileNode(function() return ShouldDanceParty(self.inst) end, "Dance Party", PriorityNode({ Leash(self.inst, GetLeaderPos, KEEP_DANCING_DIST, KEEP_DANCING_DIST), ActionNode(function() DanceParty(self.inst) end), }, .25)), WhileNode(function() return IsNearLeader(self.inst, KEEP_WORKING_DIST) end, "Leader In Range", PriorityNode({ --All shadows will avoid explosives RunAway(self.inst, { fn = ShouldAvoidExplosive, tags = { "explosive" }, notags = { "INLIMBO" } }, AVOID_EXPLOSIVE_DIST, AVOID_EXPLOSIVE_DIST), --Duelists will try to fight before fleeing IfNode(function() return self.inst.prefab == "shadowduelist" end, "Is Duelist", PriorityNode({ WhileNode(function() return self.inst.components.combat:GetCooldown() > .5 and ShouldKite(self.inst.components.combat.target, self.inst) end, "Dodge", RunAway(self.inst, { fn = ShouldKite, tags = { "_combat", "_health" }, notags = { "INLIMBO" } }, KITING_DIST, STOP_KITING_DIST)), ChaseAndAttack(self.inst), }, .25)), --All shadows will flee from danger at this point RunAway(self.inst, { fn = ShouldRunAway, oneoftags = { "monster", "hostile" }, notags = { "player", "INLIMBO" } }, RUN_AWAY_DIST, STOP_RUN_AWAY_DIST), --Workiers will try to work if not fleeing IfNode(function() return self.inst.prefab == "shadowlumber" end, "Keep Chopping", DoAction(self.inst, function() return FindEntityToWorkAction(self.inst, ACTIONS.CHOP) end)), IfNode(function() return self.inst.prefab == "shadowminer" end, "Keep Mining", DoAction(self.inst, function() return FindEntityToWorkAction(self.inst, ACTIONS.MINE) end)), IfNode(function() return self.inst.prefab == "shadowdigger" end, "Keep Digging", DoAction(self.inst, function() return FindEntityToWorkAction(self.inst, ACTIONS.DIG, DIG_TAGS) end)), }, .25)), Follow(self.inst, GetLeader, MIN_FOLLOW_DIST, TARGET_FOLLOW_DIST, MAX_FOLLOW_DIST), WhileNode(function() return GetLeader(self.inst) ~= nil end, "Has Leader", FaceEntity(self.inst, GetFaceTargetFn, KeepFaceTargetFn)), }, .25) self.bt = BT(self.inst, root) end return ShadowWaxwellBrain I can provide the other files too if anybody wants them Edited July 9, 2020 by WorldDeva Link to comment https://forums.kleientertainment.com/forums/topic/119928-how-do-behaviortreesbrains-work/ Share on other sites More sharing options...
........... Posted July 9, 2020 Author Share Posted July 9, 2020 (edited) I have also attempted adding two if nodes before anything else, still does nothing. I've also tried putting the inside a priority node, still nothing... Spoiler local root = PriorityNode( { watch_game, --#1 priority is dancing beside your leader. Obviously. WhileNode(function() return ShouldDanceParty(self.inst) end, "Dance Party", PriorityNode({ Leash(self.inst, GetLeaderPos, KEEP_DANCING_DIST, KEEP_DANCING_DIST), ActionNode(function() DanceParty(self.inst) end), }, .25)), WhileNode(function() return IsNearLeader(self.inst, KEEP_WORKING_DIST) end, "Leader In Range", PriorityNode({ --All shadows will avoid explosives RunAway(self.inst, { fn = ShouldAvoidExplosive, tags = { "explosive" }, notags = { "INLIMBO" } }, AVOID_EXPLOSIVE_DIST, AVOID_EXPLOSIVE_DIST), --Duelists will try to fight before fleeing PriorityNode( {IfNode(function() return GLOBAL.REVISED_MAXWELL_RUN end, "Constant Run", -- this is what I added RunAway(self.inst, { fn = ShouldRunAway, oneoftags = { "monster", "hostile" }, notags = { "player", "INLIMBO" } }, RUN_AWAY_DIST, STOP_RUN_AWAY_DIST) ) IfNode(function() return GLOBAL.REVISED_MAXWELL_ATT end, "Constant Attack", --also this ChaseAndAttack(self.inst) )},.30) IfNode(function() return self.inst.prefab == "shadowduelist" end, "Is Duelist", PriorityNode({ WhileNode(function() return self.inst.components.combat:GetCooldown() > .5 and ShouldKite(self.inst.components.combat.target, self.inst) end, "Dodge", RunAway(self.inst, { fn = ShouldKite, tags = { "_combat", "_health" }, notags = { "INLIMBO" } }, KITING_DIST, STOP_KITING_DIST)), ChaseAndAttack(self.inst), }, .25)), --All shadows will flee from danger at this point RunAway(self.inst, { fn = ShouldRunAway, oneoftags = { "monster", "hostile" }, notags = { "player", "INLIMBO" } }, RUN_AWAY_DIST, STOP_RUN_AWAY_DIST), --Workiers will try to work if not fleeing IfNode(function() return self.inst.prefab == "shadowlumber" end, "Keep Chopping", DoAction(self.inst, function() return FindEntityToWorkAction(self.inst, ACTIONS.CHOP) end)), IfNode(function() return self.inst.prefab == "shadowminer" end, "Keep Mining", DoAction(self.inst, function() return FindEntityToWorkAction(self.inst, ACTIONS.MINE) end)), IfNode(function() return self.inst.prefab == "shadowdigger" end, "Keep Digging", DoAction(self.inst, function() return FindEntityToWorkAction(self.inst, ACTIONS.DIG, DIG_TAGS) end)), }, .25)), Follow(self.inst, GetLeader, MIN_FOLLOW_DIST, TARGET_FOLLOW_DIST, MAX_FOLLOW_DIST), WhileNode(function() return GetLeader(self.inst) ~= nil end, "Has Leader", FaceEntity(self.inst, GetFaceTargetFn, KeepFaceTargetFn)), }, .25) Edited July 9, 2020 by WorldDeva Link to comment https://forums.kleientertainment.com/forums/topic/119928-how-do-behaviortreesbrains-work/#findComment-1352312 Share on other sites More sharing options...
........... Posted July 9, 2020 Author Share Posted July 9, 2020 Well, I figured out what the number does, it seems to be how long to wait before executing the node again I've removed it but it still doesnt work Link to comment https://forums.kleientertainment.com/forums/topic/119928-how-do-behaviortreesbrains-work/#findComment-1352321 Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now