Skip to content

Commit

Permalink
Merge branch 'Team-7--Tower-Extension' of https://github.com/UQcsse32…
Browse files Browse the repository at this point in the history
…00/2023-studio-3 into Team-7--Tower-Extension
  • Loading branch information
ThivanW committed Oct 3, 2023
2 parents a72ad50 + 81216ce commit 1750b40
Show file tree
Hide file tree
Showing 8 changed files with 450 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//BombshipController
package com.csse3200.game.components.npc;

import com.csse3200.game.components.Component;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.csse3200.game.components.tasks;

import com.badlogic.gdx.math.Vector2;
import com.csse3200.game.ai.tasks.DefaultTask;
import com.csse3200.game.ai.tasks.PriorityTask;
import com.csse3200.game.entities.Entity;
import com.csse3200.game.entities.factories.ProjectileFactory;
import com.csse3200.game.physics.PhysicsEngine;
import com.csse3200.game.physics.PhysicsLayer;
import com.csse3200.game.physics.raycast.RaycastHit;
import com.csse3200.game.services.GameTime;
import com.csse3200.game.services.ServiceLocator;

import java.util.ArrayList;

/**
* The AI Task for the Engineer entity. The Engineer will scan for targets within its detection range
* and trigger events to change its state accordingly. This task must be called once the Engineer has
* appropiately moved into position.
*/
public class BombshipCombatTask extends DefaultTask implements PriorityTask {

private static final int INTERVAL = 1; // The time interval for each target scan from the Engineer.
private static final int PRIORITY = 3; // Default priority of the combat task when mobs are in range.
private static final short TARGET1 = PhysicsLayer.BOSS; // The type of targets that the Engineer will detect.
private static final short TARGET2 = PhysicsLayer.XENO;

// Animation event names for the Engineer's state machine.
private static final String START = "start";
private static final String IDLE = "idle";
private static final String DESTROY = "destroy";

// The Engineer's attributes.
private final float maxRange; // The maximum range of the bombship.

private Vector2 bombShipPosition = new Vector2(0, 0); // Placeholder value for the Engineer's position.
private final Vector2 maxRangePosition = new Vector2();
private PhysicsEngine physics;
private GameTime timeSource;
private long endTime;
private long reloadTime;

private ArrayList<RaycastHit> hits = new ArrayList<>();
private final RaycastHit hit = new RaycastHit();
private ArrayList<Vector2> targets = new ArrayList<>();

/** The Engineer's states. */
private enum STATE {
IDLE, START , DESTROY
}
private STATE bombshipState = STATE.IDLE;

public BombshipCombatTask(float maxRange) {
this.maxRange = maxRange;
physics = ServiceLocator.getPhysicsService().getPhysics();
timeSource = ServiceLocator.getTimeSource();
}

/**
* Runs the task and triggers Bombship's idle animation.
*/
@Override
public void start() {
super.start();
this.bombShipPosition = owner.getEntity().getCenterPosition();
this.maxRangePosition.set(bombShipPosition.x + maxRange, bombShipPosition.y);
// Default to idle mode
owner.getEntity().getEvents().trigger(IDLE);
endTime = timeSource.getTime() + (INTERVAL * 600);
}

/**
* The update method is what is run every time the TaskRunner in the AiTaskComponent calls update().
* triggers events depending on the presence or otherwise of targets in the detection range
*/
@Override
public void update() {
if (timeSource.getTime() >= endTime) {
updateBombshipState();
endTime = timeSource.getTime() + (INTERVAL * 1200);
}
}

/**
* Bombship state machine
*/
public void updateBombshipState() {
// configure engineer state depending on target visibility
switch (bombshipState) {
case IDLE -> {
// targets detected in idle mode - start deployment
if (isEngineerDied()) {
combatState();
}
}
case START -> {
// targets gone - stop firing
if (!isEngineerDied()) {
owner.getEntity().getEvents().trigger(IDLE);
bombshipState = STATE.IDLE;
} else {
owner.getEntity().getEvents().trigger(START);
}
}
case DESTROY -> {
owner.getEntity().getEvents().trigger(DESTROY);
}
}
}

/**
* Puts the BombshipCombatTask state into combat mode
*/
private void combatState() {
owner.getEntity().getEvents().trigger(START);
bombshipState = STATE.START;
}
/**
* For stopping the running task
*/
@Override
public void stop() {
super.stop();
}

/**
* Simplified getPriority function, returns the priority of the task
* @return priority as an integer value. If mobs are visible, return the current priority, otherwise return 0.
*/
@Override
public int getPriority() {
return isEngineerDied() ? PRIORITY : 0;
}

/**
* Uses a raycast to determine whether there are any targets in detection range. Performs multiple raycasts
* to a range of points at x = engineer.x + maxRange, and a range of y values above and below current y position.
* Allows the bombship entity to detect mobs in adjacent lanes.
* @return true if a target is detected, false otherwise
*/
public boolean isEngineerDied() {
// If there is an obstacle in the path to the max range point, mobs visible.
Vector2 position = owner.getEntity().getCenterPosition();
hits.clear();
for (int i = 8; i > -8; i--) {
if (physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET1, hit)
|| physics.raycast(position, new Vector2(position.x + maxRange, position.y + i), TARGET2, hit)) {
hits.add(hit);
targets.add(new Vector2(position.x + maxRange, position.y + i));
}
}
return !hits.isEmpty();
}

