Jump to content

(LUA) help with Monster Ai with loaded model


burgelkat
 Share

Recommended Posts

MonsterSkeleton = {}

MonsterSkeleton.name = "MonsterSkeleton"
MonsterSkeleton.team = 2
MonsterSkeleton.alive = true
MonsterSkeleton.nextscanfortargettime = 0
MonsterSkeleton.scanrange = 100
MonsterSkeleton.eyeheight = 1.65
MonsterSkeleton.sound_attack = {}
MonsterSkeleton.navmeshindex = 1
MonsterSkeleton.damage = 20
MonsterSkeleton.health = 100
MonsterSkeleton.meleedamage = 20

-- Animationssteuerung für das Skelett
MonsterSkeleton.enabled = false
MonsterSkeleton.idle = false
MonsterSkeleton.animation = false
MonsterSkeleton.GetUpPlayed = false
MonsterSkeleton.getUpDelay = 0
MonsterSkeleton.getUpStart = 0
MonsterSkeleton.idleDelay = 6000

function MonsterSkeleton:Enable()
    self.enabled = true
    self.animation = true
    self.getUpStart = Millisecs()  -- Startzeit für GetUp setzen
    Component(self):Enable()
end

function MonsterSkeleton:Start()
    -- Sicherstellen, dass self.entity existiert
    if not self.entity then
        DebugLog("Fehler: self.entity ist nil!")
        return
    end


    -- Skelett-Modell laden und in liegender Position starten
    self.skeleton = LoadModel(world, "Models/Characters/Skeletton/Skeleton_complete.mdl")
    self.skeleton:SetRotation(0, 180, 0)  -- Liegt auf dem Rücken
    self.skeleton:SetPosition(self.entity:GetPosition())
    
    local entity = Model(self.skeleton)
    local model = Model(self.skeleton)

    entity.team = self.team
    entity.health = self.health
    self.health = nil


    -- GetUp Verzögerung setzen
    self.getUpDelay = math.random(1000, 8000)
    
    -- Initiale Animation stoppen
    self.skeleton:Animate(1, 0.25, 2000, ANIMATION_STOP, 0)

    -- Sound laden und Lautstärke setzen
    local soundFile = self.soundPath
    self.skeletonsound = CreateSpeaker(LoadSound(soundFile))
    self.skeletonsound:SetLooping(false)

    -- Navigation
    if self.navmesh then
        self.agent = CreateNavAgent(self.navmesh, 0.5, 1.8)
        self.agent:SetPosition(entity:GetPosition(true))
        self.agent:SetRotation(entity:GetRotation(true).y)
        entity:SetPosition(0, 0, 0)
        entity:SetRotation(0, 180, 0)
        entity:Attach(self.agent)
    end
    
    entity:SetNavObstacle(false)
    entity:SetCollisionType(COLLISION_PLAYER)
    entity:SetMass(0)
    entity:SetPhysicsMode(PHYSICS_RIGIDBODY)
    entity:AddTag("player")
  

    if model then
        local seq = model:FindAnimation("Attack1")
        if seq ~= 0 then
            local count = model:CountAnimationFrames(seq)
            model.skeleton:AddHook(seq, count - 1, Monster.EndAttackHook, self)
        end
        
        seq = model:FindAnimation("Attack1")
        if seq ~= 0 then
            local count = model:CountAnimationFrames(seq)
            model.skeleton:AddHook(seq, count - 1, Monster.EndAttackHook, self)
        end
    end
end

function MonsterSkeleton:Load(properties, binstream, scene, flags, extra)

    if type(properties.alertsound) == "string" then
        self.sound_alert = LoadSound(properties.alertsound)
    end

    for n = 1, 2 do
        local key = "attacksound" .. tostring(n)
        if type(properties[key]) == "string" then
            self.sound_attack[n] = LoadSound(properties[key])
        end
    end

    self.navmesh = nil
    if #scene.navmeshes > 0 then
        self.navmesh = scene.navmeshes[self.navmeshindex]
    end

    return true
end

-- "GetUp" Animation nach zufälliger Verzögerung starten
function MonsterSkeleton:GetUp()
    if self.GetUpPlayed then return end  -- Falls bereits abgespielt, nichts tun

    local currentTime = Millisecs()
    if currentTime - self.getUpStart >= self.getUpDelay then
        self.skeletonsound:Play()
        self.skeleton:Animate(1, 0.5, 2000, ANIMATION_ONCE)
        self.GetUpPlayed = true
        self.startTime = Millisecs()  -- Startzeit für Idle-Übergang setzen
    end
