2022-04-10 18:58:09 -04:00
|
|
|
import StateMachineAI from "../../Wolfie2D/AI/StateMachineAI";
|
|
|
|
import AABB from "../../Wolfie2D/DataTypes/Shapes/AABB";
|
|
|
|
import Vec2 from "../../Wolfie2D/DataTypes/Vec2";
|
|
|
|
import GameNode from "../../Wolfie2D/Nodes/GameNode";
|
|
|
|
import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite";
|
|
|
|
import OrthogonalTilemap from "../../Wolfie2D/Nodes/Tilemaps/OrthogonalTilemap";
|
|
|
|
import BattlerAI from "./BattlerAI";
|
|
|
|
|
|
|
|
import Patrol from "./EnemyStates/Patrol";
|
2022-04-24 20:15:34 -04:00
|
|
|
import Alert from "./EnemyStates/Alert";
|
2022-04-24 20:47:24 -04:00
|
|
|
import Attack from "./EnemyStates/Attack";
|
2022-04-17 15:47:55 -04:00
|
|
|
import { GameState, Statuses } from "../sword_enums";
|
2022-04-10 18:58:09 -04:00
|
|
|
|
|
|
|
import Sprite from "../../Wolfie2D/Nodes/Sprites/Sprite";
|
|
|
|
|
2022-04-10 21:32:22 -04:00
|
|
|
import { Player_Events } from "../sword_enums";
|
2022-04-17 15:47:55 -04:00
|
|
|
import InputWrapper from "../Tools/InputWrapper";
|
2022-04-20 15:11:30 -04:00
|
|
|
import Timer from "../../Wolfie2D/Timing/Timer";
|
2022-04-20 17:07:50 -04:00
|
|
|
import PlayerController from "../Player/PlayerController";
|
2022-04-20 18:31:53 -04:00
|
|
|
import Rect from "../../Wolfie2D/Nodes/Graphics/Rect";
|
|
|
|
import Color from "../../Wolfie2D/Utils/Color";
|
2022-04-24 19:51:23 -04:00
|
|
|
export default class EnemyAI extends StateMachineAI implements BattlerAI {
|
2022-04-10 18:58:09 -04:00
|
|
|
/** The owner of this AI */
|
|
|
|
owner: AnimatedSprite;
|
|
|
|
|
|
|
|
/** The total possible amount of health this entity has */
|
|
|
|
maxHealth: number;
|
|
|
|
|
|
|
|
/** The current amount of health this entity has */
|
|
|
|
CURRENT_HP: number;
|
|
|
|
|
|
|
|
/** The default movement speed of this AI */
|
|
|
|
speed: number = 20;
|
|
|
|
|
2022-04-24 20:15:34 -04:00
|
|
|
maxSpeed: number = 40;
|
|
|
|
|
2022-04-10 18:58:09 -04:00
|
|
|
/** A reference to the player object */
|
|
|
|
player: GameNode;
|
|
|
|
|
|
|
|
// The current known position of the player
|
|
|
|
playerPos: Vec2;
|
|
|
|
|
|
|
|
tilemap: OrthogonalTilemap;
|
|
|
|
|
|
|
|
velocity: Vec2 = Vec2.ZERO;
|
|
|
|
|
|
|
|
direction: number; //1 for right, -1 for left
|
|
|
|
|
2022-04-20 17:07:50 -04:00
|
|
|
exp_val: number = 100; //exp value to give player when this dies
|
2022-04-18 23:52:25 -04:00
|
|
|
|
2022-04-20 15:11:30 -04:00
|
|
|
poisonTimer : Timer;
|
|
|
|
poisonCounter : number = 0;
|
|
|
|
|
|
|
|
burnTimer : Timer ;
|
|
|
|
burnCounter : number =0;
|
|
|
|
|
|
|
|
bleedTimer : Timer;
|
|
|
|
bleedCounter :number = 0;
|
|
|
|
|
2022-04-20 18:31:53 -04:00
|
|
|
healthBar: Rect;
|
2022-04-21 11:36:58 -04:00
|
|
|
poisonStat: Sprite;
|
|
|
|
burnStat: Sprite;
|
|
|
|
bleedStat: Sprite;
|
2022-04-20 18:31:53 -04:00
|
|
|
|
2022-04-21 14:34:14 -04:00
|
|
|
attackTimer : Timer;
|
|
|
|
|
2022-04-20 15:11:30 -04:00
|
|
|
|
2022-04-20 17:07:50 -04:00
|
|
|
initializeAI(owner: AnimatedSprite, options: Record<string, any>): void {
|
2022-04-10 18:58:09 -04:00
|
|
|
this.owner = owner;
|
|
|
|
|
|
|
|
//add states
|
|
|
|
// Patrol mode
|
2022-04-24 19:51:23 -04:00
|
|
|
this.addState(EnemyStates.PATROL, new Patrol(this, owner));
|
2022-04-24 20:15:34 -04:00
|
|
|
this.addState(EnemyStates.ALERT, new Alert(this, owner));
|
2022-04-24 20:47:24 -04:00
|
|
|
this.addState(EnemyStates.ATTACK, new Attack(this, owner));
|
2022-04-10 18:58:09 -04:00
|
|
|
|
|
|
|
this.maxHealth = options.health;
|
|
|
|
|
|
|
|
this.CURRENT_HP = options.health;
|
|
|
|
|
|
|
|
this.player = options.player;
|
|
|
|
|
|
|
|
//TODO - get correct tilemap
|
|
|
|
this.tilemap = <OrthogonalTilemap>this.owner.getScene().getLayer("Wall").getItems()[0];
|
|
|
|
// Initialize to the default state
|
2022-04-24 19:51:23 -04:00
|
|
|
this.initialize(EnemyStates.PATROL);
|
2022-04-10 18:58:09 -04:00
|
|
|
|
|
|
|
this.direction = 1; //default moving to the right
|
2022-04-18 23:52:25 -04:00
|
|
|
|
|
|
|
//exp value
|
|
|
|
this.exp_val = options.exp;
|
2022-04-20 15:11:30 -04:00
|
|
|
|
|
|
|
//TODO - dots every 1 sec? can change
|
|
|
|
this.burnTimer = new Timer(1000);
|
|
|
|
this.bleedTimer = new Timer(1000);
|
|
|
|
this.poisonTimer = new Timer(1000);
|
|
|
|
|
2022-04-23 18:53:29 -04:00
|
|
|
this.attackTimer = new Timer(2500);
|
2022-04-10 18:58:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
damage(damage: number): void {
|
2022-04-22 12:56:49 -04:00
|
|
|
// enemy already dead, do not send new event
|
|
|
|
if (this.CURRENT_HP <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-20 13:53:55 -04:00
|
|
|
console.log(damage +" damage taken, "+this.CURRENT_HP+" hp left");
|
2022-04-10 18:58:09 -04:00
|
|
|
this.CURRENT_HP -= damage;
|
2022-04-10 21:32:22 -04:00
|
|
|
//TODO -
|
2022-04-16 23:01:54 -04:00
|
|
|
this.owner.animation.play("HURT",false);
|
2022-04-20 15:11:30 -04:00
|
|
|
console.log(damage +" damage taken, "+this.CURRENT_HP+" hp left");
|
2022-04-10 18:58:09 -04:00
|
|
|
|
|
|
|
// If health goes below 0, disable AI and fire enemyDied event
|
|
|
|
if (this.CURRENT_HP <= 0) {
|
|
|
|
this.owner.setAIActive(false, {});
|
|
|
|
this.owner.isCollidable = false;
|
|
|
|
this.owner.visible = false;
|
2022-04-21 15:53:05 -04:00
|
|
|
if (this.healthBar) {
|
|
|
|
this.healthBar.destroy();
|
|
|
|
this.healthBar = undefined;
|
|
|
|
}
|
|
|
|
if (this.poisonStat) {
|
|
|
|
this.poisonStat.destroy();
|
|
|
|
this.poisonStat = undefined;
|
|
|
|
}
|
|
|
|
if (this.burnStat) {
|
|
|
|
this.burnStat.destroy();
|
|
|
|
this.burnStat = undefined;
|
|
|
|
}
|
|
|
|
if (this.bleedStat) {
|
|
|
|
this.bleedStat.destroy();
|
|
|
|
this.bleedStat = undefined;
|
|
|
|
}
|
|
|
|
|
2022-04-10 21:32:22 -04:00
|
|
|
this.emitter.fireEvent(Player_Events.ENEMY_KILLED, {owner: this.owner.id, ai:this});
|
2022-04-10 18:58:09 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO - need to modify for side view
|
|
|
|
isPlayerVisible(pos: Vec2): Vec2{
|
2022-04-21 04:51:56 -04:00
|
|
|
//Check ifplayer is visible, taking into account walls
|
2022-04-10 18:58:09 -04:00
|
|
|
|
|
|
|
// Get the new player location
|
|
|
|
let start = this.owner.position.clone();
|
|
|
|
let delta = pos.clone().sub(start);
|
|
|
|
|
|
|
|
// Iterate through the tilemap region until we find a collision
|
|
|
|
let minX = Math.min(start.x, pos.x);
|
|
|
|
let maxX = Math.max(start.x, pos.x);
|
|
|
|
let minY = Math.min(start.y, pos.y);
|
|
|
|
let maxY = Math.max(start.y, pos.y);
|
|
|
|
|
|
|
|
// Get the wall tilemap
|
2022-04-21 04:51:56 -04:00
|
|
|
let walls = this.tilemap
|
2022-04-10 18:58:09 -04:00
|
|
|
|
|
|
|
let minIndex = walls.getColRowAt(new Vec2(minX, minY));
|
|
|
|
let maxIndex = walls.getColRowAt(new Vec2(maxX, maxY));
|
|
|
|
|
|
|
|
let tileSize = walls.getTileSize();
|
|
|
|
|
|
|
|
for (let col = minIndex.x; col <= maxIndex.x; col++) {
|
|
|
|
for (let row = minIndex.y; row <= maxIndex.y; row++) {
|
|
|
|
if (walls.isTileCollidable(col, row)) {
|
|
|
|
// Get the position of this tile
|
|
|
|
let tilePos = new Vec2(col * tileSize.x + tileSize.x / 2, row * tileSize.y + tileSize.y / 2);
|
|
|
|
|
|
|
|
// Create a collider for this tile
|
|
|
|
let collider = new AABB(tilePos, tileSize.scaled(1 / 2));
|
|
|
|
|
|
|
|
let hit = collider.intersectSegment(start, delta, Vec2.ZERO);
|
|
|
|
|
|
|
|
if (hit !== null && start.distanceSqTo(hit.pos) < start.distanceSqTo(pos)) {
|
|
|
|
// We hit a wall, we can't see the player
|
2022-04-23 18:53:29 -04:00
|
|
|
//console.log("player not visible")
|
2022-04-10 18:58:09 -04:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pos;
|
|
|
|
}
|
2022-04-21 04:51:56 -04:00
|
|
|
|
2022-04-10 18:58:09 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
2022-04-21 04:51:56 -04:00
|
|
|
/**
|
|
|
|
* gets the position of the player
|
|
|
|
* @returns position of the player if visible, else null
|
|
|
|
*/
|
|
|
|
getPlayerPosition(): Vec2 {
|
2022-04-24 19:51:23 -04:00
|
|
|
return this.isPlayerVisible(this.player.position);
|
2022-04-21 04:51:56 -04:00
|
|
|
}
|
2022-04-24 19:51:23 -04:00
|
|
|
|
2022-04-10 18:58:09 -04:00
|
|
|
update(deltaT: number){
|
2022-04-17 15:47:55 -04:00
|
|
|
if (InputWrapper.getState() != GameState.GAMING) {
|
2022-04-17 16:27:47 -04:00
|
|
|
this.owner.animation.pause();
|
2022-04-17 15:47:55 -04:00
|
|
|
return;
|
|
|
|
}
|
2022-04-17 16:27:47 -04:00
|
|
|
this.owner.animation.resume();
|
2022-04-10 18:58:09 -04:00
|
|
|
super.update(deltaT);
|
|
|
|
|
2022-04-20 15:11:30 -04:00
|
|
|
if(this.burnTimer.isStopped() && this.burnCounter >0){
|
|
|
|
this.burnCounter --;
|
|
|
|
this.burnTimer.start();
|
2022-04-20 17:07:50 -04:00
|
|
|
this.damage(5 + (<PlayerController>this.player._ai).extraDotDmg + (<PlayerController>this.player._ai).CURRENT_ATK * .2);
|
2022-04-20 15:11:30 -04:00
|
|
|
}
|
|
|
|
if(this.poisonTimer.isStopped() && this.poisonCounter >0){
|
|
|
|
this.poisonCounter --;
|
|
|
|
this.poisonTimer.start();
|
2022-04-20 17:07:50 -04:00
|
|
|
this.damage(5 + (<PlayerController>this.player._ai).extraDotDmg + (<PlayerController>this.player._ai).CURRENT_ATK * .2);
|
2022-04-20 15:11:30 -04:00
|
|
|
}
|
|
|
|
if(this.bleedTimer.isStopped() && this.bleedCounter >0){
|
|
|
|
this.bleedCounter --;
|
|
|
|
this.bleedTimer.start();
|
2022-04-20 17:07:50 -04:00
|
|
|
this.damage(5 + (<PlayerController>this.player._ai).extraDotDmg + (<PlayerController>this.player._ai).CURRENT_ATK * .08);
|
2022-04-20 15:11:30 -04:00
|
|
|
}
|
2022-04-21 04:51:56 -04:00
|
|
|
|
2022-04-21 15:53:05 -04:00
|
|
|
if (this.healthBar) {
|
|
|
|
this.healthBar.position = this.owner.collisionShape.center.clone().add(new Vec2(0, -((<AABB>this.owner.collisionShape).hh+5)));
|
|
|
|
this.healthBar.fillWidth = this.CURRENT_HP/this.maxHealth * this.owner.collisionShape.hw * 3;
|
|
|
|
if (this.CURRENT_HP/this.maxHealth >= 2/3) {
|
|
|
|
this.healthBar.color = Color.GREEN;
|
|
|
|
}
|
|
|
|
else if (this.CURRENT_HP/this.maxHealth >= 1/3) {
|
|
|
|
this.healthBar.color = Color.YELLOW;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.healthBar.color = Color.RED;
|
|
|
|
}
|
2022-04-20 18:31:53 -04:00
|
|
|
}
|
2022-04-21 15:53:05 -04:00
|
|
|
if (this.poisonStat) {
|
|
|
|
this.poisonStat.position = this.owner.collisionShape.center.clone().add(new Vec2(-((<AABB>this.owner.collisionShape).hw)*1.5+10, -((<AABB>this.owner.collisionShape).hh+15)));
|
|
|
|
this.poisonStat.visible = this.poisonCounter > 0;
|
2022-04-20 18:31:53 -04:00
|
|
|
}
|
2022-04-21 15:53:05 -04:00
|
|
|
if (this.burnStat) {
|
|
|
|
this.burnStat.position = this.poisonStat.position.clone().add(new Vec2(15, 0));
|
|
|
|
this.burnStat.visible = this.burnCounter > 0;
|
2022-04-20 18:31:53 -04:00
|
|
|
}
|
2022-04-21 15:53:05 -04:00
|
|
|
if (this.bleedStat) {
|
|
|
|
this.bleedStat.position = this.poisonStat.position.clone().add(new Vec2(30, 0));
|
|
|
|
this.bleedStat.visible = this.bleedCounter > 0;
|
2022-04-24 20:35:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this.owner.position.y > this.tilemap.getDimensions().y * this.tilemap.getTileSize().y) {
|
|
|
|
this.CURRENT_HP = -1;
|
|
|
|
this.emitter.fireEvent(Player_Events.ENEMY_KILLED, {owner: this.owner.id, ai:this});
|
|
|
|
}
|
2022-04-10 18:58:09 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum EnemyStates {
|
2022-04-24 19:51:23 -04:00
|
|
|
PATROL = "patrol",
|
2022-04-10 18:58:09 -04:00
|
|
|
ALERT = "alert",
|
2022-04-24 19:51:23 -04:00
|
|
|
ATTACK = "attack"
|
|
|
|
}
|