Strange issue with .listen in the state machine

so I have a weird thing that is occurring. here is the same script 2 times but with 3 little changes:

import * as ecs from '@8thwall/ecs'  // This is how you access the ecs library.

ecs.registerComponent({
  name: 'game-manager',
  schema: {
    // Add data that can be configured on the component.
    levelOneContainer: ecs.eid,
    levelTwoContainer: ecs.eid,
    levelThreeContainer: ecs.eid,
    levelFourContainer: ecs.eid,
    levelFiveContainer: ecs.eid,
    santaEntity: ecs.eid,
    levelText: ecs.eid,
    houseText: ecs.eid,
    dropText: ecs.eid,
    scoreText: ecs.eid,
    buttonController: ecs.eid,
  },
  schemaDefaults: {
    // Add defaults for the schema fields.
  },
  data: {
    // Add data that cannot be configured outside of the component.
    dropsAvailable: ecs.i32,
  },
  stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
    const resetText = (level, house, drop) => {
      ecs.Ui.set(world, schemaAttribute.cursor(eid).levelText, {text: `Level: ${level}`})
      ecs.Ui.set(world, schemaAttribute.cursor(eid).houseText, {text: `House Count: ${house}`})
      ecs.Ui.set(world, schemaAttribute.cursor(eid).dropText, {text: `Drop Count: ${drop}`})
      ecs.Ui.set(world, schemaAttribute.cursor(eid).scoreText, {text: 'Score: 0'})
      dataAttribute.cursor(eid).dropsAvailable = drop
    }
    const setSantaHeight = (height) => {
      ecs.Position.mutate(world, schemaAttribute.cursor(eid).santaEntity, (cursor) => {
        cursor.y = height
      })
    }
    const DropItem = () => {
      if (dataAttribute.cursor(eid).dropsAvailable > 0) {
        dataAttribute.cursor(eid).dropsAvailable -= 1
        ecs.Ui.set(world, schemaAttribute.cursor(eid).dropText,
          {text: `Drop Count: ${dataAttribute.cursor(eid).dropsAvailable}`})
        // world.events.dispatch(eid, type)
      }
    }
    ecs.defineState('Instructions').initial()  // creating the paused state
      .wait(2000, 'Starting Scene')
      .onEvent('Instructions Complete', 'Starting Scene')
    ecs.defineState('Starting Scene')
      .onEnter(() => {
        ecs.Ui.set(world, schemaAttribute.cursor(eid).levelText, {opacity: 1})
        ecs.Ui.set(world, schemaAttribute.cursor(eid).houseText, {opacity: 1})
        ecs.Ui.set(world, schemaAttribute.cursor(eid).dropText, {opacity: 1})
        ecs.Ui.set(world, schemaAttribute.cursor(eid).scoreText, {opacity: 1})
      })
      .onEvent('Game Start', 'Level One')
    ecs.defineState('Level One')
      .onEvent('Level Won', 'Level Two')
      .onEnter(() => {
        resetText(1, 3, 6)
        setSantaHeight(33)
        world.events.dispatch(schemaAttribute.cursor(eid).levelOneContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelOneContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelOneContainer)
      })
    ecs.defineState('Level Two')
      .onEvent('Level Won', 'Level Three')
      .onEnter(() => {
        resetText(2, 7, 12)
        setSantaHeight(36)
        world.events.dispatch(schemaAttribute.cursor(eid).levelTwoContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelTwoContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelTwoContainer)
      })
    ecs.defineState('Level Three')
      .onEvent('Level Won', 'Level Four')
      .onEnter(() => {
        resetText(3, 3, 6)
        setSantaHeight(39)
        world.events.dispatch(schemaAttribute.cursor(eid).levelThreeContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelThreeContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelThreeContainer)
      })
    ecs.defineState('Level Four')
      .onEvent('Level Won', 'Level Five')
      .onEnter(() => {
        resetText(4, 3, 6)
        setSantaHeight(42)
        world.events.dispatch(schemaAttribute.cursor(eid).levelFourContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelFourContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelFourContainer)
      })
    ecs.defineState('Level Five')
      .onEvent('Level Won', 'Game Over')
      .onEnter(() => {
        resetText(5, 3, 6)
        setSantaHeight(45)
        world.events.dispatch(schemaAttribute.cursor(eid).levelFiveContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelFiveContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelFiveContainer)
      })
    ecs.defineState('Game Over')
    ecs.defineStateGroup(['Level One', 'Level Two', 'Level Three', 'Level Four', 'Level Five'])
      .onEvent('Level Lost', 'Game Over')
      .listen(schemaAttribute.cursor(eid).buttonController, 'Coal', () => DropItem())
      .listen(schemaAttribute.cursor(eid).buttonController, 'Gift', () => DropItem())
  },
  add: (world, component) => {
    // Runs when the component is added to the world.
  },
  tick: (world, component) => {
    // Runs every frame.
  },
  remove: (world, component) => {
    // Runs when the component is removed from the world.
  },
})

and here

import * as ecs from '@8thwall/ecs'  // This is how you access the ecs library.

