Attack Function Revisited - UQdeco2800/2022-studio-2 GitHub Wiki

Summary

This wiki page will explain the implementation of the Attack() function which allows the player to deal damage to enemies (revisited for Sprint 3 on 04/10/22, see original wiki: https://github.com/UQdeco2800/2022-studio-2/wiki/%5BDeprecated%5D-Attack-function)

Key Binds

Press SPACE to attack

  • With no weapon in hand, this will not play an animation. 5 attack damage is dealt to nearby enemies.
  • With a weapon in hand, this will play the attack animation for whichever weapon has been equipped from the inventory and show the weapon in the player's hand, as well as deal damage to the closest enemy based on the attack statistics of the weapons defined in source/core/assets/configs/Weapons.json

How does attacking deal damage?

PlayerTouchAttackComponent

Located in: source/core/src/main/com/deco2800/game/components/player/PlayerTouchAttackComponent.java

We created a new class PlayerTouchAttackComponent which extends TouchAttackComponent to create a player-specifc attack function.

First, the attack listeners are created which actively check for any attacks and collisions and the combat statistics of each weapons are fetched from CombatStatsComponent.

entity.getEvents().addListener("attack", this::attack);
entity.getEvents().addListener("collisionStart", this::playerCollidesEnemyStart);
combatStats = entity.getComponent(CombatStatsComponent.class);
entity.getEvents().addListener("collisionEnd", this::playerCollidesEnemyEnd);

playerCollidesEnemyStart function

Upon recording a collision from the listener shown above, the code below will check that when the Entity the Player is colliding with is an ENEMY.

If the collision is with an ENEMY, then the variable enemyCollide will be set to TRUE.

private void onCollisionStart(Fixture me, Fixture other) {
     if (((BodyUserData) other.getBody().getUserData()).entity.checkEntityType(EntityTypes.ENEMY)) {
          target = ((BodyUserData) other.getBody().getUserData()).entity;
          enemyCollide = true;
     }
}

Fixture me: The fixture of the object which is colliding (player) Fixture other: The fixture of the object which is being collided with (enemy)

Then, when the collision is over, the playerCollidesEnemyEnd function is called which sets the enemyCollide variable to FALSE

private void playerCollidesEnemyEnd(Fixture me, Fixture other) {
    enemyCollide = false;
}

Given the condition for collision has been met, the attack() function will be triggered.

Attack() function

The attack function is triggered upon press of SPACE, but will only be applied if bool canAttack is TRUE. This Boolean is a part of the weapon cooldown system and is explained here: Attack Cooldown Function

Given this condition has been satisfied, the currently equipped weapon and equipped auras will be checked. This will determine which attack animations will be played. For more information about the logic behind Aura implementation, refer to: Aura Pickup and Application.

To learn more about how the animation system works, see: Combat Animation System

Below, we will discuss_** how damage is applied upon performing an attack.**_

When enemyCollide is set to TRUE, then applyDamageToTarget is called.

if (enemyCollide) {
     applyDamageToTarget(target);

applyDamageToTarget() function

This function gets the CombatStatsComponent to determine the appropriate damage to deal to the enemy and calls the CombatStatsComponent's hit function.

private void applyDamage(Entity target) {
    CombatStatsComponent targetStats = target.getComponent(CombatStatsComponent.class);
    targetStats.hit(combatStats);
}

hit() function

Located in: source/core/src/main/com/deco2800/game/components/player/CombatStatsComponent.java

In the hit function, if there is a weapon currently equipped by the player, the attackDmg variable is set to be the damage of the weapon that is currently equipped, by using the getEquipable() function of InventoryComponent and checking index 0, which is the slot dedicated to the weapons.

  public void hit(CombatStatsComponent attacker) {
    if (attacker.getEntity().checkEntityType(EntityTypes.PLAYER) &&
            (playerWeapon = attacker.getEntity().getComponent(InventoryComponent.class).getEquipable(0)) != null) {
        attackDmg = (int) playerWeapon.getComponent(MeleeStatsComponent.class).getDamage();
        int newHealth = getHealth() - (int)((1 - damageReduction) * attackDmg);
        setHealth(newHealth);
      }
    else { //if it's not a player, or if it is a player without a weapon
      int newHealth = getHealth() - (int)((1 - damageReduction) * attacker.getBaseAttack());
      setHealth(newHealth);
    }
  }

However, if the player does not currently have a weapon equipped, the base attack of the player is pulled from the player config file (source/core/assets/configs/player.json) and this is used to make the attack. Or, if the attacker is an enemy (presumably attacking the player), the base attack of the enemy is used.

A death condition has been implemented where if the enemy's health reaches 0, that the entity is disposed of and the animation is stopped. Additionally, that materials are dropped for the player to pick up.

if (isDead() && entity.checkEntityType(EntityTypes.ENEMY)) {

      Gdx.app.postRunnable(() -> {
        dropMaterial();
        dropWeapon();
        entity.dispose();
      });

Modifications had to be made to the dispose() method in the Entity class to not dispose the AnimationRenderComponent (TextureAtlas) shared by all enemies when a enemy entity is disposed.

public void dispose() {
    for (Component component : createdComponents) {
      if (!(component instanceof AnimationRenderComponent)) {
        component.dispose();
      } //this prevents the other entities using the same animation from having their atlases disposed (black box)
    }
    ServiceLocator.getEntityService().unregister(this);
  }

Additionally, the animations of the enemy are stopped to avoid visual glitches.

      if (entity.getComponent(AnimationRenderComponent.class) != null) {
        Gdx.app.postRunnable(() -> entity.getComponent(AnimationRenderComponent.class).stopAnimation()); //this is the magic line)
      } 

To learn more about the implementation of ranged weapons, see: Ranged Weapon Implementation

Author

  • Lachlan Benson
  • GitHub: @LachlanBenson
  • Discord: Lachlan.Benson#4926
  • Slack: Lachlan Benson