2022-11-24 20:35:08 +00:00
|
|
|
import asyncio
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from pyshock import PiShock
|
2022-11-25 06:15:41 +00:00
|
|
|
from datapuller import LiveData
|
2022-11-24 20:35:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
2022-11-24 23:07:25 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2022-11-24 20:35:08 +00:00
|
|
|
|
|
|
|
|
2022-11-25 06:15:41 +00:00
|
|
|
class ZapSaber:
|
|
|
|
"""
|
|
|
|
ZapSaber contains methods for reacting to DataPuller events.
|
|
|
|
"""
|
|
|
|
|
2022-11-24 20:35:08 +00:00
|
|
|
combo_A = 100
|
|
|
|
combo_B = 300
|
2022-11-24 23:07:25 +00:00
|
|
|
combo_A_break = ['vibrate', 0.5, 20]
|
|
|
|
combo_B_break = ['shock', 0.3, 5]
|
|
|
|
fail = ['shock', 1, 10]
|
2022-11-24 20:35:08 +00:00
|
|
|
|
2022-11-24 23:07:25 +00:00
|
|
|
playing = False
|
|
|
|
combo_level = None
|
|
|
|
map_failed = False
|
2022-11-24 20:35:08 +00:00
|
|
|
|
2022-11-24 23:07:25 +00:00
|
|
|
def __init__(self, config, shocker):
|
2022-11-24 20:35:08 +00:00
|
|
|
self.combo_A = config['comboA']
|
|
|
|
self.combo_B = config['comboB']
|
|
|
|
self.combo_A_break = config['comboABreak']
|
|
|
|
self.combo_B_break = config['comboBBreak']
|
|
|
|
self.fail = config['fail']
|
2022-11-24 23:07:25 +00:00
|
|
|
self.shocker = shocker
|
|
|
|
|
|
|
|
def update_combo(self, message):
|
2022-11-25 06:15:41 +00:00
|
|
|
"""
|
|
|
|
update_combo receives a message when the score chages. It first checks if the map
|
|
|
|
has been failed, and if so, sends the `fail` event. Next, it checks whether the
|
|
|
|
current combo is greater than `combo_A` or `combo_B` and sets the combo level
|
|
|
|
accordingly.
|
|
|
|
"""
|
|
|
|
|
2022-11-24 23:07:25 +00:00
|
|
|
combo = message['Combo']
|
|
|
|
health = message['PlayerHealth']
|
2022-11-25 06:15:41 +00:00
|
|
|
|
|
|
|
# Determine whether or not the player has failed.
|
2022-11-24 23:07:25 +00:00
|
|
|
if not self.map_failed and health == 0:
|
|
|
|
logger.info('Failed the map!')
|
|
|
|
self.shocker.act(*self.fail)
|
|
|
|
self.map_failed = True
|
|
|
|
self.combo_level = None
|
2022-11-25 06:15:41 +00:00
|
|
|
|
|
|
|
# Set appropriate combo levels.
|
2022-11-24 23:07:25 +00:00
|
|
|
if combo > self.combo_A:
|
|
|
|
self.combo_level = 'A'
|
|
|
|
if combo > self.combo_B:
|
|
|
|
self.combo_level = 'B'
|
|
|
|
|
|
|
|
def break_combo(self, message):
|
2022-11-25 06:15:41 +00:00
|
|
|
"""
|
|
|
|
break_combo receives a message when a note is missed. If the combo broken is
|
|
|
|
greater than one of the combo levels (as determined by `update_combo`), then
|
|
|
|
the appropriate message is sent to PiShock.
|
|
|
|
"""
|
|
|
|
|
2022-11-24 23:07:25 +00:00
|
|
|
if self.combo_level == 'A':
|
|
|
|
logger.info('Broke combo A!')
|
|
|
|
self.shocker.act(*self.combo_A_break)
|
|
|
|
if self.combo_level == 'B':
|
|
|
|
logger.info('Broke combo B!')
|
|
|
|
self.shocker.act(*self.combo_B_break)
|
2022-11-25 06:15:41 +00:00
|
|
|
|
|
|
|
# Since the combo has been broken, unset the combo level.
|
2022-11-24 23:07:25 +00:00
|
|
|
self.combo_level = None
|
|
|
|
|
|
|
|
def tick(self, message):
|
2022-11-25 06:15:41 +00:00
|
|
|
"""
|
|
|
|
tick runs every time the map timer elapses. This is where whether or not the
|
|
|
|
player is actively playing is determined.
|
|
|
|
"""
|
|
|
|
|
|
|
|
logger.debug('Tick')
|
2022-11-24 23:07:25 +00:00
|
|
|
if self.playing and message['TimeElapsed'] == 0:
|
|
|
|
logger.info('Stopping map...')
|
2022-11-25 06:15:41 +00:00
|
|
|
self.playing = False
|
2022-11-24 23:07:25 +00:00
|
|
|
self.map_failed = False
|
|
|
|
elif not self.playing and message['TimeElapsed'] != 0:
|
|
|
|
logger.info('Starting map...')
|
|
|
|
self.playing = True
|
2022-11-24 20:35:08 +00:00
|
|
|
|
|
|
|
|
2022-11-24 23:07:25 +00:00
|
|
|
async def main(config):
|
|
|
|
logger.info('Initializing...')
|
2022-11-25 06:15:41 +00:00
|
|
|
|
|
|
|
# Set up the shocker.
|
2022-11-25 06:20:35 +00:00
|
|
|
shocker = PiShock(config['username'], config['apiKey'], config['code'],
|
|
|
|
name='ZapSaber')
|
2022-11-25 06:15:41 +00:00
|
|
|
shocker.beep()
|
|
|
|
events = ZapSaber(config, shocker)
|
2022-11-24 20:35:08 +00:00
|
|
|
|
2022-11-25 06:15:41 +00:00
|
|
|
# Set up the BSDataPuller listeners.
|
2022-11-24 20:35:08 +00:00
|
|
|
livedata = LiveData()
|
2022-11-24 23:07:25 +00:00
|
|
|
livedata.on('ScoreChange', events.update_combo)
|
|
|
|
livedata.on('NoteMissed', events.break_combo)
|
|
|
|
livedata.on('TimerElapsed', events.tick)
|
2022-11-24 20:35:08 +00:00
|
|
|
|
2022-11-25 06:15:41 +00:00
|
|
|
# Start listening.
|
2022-11-24 20:35:08 +00:00
|
|
|
await livedata.listen()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
with open('config.json') as config_file:
|
|
|
|
config = json.load(config_file)
|
2022-11-24 23:07:25 +00:00
|
|
|
asyncio.run(main(config))
|