Skip to content

Commit

Permalink
feat: task based AI controllers and basic plane AI
Browse files Browse the repository at this point in the history
  • Loading branch information
vinceh121 committed Feb 5, 2024
1 parent 6ba9111 commit b1be683
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 12 deletions.
1 change: 1 addition & 0 deletions android/assets/prototypes/machinegunPlanes.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"j_scout01": {
"collisionModel": "orig/j_scout01.n/collide.obj",
"maxTurboTime": 1.5,
"targetSearchDistance": 750,
"engineSound": "orig/lib/sound/scoutdrive.wav",
"turboSound": "orig/lib/sound/scoutdrive.wav",
"explosionSound": "orig/lib/sound/crash1.wav",
Expand Down
18 changes: 17 additions & 1 deletion core/src/me/vinceh121/wanderer/Wanderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,10 @@ public AbstractEntity getEntity(final String symbolicName) {
return null;
}

public void addClan(final Clan clan) {
this.clans.add(clan);
}

public Clan getClanForMember(final IClanMember member) {
for (final Clan c : this.clans) {
if (c.getMembers().contains(member.getId(), false)) {
Expand All @@ -521,6 +525,14 @@ public Clan getClan(final int id) {
return null;
}

public Stream<Clan> getClansByName(String name) {
return this.streamClans().filter(c -> name.equals(c.getName()));
}

public Stream<Clan> streamClans() {
return Stream.of((Object[]) this.clans.items).map(c -> (Clan) c).limit(this.clans.size);
}

public Clan getPlayerClan() {
return this.playerClan;
}
Expand Down Expand Up @@ -813,13 +825,17 @@ public AbstractEntity findFirstEntityByClass(final Class<? extends AbstractEntit

@SuppressWarnings("unchecked")
public <T extends AbstractEntity> Stream<T> findEntitiesByClass(final Class<T> cls) {
return this.streamEntities().filter(e -> cls.isInstance(e)).map(e -> (T) e);
}

public Stream<AbstractEntity> streamEntities() {
// This game of casts looks redundant but it's not!
// Using Stream.of(this.entities.items) causes a ClassCastException
// This is due to an implicit (AbtractEntity[]) this.entities.items added by the
// compiler that will always fail!
// GDX's Array<T>#items has a T[] type, which the compiler will always compile
// as Object[]
return Stream.of((Object[]) this.entities.items).filter(e -> cls.isInstance(e)).map(e -> (T) e);
return Stream.of((Object[]) this.entities.items).map(e -> (AbstractEntity) e);
}

public void showMessage(final String message) {
Expand Down
14 changes: 14 additions & 0 deletions core/src/me/vinceh121/wanderer/ai/Task.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package me.vinceh121.wanderer.ai;

import me.vinceh121.wanderer.Wanderer;
import me.vinceh121.wanderer.entity.IControllableEntity;

public abstract class Task<T extends IControllableEntity> {
/**
* @param delta Delta time in seconds
* @param game TODO
* @param controlled Entity being controlled
* @return The next task to use, or null to keep current task
*/
public abstract Task<T> process(final float delta, final Wanderer game, final T controlled);
}
31 changes: 31 additions & 0 deletions core/src/me/vinceh121/wanderer/ai/TaskAIController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package me.vinceh121.wanderer.ai;

import me.vinceh121.wanderer.Wanderer;
import me.vinceh121.wanderer.entity.IControllableEntity;

public class TaskAIController<T extends IControllableEntity> extends AIController<T> {
private Task<T> currentTask;

public TaskAIController(Wanderer game, T target) {
super(game, target);
}

@Override
public void tick(float delta) {
if (this.currentTask != null) {
final Task<T> maybeNewTask = this.currentTask.process(delta, this.game, this.target);

if (maybeNewTask != null) {
this.currentTask = maybeNewTask;
}
}
}

public Task<T> getCurrentTask() {
return currentTask;
}

public void setCurrentTask(Task<T> currentTask) {
this.currentTask = currentTask;
}
}
7 changes: 7 additions & 0 deletions core/src/me/vinceh121/wanderer/entity/AbstractEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public abstract class AbstractEntity implements Disposable, ISaveable {
private final Matrix4 transform = new Matrix4();
// note: ID can't be final to be able to set explicit values when loading a save
private ID id = new ID();
private boolean disposed;
private String collideModel, symbolicName;
private btRigidBody collideObject;
private float mass;
Expand Down Expand Up @@ -532,12 +533,18 @@ public void readState(final ObjectNode node) {
}
}

public boolean isDisposed() {
return this.disposed;
}

public ID getId() {
return this.id;
}

@Override
public void dispose() {
this.disposed = true;

for (final ParticleEmitter e : this.particles) {
this.game.getGraphicsManager().removeParticle(e);
e.dispose();
Expand Down
117 changes: 108 additions & 9 deletions core/src/me/vinceh121/wanderer/entity/plane/AbstractPlane.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package me.vinceh121.wanderer.entity.plane;

import java.util.Optional;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g3d.attributes.BlendingAttribute;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
Expand All @@ -14,7 +16,11 @@
import me.vinceh121.wanderer.Preferences;
import me.vinceh121.wanderer.Wanderer;
import me.vinceh121.wanderer.WandererConstants;
import me.vinceh121.wanderer.ai.AIController;
import me.vinceh121.wanderer.ai.Task;
import me.vinceh121.wanderer.ai.TaskAIController;
import me.vinceh121.wanderer.building.ExplosionPart;
import me.vinceh121.wanderer.clan.Amicability;
import me.vinceh121.wanderer.combat.DamageType;
import me.vinceh121.wanderer.entity.AbstractClanLivingEntity;
import me.vinceh121.wanderer.entity.DisplayModel;
Expand All @@ -31,13 +37,15 @@ public abstract class AbstractPlane extends AbstractClanLivingEntity implements
private final Array<DisplayModel> explosionParts = new Array<>();
private final PlaneSpeedProfile normal, turbo;
protected final SoundEmitter3D engineEmitter, turboEmitter;
private AIController<? extends AbstractPlane> aiController;
private String explosionSound;
private InputListener inputListener;
private ColorAttribute colorAttr;
private BlendingAttribute blendingAttr;
private Garage garage;
private boolean controlled, isTurbo;
private float speedUpTime, maxTurboTime, turboTime, yaw, pitch, roll, currentYawTime, currentPitchTime,
currentRollTime;
currentRollTime, targetSearchDistance;
private long turboPressTime;

public AbstractPlane(final Wanderer game, final AbstractPlanePrototype prototype) {
Expand All @@ -62,6 +70,8 @@ public AbstractPlane(final Wanderer game, final AbstractPlanePrototype prototype

this.explosionSound = prototype.getExplosionSound();

this.targetSearchDistance = prototype.getTargetSearchDistance();

this.engineEmitter =
WandererConstants.getAssetOrHotload(prototype.getEngineSound(), Sound3D.class).playSource3D();
this.engineEmitter.setLooping(true);
Expand All @@ -72,6 +82,13 @@ public AbstractPlane(final Wanderer game, final AbstractPlanePrototype prototype
this.turboEmitter.stop();
this.addSoundEmitter(this.turboEmitter);

this.setupAi();
}

protected void setupAi() {
final TaskAIController<AbstractPlane> cont = new TaskAIController<>(game, this);
cont.setCurrentTask(new TaskTargetSearch<AbstractPlane>());
this.aiController = cont;
}

@Override
Expand All @@ -86,7 +103,7 @@ public void tick(final float delta) {
}
}

final PlaneSpeedProfile profile = this.isTurbo ? this.turbo : this.normal;
final PlaneSpeedProfile profile = this.getCurrentProfile();

if (this.controlled) {
if (this.game.getInputManager().isPressed(Input.FLY_BOOST)) {
Expand Down Expand Up @@ -135,15 +152,16 @@ public void tick(final float delta) {
: Math.min(this.roll + profile.getRollTime(), 0);
}

this.yaw += this.currentYawTime;
this.pitch =
MathUtils.clamp(this.currentPitchTime + this.pitch, -profile.getMaxPitch(), profile.getMaxPitch());
this.roll = MathUtils.clamp(this.currentRollTime + this.roll, -profile.getMaxRoll(), profile.getMaxRoll());

MathUtilsW.setRotation(this.getTransform(),
new Quaternion().setEulerAngles(this.yaw, this.pitch, this.roll));
} else if (this.aiController != null) {
this.aiController.tick(delta);
}

this.yaw = (this.yaw + this.currentYawTime) % 360;
this.pitch = MathUtils.clamp(this.currentPitchTime + this.pitch, -profile.getMaxPitch(), profile.getMaxPitch());
this.roll = MathUtils.clamp(this.currentRollTime + this.roll, -profile.getMaxRoll(), profile.getMaxRoll());

MathUtilsW.setRotation(this.getTransform(), new Quaternion().setEulerAngles(this.yaw, this.pitch, this.roll));

this.advance(speed * delta);

if (this.controlled) {
Expand All @@ -155,6 +173,25 @@ public void tick(final float delta) {
}
}

public PlaneSpeedProfile getCurrentProfile() {
return this.isTurbo ? this.turbo : this.normal;
}

public void turnTowards(final Vector3 pos, final float delta) {
final Vector3 myDir = new Vector3(0, 1, 0).mul(this.getRotation());
final Vector3 dif = this.getTranslation().sub(pos).nor();

float toYaw = Math.abs(MathUtils.atan2(myDir.z, myDir.x) * MathUtils.radiansToDegrees
- MathUtils.atan2(dif.z, dif.x) * MathUtils.radiansToDegrees) - 90;
// final float toPitch = MathUtils.asin(dir.z);

final PlaneSpeedProfile profile = this.getCurrentProfile();

this.currentRollTime = Math.max(this.currentRollTime - delta, Math.signum(toYaw) * profile.getRollTime());
this.currentYawTime = Math.max(this.currentYawTime - delta, Math.signum(toYaw) * profile.getYawSpeed());
// this.currentPitchTime = Math.max(this.currentPitchTime - delta, -profile.getYawSpeed());
}

protected void advance(final float dist) {
if (this.getCollideObject() != null) {
final Matrix4 start = this.getTransform();
Expand Down Expand Up @@ -284,4 +321,66 @@ public InputListener getInputProcessor() {

return this.inputListener;
}

public float getTargetSearchDistance() {
return targetSearchDistance;
}

public void setTargetSearchDistance(float targetSearchDistance) {
this.targetSearchDistance = targetSearchDistance;
}

public Garage getGarage() {
return garage;
}

public void setGarage(Garage garage) {
this.garage = garage;
}

public static class TaskTargetSearch<T extends AbstractPlane> extends Task<T> {
@Override
public Task<T> process(final float delta, final Wanderer game, final T controlled) {
if (controlled.getClan() == null) {
return null;
}

final Vector3 pos = controlled.getTranslation();

final Optional<AbstractClanLivingEntity> newTarget = game.streamEntities()
.filter(e -> e instanceof AbstractClanLivingEntity) // XXX should I only compare this to interfaces?
.map(e -> (AbstractClanLivingEntity) e)
.filter(e -> controlled.getClan() != e.getClan()
&& controlled.getClan().getRelationship(e.getClan()) == Amicability.HOSTILE)
.filter(e -> pos.dst(e.getTranslation()) < controlled.getTargetSearchDistance())
.sorted((o1, o2) -> Float.compare(pos.dst(o1.getTranslation()), pos.dst(o2.getTranslation())))
.findFirst();

if (newTarget.isPresent()) {
return new TaskGotoTarget<T>(newTarget.get());
}

return null;
}
}

public static class TaskGotoTarget<T extends AbstractPlane> extends Task<T> {
private final AbstractClanLivingEntity target;

public TaskGotoTarget(AbstractClanLivingEntity target) {
this.target = target;
}

@Override
public Task<T> process(float delta, Wanderer game, T controlled) {
if (target.isDisposed() || target.isDead()) {
return new TaskTargetSearch<T>();
}

controlled.turnTowards(this.target.getTranslation(), delta);

return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public abstract class AbstractPlanePrototype implements IPrototype {
private Array<DisplayModel> explosionParts = new Array<>();
private String collisionModel, engineSound, turboSound, explosionSound;
private final PlaneSpeedProfile normal = new PlaneSpeedProfile(), turbo = new PlaneSpeedProfile();
private float maxTurboTime;
private float maxTurboTime, targetSearchDistance;

@Override
public void getAssetsToLoad(final List<AssetDescriptor<?>> descriptors) {
Expand Down Expand Up @@ -100,4 +100,12 @@ public float getMaxTurboTime() {
public void setMaxTurboTime(final float maxTurboTime) {
this.maxTurboTime = maxTurboTime;
}

public float getTargetSearchDistance() {
return targetSearchDistance;
}

public void setTargetSearchDistance(float targetSearchDistance) {
this.targetSearchDistance = targetSearchDistance;
}
}
12 changes: 12 additions & 0 deletions core/src/me/vinceh121/wanderer/entity/plane/Garage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.vinceh121.wanderer.entity.plane;

import me.vinceh121.wanderer.Wanderer;
import me.vinceh121.wanderer.building.AbstractBuilding;

public class Garage extends AbstractBuilding {

public Garage(Wanderer game, GaragePrototype prototype) {
super(game, prototype);
}

}
12 changes: 12 additions & 0 deletions core/src/me/vinceh121/wanderer/entity/plane/GaragePrototype.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.vinceh121.wanderer.entity.plane;

import me.vinceh121.wanderer.Wanderer;
import me.vinceh121.wanderer.building.AbstractBuildingPrototype;
import me.vinceh121.wanderer.entity.AbstractEntity;

public class GaragePrototype extends AbstractBuildingPrototype {
@Override
public AbstractEntity create(Wanderer game) {
return new Garage(game, this);
}
}
Loading

0 comments on commit b1be683

Please sign in to comment.