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?
How exactly do the lua AI files work with each other? Give this a read, mate. Should explain most of your questions.
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
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")
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.
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.
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
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~
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 ) and I just sugest looking trough the workstates and build states in severdSkull's colony mod
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.