Add senses and use animal definitions
This commit is contained in:
parent
22fe8e9fb1
commit
a9f2a8824e
|
@ -1,6 +1,7 @@
|
||||||
import * as planck from 'planck'
|
import * as planck from 'planck'
|
||||||
|
import { AnimalDefinition, CreatureDefinition } from '../data/animals'
|
||||||
import { Action } from "./Action"
|
import { Action } from "./Action"
|
||||||
import { Entity } from "./Entity"
|
import { Entity, Sensor } from "./Entity"
|
||||||
import { PuddleEntity } from './PuddleEntity'
|
import { PuddleEntity } from './PuddleEntity'
|
||||||
import { WorldContext } from './World'
|
import { WorldContext } from './World'
|
||||||
|
|
||||||
|
@ -8,9 +9,19 @@ export class AnimalEntity extends Entity {
|
||||||
action?: Action
|
action?: Action
|
||||||
puddleTimer: number = 0
|
puddleTimer: number = 0
|
||||||
isPlayer: boolean = false
|
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) {
|
constructor(def: AnimalDefinition) {
|
||||||
super(ctor)
|
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[]) {
|
act(actions: Action[]) {
|
||||||
this.action = actions.sort((a, b) => {
|
this.action = actions.sort((a, b) => {
|
||||||
|
@ -46,9 +57,9 @@ export class AnimalEntity extends Entity {
|
||||||
case 'west':
|
case 'west':
|
||||||
if (this.direction !== 0) {
|
if (this.direction !== 0) {
|
||||||
if (this.direction < 180) {
|
if (this.direction < 180) {
|
||||||
this.direction -= this.turnRate
|
this.direction -= this.mode.turnRate
|
||||||
} else {
|
} else {
|
||||||
this.direction += this.turnRate
|
this.direction += this.mode.turnRate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shouldMove = true
|
shouldMove = true
|
||||||
|
@ -56,9 +67,9 @@ export class AnimalEntity extends Entity {
|
||||||
case 'east':
|
case 'east':
|
||||||
if (this.direction !== 180) {
|
if (this.direction !== 180) {
|
||||||
if (this.direction < 180) {
|
if (this.direction < 180) {
|
||||||
this.direction += this.turnRate
|
this.direction += this.mode.turnRate
|
||||||
} else {
|
} else {
|
||||||
this.direction -= this.turnRate
|
this.direction -= this.mode.turnRate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shouldMove = true
|
shouldMove = true
|
||||||
|
@ -66,9 +77,9 @@ export class AnimalEntity extends Entity {
|
||||||
case 'north':
|
case 'north':
|
||||||
if (this.direction !== 90) {
|
if (this.direction !== 90) {
|
||||||
if (this.direction < 90 || this.direction > 270) {
|
if (this.direction < 90 || this.direction > 270) {
|
||||||
this.direction += this.turnRate
|
this.direction += this.mode.turnRate
|
||||||
} else {
|
} else {
|
||||||
this.direction -= this.turnRate
|
this.direction -= this.mode.turnRate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shouldMove = true
|
shouldMove = true
|
||||||
|
@ -76,9 +87,9 @@ export class AnimalEntity extends Entity {
|
||||||
case 'south':
|
case 'south':
|
||||||
if (this.direction !== 270) {
|
if (this.direction !== 270) {
|
||||||
if (this.direction > 90 && this.direction < 270) {
|
if (this.direction > 90 && this.direction < 270) {
|
||||||
this.direction += this.turnRate
|
this.direction += this.mode.turnRate
|
||||||
} else {
|
} else {
|
||||||
this.direction -= this.turnRate
|
this.direction -= this.mode.turnRate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shouldMove = true
|
shouldMove = true
|
||||||
|
@ -93,11 +104,11 @@ export class AnimalEntity extends Entity {
|
||||||
|
|
||||||
if (shouldMove) {
|
if (shouldMove) {
|
||||||
let r = this.direction * (Math.PI/180)
|
let r = this.direction * (Math.PI/180)
|
||||||
if (Math.abs(this.velocity[0]) < this.maxSpeed) {
|
if (Math.abs(this.velocity[0]) < this.mode.maxSpeed) {
|
||||||
this.velocity[0] -= Math.cos(r) * this.acceleration
|
this.velocity[0] -= Math.cos(r) * this.mode.acceleration
|
||||||
}
|
}
|
||||||
if (Math.abs(this.velocity[1]) < this.maxSpeed) {
|
if (Math.abs(this.velocity[1]) < this.mode.maxSpeed) {
|
||||||
this.velocity[1] -= Math.sin(r) * this.acceleration
|
this.velocity[1] -= Math.sin(r) * this.mode.acceleration
|
||||||
}
|
}
|
||||||
let cardinal = this.getCardinal()
|
let cardinal = this.getCardinal()
|
||||||
this.sprite.setKey = 'run'
|
this.sprite.setKey = 'run'
|
||||||
|
@ -131,6 +142,73 @@ export class AnimalEntity extends Entity {
|
||||||
}
|
}
|
||||||
return 'south'
|
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 {
|
export function isAnimalEntity(o: any): o is AnimalEntity {
|
||||||
|
|
|
@ -9,10 +9,11 @@ import { SpriteInstance, sprites } from "../shared/sprites"
|
||||||
import { ShapeCircle, ShapePoints } from "../data/sprite"
|
import { ShapeCircle, ShapePoints } from "../data/sprite"
|
||||||
import { SegmentZone } from "../data/segment"
|
import { SegmentZone } from "../data/segment"
|
||||||
import { Zone } from "../live/Zone"
|
import { Zone } from "../live/Zone"
|
||||||
import { Entity } from "../live/Entity"
|
import { Entity, Sensor } from "../live/Entity"
|
||||||
import { AnimalEntity, isAnimalEntity } from "../live/AnimalEntity"
|
import { AnimalEntity, isAnimalEntity } from "../live/AnimalEntity"
|
||||||
import { Action } from "../live/Action"
|
import { Action } from "../live/Action"
|
||||||
import { WorldContext } from "../live/World"
|
import { WorldContext } from "../live/World"
|
||||||
|
import { animals } from "../data/animals"
|
||||||
|
|
||||||
export interface PIXIMissingColorMatrix extends PIXI.Filter {
|
export interface PIXIMissingColorMatrix extends PIXI.Filter {
|
||||||
night(intensity: number, multiply: boolean): void
|
night(intensity: number, multiply: boolean): void
|
||||||
|
@ -38,6 +39,7 @@ export function GameState(ctx: ContextI): StateI {
|
||||||
let nightTime = 30 * 1000
|
let nightTime = 30 * 1000
|
||||||
let dayTime = 30 * 1000
|
let dayTime = 30 * 1000
|
||||||
let lastTime: number = performance.now()
|
let lastTime: number = performance.now()
|
||||||
|
let player: AnimalEntity
|
||||||
|
|
||||||
let world: planck.World = planck.World({
|
let world: planck.World = planck.World({
|
||||||
gravity: planck.Vec2(0, 0),
|
gravity: planck.Vec2(0, 0),
|
||||||
|
@ -58,6 +60,16 @@ export function GameState(ctx: ContextI): StateI {
|
||||||
b.addEntityContact(a)
|
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) => {
|
world.on('end-contact', (contact: planck.Contact) => {
|
||||||
let a = contact.getFixtureA().getUserData()
|
let a = contact.getFixtureA().getUserData()
|
||||||
|
@ -72,6 +84,16 @@ export function GameState(ctx: ContextI): StateI {
|
||||||
b.removeEntityContact(a)
|
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()
|
let rootContainer = new PIXI.Container()
|
||||||
|
@ -145,14 +167,13 @@ export function GameState(ctx: ContextI): StateI {
|
||||||
addZone(new Zone(z))
|
addZone(new Zone(z))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add bogus entity
|
// Add player entity
|
||||||
let player = new AnimalEntity('deer.animal.stand.west.0')
|
player = new AnimalEntity(animals.deer)
|
||||||
player.isPlayer = true
|
player.isPlayer = true
|
||||||
|
|
||||||
addEntity(player, 'objects', 300, 100)
|
addEntity(player, 'objects', 300, 100)
|
||||||
|
|
||||||
// Add fake others
|
// 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)
|
ctx.app.stage.addChild(rootContainer)
|
||||||
}
|
}
|
||||||
|
@ -190,11 +211,21 @@ export function GameState(ctx: ContextI): StateI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let entity of entities) {
|
for (let entity of entities) {
|
||||||
if (isAnimalEntity(entity) && entity.isPlayer) {
|
if (isAnimalEntity(entity)) {
|
||||||
entity.act(desiredActions)
|
if (entity.isPlayer) {
|
||||||
// FIXME: This isn't the right place for this.
|
entity.act(desiredActions)
|
||||||
rootContainer.position.set(Math.round(ctx.app.renderer.width/2), Math.round(ctx.app.renderer.height/2))
|
// FIXME: This isn't the right place for this.
|
||||||
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)))
|
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 {
|
} else {
|
||||||
//console.log('hmm, we do be tickin', entity, entity.x, entity.y)
|
//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)
|
fixture.setUserData(entity)
|
||||||
entity.body = body
|
entity.body = body
|
||||||
body.setUserData(entity)
|
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
|
entity.x = x
|
||||||
|
|
Loading…
Reference in New Issue
Block a user