Skip to content

Engineer Combat Task

Praneet Dhoolia edited this page Sep 11, 2023 · 46 revisions

Introduction

The EngineerCombatTask implements the entire shooting functionality of Engineers and enables them to detect and attack enemy mobs. It scans for mobs every second and takes a high priority if a mob is detected.

It is ran by the HumanWanderTask and works in coordination with HumanWaitTask and HumanMovementTask to give the Engineers a fighting chance as a last straw against enemies.

Responsibilities of this task include switching through multiple Engineer states, playing the required animations and sounds, and spawning Projectiles in the enemy's direction.

Usage

This task is instantiated by HumanWanderTask through the EngineerCombatTask(float maxRange) constructor, which takes the maximum shooting range of the Engineer entity as a parameter.

UML

The UML for this class has been detailed in the HumanWanderTask's wiki page.

Task Actions

The following task actions are called based on the game area's current state inside the overridden update() method, which is handled by the task runner implemented in AITaskComponent.

Primary Methods

Helper Methods

Detecting Enemy Mobs: isTargetVisible()

This method is used to detect any mobs within 5 lanes of the Engineer on either end of the map.

To detect a mob in a certain lane, the inbuilt physics.raycast() method is used between two vector positions on the map to detect any entities within a certain layer.

If detected, the position of the enemy mob is stored in a list. After the detection sequence finishes running, the method returns true or false depending on whether one or more targets have been found.

Code:

public boolean isTargetVisible() {
    Vector2 position = owner.getEntity().getCenterPosition();

    for (int i = 5; i > -5; i--) {
        if (physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET, hit)) {
            hits.add(hit);
            targets.add(new Vector2(position.x + maxRange, position.y + i));
        }
    }
    return !hits.isEmpty();
}

Changing Engineer State: updateEngineerState()

This method runs a state machine for the Engineer entity to enable switching between idle and firing states.

If the Engineer is idle, i.e in the IDLE_RIGHT (Right facing) state, the state machine detects for any targets using the isTargetVisible() method. If a target is found, the state is updated to FIRING and the firing animation is played for the Engineer entity.

If the Engineer is in the FIRING state, the state machine first detects whether all targets have been downed. If there is no remaining target, the state is switched back to IDLE_RIGHT with the corresponding animation. If there are existing targets, the FIRING event is triggered to play the animation, and a new Projectile is created towards the enemy's direction.

The state machine also checks whether the Engineer has fired a certain number of rounds, defined by weaponCapacity. Once this number is reached, the state machine will wait out exactly one update sequence (1 second in real time) to make it seem like the Engineer is reloading its weapon. Once this period is over, the shotsFired variable is reset to allow the engineer to fire consistently again.

Code:

public void updateEngineerState() {
    // configure engineer state depending on target visibility
    switch (engineerState) {
        case IDLE_RIGHT -> {
            // targets detected in idle mode - start deployment
            if (isTargetVisible()) {
                combatState();
            }
        }
        case FIRING -> {
            // targets gone - stop firing
            if (!isTargetVisible()) {
                owner.getEntity().getEvents().trigger(IDLE_RIGHT);
                engineerState = STATE.IDLE_RIGHT;
            } else {
                if (shotsFired <= weaponCapacity) {
                    owner.getEntity().getEvents().trigger(FIRING);
                    // this might be changed to an event which gets triggered everytime the tower enters the firing state
                    Entity newProjectile = ProjectileFactory.createEngineerBullet(PhysicsLayer.NPC,
                            new Vector2(100, owner.getEntity().getPosition().y),
                            new Vector2(4f, 4f));
                    newProjectile.setScale(0.8f, 0.8f);
                    newProjectile.setPosition((float) (owner.getEntity().getPosition().x + 0.3),
                            (float) (owner.getEntity().getPosition().y + 0.15));
                    ServiceLocator.getEntityService().register(newProjectile);
                    shotsFired ++;
                    reloadTime = timeSource.getTime();
                } else {
                    // engineer needs to reload
                    if (reloadTime < timeSource.getTime()) {
                        // engineer has reloaded
                        shotsFired = 0;
                        reloadTime = timeSource.getTime();
                    }
                }
            }
        }
    }
}

Helper Methods

The following methods are used to optimize the primary methods and remove code redundancy.

Starting the EngineerCombatTask: start()

This method starts the EngineerCombatTask, stores the position of the 'owner' Engineer entity, and triggers the idle animation for the Engineer.

Stopping the EngineerCombatTask: stop()

This method executes the parent's stop method to stop the current task.

Updating the EngineerCombatTask: update()

This method calls updateEngineerState() to change the Engineer's state every 1000 milliseconds (1 second).

Retrieving the closest target: fetchTarget()

This method is responsible for returning the nearest target's position from the list of detected targets. To implement this logic, it uses a placeholder integer to store the distance between the Engineer and Mob lanes, and iterates through the stored positions to find the one closest to the Engineer.

Getting the task priority: getPriority()

This method returns the task's priority, thus deciding if it should be run before any other game tasks or not. It returns the default priority (3) if any enemies are visible, otherwise 0.

Entering the combat state: combatState()

This method triggers a FIRING event, of a string value "firingSingleStart". This trigger runs the required firing animation in the Engineer entity that owns this task. The method also changes the Engineer's state to enable the firing routine.

Clone this wiki locally