API Docs for:
Show:

File: gameplay\AI\System.js

/**
 System responsible for processing AI entities. It triggers the same events as the keyboard input system and controlls
 AI entities that way. Thanks to this, the logic used for controlling the player character can be reused by the AI scripts.
 The system requires only the AI component to be present, but the various scripts may require more than that to function.
 @class AISystem
 @constructor
 @param entitySystemManager {Manager} The entity system manager whose entities this system will be working on.
 @param physicsSystem {PhysicsSystem} The physics system is used for various world queries.
 @param collisionMasks {Object} An object holding bit masks used for determining collision categories.
 @extends EventHandler
 */
function AISystem(entitySystemManager, physicsSystem, collisionMasks){
    
    //Inherit from the event handling object.
    EventHandler.call(this);
    
    var thisAISystem = this,
    
    //Facing directions.
        right = 'right',
        left = 'left',
        
    //Event names.
        moveLeft = 'moveLeft',
        moveRight = 'moveRight';
    
    //=====AI Scripts=====
    
    var walk = (function(){
        var queryingRay = new Ray(),
            entityPosition = new Vector2D();
            
        return function(aIEntity, deltaTime){
            var position = aIEntity.get(Position),
                groundedMovement = aIEntity.get(GroundedMovement),
                facingDirection = aIEntity.get(FacingDirection),
                aI = aIEntity.get(AI),
                scriptData = aI.getScript(AIScriptWalk),
                rayQuery, wallDetectorRightRay;
                
            if(!(position && groundedMovement && facingDirection)){
                return;
            }
            
            wallDetectorRightRay = scriptData._wallDetectorRightRay;
            
            //Quit if the required script data is not present.
            if(!wallDetectorRightRay){
                return;
            }
            
            position.getPosition(entityPosition);
            
            switch(facingDirection.getFacingDirection()){
                case left :
                    //Check for walls on the left.
                    queryingRay.begin.x = entityPosition.x - wallDetectorRightRay.begin.x;
                    queryingRay.begin.y = entityPosition.y + wallDetectorRightRay.begin.y;
                    queryingRay.vector.x = -wallDetectorRightRay.vector.x;
                    queryingRay.vector.y = wallDetectorRightRay.vector.y;
                    rayQuery = physicsSystem.world.queryEntitiesWithRay(queryingRay, 0xFFFFFFFF, collisionMasks.wall);
                        
                    //If a wall is detected, turn around.
                    if(rayQuery){
                        facingDirection.setFacingDirection(right);
                    }                    
                    break;
                case right :
                    //Check for walls on the right.
                    entityPosition.added(wallDetectorRightRay.begin, queryingRay.begin);
                    queryingRay.vector.x = wallDetectorRightRay.vector.x;
                    queryingRay.vector.y = wallDetectorRightRay.vector.y;
                    rayQuery = physicsSystem.world.queryEntitiesWithRay(queryingRay, 0xFFFFFFFF, collisionMasks.wall);
                    
                    //If a wall is detected, turn around.
                    if(rayQuery){
                        facingDirection.setFacingDirection(left);
                    }
                    break;
            }
            
            //Move.
            switch(facingDirection.getFacingDirection()){
                case left : thisAISystem.trigger(moveLeft, aIEntity);
                    break;
                case right : thisAISystem.trigger(moveRight, aIEntity);
                    break;
            }
        };
    })();
    
    var avoidGaps = (function(){
        var queryingRay = new Ray(),
            entityPosition = new Vector2D();
            
        return function(aIEntity, deltaTime){
            var position = aIEntity.get(Position),
                groundedMovement = aIEntity.get(GroundedMovement),
                facingDirection = aIEntity.get(FacingDirection),
                aI = aIEntity.get(AI),
                scriptData = aI.getScript(AIScriptAvoidGaps),
                rayQuery, gapDetectorRightRay;
                
            if(!(position && groundedMovement && facingDirection)){
                return;
            }
            
            //Quit if the entity is not on the ground.
            if(!groundedMovement.isOnGround()){
                return;
            }
            
            gapDetectorRightRay = scriptData._gapDetectorRightRay;
            
            //Quit if the required script data is not present.
            if(!gapDetectorRightRay){
                return;
            }
            
            position.getPosition(entityPosition);
            
            switch(facingDirection.getFacingDirection()){
                case left :
                    //Check for gaps.
                    queryingRay.begin.x = entityPosition.x - gapDetectorRightRay.begin.x;
                    queryingRay.begin.y = entityPosition.y + gapDetectorRightRay.begin.y;
                    queryingRay.vector.x = -gapDetectorRightRay.vector.x;
                    queryingRay.vector.y = gapDetectorRightRay.vector.y;
                    rayQuery = physicsSystem.world.queryEntitiesWithRay(queryingRay, 0xFFFFFFFF, collisionMasks.wall);
                    
                    if(!rayQuery){
                        facingDirection.setFacingDirection(right);
                    }
                    break;
                case right :
                    //Check for gaps.
                    entityPosition.added(gapDetectorRightRay.begin, queryingRay.begin);
                    queryingRay.vector.x = gapDetectorRightRay.vector.x;
                    queryingRay.vector.y = gapDetectorRightRay.vector.y;
                    rayQuery = physicsSystem.world.queryEntitiesWithRay(queryingRay, 0xFFFFFFFF, collisionMasks.wall);
                    
                    if(!rayQuery){
                        facingDirection.setFacingDirection(left);
                    }
                    break;
            }
        };
    })();
    
    var chargeEnemies = (function(){
        var queryingRay = new Ray();
            
        return function(aIEntity, deltaTime){
            var position = aIEntity.get(Position),
                groundedMovement = aIEntity.get(GroundedMovement),
                facingDirection = aIEntity.get(FacingDirection),
                aI = aIEntity.get(AI),
                spellEffects = aIEntity.get(SpellEffects),
                scriptData = aI.getScript(AIScriptChargeEnemies),
                rayQuery;
                
            if(!(position && groundedMovement && facingDirection && spellEffects)){
                return;
            }
            
            position.getPosition(queryingRay.begin);
            
            switch(facingDirection.getFacingDirection()){
                case left :
                    groundedMovement.getGroundRightTangent(queryingRay.vector);
                    queryingRay.vector.invert();
                    break;
                case right :
                    groundedMovement.getGroundRightTangent(queryingRay.vector);
                    break;
            }
            
            queryingRay.vector.multiply(scriptData._chargeTargetDetectorRange);
            
            rayQuery = physicsSystem.world.queryEntitiesWithRay(queryingRay, 0xFFFFFFFF, aI._foeCollideWith);
            
            if(rayQuery){
                //If an enemy entity was detected and this entity is already charging, update the charging spell effect.
                if(scriptData._isCharging){
                    scriptData._chargingSpellEffect._durationRemaining = deltaTime * 4;
                
                //If an enemy entity was detected and this entity is not already charging
                //create a new spell effect to increase the movement of the entity and begin charging.
                }else{
                    scriptData._chargingSpellEffect = new MovementSpeedSpellEffect(deltaTime * 4, scriptData._chargingSpeedMultiplier);
                    spellEffects.applySpellEffect(scriptData._chargingSpellEffect);
                    
                   scriptData._isCharging = true;
                }
                
            //If an entity was not detected, end the charge.
            }else{
                scriptData._isCharging = false;
            }
        };
    })();
    
    //The script function indices in this array should correspond to the script identifiers on the script objects.
    var scripts = [
                   walk,
                   avoidGaps,
                   chargeEnemies
                   ];
    
    var aIEntities = entitySystemManager.createAspect([AI]),
        handleAI = true;
        
    /**
     Disables the processing of all AI entities.
     @method disable
     */
    this.disable = function(){
        handleAI = false;
    };
    
    /**
     Enables the processing of all AI entities.
     @method enable
     */
    this.enable = function(){
        handleAI = true;
    };
    
    /**
     Updates all AI entities by processing scripts they have attached.
     @method update
     @param deltaTime {Number}
     */
    this.update = function(deltaTime){
        if(handleAI){
            aIEntities.iterate(function(entity){
                var aI = entity.get(AI);
                
                for(var i=0; i<aI._scripts.length; ++i){
                    //Check if a script is present.
                    if(aI._scripts[i]){
                        //Call a script function corresponding to this script for this entity.
                        scripts[i](entity, deltaTime);
                    }
                }
            });
        }
    };
    
    /**
     @method destroy
     */
    this.destroy = function(){
        aIEntities.destroy();
    };
}

//Inherit from the event handling object.
AISystem.prototype = Object.create(EventHandler.prototype);
AISystem.prototype.constructor = AISystem;