end

-- Wechsel zu Idle-Animation
function MonsterSkeleton:Idle()
    if not self.idle then return end
        
    if self.idle then 
        self.skeleton:Animate(0, 0.7, 2000, ANIMATION_LOOP)
    end
end

function MonsterSkeleton.EndAttackHook(skeleton, monster)  
    -- Uncomment the following block if you want to implement the attack logic
    -- if not monster.attackfinished then
    --     monster.attackfinished = true
    --     local target = monster.target:lock()
    --     local entity = monster:GetEntity()
    --     local attackrange = 2.5
    --     if entity:GetDistance(target) < attackrange then
    --         for _, c in ipairs(target.components) do
    --             local base = c:As("BaseComponent")
    --             if base then base:Damage(1, entity) end
    --         end
    --     end
    -- end
    local monster = Component(monster)
    monster.attacking = false
end


function MonsterSkeleton:Kill()
    if not self.alive then return end
    self.alive = false
    self.skeleton:Animate("Death", 1, 250, ANIMATION_ONCE)
    if self.agent then
        self.agent:Stop()
        self.entity:Detach()
        self.agent = nil
    end
    self.entity:SetCollisionType(COLLISION_NONE)
    self.entity:SetPickMode(PICK_NONE)
    self:Disable()
end

-- Update-Funktion: Steuerung von GetUp und Idle
function MonsterSkeleton:Update()


    local entity = Model(self.skeleton)
    local model = Model(self.skeleton)

    if not self.enabled then return end

    if self.enabled then 
         if self.animation and not self.GetUpPlayed then
            self:GetUp()
         elseif self.GetUpPlayed and not self.target and Millisecs() - self.startTime >= self.idleDelay then
             self:Idle()
             self.idle=true
             --self.ScanForTarget()
        end
        
        if not self.target then 
            self:ScanForTarget() 
        end
        if entity.health <= 0 then
           self:Disable()
           return
        end  

        local entity = Model(self.skeleton)
        
        local model = Model(self.skeleton)
  
    
        -- Stop attacking if target is dead
        if self.target then
            if self.target.health ~= nil and type(self.target.health) == "number" and self.target.health <= 0 then
                self.target = nil
                if self.agent then self.agent:Stop()
                    self.Idle()
                    self.idle=true
                    --self.ScanForTarget()
                end
            end
        end
    
        if self.attacking and self.target then
            if self.agent then
                local a = ATan(entity.matrix.t.x - self.target.matrix.t.x, entity.matrix.t.z - self.target.matrix.t.z)
                self.agent:SetRotation(a + 180)
            end
            if not self.attackfinished then
                local world = self.entity:GetWorld()
                if world then
                    if world:GetTime() - self.meleeattacktime > 300 then
                        self.attackfinished = true
                        local pos = entity:GetPosition(true)
                        local dest = self.target:GetPosition(true) + self.target:GetVelocity()
                        local attackrange = 3.0
                        if pos:DistanceToPoint(dest) < attackrange then
                            self.target.health = self.target.health - self.meleedamage
                            for _, c in ipairs(self.target.components) do
                                if c.Damage and type(c.Damage) == "function" then
                                    c:Damage(self.meleedamage, entity)
                                    if self.target.health <= 0 and type(c.Kill) == "function" then
                                        c:Kill(entity)
                                    end
                                end
                            end
                            if self.target.health <= 0 then
                                self.target = nil
                            end
                        end
                    end
                end
            end
            return
        end
        
        if not self.target then self:ScanForTarget() end
    
        if self.target then
            self.idle=false
            if self.attacking == false and entity:GetDistance(self.target) > 3.0 then
                if self.agent then
                    self.agent:Navigate(self.target:GetPosition(true))
                    self.agent:SetMaxSpeed(6)
                end
                if model then
                    model:Animate("Walk")
                end
            else
                if self.agent then self.agent:Stop() end
                if not self.attacking then
                    local world = entity:GetWorld()
                    if world then self.meleeattacktime = world:GetTime() end
                    local index = Ceil(math.random(1, 2))
                    if model then model:Animate("Attack" .. tostring(index), 1, 100, ANIMATION_ONCE) end
                    if Ceil(Random(0, 2)) == 0 then
                        index = Ceil(Random(1, 2))
                        if self.sound_attack[index] then entity:EmitSound(self.sound_attack[index]) end
                    end
                    self.attacking = true
                    self.attackfinished = false
                end
            end
        else
            if not self.idle then 
            self:Idle()
            end
        end
    end

