Modding Help NPC State help.

Discussion in 'Starbound Modding' started by DoomFire, Jun 27, 2014.

  1. DoomFire

    DoomFire Existential Complex

    Hi! I am working on a mod and am basically trying to create a state that would make an NPC go for a switch when encountering a hostile.
    Basically I have no idea where to even begin. I have taken the Fleestate and copied it, because that is basically what it's going to be. I have also taken a look at the sitstate and such. But yeah, I'm not completely sure.

    What the state should do is make them go for a switch when they see the hostile, then run away, or simply run away if the hostile is too close to them.
    Would this be possible? If so, can I please have some suggestions as to how I might go about doing this?
     
  2. DoomFire

    DoomFire Existential Complex

    Thanks! That helped a bit.

    I have another question though.
    As I mentioned in the OP. I am trying to make an NPC run for a switch. I am just trying to figure out how I might get them to recognise a switch.

    From Main.lua I got this, which is what the NPC's use to open doors that are in their way.
    Code:
      local closedDoorIds = world.objectLineQuery(position, { position[1] + util.clamp(delta[1], -2, 2), position[2] }, { callScript = "hasCapability", callScriptArgs = { "closedDoor" } })
      for _, closedDoorId in pairs(closedDoorIds) do
        if options.openDoorCallback == nil or options.openDoorCallback(closedDoorId) then
          world.callScriptedEntity(closedDoorId, "openDoor")
        end
      end
    Also this, which is from sleepstate.
    Code:
    function sleepState.findUnoccupiedBed()
      local entityIds = world.loungeableQuery(storage.spawnPosition, entity.configParameter("sleep.searchRadius"), { orientation = "lay", order = "nearest" })
      for _, entityId in pairs(entityIds) do
        if not world.loungeableOccupied(entityId) then
          return entityId
        end
      end
    
      return nil
    end
    So far, I have summerised that I need world.objectLineQuery to tell the NPC what he's looking for. But I am trying to figure out how I might do that if he were looking for a, say, momentary switch?

    So far this is what I have done by just guessing. You'll notice that there are a few parts that don't make sense and are directly copied from the other two bits of code.
    Code:
    function leverState.findLever
        local entityIds = world.objectLineQuery(storage.spawnPosition, entity.configParameter("lever.searchRadius"), { callScript = "hasCapability", callScriptArgs = { "output" }, order = "nearest" })
        for _, entityId in pairs(entityIds) do
            if not storage.state(true) then
                world.callScriptedEntity(closedDoorId, "openDoor")
            end
        end
       
    return nil
    end
    The main part I don't completely understand is the...
    { callScript = "hasCapability", callScriptArgs = { "output" }, order = "nearest" }

    But yeah, I can guess that a lot of what I wrote out is completely wrong. I'm kinda new to this LUA thing. o ~ o
     
  3. Well first of all... Line query only searches in a line in front of the NPC. Use world.objectQuery() instead.
    Here is the definition of world.ObjectQuery:
    Other than that though, you are on the right track. There are lots of grammatical/syntax errors in the code you provided, but I am sure you will be able to figure them out on your own. :)

    So to answer your question:
    { callScript = "hasCapability", callScriptArgs = { "output" }, order = "nearest" }

    It is caling a function name called "hasCapability" on the object. passing in the parameter "output" and ordering the entity id's by the closest first and further away objects later in the table.

    If that still isn't enough or you still don't understand. it is essentially doing this:

    Code:
    // "Entity" is the entity in question... not the entity table for functions in this example.
    
    Entity.hasCapability("output")
     
    Last edited: Jun 27, 2014
    connery0 and The | Suit like this.
  4. DoomFire

    DoomFire Existential Complex

    Aah alrighty. Thank you!
    Code:
    function leverState.findLever
        local entityIds = world.objectQuery(storage.spawnPosition, entity.configParameter("lever.searchRadius"), { callScript = "onInteraction", callScriptArgs = { "entity.setAllOutboundNodes" }, order = "nearest" })
        for _, entityId in pairs(entityIds) do
            if not entity.setAllOutboundNodes(true) then
                return entityId
            end
        end
    
    return nil
    end
    So would this be more accurate to what I am trying to do?
    Though I understand that this might make them go for doors and the such. I wasn't too sure how you might use...

    withoutEntityId - an entity id (numeric) that will not be returned, even if it is found in the search region.
    Where might I figure out the numeric ID of an Entity?

    As you may have noticed already. I am struggling to understand a lot of this. o ~ o Thank you so much for explaining things to me.
     
    Last edited: Jun 28, 2014
  5. DoomFire

    DoomFire Existential Complex

    Alright. So we have made a little progress... I 'think' we're getting closer...

    I have created a modified Fleestate which will have the NPC go for a switch before running away from the player...

    Code:
    leverState = {}
    
    function leverState.enterWith(args)
      if args.attackTargetId == nil then return nil end
      if stateName() == "leverState" then return nil end
    
      return {
        reactionTimer = entity.randomizeParameterRange("lever.reactionTimeRange"),
        targetId = args.attackTargetId,
        sourceId = args.attackSourceId,
        wasSourceEntity = wasSourceEntity,
        safeTimer = entity.configParameter("lever.safeTimer"),
        dialogTimer = entity.randomizeParameterRange("lever.dialogTimeRange"),
        foundProtector = false,
        lastPosition = entity.position(),
        stuckTimer = 0,
        activatedALever = false,
      }
    end
    
    function leverState.update(dt, stateData)
      local targetPosition = world.entityPosition(stateData.targetId)
      if targetPosition == nil then
        return true
      end
      local position = entity.position()
      local fromTarget = world.distance(position, targetPosition)
    
      -- It can take a moment to react to whatever just happened
      if stateData.reactionTimer > 0 then
        setFacingDirection(-fromTarget[1])
    
        stateData.reactionTimer = stateData.reactionTimer - dt
        if stateData.reactionTimer <= 0 then
          -- If this npc just saw another npc get attacked, they'll behave differently
          -- than if they were attacked themself
          local wasSourceEntity = stateData.sourceId == entity.id()
          if wasSourceEntity then
            sayToTarget("lever.dialog.helpme", stateData.targetId)
          else
            sayToTarget("lever.dialog.helpthem", stateData.targetId)
          end
        else
          return false
        end
      end
    
      -- If you can find a lever to flip and you're relatively safe, try and activate the alarm
      local entityId = leverState.findLever()
      local leverPosition = world.entityPosition(entityId)
    
      if entityId == not nil and world.magnitude(fromTarget) > entity.configParameter("lever.dangerDistance") and not stateData.activatedALever then
        leverPosition[2] = leverPosition[2] + 1.0
        local position = entity.position()
        local toLever = world.distance(leverPosition, position)
      
        if world.magnitude(toLever) < entity.configParameter("lever.leverRadius") and not world.lineCollision(position, leverPosition, true) then
          world.callScriptedEntity(entityId, "onInteraction")
          stateData.activatedALever = true
        else
          moveTo(leverPosition, dt)
        end
      else
      -- Try to move a safe distance away
      local safeDistance
      if stateData.foundProtector then
        safeDistance = entity.configParameter("lever.safeDistanceWithGuards")
      else
        safeDistance = entity.configParameter("lever.safeDistance")
      end
    
      local safe = not entity.entityInSight(stateData.targetId) or world.magnitude(fromTarget) > safeDistance
    
      if safe then
        stateData.safeTimer = stateData.safeTimer - dt
        if stateData.safeTimer < 0 then
          return true
        end
      else
        moveTo(targetPosition, dt, { run = true, fleeDistance = safeDistance })
    
        -- Don't stay stuck running against a wall
        if position[1] == stateData.lastPosition[1] then
          stateData.stuckTimer = stateData.stuckTimer + dt
          if stateData.stuckTimer >= entity.configParameter("lever.stuckTime") then
            return true, entity.configParameter("lever.stuckCooldown")
          end
        else
          stateData.stuckTimer = 0
        end
    
        stateData.safeTimer = entity.configParameter("lever.safeTimer")
      end
    end
      stateData.lastPosition = position
    
      if stateData.wasSourceEntity then
        if sendNotification("attack", { targetId = stateData.targetId, sourceId = entity.id(), sourceDamageTeam = entity.damageTeam() }) then
          if not stateData.foundProtector then
            local attackerIds = world.npcQuery(entity.position(), 25.0, { callScript = "isAttacking" })
            if #attackerIds > 0 then
              stateData.foundProtector = true
            end
          end
        end
    
        stateData.dialogTimer = stateData.dialogTimer - dt
        if stateData.dialogTimer <= 0 then
          if stateData.foundProtectors then
            sayToTarget("lever.dialog.encourage", stateData.sourceId)
          elseif safe then
            sayToTarget("lever.dialog.safe", stateData.sourceId)
          else
            sayToTarget("lever.dialog.help", stateData.sourceId)
          end
    
          stateData.dialogTimer = entity.randomizeParameterRange("lever.dialogTimeRange")
        end
      end
    
      return false
    end
    
    function leverState.findLever
        local entityIds = world.objectQuery(storage.spawnPosition, entity.configParameter("lever.searchRadius"), { callScript = "onInteraction", callScriptArgs = { "entity.setAllOutboundNodes" }, order = "nearest" })
        for _, entityId in pairs(entityIds) do
            if not entity.setAllOutboundNodes(true) then
                return entityId
            end
        end
      
    return nil
    end
    
    The main issue ( That I can see anyway ) is that this current code would have them leap for anything with an output node. So doors for example. ( Assuming I have used them right. > - > )

    I was mostly wondering if there was anything wrong standing out that was completely wrong? I think I just about have it... but yeah, it would be great if you could clarify anything you see wrong. ^ ~ ^ Thanks again.
     
    Last edited: Jun 29, 2014
  6. In Disney for a week and no laptop now. Only my phone. If you cant figure it out before I get back ill give it a shot
     
  7. DoomFire

    DoomFire Existential Complex

    That's fine~ In no particular rush. ^ - ^

    Thank you very much~ I've been testing it and trying to figure stuff out. But yeah.

    Thank you again for your help so far~
     
  8. connery0

    connery0 Void-Bound Voyager

    I'm helping DoomFire with this mod and just want to say that we figured out how to do what we wanted, thanks a lot to severdskullz posts here, and even more by looking through his mod :)

    For everybody else that sumbles on this thread looking on how to use world.objectQuery, you can also use a object's name as a filter.
    for example:
    Code:
    world.objectQuery(entity.position(), entity.configParameter("defenceState.searchDistance"), {order = "nearest", name = "defenceButton"})
    this would return the closest "defenceButton" object ( in a table? I'm not too sure about that yet :p )

    and I just sugest looking trough the workstates and build states in severdSkull's colony mod
     
  9. No, closest is just the ordering of the IDs. Closes will be first in the table and further away ones will be later. So to clarify... if 10 objescts match the search criteria then you will get a table of 10 id's. if oder is set to nearest... then id 1 will be the closest and id 10 will be the furthest.

    Edit: My states are *decent* but may not be what he wants to do. Might help for a template or function usage though. I dont mind sharing what I know... but keep in mind im limited to a phone so typing long explanations is a no-go right now.
     
    Last edited: Jun 30, 2014

Share This Page