News Forums Search Search Results for 'tree set variable'

Viewing 15 results - 31 through 45 (of 112 total)
  • Author
    Search Results
  • #32961

    prime
    Keymaster

    Here’s a quick rundown of Memory variables in RAIN.

    By default, RAIN uses a BasicMemory with every AI. BasicMemory stores information accessible to an AI through code or through the Behavior Tree system. Variables stored in memory are specific to each individual AI (i.e., it isn’t a global blackboard.)

    From code, you can get the value of a variable like this:

    ai.WorkingMemory.GetItem<float>("myvariable")

    of course, the method call is Generic, so replace “float” with whatever variable type you are retrieving (string, GameObject, etc.)

    You will set the value of a variable like this:

    ai.WorkingMemory.SetItem<float>("myvariable", 10.0f);

    You will commonly call that code from within a CustomAction in your behavior tree, but really you could call it from anywhere in your own code. You just need access to the AI object. To get that, you would look for an AIRig component.

    AIRig tRig = gameObject.GetComponentInChildren<AIRig>();

    and then it would be

    tRig.AI.WorkingMemory.GetItem<float>("myvariable");

    In the behavior tree itself, you can access variables from any expression field. Expression fields are marked with an “e” symbol. Simply refer to the variable name in the expression field in order to access its value. So in an expression field

    myvariable = 10.0

    or

    myvariable < 10

    would work fine.

    Expression fields in BT nodes exist all over the place. There’s even a node called “Evaluate Expression” which let’s you execute or evaluate any expression you like.

    Hope that helps. Feel free to send a support email if you have further questions. Or post back here and I’ll try to answer.

    • This reply was modified 4 months, 1 week ago by  prime. Reason: fixed bad code formatting

    tapticc
    Participant

    <behaviortree version=”1.1″ repeatuntil=”” name=”NerdBehaviour”>

    <parallel tiebreaker=”fail” succeed=”all” repeatuntil=”” name=”parallel” fail=”any”>

    <action repeatuntil=”” parametervalues=”” parameters=”” namespace=”(global)” name=”Set Next Wander Location” classname=”WanderLocation” />

    <waypointpath waypointsetvariable=”"Wander"” waypointactiontype=”path” traversetype=”pingpong” traverseorder=”forward” repeatuntil=”” pathtargetvariable=”nextPathStop” name=”waypointpath” movetargetvariable=”nextMoveStop”>

    <parallel tiebreaker=”fail” succeed=”all” repeatuntil=”” name=”parallel” fail=”any”>

    <mecparam valueexpression=”1″ repeatuntil=”” parametertype=”float” parametername=”AnimSpeed” name=”Set AnimSpeed to 1″ damptime=”0.5″ />

    <move turnspeed=”” repeatuntil=”” name=”Move to next stop” movetarget=”nextMoveStop” movespeed=”1″ facetarget=”nextMoveStop” closeenoughdistance=”” closeenoughangle=”” />

    </parallel>
    </waypointpath>
    </parallel>
    </behaviortree>

    #32824

    CodersExpo
    Participant

    Hey Yeuwah, I recently purchased Behavior Designer, NC, AI for Mechanim and A* Pathfinding. I’ve been working with Justin Mosiman and Gavalakis Vaggelis (nuverian) on BD and NC lately. Got to love the competition and new products coming out. I absolutely love both BD and NC, though I can’t say they are better than RAIN.

    Each tool serves a purpose and will fit into your development flow differently depending on your project size, developer skill level, required functionality, development time, budget, etc….you understand. Nothing wrong with having several tools at your disposal. BD and NC are so similar and offer great behavior tree layouts and functionality.

    I find with ALL of these (BD, NC, AI4Mec, A* and RAIN) your code skills need to be up to speed to get your AI working to customized/acceptable degree. However, I give RAIN the gold star on “out of the box in minutes” setting up and configuring of basic (patrol, attack, search, flee, cover) type behaviors.

    With all of these tools documentation is a bit dated and there is very little in the way of video tutorials (though RAIN did just revamp the wiki and posts videos quite regularly). However, that really applies to nearly all the assets I’ve acquired. It’s simply the nature of advancing technology and not necessarily a show stopper if you get good developer and community support. I have received excellent support from each one in this case.

    In terms of functionality, each offers essentially the same out of the box abilities, in terms of behavior trees and how they manage your character(s). None of these solutions are really “state machines”; though, I like what BD and NC have done to create a canvas/designer for your own state machine and speech work flows. This is very nice indeed for helping to manage YOUR code. I’m sure they have learned from each other and taken bits and pieces from React, RAIN and others….This is the nature of the market. We all learn from each other and try to improve.

    I know RAIN is entertaining ways to integrate with other known/popular Unity plugins (per the communities request) and have considered ways to incorporate some of the most appreciated features of other tools. Keep in mind, they have a huge list of items that are in their development plans and are in the works now. In fact, they are getting ready to release another package soon with several updates and bug fixes.

    I was confused when I looked at the NC comparison list because right off the bat I saw RAIN didn’t have a check for things like Live Editing, Execution Debug Controls, Custom Variable Types, Saving/Loading of Variables, Agent Blackboard Sharing among others. I have been able to do most everything in RAIN that I can in both BD and NC. I wish they would explain better what exactly they are able to do here that can’t be done in RAIN. If you know about RAINS custom implementations of Memory, Mind, Motion, Extensions etc…then you know you can share any interface you create and cover most of those points.

    RAIN does use dll libraries and in some cases this prevents some “Live Editing” and Debugging. However, if you have an issue you feel is in those libraries just ask the RAIN developers and they are all over it and will provide help (and possibly add new application enhancements). I understand they don’t want to expose their code publicly and why would they. Also, compiled code performs better than scripts in most cases. I also like that it is all written in C#, which also improves performance. Anyway, I would leave the rest here for the developers to comment on since they know more than me about RAIN This is just my observation.

    All in all…these are ALL excellent tools and each has its ++ and -. I like very much that RAIN is FREE, has been around a long time, has exciting plans on the horizon for making RAIN MORE than just a behavior tree work flow designer. RAINS goal is to provide a way to give your characters personality, true memory and learned behavior. I don’t get that from the other tools or see that comming. So while I like what each has to offer, I’m going to stay close to RAIN because I like the direction and philosophy this company is moving. Good things come to those who wait….Great things come to those who are actively involved

    Thanks for the post! This will open up a great discussion I hope. I’m very interested to know what you like about any of these tools that you just can’t get out of RAIN.

    #32600

    ZackHanzo
    Participant

    Hi guys im new with unity and of course new with Rain. I follow all the tuts from the utube channel and all works good. Now im working with Meca, and all works good too.

    So.. my AI character, chases me, stop when he reachs my position and do the animation of the hit…. So… i get the point of all the tuts but:

    Now i want to control the death of my AI character, but i dont know how. I mean

    I have in the pistol a script which shoots, and if the AI character is in that layer/tag do all the things. I have a simple script to receive the damage with the function TakeDamage like this:

    `public class HealthEnemy : MonoBehaviour {

    public float hitPoints=100f;
    public float currentHitPoints;
    void Start () {

    currentHitPoints = hitPoints;

    }

    void Update () {

    }

    public void TakeDamage(float amt)
    {
    currentHitPoints -= amt;
    }

    }`

    Well… i have this script on the root of my AI character, to deal with the death. And now i want to use that variable currentHitPoints on Rain…. So if currentHitPoints>0 make all the stuff i put on the behaviour tree , and if currentHitPoints<=0 deal with the death of AI character and get destroyed after the animation…

    So, my question is, how i get that var (currentHitPoints) value on Rain? I tried making it public, and using a constraint with the condition currentHitPoints>0 but it didnt work.

    Anyone can help me? Or know any tut about this?

    PS: I think i could deal with the dead making a script who destroy the AI gameobject, and making it through scripting on the animador (like… animador.setBool(“Death”,true);) But i think its interesting to me understand how it works, to make new conditions like a animation when the AI received the hit… or so..

    • This topic was modified 5 months ago by  ZackHanzo.

    Sigil
    Keymaster

    Sorry I didn’t get back to this sooner, the idea I came up with is similar to your second option.

    If you have the networks in place ahead of time you can use a Waypoint Patrol node whenever you go into an alarmed mode. Normally you just put a Move node inside the Waypoint Patrol, but you can also put additional behavior tree logic. So for instance (using shorthand here):

    Sequencer
       Evaluate Expression (if alarmed)
       Waypoint Patrol (use the quoted name of your Waypoint Route)
          Move (move to the Move Target Variable specified in the Waypoint Patrol)
          Random
             Evaluate Expression (just have it return success)
             Animate (play a looking around animation)
    

    So what this will do for you is this:

    If the AI is currently alarmed, patrol the Waypoint Route specified. After successfully reaching each waypoint randomly choose to either do nothing (the Evaluate Expression that just returns) or to play an animation (Animate). Then head on to the next waypoint.

    The patrol will repeat forever, so you can put the whole thing in a parallel, making the AI only patrol for a limited time, like so:

    Sequencer
       Evaluate Expression
       Parallel (set Succeed to any
          Timer (like 10 seconds, or random(7, 10) would work too)
          Waypoint Patrol
             Move
             Random
    ...
    

    Let me know if you need more info, Behavior Trees can be complex sometimes (and we’re working on making the documentation better).

    #31989

    TRALLALAL
    Participant

    Since I cannot edit posts I’ll leave this right here, that XML version might be wrong as I had a node set to repeat forever but it shouldn’t be like that.
    This one is the one I’m using now, still doesn’t work.

    #31984

    TRALLALAL
    Participant

    Fixed, I was stupid, the player was not in its field of view

    Now I’ve got another problem, I’ll write her so I don’t have to start another topic.
    The behavior tree is not looping, I noticed actions never return true and sometimes I get the same error I posted above,
    NullReferenceException: Object reference not set to an instance of an object
    RAIN.BehaviorTrees.BTWaypointNode.Start (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTParallelNode.Execute (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTConstraintNode.Execute (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTSelectorNode.Execute (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTParallelNode.Execute (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTParallelNode.Execute (RAIN.Core.AI ai)
    RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai)
    RAIN.Minds.BasicMind.Think ()
    RAIN.Core.AI.Think ()
    RAIN.Core.AIRig.AIUpdate ()
    RAIN.Core.AIRig.Update ()

    If needed, this is the XML of the behavior tree

    <behaviortree version=”1.1″ repeatuntil=”” name=”Patrol”><parallel tiebreaker=”fail” succeed=”all” repeatuntil=”running” name=”root” fail=”any”><detect sensor=”"Eyes"” repeatuntil=”running” name=”Player team in field of view” entityobjectvariable=”varHero” aspectvariable=”” aspectobjectvariable=”” aspect=”"Hero"” /><detect sensor=”"CloseCover"” repeatuntil=”running” name=”Cover is close to entity” entityobjectvariable=”closeCover” aspectvariable=”” aspectobjectvariable=”” aspect=”"Cover"” /><parallel tiebreaker=”fail” succeed=”all” repeatuntil=”running” name=”parallel” fail=”any”><selector usepriorities=”False” repeatuntil=”” name=”selector”><constraint repeatuntil=”” priority=”” name=”Wander around waypoints” constraint=”varHero == null”><parallel tiebreaker=”fail” succeed=”all” repeatuntil=”” name=”parallel” fail=”any”><waypointpatrol waypointsetvariable=”WP” waypointactiontype=”patrol” traversetype=”loop” traverseorder=”forward” repeatuntil=”” pathtargetvariable=”” name=”WP” movetargetvariable=”WP”><move turnspeed=”” repeatuntil=”” name=”Move to Waypoint” movetarget=”WP” movespeed=”5″ facetarget=”” closeenoughdistance=”” closeenoughangle=”” /></waypointpatrol><animate repeatuntil=”” name=”Walking animation” animationstate=”Soldier_Run” /></parallel></constraint><constraint repeatuntil=”” priority=”” name=”Enter combat status” constraint=”varHero != null”><parallel tiebreaker=”fail” succeed=”all” repeatuntil=”” name=”parallel” fail=”any”><action repeatuntil=”” parametervalues=”” parameters=”” namespace=”(global)” name=”Find closest cover(Script)” classname=”AIGunHandling” /><move turnspeed=”” repeatuntil=”” name=”Move to cover” movetarget=”temporaryCover” movespeed=”10″ facetarget=”” closeenoughdistance=”2″ closeenoughangle=”” /><animate repeatuntil=”” name=”Sprint animation” animationstate=”Soldier_Sprint” /></parallel></constraint><constraint repeatuntil=”” priority=”” name=”Get in cover” constraint=”closeCover != null”><parallel tiebreaker=”succeed” succeed=”all” repeatuntil=”” name=”parallel” fail=”any”><random repeatuntil=”” name=”Random action”><animate weight=”” repeatuntil=”” name=”Stay in cover” animationstate=”Soldier_Crouch_IdleTrans” /><animate weight=”” repeatuntil=”” name=”Get up and shoot” animationstate=”Soldier_Aim_Idle” /></random></parallel></constraint></selector></parallel></parallel></behaviortree>

    • This reply was modified 5 months, 1 week ago by  TRALLALAL.

    Sigil
    Keymaster

    Probably the easiest way to handle this is with a custom action, and it only needs to do one small thing: copy the current position of your target to new variable. Drop this code into a new CSharp file in your project and you can add a custom action to your behavior tree that points to it. You would put this right after a successful detect of the player.

    
    using RAIN.Action;
    using RAIN.Core;
    using RAIN.Entities.Aspects;
    using UnityEngine;
    [RAINAction]
    public class StoreLastPosition : RAINAction
    {
        public StoreLastPosition()
        {
            actionName = "StoreLastPosition";
        }
        public override ActionResult Execute(AI ai)
        {
            // If you use the Aspect Variable property
            //RAINAspect tPlayerAspect = ai.WorkingMemory.GetItem<RAINAspect>("playerAspect");
            // If you use the Mount Point Variable property
            //Transform tPlayerMount = ai.WorkingMemory.GetItem<Transform>("playerMount");
            // If you use the Form Variable property
            GameObject tPlayerForm = ai.WorkingMemory.GetItem<GameObject>("playerForm");
            // Store the last position in our memory for use later
            ai.WorkingMemory.SetItem<Vector3>("playerLastPosition", tPlayerForm.transform.position);
            return ActionResult.SUCCESS;
        }
    }
    

    This will give you a new variable (playerLastPosition) to use in a move node when you no longer detect the player. Let me know if you need any help with it.

    #31549

    OMGItzGnaR
    Participant

    Hey everyone! I’m going to do my best to explain the situation. My brains are fried at the moment, so I decided to turn to the community for help. :)

    So for the past couple days, I’ve been trying to create a simple Zombie AI. To wander (at the moment, follow a set path), Detect the player, and when the zombie is within a specific distance attack. All is working well, I have the zombie detect the player, and run towards him, and once he’s within a certain distance, do an attack animation. And if the player leaves the First Detector range, he returns to wandering/path. The only issue I am having, is that the Sensor for detection, (eyes) has a limited FOV. So the player can run around behind the zombie, without attracting aggro. So, naturally I thought, I’d just make another sensor with basically the same properties as the “eyes” sensor. and name it something like Sense. Here’s a screenshot of my sensors ATM.

    Sensor Setup

    The Blue sensor are the “eyes” the Red is the “sense” and the smaller white one is the “touch” for the attack anim.

    I can aggro with the eyes sensor, but not the Sense sensor.

    The current setup I have, the two aggro sensors (eyes, sense) both have the same FormVariable, because I figured, if you could trigger one, you could trigger both. I don’t know how to set up/use Audio sensors, I figured visual would work. The Eyes Sensor has a horizontal angle of 136 with LOS required, while the Sense Sensor has a 360 with no LOS required. I’ve been trying to get this to work for a while. Now here’s the strange part. If I Cut/remove the “eyes” Detector from the BT, I can aggro with the Sense Sensor.

    Should I just learn how to set up audio sensors? or is it possible to get this working with multiple visual sensors.
    If it’s needed, here’s my BT XML

    <behaviortree version="1.1" repeatuntil="" name="Zombie1"><parallel tiebreaker="fail" succeed="all" repeatuntil="" name="parallel" fail="any"><detect sensor=""close"" repeatuntil="running" name="ATKRange" entityobjectvariable="varTouch" aspectvariable="" aspectobjectvariable="" aspect=""aSurvivor"" /><detect sensor=""sense"" repeatuntil="running" name="Sense" entityobjectvariable="varSeen" aspectvariable="" aspectobjectvariable="" aspect=""aSurvivor"" /><detect sensor=""eyes"" repeatuntil="running" name="See" entityobjectvariable="varSeen" aspectvariable="" aspectobjectvariable="" aspect=""aSurvivor"" /><selector usepriorities="False" repeatuntil="" name="Is Survivor seen?"><constraint repeatuntil="" priority="" name="No" constraint="varSeen == null"><parallel tiebreaker="fail" succeed="all" repeatuntil="" name="parallel" fail="any"><mecparam valueexpression="0.0" repeatuntil="" parametertype="float" parametername="Atk" name="ATK = 0" damptime="0" /><waypointpatrol waypointsetvariable="PatrolRoute" waypointactiontype="patrol" traversetype="loop" traverseorder="forward" repeatuntil="" pathtargetvariable="" name="Walk" movetargetvariable="nextWP"><move turnspeed="" repeatuntil="" name="move to WP" movetarget="nextWP" movespeed="1" facetarget="" closeenoughdistance="" closeenoughangle="" /></waypointpatrol><animate repeatuntil="" name="Walk" animationstate="Walk" /></parallel></constraint><constraint repeatuntil="" priority="" name="Yes" constraint="varSeen != null"><selector usepriorities="False" repeatuntil="running" name="Is Survivor within attack range?"><constraint repeatuntil="" priority="" name="Yes" constraint="varTouch != null"><move turnspeed="" repeatuntil="" name="Face" movetarget="" movespeed="" facetarget="varSurvivor" closeenoughdistance="" closeenoughangle="" /><animate repeatuntil="" name="Attack" animationstate="Atk" /></constraint><constraint repeatuntil="" priority="" name="No" constraint="varTouch == null"><parallel tiebreaker="fail" succeed="all" repeatuntil="" name="parallel" fail="any"><animate repeatuntil="" name="Run" animationstate="Run" /><mecparam valueexpression="0.0" repeatuntil="" parametertype="float" parametername="Atk" name="Atk = 0" damptime="0" /><move turnspeed="" repeatuntil="" name="Move to Survivor" movetarget="varSeen" movespeed="4" facetarget="" closeenoughdistance="" closeenoughangle="" /></parallel></constraint></selector></constraint></selector></parallel></behaviortree>
    
    • This topic was modified 5 months, 2 weeks ago by  OMGItzGnaR.
    • This topic was modified 5 months, 2 weeks ago by  OMGItzGnaR.
    #31386

    Sigil
    Keymaster

    Alright, let’s take a step back real quick.

    The CustomMind example is probably more than you need for going between two behavior trees. In particular is there a reason the trees are separate? Using a Selector or Sequencer you can probably set this up in one behavior tree.

    If you are using a behavior tree to start the talking/dialogue you can set variables directly in the tree (with the Expression node). To affect the NPCs behavior you can use a custom action to set variables in the NPCs memory to change its behavior.

    If you are still working on this reply back and I can give you some pointers.

    #31325

    Sigil
    Keymaster

    I’m assuming you want to get your AI back to its start state, or at least close. ResetBehavior resets the behavior tree back to its start state, but it is very likely your behavior tree is storing additional information in the AI memory (any variables it created for instance). You would have to clear any of those values out as well (AI.WorkingMemory.RemoveItem(“someVariable”)).

    #28275

    prime
    Keymaster

    Based on your description, I would create a Custom Action in the behavior tree. In the associated custom action script, you can perform all the steps you described.

    The trickiest part may be locating the score keeper script. The two common ways we link game scripts to AI are:

    1) Attach the game script to a known object that you can detect with a sensor. For example, if the score keeper script is attached to the player, you could have the AI detect the player and then just use GetComponentInChildren() to find the component and make whatever calls you need. I usually find scripts like that in my Start() method of my custom action.

    2) Create a singleton that provides access to shared functionality. This can be done in a couple of ways:
    - You could create an easily Findable object in the root of the scene hierarchy. Maybe a game object called Global that you could find by name. Find the object once in the Start() of whatever custom actions need it, then store it in a variable so you don’t need to keep finding it.
    - You could create a public static class with an Instance() method that returns the singleton object. This is how we often handle it.

    One last note - it is usually slow to actually destroy and recreate game objects. I recommend just re-using your AI when you “destroy and respawn”. You can clear out an AI memory by calling ai.WorkingMemory.Clear() from a custom action script. You can restart the behavior tree by calling ((BasicMind)ai.Mind).ResetBehavior()

    #28113

    prime
    Keymaster

    RAIN 2 uses Waypoint nodes in the behavior tree. To use a waypoint network, add a “Choose Path Waypoints” node from the Behavior Tree Actions menu. Then put your Move node inside it. Set the Waypoint Network to the name of your waypoint network in the scene (use quotes as “My Network”). Set your Path Target to a variable representing where you’re moving (no quotes for variable names) or set a navigation target (as navigationtarget(“MyTarget”)). Set the Move Variable to the name of the variable that the Move node you added will be moving to. (I usually use moveTarget). Make sure your Move node uses the same variable name.

    That’s for a waypoint network used as a path, which is always “one way”.


    mqnguyen42
    Participant

    When I started using RAIN, I created a simple guard behavior system.
    When a guard got passed the name of a new enemy, it would stop chasing the previous enemy and start chasing the new one.

    This involved:
    1. A Non-RAINAction script that contained the name of the entity that the guard should be chasing
    2. A custom action script that checked the above script and placed the name into the AI’s working memory
    3. A constraint system that selected the guard’s behavior based on the variable in the AI’s memory

    Behavior tree, notice constraints checking for “enemyName”

    Setting variable “enemyName” into AI memory. This is the action “Find Nearest Enemy”

    So in your case, create a unity script for time of day.
    Create a custom action (Like “Find Nearest Enemy”) to read off that data and put it in memory
    Add three constraints checking for each time of day.

    I hope this wasn’t too lengthy, but it should help you out a lot.

    #27760

    glennpow
    Participant

    Update: I do now strongly believe that Selectors will only restart from the first child when they’ve received a Success response from another child (and the Selector is set to repeat Forever). Is this correct?
    If so, then what is the proper way for the following simple BT to work, so that when the “itr” memory variable reaches 100, it will break the patrol branch, and allow the first Condition to execute? This would cause it to pause for a few seconds and then restart “itr” to zero. (Assume that the patrol branch always returns Running, since the character will keep walking to waypoints forever).

    <behaviortree version="1.1" repeatuntil="" name="Selector Test">
      <parallel tiebreaker="fail" succeed="all" repeatuntil="" name="root" fail="any">
        <expression returnvalue="success" repeatuntil="running" name="expression" expression="itr += 1" />
        <selector usepriorities="False" repeatuntil="running" name="check task">
          <constraint repeatuntil="" priority="" name="animate" constraint="itr >= 100">
            <timer waitforsec="3" returnvalue="success" name="timer" />
            <expression returnvalue="success" repeatuntil="" name="expression" expression="itr = 0" />
          </constraint>
          <constraint repeatuntil="" priority="" name="patrol" constraint="true">
            <parallel tiebreaker="fail" succeed="any" repeatuntil="" name="patrol" fail="any">
              <waypointpatrol waypointsetvariable=""Waypoint Route"" waypointactiontype="patrol" traversetype="loop" traverseorder="forward" repeatuntil="" pathtargetvariable="" name="waypointpatrol" movetargetvariable="moveTarget">
                <move turnspeed="" repeatuntil="" name="move" movetarget="moveTarget" movespeed="" facetarget="" closeenoughdistance="" closeenoughangle="" />
              </waypointpatrol>
              <animate repeatuntil="running" name="animate patrol" animationstate="Walk" />
            </parallel>
          </constraint>
        </selector>
      </parallel>
    </behaviortree>
Viewing 15 results - 31 through 45 (of 112 total)