Skip to content

Commit

Permalink
Implement enemy aggro
Browse files Browse the repository at this point in the history
  • Loading branch information
hdescottes committed Feb 13, 2024
1 parent b88730f commit e3b8aa5
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,7 @@ protected boolean isCollisionWithMapEntities(Entity entity, MapManager mapMgr) {
Rectangle targetRect = mapEntity.getCurrentBoundingBox();
if (boundingBox.overlaps(targetRect)){
//Collision
if ("FOE".equals(mapEntity.getEntityConfig().getEntityStatus())) {
entity.sendMessage(MESSAGE.COLLISION_WITH_FOE, mapEntity.getEntityConfig().getEntityID());
} else {
entity.sendMessage(MESSAGE.COLLISION_WITH_ENTITY, mapEntity.getEntityConfig().getEntityID());
}
entity.sendMessage(MESSAGE.COLLISION_WITH_ENTITY, mapEntity.getEntityConfig().getEntityID());
isCollisionWithMapEntities = true;
break;
}
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/java/com/gdx/game/entities/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,12 @@ public static Hashtable<String, Entity> initEntities(Array<EntityConfig> configs

public static Entity initEntity(EntityConfig entityConfig) {
Json json = new Json();
Entity entity = EntityFactory.getInstance().getEntity(EntityFactory.EntityType.NPC);
Entity entity;
if ("FOE".equals(entityConfig.getEntityStatus())) {
entity = EntityFactory.getInstance().getEntity(EntityFactory.EntityType.ENEMY);
} else {
entity = EntityFactory.getInstance().getEntity(EntityFactory.EntityType.NPC);
}
entity.setEntityConfig(entityConfig);

entity.sendMessage(Component.MESSAGE.LOAD_ANIMATIONS, json.toJson(entity.getEntityConfig()));
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/java/com/gdx/game/entities/EntityFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.gdx.game.entities.npc.NPCGraphicsComponent;
import com.gdx.game.entities.npc.NPCInputComponent;
import com.gdx.game.entities.npc.NPCPhysicsComponent;
import com.gdx.game.entities.npc.enemy.EnemyPhysicsComponent;
import com.gdx.game.entities.player.PlayerGraphicsComponent;
import com.gdx.game.entities.player.PlayerInputComponent;
import com.gdx.game.entities.player.PlayerPhysicsComponent;
Expand Down Expand Up @@ -125,10 +126,14 @@ public Entity getEntity(EntityType entityType) {
entity = new Entity(new NPCInputComponent(), new PlayerPhysicsComponent(), new PlayerGraphicsComponent());
return entity;
}
case NPC, ENEMY -> {
case NPC -> {
entity = new Entity(new NPCInputComponent(), new NPCPhysicsComponent(), new NPCGraphicsComponent());
return entity;
}
case ENEMY -> {
entity = new Entity(new NPCInputComponent(), new EnemyPhysicsComponent(), new NPCGraphicsComponent());
return entity;
}
default -> {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

public class NPCPhysicsComponent extends PhysicsComponent {

private Entity.State state;
protected Entity.State state;

public NPCPhysicsComponent() {
boundingBoxLocation = BoundingBoxLocation.CENTER;
Expand Down Expand Up @@ -63,20 +63,14 @@ public void update(Entity entity, MapManager mapMgr, float delta) {

private boolean isEntityFarFromPlayer(MapManager mapMgr) {
//Check distance
Vector3 vec3Player = new Vector3(mapMgr.getPlayer().getCurrentBoundingBox().x, mapMgr.getPlayer().getCurrentBoundingBox().y, 0.0f);
Vector3 vec3Npc = new Vector3(boundingBox.x, boundingBox.y, 0.0f);
float distance = vec3Player.dst(vec3Npc);
float distance = calculateDistance(mapMgr);

return !(distance <= SELECT_RAY_MAXIMUM_DISTANCE);
}

@Override
protected boolean isCollisionWithMapEntities(Entity entity, MapManager mapMgr) {
//Test against player
if (isCollision(entity, mapMgr.getPlayer())) {
return true;
}

return super.isCollisionWithMapEntities(entity, mapMgr);
protected float calculateDistance(MapManager mapMgr) {
Vector3 vec3Player = new Vector3(mapMgr.getPlayer().getCurrentBoundingBox().x, mapMgr.getPlayer().getCurrentBoundingBox().y, 0.0f);
Vector3 vec3Npc = new Vector3(boundingBox.x, boundingBox.y, 0.0f);
return vec3Player.dst(vec3Npc);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.gdx.game.entities.npc.enemy;

import com.gdx.game.entities.Entity;
import com.gdx.game.entities.EntityConfig;
import com.gdx.game.entities.npc.NPCPhysicsComponent;
import com.gdx.game.map.MapManager;

public class EnemyPhysicsComponent extends NPCPhysicsComponent {

private static final float followRadius = 75.0f;

@Override
public void update(Entity entity, MapManager mapMgr, float delta) {
updateBoundingBoxPosition(nextEntityPosition);

if (calculateDistance(mapMgr) <= followRadius) {
followPlayer(mapMgr, entity, delta);
return;
}

if (state == Entity.State.IMMOBILE) {
return;
}

if (!isCollisionWithMapLayer(entity, mapMgr) && !isCollisionWithMapEntities(entity, mapMgr) && state == Entity.State.WALKING) {
setNextPositionToCurrent(entity);
} else {
updateBoundingBoxPosition(currentEntityPosition);
}
calculateNextPosition(delta);
}

private void followPlayer(MapManager mapMgr, Entity entity, float delta) {
float speed = Float.parseFloat(entity.getEntityConfig().getEntityProperties().get(EntityConfig.EntityProperties.ENTITY_SPEED_POINTS.name()));

float dx = mapMgr.getPlayer().getCurrentPosition().x - currentEntityPosition.x;
float dy = mapMgr.getPlayer().getCurrentPosition().y - currentEntityPosition.y;

// Check which axis has the greater distance
if (Math.abs(dx) > Math.abs(dy)) {
nextEntityPosition.x += Math.signum(dx) * speed/3 * delta;
} else {
nextEntityPosition.y += Math.signum(dy) * speed/3 * delta;
}
setNextPositionToCurrent(entity);
updateBoundingBoxPosition(currentEntityPosition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,35 @@ public void update(Entity entity, MapManager mapMgr, float delta) {
calculateNextPosition(delta);
}

@Override
protected boolean isCollisionWithMapEntities(Entity entity, MapManager mapMgr) {
tempEntities.clear();
tempEntities.addAll(mapMgr.getCurrentMapEntities());
tempEntities.addAll(mapMgr.getCurrentMapQuestEntities());
boolean isCollisionWithMapEntities = false;

for(Entity mapEntity: tempEntities) {
//Check for testing against self
if (mapEntity.equals(entity)) {
continue;
}

Rectangle targetRect = mapEntity.getCurrentBoundingBox();
if (boundingBox.overlaps(targetRect)){
//Collision
if ("FOE".equals(mapEntity.getEntityConfig().getEntityStatus())) {
entity.sendMessage(MESSAGE.COLLISION_WITH_FOE, mapEntity.getEntityConfig().getEntityID());
} else {
entity.sendMessage(MESSAGE.COLLISION_WITH_ENTITY, mapEntity.getEntityConfig().getEntityID());
}
isCollisionWithMapEntities = true;
break;
}
}
tempEntities.clear();
return isCollisionWithMapEntities;
}

private void selectMapEntityCandidate(MapManager mapMgr) {
tempEntities.clear();
tempEntities.addAll(mapMgr.getCurrentMapEntities());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
import com.gdx.game.GdxRunner;
import com.gdx.game.entities.Entity;
import com.gdx.game.entities.EntityFactory;
import com.gdx.game.entities.player.PlayerPhysicsComponent;
import com.gdx.game.entities.npc.NPCPhysicsComponent;
import com.gdx.game.map.MapFactory;
import com.gdx.game.map.MapManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
Expand Down Expand Up @@ -50,17 +49,17 @@ public void testCollisionMapEntities(MapFactory.MapType mapType, Component.MESSA
MapManager mapManager = new MapManager();
mapManager.setPlayer(player);
mapManager.loadMap(mapType);
PlayerPhysicsComponent playerPhysicsComponent = new PlayerPhysicsComponent();
NPCPhysicsComponent npcPhysicsComponent = new NPCPhysicsComponent();

boolean result = playerPhysicsComponent.isCollisionWithMapEntities(player, mapManager);
boolean result = npcPhysicsComponent.isCollisionWithMapEntities(player, mapManager);

verify(player).sendMessage(message, entityId);
assertTrue(result);
}

private static Stream<Arguments> collisionMapData() {
return Stream.of(
Arguments.of(MapFactory.MapType.TOPPLE_ROAD_1, Component.MESSAGE.COLLISION_WITH_FOE, "RABITE"),
Arguments.of(MapFactory.MapType.TOPPLE_ROAD_1, Component.MESSAGE.COLLISION_WITH_ENTITY, "RABITE"),
Arguments.of(MapFactory.MapType.TOPPLE, Component.MESSAGE.COLLISION_WITH_ENTITY, "TOWN_INNKEEPER")
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.gdx.game.entities.npc.enemy;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Json;
import com.gdx.game.GdxRunner;
import com.gdx.game.component.Component;
import com.gdx.game.entities.Entity;
import com.gdx.game.entities.EntityFactory;
import com.gdx.game.map.Map;
import com.gdx.game.map.MapFactory;
import com.gdx.game.map.MapManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedConstruction;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;

@ExtendWith(GdxRunner.class)
public class EnemyPhysicsComponentTest {

private MockedConstruction<SpriteBatch> mockSpriteBatch;
private MockedConstruction<ShapeRenderer> mockShapeRenderer;

@BeforeEach
void init() {
Gdx.gl = mock(GL20.class);
Gdx.gl20 = mock(GL20.class);
mockSpriteBatch = mockConstruction(SpriteBatch.class);
mockShapeRenderer = mockConstruction(ShapeRenderer.class);
}

@AfterEach
void end() {
mockSpriteBatch.close();
mockShapeRenderer.close();
}

@Test
public void should_follow_player_when_close_enough() {
Json json = new Json();
Entity player = EntityFactory.getInstance().getEntity(EntityFactory.EntityType.WARRIOR);
MapManager mapManager = new MapManager();
mapManager.setCamera(new OrthographicCamera());
mapManager.setPlayer(player);
mapManager.loadMap(MapFactory.MapType.TOPPLE_ROAD_1);
OrthogonalTiledMapRenderer mapRenderer = new OrthogonalTiledMapRenderer(mapManager.getCurrentTiledMap(), Map.UNIT_SCALE);
Entity enemy = mapManager.getCurrentMapEntities().get(1);

Vector2 currentEntityPosition = new Vector2(13,14);
player.sendMessage(Component.MESSAGE.INIT_START_POSITION, json.toJson(currentEntityPosition));
player.update(mapManager, mapRenderer.getBatch(), 1.0f);

float dy = player.getCurrentPosition().y - enemy.getCurrentPosition().y;

enemy.update(mapManager, mapRenderer.getBatch(), 1.0f);

float dy2 = player.getCurrentPosition().y - enemy.getCurrentPosition().y;
assertTrue(Math.abs(dy2) < Math.abs(dy));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.gdx.game.entities.player;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.gdx.game.GdxRunner;
import com.gdx.game.component.Component;
import com.gdx.game.entities.Entity;
import com.gdx.game.entities.EntityFactory;
import com.gdx.game.map.Map;
import com.gdx.game.map.MapFactory;
import com.gdx.game.map.MapManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.MockedConstruction;

import java.util.stream.Stream;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

@ExtendWith(GdxRunner.class)
public class PlayerPhysicsComponentTest {

private MockedConstruction<SpriteBatch> mockSpriteBatch;
private MockedConstruction<ShapeRenderer> mockShapeRenderer;

@BeforeEach
void init() {
Gdx.gl = mock(GL20.class);
Gdx.gl20 = mock(GL20.class);
mockSpriteBatch = mockConstruction(SpriteBatch.class);
mockShapeRenderer = mockConstruction(ShapeRenderer.class);
}

@AfterEach
void end() {
mockSpriteBatch.close();
mockShapeRenderer.close();
}

@ParameterizedTest
@MethodSource("collisionMapData")
public void testCollisionMapEntities(MapFactory.MapType mapType, Component.MESSAGE message, String entityId) {
Entity player = spy(EntityFactory.getInstance().getEntity(EntityFactory.EntityType.WARRIOR));
MapManager mapManager = new MapManager();
mapManager.setCamera(new OrthographicCamera());
mapManager.setPlayer(player);
mapManager.loadMap(mapType);
OrthogonalTiledMapRenderer mapRenderer = new OrthogonalTiledMapRenderer(mapManager.getCurrentTiledMap(), Map.UNIT_SCALE);

player.update(mapManager, mapRenderer.getBatch(), 1.0f);

verify(player).sendMessage(message, entityId);
}

private static Stream<Arguments> collisionMapData() {
return Stream.of(
Arguments.of(MapFactory.MapType.TOPPLE_ROAD_1, Component.MESSAGE.COLLISION_WITH_FOE, "RABITE"),
Arguments.of(MapFactory.MapType.TOPPLE, Component.MESSAGE.COLLISION_WITH_ENTITY, "TOWN_INNKEEPER")
);
}
}

0 comments on commit e3b8aa5

Please sign in to comment.