end

-- Kampflogik für das Monster bleibt erhalten
function MonsterSkeleton:Damage(amount, attacker)
    if entity.health <= 0 then return end
    if entity.health <= 0 then
        local n
        --[[for n = 1, #self.entity.components do
            if type(self.entity.components[n].Kill) == "function" then
                self.entity.components[n]:Kill(attacker)
            end
        end]]
        return
    end
    if self.target == nil and attacker ~= Model(self.skeleton) then self.target = attacker end --Make him angry if someone hurts him!    
end

function MonsterSkeleton.RayFilter(entity, extra)
    local ctype = entity:GetCollisionType()
    return ctype == COLLISION_SCENE or ctype == COLLISION_PROP
end

function MonsterSkeleton:ScanForTarget()
     
    local entity = Model(self.skeleton)
    local world = entity:GetWorld()
    if world then
        local now = world:GetTime()
        if now < self.nextscanfortargettime then return end
        self.nextscanfortargettime = now + math.random(300, 500)

        local temp = entity:GetPickMode()
        entity:SetPickMode(PICK_NONE)

        local players = world:GetTaggedEntities("good")
        for _, player in ipairs(players) do
            if player.health ~= nil and type(player.health) == "number" and player.health > 0 then
                if player:GetDistance(entity) < self.scanrange then
                    local temp2 = player:GetPickMode()
                    player:SetPickMode(PICK_NONE)
                    local A = entity:GetPosition(true) + Vec3(0, self.eyeheight, 0)
                    local B = player:GetPosition(true) + Vec3(0, 0.5, 0)
                    local pick = world:Pick(A, B, 0, false)
                    player:SetPickMode(temp2)
                    if not pick.success then
                        self.target = player
                        break
                    end
                end
            end
        end
        entity:SetPickMode(temp)
    end
end

function MonsterSkeleton:Save(properties, binstream, scene, flags, extra)
    properties.health = self.entity.health
end

RegisterComponent("MonsterSkeleton", MonsterSkeleton)

return MonsterSkeleton
Quote

 

 Hello, maybe someone can help. I tryed to combine monster AI Script with my sceleton get up Script. 

In the  script, the skeleton is activated and immediately executes the Attack function. However, it should first play the GetUp function completely and then execute the Scan for Target function. And, the Attack function only be executed once,  and the Walk function  run continuously. 

sometimes the sceleton get up and do nothing . 

what i do wrong here and or where i get lost in that code 😅

second question. I have a build a nav mesh. But the monster ai don’t use it. Even not if i use josh‘s Monster AI script on my sceleton model . Is there something to pay  attention ? (It seems ultraengine is heredifferent to leadwerks )

Sorry but i try to learn all in Ultraengine again 😅

 

Link to comment
Share on other sites

MonsterSkeleton = {}

MonsterSkeleton.name = "MonsterSkeleton"
MonsterSkeleton.team = 2
MonsterSkeleton.alive = true
MonsterSkeleton.nextscanfortargettime = 0
MonsterSkeleton.scanrange = 100
MonsterSkeleton.eyeheight = 1.65
MonsterSkeleton.sound_attack = {}
MonsterSkeleton.navmeshindex = 1
MonsterSkeleton.damage = 20
MonsterSkeleton.health = 100
MonsterSkeleton.meleedamage = 20

-- Animationssteuerung für das Skelett
MonsterSkeleton.enabled = false
MonsterSkeleton.idle = false
MonsterSkeleton.animation = false
MonsterSkeleton.GetUpPlayed = false
MonsterSkeleton.getUpDelay = 0
MonsterSkeleton.getUpStart = 0
MonsterSkeleton.idleDelay = 6000
MonsterSkeleton.combatready = false


function MonsterSkeleton:Enable()
    self.enabled = true
    self.animation = true
    self.getUpStart = Millisecs()  -- Startzeit für GetUp setzen
    Component(self):Enable()
end

