diff --git a/mpf/config_spec.yaml b/mpf/config_spec.yaml index 032909744..d1b2c7ad8 100644 --- a/mpf/config_spec.yaml +++ b/mpf/config_spec.yaml @@ -668,6 +668,7 @@ fast_exp_board: led_ports: list|subconfig(fast_led_port)|None led_fade_time: single|ms|0 led_hz: single|float|30 + ignore_led_errors: single|bool|false fast_breakout: port: single|enum(1,2,3)| model: single|str| diff --git a/mpf/modes/bonus/code/bonus.py b/mpf/modes/bonus/code/bonus.py index 9b9d2891f..3a8dfdcfd 100644 --- a/mpf/modes/bonus/code/bonus.py +++ b/mpf/modes/bonus/code/bonus.py @@ -95,29 +95,28 @@ def _bonus_next_item(self): # following names are reserved for end-of-bonus behavior. assert entry not in ["subtotal", "multiplier", "total"], "Bonus entry cannot be reserved word '%s'" % entry - # Calling player.vars.get() instead of player.get() bypasses the - # auto-fill zero and will throw if there is no player variable. - # The fallback value of 1 is used for bonus entries that don't use - # a player score, which are multiplied by one to get the bonus. - hits = self.player.vars.get(entry['player_score_entry'], 1) + # If a player_score_entry is provided, use player getattr to get a + # fallback value of zero if the variable is not set. Otherwise + # use 1 as the multiplier for non-player-score bonuses. + hits = self.player[entry['player_score_entry']] if entry['player_score_entry'] else 1 score = entry['score'].evaluate([]) * hits if (not score and entry['skip_if_zero']) or (score < 0 and entry['skip_if_negative']): - self.debug_log("Skipping bonus entry '%s' because its value is 0", - entry['entry']) + self.info_log("Skipping bonus entry '%s' because its value is 0", + entry['entry']) self._bonus_next_item() return # pylint: disable=superfluous-parens if self.settings["rounding_value"] and (r := (score % self.settings["rounding_value"])): - self.debug_log("rounding bonus score %s remainder of %s", score, r) + self.info_log("rounding bonus score %s remainder of %s", score, r) if self.settings["rounding_direction"] == "down": score -= r else: score += self.settings["rounding_value"] - r - self.debug_log("Bonus Entry '%s': score: %s player_score_entry: %s=%s", - entry['entry'], score, entry['player_score_entry'], hits) + self.info_log("Bonus Entry '%s': score: %s player_score_entry: %s=%s", + entry['entry'], score, entry['player_score_entry'], hits) self.bonus_score += score self.machine.events.post("bonus_entry", entry=entry['entry'], diff --git a/mpf/platforms/fast/communicators/net_neuron.py b/mpf/platforms/fast/communicators/net_neuron.py index bb16fc1e7..34e1b5ee4 100644 --- a/mpf/platforms/fast/communicators/net_neuron.py +++ b/mpf/platforms/fast/communicators/net_neuron.py @@ -293,7 +293,7 @@ def update_switches_from_hw_data(self): This will silently sync the switch.hw_state. If the logical state changes, it will process it like any switch change. """ - for switch in self.machine.switches: + for switch in self.machine.switches.values(): hw_state = self.platform.hw_switch_data[switch.hw_switch.number] if hw_state != switch.hw_state: diff --git a/mpf/platforms/fast/fast_exp_board.py b/mpf/platforms/fast/fast_exp_board.py index 4fc6e31c9..98eab1619 100644 --- a/mpf/platforms/fast/fast_exp_board.py +++ b/mpf/platforms/fast/fast_exp_board.py @@ -2,6 +2,7 @@ import asyncio from base64 import b16decode +from binascii import Error as binasciiError from importlib import import_module from packaging import version @@ -176,11 +177,12 @@ def update_leds(self): try: self.communicator.send_bytes(b16decode(f'{msg_header}{msg}'), log_msg) - except Exception as e: + except binasciiError as e: self.log.error( f"Error decoding the following message for board {breakout_address} : {msg_header}{msg}") - self.log.debug("Attempted update that caused this error: %s", dirty_leds) - raise e + self.log.info("Attempted update that caused this error: %s", dirty_leds) + if not self.config['ignore_led_errors']: + raise e def set_led_fade(self, rate: int) -> None: """Set LED fade rate in ms.""" diff --git a/mpf/platforms/virtual.py b/mpf/platforms/virtual.py index fc04f8d6f..7d8164a69 100644 --- a/mpf/platforms/virtual.py +++ b/mpf/platforms/virtual.py @@ -105,16 +105,16 @@ async def get_hw_switch_states(self): if 'virtual_platform_start_active_switches' in self.machine.config: initial_active_switches = [] - for switch in Util.string_to_list(self.machine.config['virtual_platform_start_active_switches']): - if switch not in self.machine.switches: - if " " in switch: + for switch_name in Util.string_to_list(self.machine.config['virtual_platform_start_active_switches']): + if switch_name not in self.machine.switches.keys(): + if " " in switch_name: self.raise_config_error("MPF no longer supports lists separated by space in " "virtual_platform_start_active_switches. Please separate " - "switches by comma: {}.".format(switch), 1) + "switches by comma: {}.".format(switch_name), 1) else: self.raise_config_error("Switch {} used in virtual_platform_start_active_switches was not " - "found in switches section.".format(switch), 1) - initial_active_switches.append(self.machine.switches[switch].hw_switch.number) + "found in switches section.".format(switch_name), 1) + initial_active_switches.append(self.machine.switches[switch_name].hw_switch.number) for k in self.hw_switches: if k in initial_active_switches: diff --git a/mpf/tests/machine_files/bonus/modes/bonus/config/bonus.yaml b/mpf/tests/machine_files/bonus/modes/bonus/config/bonus.yaml index d432a6686..868fb90ed 100644 --- a/mpf/tests/machine_files/bonus/modes/bonus/config/bonus.yaml +++ b/mpf/tests/machine_files/bonus/modes/bonus/config/bonus.yaml @@ -12,3 +12,9 @@ mode_settings: score: 5000 player_score_entry: modes reset_player_score_entry: False + - entry: bonus_undefined_var + score: 5000 + skip_if_zero: false + player_score_entry: undefined_var + - entry: bonus_static + score: 2000 diff --git a/mpf/tests/machine_files/shots/config/test_shot_groups.yaml b/mpf/tests/machine_files/shots/config/test_shot_groups.yaml index 4b06f7f00..80767f464 100644 --- a/mpf/tests/machine_files/shots/config/test_shot_groups.yaml +++ b/mpf/tests/machine_files/shots/config/test_shot_groups.yaml @@ -12,6 +12,10 @@ switches: number: switch_4: number: + switch_5: + number: + switch_6: + number: s_rotate_l: number: s_rotate_r: diff --git a/mpf/tests/machine_files/shots/modes/base2/config/base2.yaml b/mpf/tests/machine_files/shots/modes/base2/config/base2.yaml index ddf6ae685..cf82e013d 100644 --- a/mpf/tests/machine_files/shots/modes/base2/config/base2.yaml +++ b/mpf/tests/machine_files/shots/modes/base2/config/base2.yaml @@ -20,6 +20,10 @@ shots: light: tag1 shot_4: switch: switch_1 + shot_5: + switch: switch_5 + shot_6: + switch: switch_6 led_1: switch: switch_1 show_tokens: diff --git a/mpf/tests/machine_files/shots/modes/mode1/config/mode1.yaml b/mpf/tests/machine_files/shots/modes/mode1/config/mode1.yaml index 299ab4a90..80bc73d12 100644 --- a/mpf/tests/machine_files/shots/modes/mode1/config/mode1.yaml +++ b/mpf/tests/machine_files/shots/modes/mode1/config/mode1.yaml @@ -24,6 +24,12 @@ shots: mode1_shot_3: switch: switch_3 profile: mode1_shot_3 + mode1_shot_5: + switch: switch_5 + profile: mode1_shot_5 + mode1_shot_6: + switch: switch_6 + profile: mode1_shot_6 shot_profiles: mode1_shot_2: @@ -32,10 +38,21 @@ shot_profiles: - name: mode1_one - name: mode1_two - name: mode1_three - mode1_shot_3: + mode1_shot_3: # Test block: True show: rainbow2 block: True states: - name: mode1_one - name: mode1_two - name: mode1_three + mode1_shot_5: # Test block: False + show: rainbow2 + block: False + states: + - name: mode1_one + - name: mode1_two + mode1_shot_6: # Test block default + show: rainbow2 + states: + - name: mode1_one + - name: mode1_two diff --git a/mpf/tests/test_Bonus.py b/mpf/tests/test_Bonus.py index 1532cd489..3e45e2bd1 100644 --- a/mpf/tests/test_Bonus.py +++ b/mpf/tests/test_Bonus.py @@ -78,15 +78,23 @@ def testBonus(self): self.assertEqual(10000, self._last_event_kwargs["bonus_entry"]["score"]) self.assertEqual(2, self._last_event_kwargs["bonus_entry"]["hits"]) self.advance_time_and_run(2) + self.assertEqual("bonus_undefined_var", self._last_event_kwargs["bonus_entry"]["entry"]) + self.assertEqual(0, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(0, self._last_event_kwargs["bonus_entry"]["hits"]) + self.advance_time_and_run(2) + self.assertEqual("bonus_static", self._last_event_kwargs["bonus_entry"]["entry"]) + self.assertEqual(2000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(1, self._last_event_kwargs["bonus_entry"]["hits"]) + self.advance_time_and_run(2) self.assertEqual("subtotal", self._last_event_kwargs["bonus_entry"]["entry"]) - self.assertEqual(13000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(15000, self._last_event_kwargs["bonus_entry"]["score"]) self.advance_time_and_run(2) self.assertEqual("multiplier", self._last_event_kwargs["bonus_entry"]["entry"]) self.assertEqual(5, self._last_event_kwargs["bonus_entry"]["score"]) self.advance_time_and_run(2) self.assertEqual("total", self._last_event_kwargs["bonus_entry"]["entry"]) - self.assertEqual(65000, self._last_event_kwargs["bonus_entry"]["score"]) - self.assertEqual(66337, self.machine.game.player.score) + self.assertEqual(75000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(76337, self.machine.game.player.score) # check resets self.assertEqual(0, self.machine.game.player.ramps) @@ -110,15 +118,23 @@ def testBonus(self): self.assertEqual(10000, self._last_event_kwargs["bonus_entry"]["score"]) self.assertEqual(2, self._last_event_kwargs["bonus_entry"]["hits"]) self.advance_time_and_run(2) + self.assertEqual("bonus_undefined_var", self._last_event_kwargs["bonus_entry"]["entry"]) + self.assertEqual(0, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(0, self._last_event_kwargs["bonus_entry"]["hits"]) + self.advance_time_and_run(2) + self.assertEqual("bonus_static", self._last_event_kwargs["bonus_entry"]["entry"]) + self.assertEqual(2000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(1, self._last_event_kwargs["bonus_entry"]["hits"]) + self.advance_time_and_run(2) self.assertEqual("subtotal", self._last_event_kwargs["bonus_entry"]["entry"]) - self.assertEqual(10000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(12000, self._last_event_kwargs["bonus_entry"]["score"]) self.advance_time_and_run(2) self.assertEqual("multiplier", self._last_event_kwargs["bonus_entry"]["entry"]) self.assertEqual(5, self._last_event_kwargs["bonus_entry"]["score"]) self.advance_time_and_run(2) self.assertEqual("total", self._last_event_kwargs["bonus_entry"]["entry"]) - self.assertEqual(50000, self._last_event_kwargs["bonus_entry"]["score"]) - self.assertEqual(116337, self.machine.game.player.score) + self.assertEqual(60000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(136337, self.machine.game.player.score) # multiplier should stay the same self.assertEqual(0, self.machine.game.player.ramps) @@ -149,6 +165,10 @@ def testBonus(self): self.post_event('flipper_cancel', .1) self.assertEqual("bonus_modes", self._last_event_kwargs["bonus_entry"]["entry"]) + self.advance_time_and_run(.5) + self.assertEqual("bonus_undefined_var", self._last_event_kwargs["bonus_entry"]["entry"]) + self.advance_time_and_run(.5) + self.assertEqual("bonus_static", self._last_event_kwargs["bonus_entry"]["entry"]) self.advance_time_and_run(.5) self.assertEqual("subtotal", self._last_event_kwargs["bonus_entry"]["entry"]) @@ -179,9 +199,17 @@ def testBonus(self): self.assertEqual(10000, self._last_event_kwargs["bonus_entry"]["score"]) self.assertEqual(2, self._last_event_kwargs["bonus_entry"]["hits"]) self.advance_time_and_run(2) + self.assertEqual("bonus_undefined_var", self._last_event_kwargs["bonus_entry"]["entry"]) + self.assertEqual(0, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(0, self._last_event_kwargs["bonus_entry"]["hits"]) + self.advance_time_and_run(2) + self.assertEqual("bonus_static", self._last_event_kwargs["bonus_entry"]["entry"]) + self.assertEqual(2000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(1, self._last_event_kwargs["bonus_entry"]["hits"]) + self.advance_time_and_run(2) self.assertEqual("total", self._last_event_kwargs["bonus_entry"]["entry"]) - self.assertEqual(11000, self._last_event_kwargs["bonus_entry"]["score"]) - self.assertEqual(149337, self.machine.game.player.score) + self.assertEqual(13000, self._last_event_kwargs["bonus_entry"]["score"]) + self.assertEqual(175337, self.machine.game.player.score) @test_config_directory("tests/machine_files/bonus_no_keep_multiplier/") diff --git a/mpf/tests/test_Shots.py b/mpf/tests/test_Shots.py index 3103c28a1..c48360421 100644 --- a/mpf/tests/test_Shots.py +++ b/mpf/tests/test_Shots.py @@ -29,36 +29,51 @@ def stop_game(self): self.advance_time_and_run() self.assertIsNone(self.machine.game) - def test_block(self): + def block_test(self, switch, shot, should_block): + high_priority_shot = "mode1_" + shot self.mock_event("playfield_active") - self.hit_and_release_switch("switch_3") + self.hit_and_release_switch(switch) self.advance_time_and_run(.1) self.assertEventCalled("playfield_active") self.start_game() - self.assertEqual("unlit", self.machine.shots["shot_3"].state_name) + self.assertEqual("unlit", self.machine.shots[shot].state_name) - self.hit_and_release_switch("switch_3") + self.hit_and_release_switch(switch) self.advance_time_and_run(.1) - self.assertTrue(self.machine.shots["shot_3"].enabled) - self.assertEqual("lit", self.machine.shots["shot_3"].state_name) + self.assertFalse(self.machine.shots[high_priority_shot].enabled) + self.assertTrue(self.machine.shots[shot].enabled) + self.assertEqual("lit", self.machine.shots[shot].state_name) - self.machine.shots["shot_3"].reset() - self.assertEqual("unlit", self.machine.shots["shot_3"].state_name) + self.machine.shots[shot].reset() + self.assertEqual("unlit", self.machine.shots[shot].state_name) # Start the mode and make sure those shots load self.start_mode("mode1") - self.assertTrue(self.machine.shots["shot_3"].enabled) - self.assertTrue(self.machine.shots["mode1_shot_3"].enabled) - self.assertEqual("unlit", self.machine.shots["shot_3"].state_name) - self.assertEqual("mode1_one", self.machine.shots["mode1_shot_3"].state_name) + self.assertTrue(self.machine.shots[shot].enabled) + self.assertTrue(self.machine.shots[high_priority_shot].enabled) + self.assertEqual("unlit", self.machine.shots[shot].state_name) + self.assertEqual("mode1_one", self.machine.shots[high_priority_shot].state_name) - self.hit_and_release_switch("switch_3") + self.hit_and_release_switch(switch) self.advance_time_and_run(.1) - self.assertEqual("unlit", self.machine.shots["shot_3"].state_name) - self.assertEqual("mode1_two", self.machine.shots["mode1_shot_3"].state_name) + if should_block: + self.assertEqual("unlit", self.machine.shots[shot].state_name) + else: + self.assertEqual("lit", self.machine.shots[shot].state_name) + + self.assertEqual("mode1_two", self.machine.shots[high_priority_shot].state_name) + + def test_block_true(self): + self.block_test("switch_3", "shot_3", True) + + def test_block_false(self): + self.block_test("switch_5", "shot_5", False) + + def test_block_default(self): #Default behaves as false + self.block_test("switch_6", "shot_6", False) def test_loading_shots(self): # Make sure machine-wide shots load & mode-specific shots do not