import asyncio import json import logging from pyshock import PiShock from datapuller import LiveData logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class ZapSaber: """ ZapSaber contains methods for reacting to DataPuller events. """ combo_A = 100 combo_B = 300 combo_A_break = ['vibrate', 0.5, 20] combo_B_break = ['shock', 0.3, 5] fail = ['shock', 1, 10] playing = False combo_level = None map_failed = False def __init__(self, config, shocker): 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'] self.shocker = shocker def update_combo(self, message): """ 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. """ combo = message['Combo'] health = message['PlayerHealth'] # Determine whether or not the player has failed. 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 # Set appropriate combo levels. if combo > self.combo_A: self.combo_level = 'A' if combo > self.combo_B: self.combo_level = 'B' def break_combo(self, message): """ 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. """ 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) # Since the combo has been broken, unset the combo level. self.combo_level = None def tick(self, message): """ tick runs every time the map timer elapses. This is where whether or not the player is actively playing is determined. """ logger.debug('Tick') if self.playing and message['TimeElapsed'] == 0: logger.info('Stopping map...') self.playing = False self.map_failed = False elif not self.playing and message['TimeElapsed'] != 0: logger.info('Starting map...') self.playing = True async def main(config): logger.info('Initializing...') # Set up the shocker. shocker = PiShock(config['username'], config['apiKey'], config['code'], name='ZapSaber') shocker.beep() events = ZapSaber(config, shocker) # Set up the BSDataPuller listeners. livedata = LiveData() livedata.on('ScoreChange', events.update_combo) livedata.on('NoteMissed', events.break_combo) livedata.on('TimerElapsed', events.tick) # Start listening. await livedata.listen() if __name__ == '__main__': with open('config.json') as config_file: config = json.load(config_file) asyncio.run(main(config))