function MonsterSkeleton:Start()

    -- Skelett-Modell laden und in liegender Position starten
    self.skeleton = LoadModel(world, "Models/Characters/Skeletton/Skeleton_complete.mdl")
    self.skeleton:SetRotation(0, 0, 0)  -- Liegt auf dem Rücken
    self.skeleton:SetPosition(self.entity:GetPosition())
    
    local entity = Model(self.skeleton)
    local model = Model(self.skeleton)

    entity.team = self.team
    entity.health = self.health
    self.health = nil


    -- GetUp Verzögerung setzen
    self.getUpDelay = math.random(1000, 8000)
    
    -- Initiale Animation stoppen
    self.skeleton:Animate(1, 0.25, 2000, ANIMATION_STOP, 0)

    -- Sound laden und Lautstärke setzen
    local soundFile = self.soundPath
    self.skeletonsound = CreateSpeaker(LoadSound(soundFile))
    self.skeletonsound:SetLooping(false)

    -- Navigation
    if self.navmesh then
        self.agent = CreateNavAgent(self.navmesh, 0.5, 1.8)
        self.agent:SetPosition(entity:GetPosition(true))
        self.agent:SetRotation(entity:GetRotation(true).y)
        entity:SetPosition(0, 0, 0)
        entity:SetRotation(0, 180, 0)
        entity:Attach(self.agent)
    end
    
    entity:SetNavObstacle(false)
    entity:SetCollisionType(COLLISION_PLAYER)
    entity:SetMass(0)
    entity:SetPhysicsMode(PHYSICS_RIGIDBODY)
    entity:AddTag("player")
  

    if model then
        local seq = model:FindAnimation("Attack1")
        if seq ~= 0 then
            local count = model:CountAnimationFrames(seq)
            model.skeleton:AddHook(seq, count - 1, Monster.EndAttackHook, self)
        end
    end
end

function MonsterSkeleton:Load(properties, binstream, scene, flags, extra)

    if type(properties.alertsound) == "string" then
        self.sound_alert = LoadSound(properties.alertsound)
    end

    for n = 1, 2 do
        local key = "attacksound" .. tostring(n)
        if type(properties[key]) == "string" then
            self.sound_attack[n] = LoadSound(properties[key])
        end
    end

    self.navmesh = nil
    if #scene.navmeshes > 0 then
        self.navmesh = scene.navmeshes[self.navmeshindex]
    end

    return true
end

-- "GetUp" Animation nach zufälliger Verzögerung starten
function MonsterSkeleton:GetUp()
    if not self.GetUpPlayed then 
       local currentTime = Millisecs()
      if currentTime - self.getUpStart >= self.getUpDelay then
        self.skeletonsound:Play()
        self.skeleton:Animate(1, 0.5, 2000, ANIMATION_ONCE)
        self.GetUpPlayed = true
        self.startTime = Millisecs()  -- Startzeit für Idle-Übergang setzen
      end
    end
end

-- Wechsel zu Idle-Animation
function MonsterSkeleton:Idle()
    if self.GetUpPlayed then        
       self.skeleton:Animate(0, 0.7, 2000, ANIMATION_LOOP)
      -- if self.idle==false then 
       self.idle=true 
       --end
    end
end

function MonsterSkeleton.EndAttackHook(skeleton, monster)  
    local monster = Component(monster)
    monster.attacking = false
end


function MonsterSkeleton:Kill()
    if not self.alive then return end
    self.alive = false
        self.skeleton:Animate("Death", 1, 250, ANIMATION_ONCE)
    if self.agent then
        self.agent:Stop()
        self.skeleton:Detach()
        self.agent = nil
    end
    self.skeleton:SetCollisionType(COLLISION_NONE)
    self.skeleton:SetPickMode(PICK_NONE)
    self:Disable()
end

