This magically stopped happening but then started again like 45min ago.
Here is the code for ufo.ts
Thank you!
import * as ecs from '@8thwall/ecs'
import {UFOAnimations, CowAnimations, getRandomOffset} from './helpers/animations'
import {cloneComponents} from './helpers/clone'
import {getRandInt} from './helpers/math'
import {addCleanup, doCleanup} from './helpers/cleanup'
import {newBeam} from './beam'
const UFOStates = {
SPAWN: 'spawn',
ROAM: 'roam',
SEEKING: 'seeking',
BEAMING: 'beaming',
ZAPPED: 'zapped',
END: 'end',
}
const UFO = ecs.registerComponent({
name: 'UFO',
schema: {
roamingAnimationTiming: ecs.ui32,
roamingAnimationIdleTiming: ecs.ui32,
roamingCycles: ecs.ui32,
seekDuration: ecs.ui32,
beamDuration: ecs.ui32,
zappedRespawnDuration: ecs.ui32,
dropDuration: ecs.ui32,
spawnDuration: ecs.ui32,
baseBeamEid: ecs.eid,
},
schemaDefaults: {
roamingAnimationTiming: 2000,
roamingAnimationIdleTiming: 1000,
roamingCycles: 4,
seekDuration: 2000,
beamDuration: 5000,
zappedRespawnDuration: 7000,
dropDuration: 1500,
spawnDuration: 1500,
},
data: {
currentRoamingCycle: ecs.ui32,
currentIdleTimeout: ecs.ui32,
currentAnimationTimeout: ecs.ui32,
currentTarget: ecs.eid,
},
add: (world, component) => {
const {eid, dataAttribute} = component
ecs.Collider.set(world, eid, {
shape: ecs.ColliderShape.Cylinder,
radius: 3,
height: 2.5,
mass: 1,
gravityFactor: 0,
friction: 0.5,
})
function setNewTarget(e) {
if (e.data.eid) dataAttribute.set(eid, {currentTarget: e.data.eid})
}
function resetCurrentCycle() {
dataAttribute.set(eid, {currentRoamingCycle: 0})
}
async function handleCollision(e) {
UFOAnimations.COLLISION(world, eid, 500)
}
function handleCleanup() {
world.events.removeListener(eid, 'seek', setNewTarget)
world.events.removeListener(eid, 'noCows', resetCurrentCycle)
world.events.removeListener(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)
}
world.three.entityToObject.get(eid).name = 'ufo'
world.events.addListener(eid, 'seek', setNewTarget)
world.events.addListener(eid, 'noCows', resetCurrentCycle)
world.events.addListener(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)
addCleanup(component, handleCleanup)
},
remove: (world, component) => {
doCleanup(component)
},
stateMachine: (component) => {
const {eid, world, schemaAttribute, dataAttribute} = component
const {seekDuration, beamDuration, zappedRespawnDuration, dropDuration, spawnDuration} = schemaAttribute.get(eid)
const roamTrigger = ecs.defineTrigger()
const managerComponent = world.getParent(eid)
ecs.defineState(UFOStates.SPAWN)
.initial()
.onEnter(() => {
UFOAnimations.SPAWN(world, eid, spawnDuration)
})
.wait(spawnDuration, UFOStates.ROAM)
ecs.defineState(UFOStates.ROAM)
// .initial()
.onEnter(() => {
const {currentRoamingCycle} = dataAttribute.get(eid)
const {
roamingCycles,
roamingAnimationIdleTiming,
roamingAnimationTiming,
} = schemaAttribute.get(eid)
// Done roaming, begin to seek
if (currentRoamingCycle === roamingCycles) {
world.events.dispatch(managerComponent, 'getCow', {eid})
return
}
const pos = ecs.Position.get(world, eid)
if (
pos && roamingCycles &&
roamingAnimationTiming && roamingAnimationIdleTiming
) {
// Wait for idle, then move, then toggle roam trigger
// which will loop this onEnter function
// We save these timeouts so that we can clean them up
// on exit. shouldn't be needed unless interupted by a
// 'zap' event, but we'll just clean them up by default anyway
const cursor = dataAttribute.cursor(eid)
const idleTimeout = setTimeout(() => {
UFOAnimations.ROAM(
world, eid,
ecs.Position.get(world, eid),
roamingAnimationTiming
)
const animationTimeout = setTimeout(() => {
if (roamTrigger) {
roamTrigger.trigger()
}
}, roamingAnimationTiming)
if (cursor) {
cursor.currentAnimationTimeout = animationTimeout
}
}, roamingAnimationIdleTiming)
if (cursor) {
cursor.currentRoamingCycle = (dataAttribute.get(eid)?.currentRoamingCycle || 0) + 1
cursor.currentIdleTimeout = idleTimeout
}
}
})
.onExit(() => {
const {currentIdleTimeout, currentAnimationTimeout} = dataAttribute.get(eid)
currentIdleTimeout ?? clearTimeout(currentIdleTimeout)
currentAnimationTimeout ?? clearTimeout(currentAnimationTimeout)
dataAttribute.set(eid, {
currentAnimationTimeout: null,
currentIdleTimeout: null,
})
})
.onTrigger(roamTrigger, UFOStates.ROAM)
.onEvent('seek', UFOStates.SEEKING)
.onEvent('noCows', UFOStates.ROAM)
ecs.defineState(UFOStates.SEEKING)
.onEnter(() => {
const {currentTarget} = dataAttribute.get(eid)
// Start following assigned cow
const targetPos = ecs.Position.get(world, currentTarget)
UFOAnimations.SEEK_TARGET(world, targetPos, eid, seekDuration)
})
.wait(seekDuration, UFOStates.BEAMING)
ecs.defineState(UFOStates.BEAMING)
.onEnter(() => {
const {baseBeamEid} = schemaAttribute.get(eid)
const {currentTarget} = dataAttribute.get(eid)
const ufoPos = ecs.Position.get(world, eid)
const cowPos = ecs.Position.get(world, currentTarget)
CowAnimations.BEAM_UP(world, currentTarget, cowPos, ufoPos, beamDuration)
// create beam under ufo
const beamEid = newBeam(world, eid, baseBeamEid)
// Called if beaming animation is not interrupted
const beamTimeout = setTimeout(() => {
// tell manager that cow is abducted
world.events.dispatch(managerComponent, 'cowAbducted', {cowEid: currentTarget})
ecs.Hidden.set(world, currentTarget)
// hide cow
}, beamDuration)
function cleanupBeamState() {
clearTimeout(beamTimeout)
world.deleteEntity(beamEid)
}
addCleanup(component, cleanupBeamState)
})
.wait(beamDuration + 500, UFOStates.ROAM)
.onExit(() => {
// Clean up beamTimeout in case ufo is zapped before finishing
doCleanup(component)
})
ecs.defineState(UFOStates.ZAPPED)
.onEnter(() => {
const {currentTarget} = dataAttribute.get(eid)
if (currentTarget) {
CowAnimations.DROP(world, currentTarget, dropDuration)
world.events.dispatch(managerComponent, 'removeCowTarget', {cowEid: currentTarget})
}
dataAttribute.set(eid, {currentRoamingCycle: 0, currentTarget: BigInt(0)})
// Fly away and respawn
UFOAnimations.FLY_AWAY_AND_RESPAWN(
world, eid,
ecs.Position.get(world, eid),
zappedRespawnDuration
)
})
.wait(zappedRespawnDuration, UFOStates.ROAM)
ecs.defineState(UFOStates.END)
.onEnter(() => {
world.deleteEntity(eid)
})
// All states other than ZAPPED need a handler for the zap event
ecs.defineStateGroup([UFOStates.ROAM, UFOStates.SEEKING, UFOStates.BEAMING, UFOStates.SPAWN])
.onEvent('zap', UFOStates.ZAPPED)
ecs.defineStateGroup([...Object.values(UFOStates)])
.onEvent('game-over', UFOStates.END, {
target: world.events.globalId,
})
},
})
// Helper function to spawn a new UFO
export function spawnUfo(
world: ecs.World, managerEid: BigInt,
baseUfoId: BigInt, baseBeamEid: BigInt
) {
// Create a new entity
const newEid = world.createEntity()
world.setParent(newEid, managerEid)
// Copy attributes from baseUFO in UI
cloneComponents(baseUfoId, newEid, world)
UFO.set(world, newEid, {
roamingAnimationTiming: getRandInt(1000, 2500),
roamingAnimationIdleTiming: getRandInt(300, 1000),
roamingCycles: getRandInt(3, 12),
baseBeamEid,
})
// Get random offset for new entity
// ecs.Position.set(world, newEid, {
// x: getRandomOffset(10, 20) * -1,
// y: getRandomOffset(10, 20) * -1,
// z: getRandomOffset(10, 20) * -1,
// })
ecs.Position.set(world, newEid, {
x: 0,
y: 40,
z: 20,
})
return newEid
}
export {UFO}