diff --git a/assets/behaviors/pawn.behavior b/assets/behaviors/pawn.behavior new file mode 100644 index 00000000..90b46b9b --- /dev/null +++ b/assets/behaviors/pawn.behavior @@ -0,0 +1,25 @@ +{ + dynamic: [ + { + guard: { + componentPresent: "LightAndShadow:NearbyEnemies", + values: ["N enemiesWithinRange nonEmpty"], + child: { + sequence: [ + set_target_to_enemy, + { + lookup: { + tree: "Behaviors:hostile" + } + } + ] + } + } + }, + { + lookup: { + tree: "Behaviors:stray" + } + } + ] +} diff --git a/src/main/java/org/terasology/ligthandshadow/actions/SetTargetToEnemyAction.java b/src/main/java/org/terasology/ligthandshadow/actions/SetTargetToEnemyAction.java new file mode 100644 index 00000000..b1ec29c6 --- /dev/null +++ b/src/main/java/org/terasology/ligthandshadow/actions/SetTargetToEnemyAction.java @@ -0,0 +1,34 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 +package org.terasology.ligthandshadow.componentsystem.actions; + +import org.terasology.behaviors.components.FollowComponent; +import org.terasology.engine.logic.behavior.BehaviorAction; +import org.terasology.engine.logic.behavior.core.Actor; +import org.terasology.engine.logic.behavior.core.BaseAction; +import org.terasology.engine.logic.behavior.core.BehaviorState; +import org.terasology.ligthandshadow.componentsystem.components.NearbyEnemiesComponent; + +/** + * Action which sets this agent's move target to the nearest player from the opponent team, as + * defined in {@link NearbyEnemiesComponent}. + */ +@BehaviorAction(name = "set_target_to_enemy") +public class SetTargetToEnemyAction extends BaseAction { + + @Override + public BehaviorState modify(Actor actor, BehaviorState result) { + if (!actor.hasComponent(NearbyEnemiesComponent.class)) { + return BehaviorState.SUCCESS; + } + + NearbyEnemiesComponent enemiesComponent = actor.getComponent(NearbyEnemiesComponent.class); + FollowComponent followComponent = new FollowComponent(); + + followComponent.entityToFollow = enemiesComponent.closestEnemy; + actor.getEntity().addComponent(followComponent); + + return BehaviorState.SUCCESS; + } + +} \ No newline at end of file diff --git a/src/main/java/org/terasology/ligthandshadow/componentsystem/components/NearbyEnemiesComponent.java b/src/main/java/org/terasology/ligthandshadow/componentsystem/components/NearbyEnemiesComponent.java new file mode 100644 index 00000000..d5b5e2a5 --- /dev/null +++ b/src/main/java/org/terasology/ligthandshadow/componentsystem/components/NearbyEnemiesComponent.java @@ -0,0 +1,21 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 +package org.terasology.ligthandshadow.componentsystem.components; + +import org.terasology.engine.entitySystem.Component; +import org.terasology.engine.entitySystem.entity.EntityRef; + +import java.util.List; + +/** + * Keeps track of enemies in the range of the entity. + */ +public class NearbyEnemiesComponent implements Component { + + public float searchRadius = 20f; + + public List enemiesWithinRange; + + public EntityRef closestEnemy; + +} \ No newline at end of file diff --git a/src/main/java/org/terasology/ligthandshadow/componentsystem/controllers/EnemiesUpdateSystem.java b/src/main/java/org/terasology/ligthandshadow/componentsystem/controllers/EnemiesUpdateSystem.java new file mode 100644 index 00000000..1e7e6363 --- /dev/null +++ b/src/main/java/org/terasology/ligthandshadow/componentsystem/controllers/EnemiesUpdateSystem.java @@ -0,0 +1,97 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 +package org.terasology.ligthandshadow.componentsystem.controllers; + +import com.google.common.collect.Lists; +import org.joml.Vector3f; +import org.terasology.engine.entitySystem.entity.EntityManager; +import org.terasology.engine.entitySystem.entity.EntityRef; +import org.terasology.engine.entitySystem.systems.BaseComponentSystem; +import org.terasology.engine.entitySystem.systems.RegisterMode; +import org.terasology.engine.entitySystem.systems.RegisterSystem; +import org.terasology.engine.entitySystem.systems.UpdateSubscriberSystem; +import org.terasology.engine.logic.characters.AliveCharacterComponent; +import org.terasology.engine.logic.location.LocationComponent; +import org.terasology.engine.registry.In; +import org.terasology.ligthandshadow.componentsystem.components.LASTeamComponent; +import org.terasology.ligthandshadow.componentsystem.components.NearbyEnemiesComponent; +import org.terasology.ligthandshadow.componentsystem.components.PlayerStatisticsComponent; + +/** + * Tracks nearby enemies much like {@link org.terasology.behaviors.system.FindNearbyPlayersSystem}, and stores + * the results in the required entity {@link NearbyEnemiesComponent}. + */ +@RegisterSystem(value = RegisterMode.AUTHORITY) +public class EnemiesUpdateSystem extends BaseComponentSystem implements UpdateSubscriberSystem { + + private static final float ENEMY_CHECK_DELAY = 5; + + private float counter; + + @In + private EntityManager entityManager; + + @Override + public void update(float delta) { + counter += delta; + + if (counter < ENEMY_CHECK_DELAY) { + return; + } + + counter = 0; + + for (EntityRef entity : entityManager.getEntitiesWith(NearbyEnemiesComponent.class)) { + checkForEnemies(entity); + } + } + + /** + * Checks for players of the oppsite team that are near the entity. + *

+ * Results are stored in {@link NearbyEnemiesComponent}. + * + * @param entity The entity to check enemies for. + */ + private void checkForEnemies(EntityRef entity) { + if (!entity.hasComponent(NearbyEnemiesComponent.class) || !entity.hasComponent(LASTeamComponent.class)) { + return; + } + + NearbyEnemiesComponent enemiesComponent = entity.getComponent(NearbyEnemiesComponent.class); + LASTeamComponent teamComponent = entity.getComponent(LASTeamComponent.class); + + enemiesComponent.enemiesWithinRange = Lists.newArrayList(); + enemiesComponent.closestEnemy = EntityRef.NULL; + + float minDistance = Float.MAX_VALUE; + Vector3f actorPosition = entity.getComponent(LocationComponent.class).getWorldPosition(new Vector3f()); + + for (EntityRef otherEntity : entityManager.getEntitiesWith(AliveCharacterComponent.class)) { + if (!otherEntity.hasComponent(PlayerStatisticsComponent.class) || otherEntity.equals(entity)) { + continue; + } + + LASTeamComponent otherTeamComponent = otherEntity.getComponent(LASTeamComponent.class); + if (otherTeamComponent.team.equals(teamComponent.team)) { + continue; + } + + LocationComponent otherLocationComponent = otherEntity.getComponent(LocationComponent.class); + float distanceApart = otherLocationComponent.getWorldPosition(new Vector3f()).distanceSquared(actorPosition); + + if (distanceApart > enemiesComponent.searchRadius * enemiesComponent.searchRadius) { + continue; + } + + if (distanceApart < minDistance) { + enemiesComponent.closestEnemy = otherEntity; + minDistance = distanceApart; + } + + enemiesComponent.enemiesWithinRange.add(otherEntity); + } + + entity.saveComponent(enemiesComponent); + } +}