/**
* Fetches the nearest target from the array of detected target positions created during the last call of
* isTargetVisible
* @return a Vector2 position of the nearest mob detected.
*/
public Vector2 fetchTarget() {
// Initial nearest position for comparison
int lowest = 10;

Vector2 nearest = new Vector2(owner.getEntity().getCenterPosition().x,
owner.getEntity().getCenterPosition().y);

// Find the nearest target from the array of targets
for (Vector2 tgt : targets){
if (Math.abs(tgt.y - nearest.y) < lowest) {
lowest = (int)Math.abs(tgt.y - nearest.y);
nearest = tgt;
}
}
return nearest;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.csse3200.game.components.tasks.tower;
package com.csse3200.game.components.tasks;

import com.csse3200.game.ai.tasks.DefaultTask;
import com.csse3200.game.services.GameTime;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.csse3200.game.components.tasks;

import com.badlogic.gdx.math.Vector2;
import com.csse3200.game.ai.tasks.DefaultTask;
import com.csse3200.game.ai.tasks.PriorityTask;
import com.csse3200.game.ai.tasks.Task;
import com.csse3200.game.components.CombatStatsComponent;
import com.csse3200.game.physics.PhysicsLayer;
import com.csse3200.game.physics.components.ColliderComponent;
import com.csse3200.game.physics.components.HitboxComponent;
import com.csse3200.game.rendering.AnimationRenderComponent;
import com.csse3200.game.services.ServiceLocator;

/**
* BombshipWanderTask is the entry point for the engineer entity's behaviour. Instantiates subtasks HumanWaitTask,
* BombshipMovementTask and BombshipCombatTask, and manages transitions between the tasks. Bombship damage and destruction is
* handled in this class.
*/
public class BombshipWanderTask extends DefaultTask implements PriorityTask {
private static final int TOLERANCE = 1;
private static final float STOP_DISTANCE = 0.5f;
private static final int DEFAULT_PRIORITY = 1;
private static final String START = "start";
private static final String DESTROY = "destroy";
private static final String IDLE = "idle";
private AnimationRenderComponent animator;
private final float maxRange;
private final float waitTime;
private BombshipMovementTask movementTask;
private BombshipWaitTask waitTask;
private BombshipCombatTask combatTask;
private Task currentTask;
private boolean isDestroyed = false;

/**
* Constructor of BombshipWanderTask
*
* @param waitTime How long in seconds to wait between wandering.
* @param maxRange Maximum of the entity to fight
*/
public BombshipWanderTask(float waitTime, float maxRange) {
this.waitTime = waitTime;
this.maxRange = maxRange;
}

/**
* Fetches the priority of this task.
* @return current priority of this task. Priority for this task is a set value and does not change.
*/
@Override
public int getPriority() {
return DEFAULT_PRIORITY; // Low priority task
}

/**
* Starts the BombshipWanderTask instance and instantiates subtasks (BombshipWaitTask, BombshipWanderTask, BombshipCombatTask).
*/
@Override
public void start() {
super.start();
Vector2 startPos = owner.getEntity().getCenterPosition();
waitTask = new BombshipWaitTask(waitTime);
waitTask.create(owner);

movementTask = new BombshipMovementTask(startPos, STOP_DISTANCE);
movementTask.create(owner);
movementTask.start();

combatTask = new BombshipCombatTask(maxRange);
combatTask.create(owner);
combatTask.start();

currentTask = movementTask;

animator = owner.getEntity().getComponent(AnimationRenderComponent.class);
}

/**
* Operates the main logic of the entity in this task. All calls to switch to particular states are determined during
* the update phase.
* The logical flow is:
* - Check if the entity has died since last update
* - Check if the entity has finished dying
* - If not dead
*/
@Override
public void update() {
if (!isDestroyed) {
startDestroying();
}

// Check if bombship has destroyed since last update
if (!isDestroyed) {
startDestroying();
} else if (isDestroyed && animator.isFinished()) {
owner.getEntity().setFlagForDelete(true);
}

// otherwise doing engineer things since engineer is alive
else if (!isDestroyed){
doBombshipThings();

currentTask.update();
}
}

private void doBombshipThings() {
if (currentTask.getStatus() != Status.ACTIVE) {

// if the engineer is in move state and update has been called, engineer has arrived at destination
if (currentTask == movementTask) {
startWaiting();
owner.getEntity().getEvents().trigger(IDLE);

} else if (combatTask.isEngineerDied()) {
owner.getEntity().getEvents().trigger(START);
}
}
}
/**
* Handle the dying phase of the entity. Triggers an event to play the appropriate media,
* sets HitBox and Collider components to ignore contact (stops the body being pushed around)
* and stops the current task.
*/
private void startDestroying() {
owner.getEntity().getEvents().trigger(DESTROY);
owner.getEntity().getComponent(ColliderComponent.class).setLayer(PhysicsLayer.NONE);
owner.getEntity().getComponent(HitboxComponent.class).setLayer(PhysicsLayer.NONE);
currentTask.stop();
isDestroyed = true;
}

/**
* Starts the wait task.
*/
private void startWaiting() {
swapTask(waitTask);
}

/**
* Starts the movement task, to a particular destination
* @param destination the Vector2 position to which the entity needs to move
*/
private void startMoving(Vector2 destination) {
movementTask.setTarget(destination);
swapTask(movementTask);
}

/**
* Starts the combat task.
*/
private void startCombat() {
swapTask(combatTask);
}

/**
* Allows manual switching of tasks, from the current task to the supplied newTask.
* @param newTask the task being switched to.
*/
private void swapTask(Task newTask) {
if (currentTask != null) {
currentTask.stop();
}
currentTask = newTask;
currentTask.start();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class HumanWanderTask extends DefaultTask implements PriorityTask {
private HumanWaitTask waitTask;
private EngineerCombatTask combatTask;
private Task currentTask;
private boolean isDead = false;
private boolean isDead = false;

private boolean isSelected = false;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.csse3200.game.entities.configs;

/**
* Defines the properties stored in Bombship config files to be loaded by the Bombship Factory.
*/
public class BombshipConfigs extends BaseEntityConfig {
public BaseEntityConfig bombship = new BaseEntityConfig();
public int health = 100;
public int baseAttack = 20;
}
Loading

0 comments on commit 1750b40

Please sign in to comment.