diff --git a/source/core/assets/configs/Engineers.json b/source/core/assets/configs/Engineers.json new file mode 100644 index 000000000..35848e3c2 --- /dev/null +++ b/source/core/assets/configs/Engineers.json @@ -0,0 +1,6 @@ +{ + "engineer" : { + "health": 100, + "baseAttack": 5 +} +} \ No newline at end of file diff --git a/source/core/assets/images/engineers/engineer.atlas b/source/core/assets/images/engineers/engineer.atlas new file mode 100644 index 000000000..4db973b8c --- /dev/null +++ b/source/core/assets/images/engineers/engineer.atlas @@ -0,0 +1,272 @@ + +engineer.png +size: 512, 256 +format: RGBA8888 +filter: Nearest, Nearest +repeat: none +firing + rotate: false + xy: 25, 190 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 2 +firing + rotate: false + xy: 25, 154 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 4 +firing + rotate: false + xy: 102, 190 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 1 +firing + rotate: false + xy: 25, 118 + size: 52, 31 + orig: 52, 31 + offset: 0, 0 + index: 3 +death + rotate: false + xy: 25, 81 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 6 +death + rotate: false + xy: 350, 189 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 3 +death + rotate: false + xy: 330, 152 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 5 +death + rotate: false + xy: 82, 79 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 2 +death + rotate: false + xy: 253, 78 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 4 +death + rotate: false + xy: 367, 41 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 1 +hit + rotate: false + xy: 25, 7 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 3 +hit + rotate: false + xy: 159, 115 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 2 +hit + rotate: false + xy: 253, 41 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 1 +idle_left + rotate: false + xy: 179, 189 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 2 +idle_left + rotate: false + xy: 216, 152 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 4 +idle_left + rotate: false + xy: 216, 115 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 1 +idle_left + rotate: false + xy: 139, 41 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 3 +idle_right + rotate: false + xy: 159, 152 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 2 +idle_right + rotate: false + xy: 273, 115 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 4 +idle_right + rotate: false + xy: 82, 5 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 1 +idle_right + rotate: false + xy: 310, 78 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 3 +default + rotate: false + xy: 216, 115 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 1 +walk_left + rotate: false + xy: 102, 153 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 6 +walk_left + rotate: false + xy: 293, 189 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 3 +walk_left + rotate: false + xy: 407, 189 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 8 +walk_left + rotate: false + xy: 273, 152 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 5 +walk_left + rotate: false + xy: 387, 115 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 2 +walk_left + rotate: false + xy: 82, 42 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 7 +walk_left + rotate: false + xy: 196, 78 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 4 +walk_left + rotate: false + xy: 367, 78 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 1 +walk_right + rotate: false + xy: 25, 44 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 2 +walk_right + rotate: false + xy: 236, 189 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 7 +walk_right + rotate: false + xy: 102, 116 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 4 +walk_right + rotate: false + xy: 387, 152 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 1 +walk_right + rotate: false + xy: 330, 115 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 6 +walk_right + rotate: false + xy: 139, 78 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 3 +walk_right + rotate: false + xy: 196, 41 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 8 +walk_right + rotate: false + xy: 310, 41 + size: 52, 31 + orig: 32, 32 + offset: 0, 0 + index: 5 diff --git a/source/core/assets/images/engineers/engineer.png b/source/core/assets/images/engineers/engineer.png new file mode 100644 index 000000000..acc36d01d Binary files /dev/null and b/source/core/assets/images/engineers/engineer.png differ diff --git a/source/core/assets/sounds/engineers/firing_auto.mp3 b/source/core/assets/sounds/engineers/firing_auto.mp3 new file mode 100644 index 000000000..f0b68569d Binary files /dev/null and b/source/core/assets/sounds/engineers/firing_auto.mp3 differ diff --git a/source/core/assets/sounds/engineers/firing_single.mp3 b/source/core/assets/sounds/engineers/firing_single.mp3 new file mode 100644 index 000000000..73a991588 Binary files /dev/null and b/source/core/assets/sounds/engineers/firing_single.mp3 differ diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java index c3e93e6ef..02d13745e 100644 --- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java +++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.components.CombatStatsComponent; import com.csse3200.game.input.DropInputComponent; import com.csse3200.game.areas.terrain.TerrainFactory; import com.csse3200.game.areas.terrain.TerrainFactory.TerrainType; @@ -100,7 +101,8 @@ public class ForestGameArea extends GameArea { "sounds/Impact4.ogg", "sounds/towers/gun_shot_trimmed.mp3", "sounds/towers/deploy.mp3", - "sounds/towers/stow.mp3" + "sounds/towers/stow.mp3", + "sounds/engineers/firing_auto.mp3" }; private static final String backgroundMusic = "sounds/background/Sci-Fi1.ogg"; private static final String[] forestMusic = {backgroundMusic}; @@ -134,9 +136,9 @@ public void create() { displayUI(); spawnTerrain(); - spawnBuilding1(); - spawnBuilding2(); - spawnMountains(); +// spawnBuilding1(); +// spawnBuilding2(); +// spawnMountains(); player = spawnPlayer(); playMusic(); @@ -154,6 +156,8 @@ public void create() { bossKing1 = spawnBossKing1(); bossKing2 = spawnBossKing2(); + Entity engineer = EngineerFactory.createEngineer(player); + spawnEntityAt(engineer, new GridPoint2(5, 20), true, true); playMusic(); } diff --git a/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java b/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java index a860b6547..2f157f887 100644 --- a/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java +++ b/source/core/src/main/com/csse3200/game/components/CombatStatsComponent.java @@ -127,12 +127,18 @@ public void setBaseAttack(int attack) { public void hit(Integer damage) { int newHealth = getHealth() - damage; setHealth(newHealth); + if (entity != null && !this.isDead()) { + entity.getEvents().trigger("hitStart"); + } changeState(); } // Default CombatStatsComponent that relies on the attacker's combatStatsComponent. public void hit(CombatStatsComponent attacker) { int newHealth = getHealth() - attacker.getBaseAttack(); + if (entity != null && !this.isDead()) { + entity.getEvents().trigger("hitStart"); + } setHealth(newHealth); } diff --git a/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java b/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java new file mode 100644 index 000000000..0b5c8ea1e --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/player/HumanAnimationController.java @@ -0,0 +1,91 @@ +package com.csse3200.game.components.player; + +import com.badlogic.gdx.audio.Sound; +import com.csse3200.game.components.Component; +import com.csse3200.game.rendering.AnimationRenderComponent; +import com.csse3200.game.services.ServiceLocator; + +/** + * Listens for events relevant to a Human character (Just engineers at this stage) + * Each event will trigger a certain animation + */ +public class HumanAnimationController extends Component { + // Event name constants + private static final String IDLEL = "idleLeft"; + private static final String IDLER = "idleRight"; + private static final String WALKL = "walkLeftStart"; + private static final String WALKR = "walkRightStart"; + private static final String FIRING = "firingStart"; + private static final String HIT = "hitStart"; + private static final String DEATH = "deathStart"; + // Animation name constants + private static final String IDLEL_ANIM = "idle_left"; + private static final String IDLER_ANIM = "idle_right"; + private static final String WALKL_ANIM = "walk_left"; + private static final String WALKR_ANIM = "walk_right"; + private static final String FIRE_ANIM = "firing"; + private static final String HIT_ANIM = "hit"; + private static final String DEATH_ANIM = "death"; + // Sound effects constants +// private static final String RUN_SFX = ""; + private static final String FIRE_AUTO_SFX = "sounds/engineers/firing_auto.mp3"; +// private static final String FIRE_SINGLE_SFX = "sounds/engineers/firing_single.mp3"; +// private static final String HIT_SFX = ""; +// private static final String DEATH_SFX = ""; + + AnimationRenderComponent animator; +// Sound runSound = ServiceLocator.getResourceService().getAsset( +// RUN_SFX, Sound.class); + Sound fireAutoSound = ServiceLocator.getResourceService().getAsset( + FIRE_AUTO_SFX, Sound.class); +// Sound fireSingleSound = ServiceLocator.getResourceService().getAsset( +// FIRE_SINGLE_SFX, Sound.class); +// Sound hitSound = ServiceLocator.getResourceService().getAsset( +// HIT_SFX, Sound.class); +// Sound deathSound = ServiceLocator.getResourceService().getAsset( +// HIT_SFX, Sound.class); + + @Override + public void create() { + super.create(); + animator = this.entity.getComponent(AnimationRenderComponent.class); + entity.getEvents().addListener(IDLEL, this::animateIdleLeft); + entity.getEvents().addListener(IDLER, this::animateIdleRight); + entity.getEvents().addListener(WALKL, this::animateLeftWalk); + entity.getEvents().addListener(WALKR, this::animateRightWalk); + entity.getEvents().addListener(FIRING, this::animateFiring); + entity.getEvents().addListener(HIT, this::animateHit); + entity.getEvents().addListener(DEATH, this::animateDeath); + } + + void animateIdleLeft() { + animator.startAnimation(IDLEL_ANIM); + } + void animateIdleRight() { + animator.startAnimation(IDLER_ANIM); + } + + void animateLeftWalk() { + animator.startAnimation(WALKL_ANIM); +// runSound.play(); + } + void animateRightWalk() { + animator.startAnimation(WALKR_ANIM); +// runSound.play(); + } + + void animateFiring() { + animator.startAnimation(FIRE_ANIM); + fireAutoSound.play(); + } + + void animateHit() { + animator.startAnimation(HIT_ANIM); +// hitSound.play(); + } + + void animateDeath() { + animator.startAnimation(DEATH_ANIM); +// deathSound.play(); + } +} \ No newline at end of file diff --git a/source/core/src/main/com/csse3200/game/components/player/HumanCombatStatsComponent.java b/source/core/src/main/com/csse3200/game/components/player/HumanCombatStatsComponent.java new file mode 100644 index 000000000..cc693d14e --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/player/HumanCombatStatsComponent.java @@ -0,0 +1,41 @@ +package com.csse3200.game.components.player; + +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.entities.Melee; +import com.csse3200.game.entities.Weapon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; + +/** + * Component used to store information related to combat such as health, attack, etc. Any entities + * which engage it combat should have an instance of this class registered. This class can be + * extended for more specific combat needs. + * + * health: the current health of the entity + * baseAttack: the base attack of the entity + * fullHealth: the full health of the entity + * state: the current state of the entity (full health above 66%, half health above 33%, low health below 33%) + * drops: the items that the entity drops when it dies + * closeRangeAbilities: the Melee abilities (close range) of the entity + * longRangeAbilities: the Projectile abilities (long range) of the entity + * + */ +public class HumanCombatStatsComponent extends CombatStatsComponent { + + public HumanCombatStatsComponent(int health, int baseAttack) { + super(health, baseAttack); + } + + /** + * Decrease the health of the entity based on the damage provided. + * */ + @Override + public void hit(Integer damage) { + int newHealth = getHealth() - damage; + setHealth(newHealth); + entity.getEvents().trigger("hitStart"); + changeState(); + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java new file mode 100644 index 000000000..b25c9f7a2 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanMovementTask.java @@ -0,0 +1,97 @@ +package com.csse3200.game.components.tasks.human; + +import com.badlogic.gdx.math.Vector2; +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Move to a given position, finishing when you get close enough. Requires an entity with a + * PhysicsMovementComponent. + */ +public class HumanMovementTask extends DefaultTask { + private static final Logger logger = LoggerFactory.getLogger(HumanMovementTask.class); + + private final GameTime gameTime; + private Entity target; + private float stopDistance = 0.01f; + private long lastTimeMoved; + private Vector2 lastPos; + private PhysicsMovementComponent movementComponent; + + public HumanMovementTask(Entity target) { + this.target = target; + this.gameTime = ServiceLocator.getTimeSource(); + } + + public HumanMovementTask(Entity target, float stopDistance) { + this(target); + this.stopDistance = stopDistance; + } + + @Override + public void start() { + super.start(); + this.movementComponent = owner.getEntity().getComponent(PhysicsMovementComponent.class); + movementComponent.setTarget(target.getPosition()); + movementComponent.setMoving(true); + + // Trigger the correct walk animation depending on the target location. + if (target.getPosition().x < owner.getEntity().getPosition().x) { + owner.getEntity().getEvents().trigger("walkLeftStart"); + } else { + owner.getEntity().getEvents().trigger("walkRightStart"); + } + + logger.debug("Starting movement towards {}", target); + lastTimeMoved = gameTime.getTime(); + lastPos = owner.getEntity().getPosition(); + } + + @Override + public void update() { + if (isAtTarget()) { + movementComponent.setMoving(false); + owner.getEntity().getEvents().trigger("idleStart"); + status = Status.FINISHED; + logger.debug("Finished moving to {}", target); + } else { + checkIfStuck(); + } + } + + public void setTarget(Entity target) { + this.target = target; + movementComponent.setTarget(target.getPosition()); + } + + @Override + public void stop() { + super.stop(); + movementComponent.setMoving(false); + logger.debug("Stopping movement"); + } + + private boolean isAtTarget() { + return owner.getEntity().getPosition().dst(target.getPosition()) <= stopDistance; + } + + private void checkIfStuck() { + if (didMove()) { + lastTimeMoved = gameTime.getTime(); + lastPos = owner.getEntity().getPosition(); + } else if (gameTime.getTimeSince(lastTimeMoved) > 500L) { + movementComponent.setMoving(false); + status = Status.FAILED; + logger.debug("Got stuck! Failing movement task"); + } + } + + private boolean didMove() { + return owner.getEntity().getPosition().dst2(lastPos) > 0.001f; + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWaitTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWaitTask.java new file mode 100644 index 000000000..06072c46b --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWaitTask.java @@ -0,0 +1,39 @@ +package com.csse3200.game.components.tasks.human; + +import com.csse3200.game.ai.tasks.DefaultTask; +import com.csse3200.game.services.GameTime; +import com.csse3200.game.services.ServiceLocator; + +/** + * Task that does nothing other than waiting for a given time. Status is Finished + * after the time has passed. + */ +public class HumanWaitTask extends DefaultTask { + private final GameTime timeSource; + private final float duration; + private long endTime; + + /** + * @param duration How long to wait for, in seconds. + */ + public HumanWaitTask(float duration) { + timeSource = ServiceLocator.getTimeSource(); + this.duration = duration; + } + + /** + * Start waiting from now until duration has passed. + */ + @Override + public void start() { + super.start(); + endTime = timeSource.getTime() + (int)(duration * 1000); + } + + @Override + public void update() { + if (timeSource.getTime() >= endTime) { + status = Status.FINISHED; + } + } +} diff --git a/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java new file mode 100644 index 000000000..cc27a6b6a --- /dev/null +++ b/source/core/src/main/com/csse3200/game/components/tasks/human/HumanWanderTask.java @@ -0,0 +1,117 @@ +package com.csse3200.game.components.tasks.human; + +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.entities.Entity; +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.GameTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wander around by moving a random position within a range of the starting position. Wait a little + * bit between movements. Requires an entity with a PhysicsMovementComponent. + */ +public class HumanWanderTask extends DefaultTask implements PriorityTask { + private static final Logger logger = LoggerFactory.getLogger(HumanWanderTask.class); + + private final Entity wanderRange; + private final float waitTime; + private Vector2 startPos; + private HumanMovementTask movementTask; + private HumanWaitTask waitTask; + private Task currentTask; + private GameTime endTime; + + private boolean isDead = false; + + /** + * @param target Distance in X and Y the entity can move from its position when start() is + * called. + * @param waitTime How long in seconds to wait between wandering. + */ + public HumanWanderTask(Entity target, float waitTime) { + this.wanderRange = target; + this.waitTime = waitTime; + } + + @Override + public int getPriority() { + return 1; // Low priority task + } + + @Override + public void start() { + super.start(); + startPos = owner.getEntity().getPosition(); + + waitTask = new HumanWaitTask(waitTime); + waitTask.create(owner); + + movementTask = new HumanMovementTask(this.wanderRange, 1f); + movementTask.create(owner); + + movementTask.start(); + + currentTask = movementTask; + } + + @Override + public void update() { + if (!isDead && owner.getEntity().getComponent(CombatStatsComponent.class).isDead()) { + owner.getEntity().getEvents().trigger("deathStart"); + owner.getEntity().getComponent(ColliderComponent.class).setLayer(PhysicsLayer.NONE); + owner.getEntity().getComponent(HitboxComponent.class).setLayer(PhysicsLayer.NONE); + currentTask.stop(); + // Add a time delay here to allow animation to play? + isDead = true; + } + else if (isDead && owner.getEntity().getComponent(AnimationRenderComponent.class).isFinished()) { + owner.getEntity().setFlagForDelete(true); + // make the appropriate calls to decrement the human count. + } + else if (!isDead) { + if (currentTask.getStatus() != Status.ACTIVE) { + + if (currentTask == movementTask) { + startWaiting(); + owner.getEntity().getEvents().trigger("idleRight"); + } else { + startMoving(); + } + } + currentTask.update(); + } + } + + private void startWaiting() { + logger.debug("Starting waiting"); + swapTask(waitTask); + } + + private void startMoving() { + logger.debug("Starting moving"); + movementTask.setTarget(this.wanderRange); + swapTask(movementTask); + } + + private void swapTask(Task newTask) { + if (currentTask != null) { + currentTask.stop(); + } + currentTask = newTask; + currentTask.start(); + } + + private Vector2 getDirection() { +// float y = startPos.y; +// return new Vector2(0, y); + return this.wanderRange.getPosition(); + } +} diff --git a/source/core/src/main/com/csse3200/game/entities/configs/EngineerConfigs.java b/source/core/src/main/com/csse3200/game/entities/configs/EngineerConfigs.java new file mode 100644 index 000000000..e5b5159d7 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/configs/EngineerConfigs.java @@ -0,0 +1,13 @@ +package com.csse3200.game.entities.configs; + +/** + * Defines the properties stored in Engineer config files to be loaded by the Engineer Factory. + */ +public class EngineerConfigs extends BaseEntityConfig { + public BaseEntityConfig engineer = new BaseEntityConfig(); + + public int health = 10; + public int baseAttack = 1; + + +} diff --git a/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java new file mode 100644 index 000000000..7e5a69091 --- /dev/null +++ b/source/core/src/main/com/csse3200/game/entities/factories/EngineerFactory.java @@ -0,0 +1,98 @@ +package com.csse3200.game.entities.factories; + +import com.badlogic.gdx.graphics.g2d.Animation; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.csse3200.game.ai.tasks.AITaskComponent; +import com.csse3200.game.components.CombatStatsComponent; +import com.csse3200.game.components.TouchAttackComponent; +import com.csse3200.game.components.player.HumanAnimationController; +import com.csse3200.game.components.tasks.human.HumanWanderTask; +import com.csse3200.game.entities.Entity; +import com.csse3200.game.entities.configs.*; +import com.csse3200.game.files.FileLoader; +import com.csse3200.game.physics.PhysicsLayer; +import com.csse3200.game.physics.PhysicsUtils; +import com.csse3200.game.physics.components.ColliderComponent; +import com.csse3200.game.physics.components.HitboxComponent; +import com.csse3200.game.physics.components.PhysicsComponent; +import com.csse3200.game.physics.components.PhysicsMovementComponent; +import com.csse3200.game.rendering.AnimationRenderComponent; + +/** + * Factory to create non-playable human character (NPC) entities with predefined components. + * + * These may be modified to become controllable characters in future sprints. + * + *

Each NPC entity type should have a creation method that returns a corresponding entity. + * Predefined entity properties can be loaded from configs stored as json files which are defined in + * "NPCConfigs". + * + *

If needed, this factory can be separated into more specific factories for entities with + * similar characteristics. + */ +public class EngineerFactory { + private static final EngineerConfigs configs = + FileLoader.readClass(EngineerConfigs.class, "configs/Engineers.json"); + + private static final float HUMAN_SCALE_X = 1f; + private static final float HUMAN_SCALE_Y = 0.8f; + + /** + * Creates an Engineer entity, based on a base Human entity, with the appropriate components and animations + * + * + * @return entity + */ + public static Entity createEngineer(Entity target) { + Entity engineer = createBaseHumanNPC(target); + BaseEntityConfig config = configs.engineer; + + AnimationRenderComponent animator = new AnimationRenderComponent( + new TextureAtlas("images/engineers/engineer.atlas")); + animator.addAnimation("walk_left", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("walk_right", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("idle_right", 0.2f, Animation.PlayMode.LOOP); + animator.addAnimation("firing", 0.1f, Animation.PlayMode.NORMAL); + animator.addAnimation("hit", 0.01f, Animation.PlayMode.NORMAL); + animator.addAnimation("death", 0.1f, Animation.PlayMode.NORMAL); + + + engineer + .addComponent(new CombatStatsComponent(config.health, config.baseAttack)) + .addComponent(animator) + .addComponent(new HumanAnimationController()); + + engineer.getComponent(AnimationRenderComponent.class).scaleEntity(); + engineer.setScale(HUMAN_SCALE_X, HUMAN_SCALE_Y); + return engineer; + } + + /** + * Creates a generic human npc to be used as a base entity by more specific NPC creation methods. + * + * @return entity + */ + public static Entity createBaseHumanNPC(Entity target) { + AITaskComponent aiComponent = + new AITaskComponent() + .addTask(new HumanWanderTask(target, 1f)); + + Entity human = + new Entity() + .addComponent(new PhysicsComponent()) + .addComponent(new PhysicsMovementComponent()) + .addComponent(new ColliderComponent()) + .addComponent(new HitboxComponent().setLayer(PhysicsLayer.PLAYER)) + .addComponent(new TouchAttackComponent(PhysicsLayer.NPC, 1.5f)) + .addComponent(aiComponent); + + PhysicsUtils.setScaledCollider(human, 0.9f, 0.4f); + return human; + } + + private EngineerFactory() { + throw new IllegalStateException("Instantiating static util class"); + } +} + + diff --git a/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java b/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java index dd7777f79..4005f8818 100644 --- a/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java +++ b/source/core/src/main/com/csse3200/game/entities/factories/NPCFactory.java @@ -113,9 +113,9 @@ public static Entity createXenoGrunt(Entity target) { new AnimationRenderComponent( ServiceLocator.getResourceService().getAsset("images/mobs/xenoGruntRunning.atlas", TextureAtlas.class)); animator.addAnimation("xeno_run", 0.1f, Animation.PlayMode.LOOP); - animator.addAnimation("xeno_shoot", 0.1f, Animation.PlayMode.NORMAL); - animator.addAnimation("xeno_melee", 0.1f, Animation.PlayMode.NORMAL); - animator.addAnimation("xeno_die", 0.1f, Animation.PlayMode.NORMAL); +// animator.addAnimation("xeno_shoot", 0.1f, Animation.PlayMode.NORMAL); +// animator.addAnimation("xeno_melee", 0.1f, Animation.PlayMode.NORMAL); +// animator.addAnimation("xeno_die", 0.1f, Animation.PlayMode.NORMAL); xenoGrunt .addComponent(new CombatStatsComponent(config.fullHeath, config.baseAttack, drops, melee, projectiles)) @@ -135,8 +135,8 @@ public static Entity createXenoGrunt(Entity target) { public static Entity createBaseNPC(Entity target) { AITaskComponent aiComponent = new AITaskComponent() - .addTask(new WanderTask(new Vector2(2f, 2f), 2f)) - .addTask(new ShootTask(target, 10, 3f, 4f)); + .addTask(new WanderTask(new Vector2(2f, 2f), 2f)); +// .addTask(new ShootTask(target, 10, 3f, 4f)); //.addTask(new ChaseTask(target, 10, 3f, 4f)); Entity npc = new Entity() diff --git a/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java b/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java index f4db690cb..882ad4935 100644 --- a/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java +++ b/source/core/src/main/com/csse3200/game/physics/PhysicsEngine.java @@ -45,7 +45,7 @@ public PhysicsEngine(World world, GameTime timeSource) { public void update() { // Check for deleted bodies and joints -// checkAndDeleteBodies(); + // checkAndDeleteBodies(); // Updating physics isn't as easy as triggering an update every frame. Each frame could take a // different amount of time to run, but physics simulations are only stable if computed at a @@ -91,16 +91,18 @@ public void checkAndDeleteBodies() { world.getBodies(bodies); // Check for bodies to be deleted - for(Body body : bodies) { - Entity entity = ((BodyUserData) body.getUserData()).entity; - - // If the entity is flagged for deletion, destroy the body before world.step() is called - if(entity.getFlagForDelete()) { - logger.debug("Destroying physics body {}", body); - ProjectileDestructors.destroyProjectile(entity); - - // Make sure not to delete the body twice - entity.setFlagForDelete(false); + for (Body body : bodies) { + // check for null values + if (body.getUserData() != null) { + Entity entity = ((BodyUserData) body.getUserData()).entity; + // If the entity is flagged for deletion, destroy the body before world.step() is called + if (entity.getFlagForDelete()) { + logger.debug("Destroying physics body {}", body); + ProjectileDestructors.destroyProjectile(entity); + + // Make sure not to delete the body twice + entity.setFlagForDelete(false); + } } } }