ecs.registerComponent({
  name: 'game-manager',
  schema: {
    // Add data that can be configured on the component.
    levelOneContainer: ecs.eid,
    levelTwoContainer: ecs.eid,
    levelThreeContainer: ecs.eid,
    levelFourContainer: ecs.eid,
    levelFiveContainer: ecs.eid,
    santaEntity: ecs.eid,
    levelText: ecs.eid,
    houseText: ecs.eid,
    dropText: ecs.eid,
    scoreText: ecs.eid,
    buttonController: ecs.eid,
  },
  schemaDefaults: {
    // Add defaults for the schema fields.
  },
  data: {
    // Add data that cannot be configured outside of the component.
    dropsAvailable: ecs.i32,
  },
  stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
    const resetText = (level, house, drop) => {
      ecs.Ui.set(world, schemaAttribute.cursor(eid).levelText, {text: `Level: ${level}`})
      ecs.Ui.set(world, schemaAttribute.cursor(eid).houseText, {text: `House Count: ${house}`})
      ecs.Ui.set(world, schemaAttribute.cursor(eid).dropText, {text: `Drop Count: ${drop}`})
      ecs.Ui.set(world, schemaAttribute.cursor(eid).scoreText, {text: 'Score: 0'})
      dataAttribute.cursor(eid).dropsAvailable = drop
    }
    const setSantaHeight = (height) => {
      ecs.Position.mutate(world, schemaAttribute.cursor(eid).santaEntity, (cursor) => {
        cursor.y = height
      })
    }
    const DropItem = (type) => {
      if (dataAttribute.cursor(eid).dropsAvailable > 0) {
        dataAttribute.cursor(eid).dropsAvailable -= 1
        ecs.Ui.set(world, schemaAttribute.cursor(eid).dropText,
          {text: `Drop Count: ${dataAttribute.cursor(eid).dropsAvailable}`})
        world.events.dispatch(eid, type)
      }
    }
    ecs.defineState('Instructions').initial()  // creating the paused state
      .wait(2000, 'Starting Scene')
      .onEvent('Instructions Complete', 'Starting Scene')
    ecs.defineState('Starting Scene')
      .onEnter(() => {
        ecs.Ui.set(world, schemaAttribute.cursor(eid).levelText, {opacity: 1})
        ecs.Ui.set(world, schemaAttribute.cursor(eid).houseText, {opacity: 1})
        ecs.Ui.set(world, schemaAttribute.cursor(eid).dropText, {opacity: 1})
        ecs.Ui.set(world, schemaAttribute.cursor(eid).scoreText, {opacity: 1})
      })
      .onEvent('Game Start', 'Level One')
    ecs.defineState('Level One')
      .onEvent('Level Won', 'Level Two')
      .onEnter(() => {
        resetText(1, 3, 6)
        setSantaHeight(33)
        world.events.dispatch(schemaAttribute.cursor(eid).levelOneContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelOneContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelOneContainer)
      })
    ecs.defineState('Level Two')
      .onEvent('Level Won', 'Level Three')
      .onEnter(() => {
        resetText(2, 7, 12)
        setSantaHeight(36)
        world.events.dispatch(schemaAttribute.cursor(eid).levelTwoContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelTwoContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelTwoContainer)
      })
    ecs.defineState('Level Three')
      .onEvent('Level Won', 'Level Four')
      .onEnter(() => {
        resetText(3, 3, 6)
        setSantaHeight(39)
        world.events.dispatch(schemaAttribute.cursor(eid).levelThreeContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelThreeContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelThreeContainer)
      })
    ecs.defineState('Level Four')
      .onEvent('Level Won', 'Level Five')
      .onEnter(() => {
        resetText(4, 3, 6)
        setSantaHeight(42)
        world.events.dispatch(schemaAttribute.cursor(eid).levelFourContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelFourContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelFourContainer)
      })
    ecs.defineState('Level Five')
      .onEvent('Level Won', 'Game Over')
      .onEnter(() => {
        resetText(5, 3, 6)
        setSantaHeight(45)
        world.events.dispatch(schemaAttribute.cursor(eid).levelFiveContainer, 'Start Level')
        ecs.Hidden.remove(world, schemaAttribute.cursor(eid).levelFiveContainer)
      })
      .onExit(() => {
        ecs.Hidden.set(world, schemaAttribute.cursor(eid).levelFiveContainer)
      })
    ecs.defineState('Game Over')
    ecs.defineStateGroup(['Level One', 'Level Two', 'Level Three', 'Level Four', 'Level Five'])
      .onEvent('Level Lost', 'Game Over')
      .listen(schemaAttribute.cursor(eid).buttonController, 'Coal', () => DropItem('Coal'))
      .listen(schemaAttribute.cursor(eid).buttonController, 'Gift', () => DropItem('Gift'))
  },
  add: (world, component) => {
    // Runs when the component is added to the world.
  },
  tick: (world, component) => {
    // Runs every frame.
  },
  remove: (world, component) => {
    // Runs when the component is removed from the world.
  },
})

the changes are here on the DropItem Function and the listens that call it. The top one works as intended and depletes one item at a time. the bottom one depletes all of the items in the inventory on one button press.

I fixed the problem by implementing this as the drop input:

const DropItem = (type) => {
      if (dataAttribute.cursor(eid).isProcessing) return  // Prevent re-entry
      dataAttribute.cursor(eid).isProcessing = true
      if (dataAttribute.cursor(eid).dropsAvailable > 0) {
        dataAttribute.cursor(eid).dropsAvailable -= 1
        ecs.Ui.set(world, schemaAttribute.cursor(eid).dropText,
          {text: `Drop Count: ${dataAttribute.cursor(eid).dropsAvailable}`})
        world.events.dispatch(eid, type)
      }
      world.time.setTimeout(() => {
        dataAttribute.cursor(eid).isProcessing = false
      }, 100)  // Adjust delay as needed
    }

but i thought it was very strange that it fired off a button click on tick and was wondering if this could be an engine problem @christoph_nia

Looks like depending on scene structure, dispatching the same β€˜Coal’ event in response to a β€˜Coal’ event could cause an infinite loop, even if the targets are different entities.

Seems like one option is to have a Coal-Click event and a Coal-Dropped event which wouldn’t be susceptible to this.