diff --git a/Engine/src/live/AnimalEntity.ts b/Engine/src/live/AnimalEntity.ts index 9b1debf..2d45683 100644 --- a/Engine/src/live/AnimalEntity.ts +++ b/Engine/src/live/AnimalEntity.ts @@ -1,6 +1,7 @@ import * as planck from 'planck' +import { AnimalDefinition, CreatureDefinition } from '../data/animals' import { Action } from "./Action" -import { Entity } from "./Entity" +import { Entity, Sensor } from "./Entity" import { PuddleEntity } from './PuddleEntity' import { WorldContext } from './World' @@ -8,9 +9,19 @@ export class AnimalEntity extends Entity { action?: Action puddleTimer: number = 0 isPlayer: boolean = false + isMonster: boolean = false + thinkTimer: number = 0 // Time since last thought, used by AI. + target?: Entity + def: AnimalDefinition + mode: CreatureDefinition - constructor(ctor: string) { - super(ctor) + constructor(def: AnimalDefinition) { + super(`${def.name}.animal.stand.west.0`) + this.def = def + this.mode = def.animal + this.maxSpeed = def.animal.maxSpeed + this.acceleration = def.animal.acceleration + this.turnRate = def.animal.turnRate } act(actions: Action[]) { this.action = actions.sort((a, b) => { @@ -46,9 +57,9 @@ export class AnimalEntity extends Entity { case 'west': if (this.direction !== 0) { if (this.direction < 180) { - this.direction -= this.turnRate + this.direction -= this.mode.turnRate } else { - this.direction += this.turnRate + this.direction += this.mode.turnRate } } shouldMove = true @@ -56,9 +67,9 @@ export class AnimalEntity extends Entity { case 'east': if (this.direction !== 180) { if (this.direction < 180) { - this.direction += this.turnRate + this.direction += this.mode.turnRate } else { - this.direction -= this.turnRate + this.direction -= this.mode.turnRate } } shouldMove = true @@ -66,9 +77,9 @@ export class AnimalEntity extends Entity { case 'north': if (this.direction !== 90) { if (this.direction < 90 || this.direction > 270) { - this.direction += this.turnRate + this.direction += this.mode.turnRate } else { - this.direction -= this.turnRate + this.direction -= this.mode.turnRate } } shouldMove = true @@ -76,9 +87,9 @@ export class AnimalEntity extends Entity { case 'south': if (this.direction !== 270) { if (this.direction > 90 && this.direction < 270) { - this.direction += this.turnRate + this.direction += this.mode.turnRate } else { - this.direction -= this.turnRate + this.direction -= this.mode.turnRate } } shouldMove = true @@ -93,11 +104,11 @@ export class AnimalEntity extends Entity { if (shouldMove) { let r = this.direction * (Math.PI/180) - if (Math.abs(this.velocity[0]) < this.maxSpeed) { - this.velocity[0] -= Math.cos(r) * this.acceleration + if (Math.abs(this.velocity[0]) < this.mode.maxSpeed) { + this.velocity[0] -= Math.cos(r) * this.mode.acceleration } - if (Math.abs(this.velocity[1]) < this.maxSpeed) { - this.velocity[1] -= Math.sin(r) * this.acceleration + if (Math.abs(this.velocity[1]) < this.mode.maxSpeed) { + this.velocity[1] -= Math.sin(r) * this.mode.acceleration } let cardinal = this.getCardinal() this.sprite.setKey = 'run' @@ -131,6 +142,73 @@ export class AnimalEntity extends Entity { } return 'south' } + think(delta: number) { + this.thinkTimer += delta + while (this.thinkTimer >= 500) { + if (!this.isMonster) { + if (this.target) { + // Flee from target + } else { + // Wander. + } + } + this.thinkTimer -= 500 + } + } + setTarget(entity: Entity) { + } + sense(sensor: Sensor, entity: AnimalEntity) { + if (this.isPlayer) return + if (sensor.type === 'long') { + this.smell(entity) + } else { + this.see(entity) + } + } + lost(sensor: Sensor, entity: AnimalEntity) { + if (this.isPlayer) return + if (sensor.type === 'long') { + this.unsmell(entity) + } else { + this.unsee(entity) + } + } + smell(entity: AnimalEntity) { + if (entity.isPlayer) { + if (!this.isMonster) { + console.log('alert, we can smell player') + } else { + console.log('we smell the player, wander towards them') + } + } + } + unsmell(entity: AnimalEntity) { + if (entity.isPlayer) { + if (!this.isMonster) { + console.log('no longer smell player, wander around') + } else { + console.log('no longer smell player, wander around') + } + } + } + see(entity: AnimalEntity) { + if (entity.isPlayer) { + if (!this.isMonster) { + console.log('oh no, run from player') + } else { + console.log('oh ho, chase player') + } + } + } + unsee(entity: AnimalEntity) { + if (entity.isPlayer) { + if (!this.isMonster) { + console.log('oh ye, we safe') + } else { + console.log('hunt last seen location') + } + } + } } export function isAnimalEntity(o: any): o is AnimalEntity { diff --git a/Engine/src/states/Game.ts b/Engine/src/states/Game.ts index de6cdb3..d6c311b 100644 --- a/Engine/src/states/Game.ts +++ b/Engine/src/states/Game.ts @@ -9,10 +9,11 @@ import { SpriteInstance, sprites } from "../shared/sprites" import { ShapeCircle, ShapePoints } from "../data/sprite" import { SegmentZone } from "../data/segment" import { Zone } from "../live/Zone" -import { Entity } from "../live/Entity" +import { Entity, Sensor } from "../live/Entity" import { AnimalEntity, isAnimalEntity } from "../live/AnimalEntity" import { Action } from "../live/Action" import { WorldContext } from "../live/World" +import { animals } from "../data/animals" export interface PIXIMissingColorMatrix extends PIXI.Filter { night(intensity: number, multiply: boolean): void @@ -38,6 +39,7 @@ export function GameState(ctx: ContextI): StateI { let nightTime = 30 * 1000 let dayTime = 30 * 1000 let lastTime: number = performance.now() + let player: AnimalEntity let world: planck.World = planck.World({ gravity: planck.Vec2(0, 0), @@ -58,6 +60,16 @@ export function GameState(ctx: ContextI): StateI { b.addEntityContact(a) } } + if (a instanceof Sensor && a.entity instanceof AnimalEntity) { + if (b instanceof AnimalEntity) { + a.entity.sense(a, b) + } + } + if (b instanceof Sensor && b.entity instanceof AnimalEntity) { + if (a instanceof AnimalEntity) { + b.entity.sense(b, a) + } + } }) world.on('end-contact', (contact: planck.Contact) => { let a = contact.getFixtureA().getUserData() @@ -72,6 +84,16 @@ export function GameState(ctx: ContextI): StateI { b.removeEntityContact(a) } } + if (a instanceof Sensor && a.entity instanceof AnimalEntity) { + if (b instanceof AnimalEntity) { + a.entity.lost(a, b) + } + } + if (b instanceof Sensor && b.entity instanceof AnimalEntity) { + if (a instanceof AnimalEntity) { + b.entity.lost(b, a) + } + } }) let rootContainer = new PIXI.Container() @@ -145,14 +167,13 @@ export function GameState(ctx: ContextI): StateI { addZone(new Zone(z)) } - // Add bogus entity - let player = new AnimalEntity('deer.animal.stand.west.0') + // Add player entity + player = new AnimalEntity(animals.deer) player.isPlayer = true - addEntity(player, 'objects', 300, 100) // Add fake others - addEntity(new AnimalEntity('deer.animal.stand.west.0'), 'objects', 200, 200) + addEntity(new AnimalEntity(animals.turkey), 'objects', 200, 200) ctx.app.stage.addChild(rootContainer) } @@ -190,11 +211,21 @@ export function GameState(ctx: ContextI): StateI { } } for (let entity of entities) { - if (isAnimalEntity(entity) && entity.isPlayer) { - entity.act(desiredActions) - // FIXME: This isn't the right place for this. - rootContainer.position.set(Math.round(ctx.app.renderer.width/2), Math.round(ctx.app.renderer.height/2)) - rootContainer.pivot.set(Math.max(rootContainer.width/6, Math.min(entity.x, rootContainer.width/3)), Math.max(rootContainer.height/6, Math.min(entity.y, rootContainer.height/3))) + if (isAnimalEntity(entity)) { + if (entity.isPlayer) { + entity.act(desiredActions) + // FIXME: This isn't the right place for this. + rootContainer.position.set(Math.round(ctx.app.renderer.width/2), Math.round(ctx.app.renderer.height/2)) + rootContainer.pivot.set(Math.max(rootContainer.width/6, Math.min(entity.x, rootContainer.width/3)), Math.max(rootContainer.height/6, Math.min(entity.y, rootContainer.height/3))) + } else { + //if (entity.body?.) + // Some sort of simple AI. + if (!entity.isMonster) { + // Slowly wander and run from player if close. + } else { + // Heavily wander and run towards player if close. + } + } } else { //console.log('hmm, we do be tickin', entity, entity.x, entity.y) } @@ -237,6 +268,25 @@ export function GameState(ctx: ContextI): StateI { fixture.setUserData(entity) entity.body = body body.setUserData(entity) + // Sensors + let defaultShort = 50 + let defaultLong = 100 + if (entity instanceof AnimalEntity) { + defaultLong = entity.def.animal.scent + defaultShort = entity.def.animal.sight + } + // Create a short sensor (vision). + let senseFixture = body.createFixture({ + shape: planck.Circle(planck.Vec2(entity.x, entity.y), defaultShort), + isSensor: true, + }) + senseFixture.setUserData(new Sensor(entity, senseFixture, 'short')) + // Create a long sensor (scent). + let longSenseFixture = body.createFixture({ + shape: planck.Circle(planck.Vec2(entity.x, entity.y), defaultLong), + isSensor: true, + }) + longSenseFixture.setUserData(new Sensor(entity, longSenseFixture, 'long')) } } entity.x = x