[Deprecated] Attack function - 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.

Key Binds

Press SPACE to attack, this will play the attack animation for the single dagger and show the weapon in the player's hand, as well as deal damage to the closest enemy. For now, this is hard coded. In Sprint 3, attack damage and animation will be dependent on the equipped weapon.

Press N to play the animation for the double dagger (note, does not deal damage). Implemented this way temporarily as a player cannot yet equip a weapon.

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.

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, 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;
}

Attack() function

The attack function is relatively simple and relies on other functions to complete the work.

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

if (enemyCollide) {
     applyDamageToTarget(target);

Additionally, 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.

if (target.getComponent(CombatStatsComponent.class).getHealth() == 0) {
      target.dispose();
      if (target.getComponent(AnimationRenderComponent.class) != null) {
          target.getComponent(AnimationRenderComponent.class).stopAnimation();
      }
}

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);
  }

applyDamage() function

This function gets the CombatStatsComponent 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.

Simulating a weapon equip

At the moment, there is no ability for a player to equip a weapon. This functionality is still being developed by Team 03 (Inventory). As such, to test that our code works, we used the below lines of code and added them to the hit function:

//this is how we would equip a weapon manually, given that the equip function has not yet been coded by the inventory team
Entity wep = WeaponFactory.createPlunger();
attacker.getEntity().getComponent(InventoryComponent.class).addItem(wep); //adding weapon to inventory
attacker.getEntity().getComponent(InventoryComponent.class).equipItem(wep); //equipping the weapon

This demonstrates that the change to weapon and player stats written in this function works. Try it out if you want. It's pretty fun! :)

Author

  • Lachlan Benson
  • GitHub: @LachlanBenson
  • Discord: Lachlan.Benson#4926
  • Slack: Lachlan Benson
⚠️ **GitHub.com Fallback** ⚠️