Jump to content

Recommended Posts

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 by WorldDeva

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 by WorldDeva

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...