-- Update-Funktion: Steuerung von GetUp und Idle
function MonsterSkeleton:Update()


    local entity = Model(self.skeleton)
    local model = Model(self.skeleton)
    
    if entity.health <= 0 then
        self:Kill()
        return
    end  


    if not self.enabled then return end

    if self.enabled then 
        if not self.combatready then 
            if self.animation and not self.GetUpPlayed then
                self:GetUp()            
            end        
        
            if self.GetUpPlayed and Millisecs() - self.startTime >= self.idleDelay then
                self:Idle()
            end         
        
            if self.GetUpPlayed and self.idle then        
                self.combatready=true  
            end
        end

    if self.combatready then 
        if self.combatready then 
            self:ScanForTarget() 
        end

        -- Stop attacking if target is dead
        if self.target then
            if self.target.health ~= nil and type(self.target.health) == "number" and self.target.health <= 0 then
                self.target = nil
                if self.agent then self.agent:Stop()
                end
            end
        end
    
        if self.attacking and self.target then
            if self.agent then
                local a = ATan(entity.matrix.t.x - self.target.matrix.t.x, entity.matrix.t.z - self.target.matrix.t.z)
                self.agent:SetRotation(a + 180)
            end
            if not self.attackfinished then
                local world = self.entity:GetWorld()
                if world then
                    if world:GetTime() - self.meleeattacktime > 300 then
                        self.attackfinished = true
                        local pos = entity:GetPosition(true)
                        local dest = self.target:GetPosition(true) + self.target:GetVelocity()
                        local attackrange = 3.0
                        if pos:DistanceToPoint(dest) < attackrange then
                            self.target.health = self.target.health - self.meleedamage
                            for _, c in ipairs(self.target.components) do
                                if c.Damage and type(c.Damage) == "function" then
                                    c:Damage(self.meleedamage, entity)
                                    if self.target.health <= 0 and type(c.Kill) == "function" then
                                        c:Kill(entity)
                                    end
                                end
                            end
                            if self.target.health <= 0 then
                                self.target = nil
                                self:Idle()
                            end
                        end
                    end
                end
            end
            return
        end
        
        --if not self.target and self.combatready then self:ScanForTarget() end
    
        if self.target then
            self.idle=false
            if self.attacking == false and entity:GetDistance(self.target) > 3.0 then
                if self.agent then
                    self.agent:Navigate(self.target:GetPosition(true))
                    self.agent:SetMaxSpeed(10)
                end
                if model then
                    model:Animate("Walk")
                end
            else
                if self.agent then self.agent:Stop() end
                if not self.attacking then
                    local world = entity:GetWorld()
                    if world then self.meleeattacktime = world:GetTime() end
                    local index = Ceil(math.random(1, 1))
                    if model then model:Animate("Attack" .. tostring(index), 1, 100, ANIMATION_ONCE) end
                    if Ceil(Random(0, 1)) == 0 then
                        index = Ceil(Random(1, 1))
                       -- if self.sound_attack[index] then entity:EmitSound(self.sound_attack[index]) end
                    end
                    self.attacking = true
                    self.attackfinished = false
                end
            end
        else
           -- self:Idle()
        end
    end
    end

end

-- Kampflogik für das Monster bleibt erhalten
function MonsterSkeleton:Damage(amount, attacker)
    if entity.health <= 0 then return end
    if entity.health <= 0 then
        local n
        return
    end
    if self.target == nil and attacker ~= Model(self.skeleton) then self.target = attacker end --Make him angry if someone hurts him!    
end

function MonsterSkeleton.RayFilter(entity, extra)
    local ctype = self.skeleton:GetCollisionType()
    return ctype == COLLISION_SCENE or ctype == COLLISION_PROP
end

function MonsterSkeleton:ScanForTarget()

        local entity = Model(self.skeleton)
        local world = entity:GetWorld()
         if world then
             local now = world:GetTime()
         if now < self.nextscanfortargettime then return end
        self.nextscanfortargettime = now + math.random(300, 500)

        local temp = entity:GetPickMode()
        entity:SetPickMode(PICK_NONE)

        local players = world:GetTaggedEntities("good")
        for _, player in ipairs(players) do
            if player.health ~= nil and type(player.health) == "number" and player.health > 0 then
                if player:GetDistance(entity) < self.scanrange then
                    local temp2 = player:GetPickMode()
                    player:SetPickMode(PICK_NONE)
                    local A = entity:GetPosition(true) + Vec3(0, self.eyeheight, 0)
                    local B = player:GetPosition(true) + Vec3(0, 0.5, 0)
                    local pick = world:Pick(A, B, 0, false)
                    player:SetPickMode(temp2)
                    if not pick.success then
                        self.target = player
                        break
                    end
                end
            end
        end
        entity:SetPickMode(temp)
    end
end

function MonsterSkeleton:Save(properties, binstream, scene, flags, extra)
   properties.health = self.entity.health
end

RegisterComponent("MonsterSkeleton", MonsterSkeleton)

return MonsterSkeleton

so far the script is now ok . get up--> idle --> find target--> and attack also if target is not in range walk animation start.

Only with navmesh the Model walks on place and dont move to target. i saw this too in the shooter template too. the monster in the room runs in place. The other Monster run to me

Screenshot 2025-02-12 150109.jpg

Link to comment
Share on other sites

You have to use the debugger to check if the monster is actually ever calling Navigate(), and make sure the method returns true, indicating that it can find a path.

Screenshot looks great!

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...