Skip to content

Commit 76585ea

Browse files
authored
Merge pull request #1834 from missionpinball/dev
MPF 0.57.3 Release
2 parents 8f5cfd8 + 7a96651 commit 76585ea

File tree

15 files changed

+153
-47
lines changed

15 files changed

+153
-47
lines changed

mpf/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
"""
1212

13-
__version__ = '0.57.2' # Also consider whether MPF-MC pyproject.toml should be updated
13+
__version__ = '0.57.3.dev1' # Also consider whether MPF-MC pyproject.toml should be updated
1414
'''The full version of MPF.'''
1515

1616
__short_version__ = '0.57'

mpf/config_spec.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ fast_exp_board:
662662
led_ports: list|subconfig(fast_led_port)|None
663663
led_fade_time: single|ms|0
664664
led_hz: single|float|30
665+
ignore_led_errors: single|bool|false
665666
fast_breakout:
666667
port: single|enum(1,2,3)|
667668
model: single|str|
@@ -835,6 +836,7 @@ high_score:
835836
award_slide_display_time: single|ms|4s
836837
categories: dict|str:list|
837838
defaults: dict|str:list|None
839+
filler_initials: list|str|None
838840
enter_initials_timeout: single|secs|20s
839841
reverse_sort: list|str|None
840842
reset_high_scores_events: list|event_handler|high_scores_reset,factory_reset
@@ -1111,6 +1113,7 @@ mpf:
11111113
core_modules: ignore
11121114
config_players: ignore
11131115
device_modules: ignore
1116+
min_mpf_version: single|str|None
11141117
plugins: ignore
11151118
platforms: ignore
11161119
paths: ignore

mpf/core/machine.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import threading
77
from typing import Any, Callable, Dict, List, Set, Optional
88

9+
from packaging import version
910
from pkg_resources import iter_entry_points
1011

1112
from mpf._version import __version__
@@ -368,6 +369,16 @@ def _validate_config(self) -> None:
368369
self.validate_machine_config_section('machine')
369370
self.validate_machine_config_section('game')
370371
self.validate_machine_config_section('mpf')
372+
self._validate_version()
373+
374+
def _validate_version(self):
375+
if not self.config['mpf']['min_mpf_version']:
376+
return
377+
min_version = version.parse(self.config['mpf']['min_mpf_version'])
378+
mpf_version = version.parse(__version__)
379+
if mpf_version < min_version:
380+
raise AssertionError(f'MPF version mismatch. MPF version {mpf_version} found but game config '
381+
f'requires at least {min_version} ')
371382

372383
def validate_machine_config_section(self, section: str) -> None:
373384
"""Validate a config section."""
@@ -685,7 +696,11 @@ def run(self) -> None:
685696
if not self.initialize_mpf():
686697
return
687698

688-
self.info_log("Starting the main run loop.")
699+
self.info_log("Starting the main run loop with active modes: %s",
700+
self.mode_controller.active_modes)
701+
if not self.modes['attract'] in self.mode_controller.active_modes:
702+
self.warning_log("Attract mode is not active, game will not be playable. "
703+
"Please check your attract mode configuration.")
689704
self._run_loop()
690705

691706
def stop_with_exception(self, exception) -> None:

mpf/modes/bonus/code/bonus.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,10 @@ def _bonus_next_item(self):
9191
self._subtotal()
9292
return
9393

94-
# Calling player.vars.get() instead of player.get() bypasses the
95-
# auto-fill zero and will throw if there is no player variable.
96-
# The fallback value of 1 is used for bonus entries that don't use
97-
# a player score, which are multiplied by one to get the bonus.
98-
hits = self.player.vars.get(entry['player_score_entry'], 1)
94+
# If a player_score_entry is provided, use player getattr to get a
95+
# fallback value of zero if the variable is not set. Otherwise
96+
# use 1 as the multiplier for non-player-score bonuses.
97+
hits = self.player[entry['player_score_entry']] if entry['player_score_entry'] else 1
9998
score = entry['score'].evaluate([]) * hits
10099

101100
if (not score and entry['skip_if_zero']) or (score < 0 and entry['skip_if_negative']):

mpf/modes/high_score/code/high_score.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Contains the High Score mode code."""
22
import asyncio
3+
from random import choice
34

45
from mpf.core.async_mode import AsyncMode
56
from mpf.core.player import Player
@@ -195,7 +196,8 @@ async def _run(self) -> None:
195196
# ask player for initials if we do not know them
196197
if not player.initials:
197198
try:
198-
player.initials = await self._ask_player_for_initials(player, award_names[i], value)
199+
player.initials = await self._ask_player_for_initials(player, award_names[i],
200+
value, category_name)
199201
except asyncio.TimeoutError:
200202
del new_list[i]
201203
# no entry when the player missed the timeout
@@ -241,7 +243,7 @@ def _assign_vars(self, category_name, player):
241243
return var_dict
242244

243245
# pylint: disable-msg=too-many-arguments
244-
async def _ask_player_for_initials(self, player: Player, award_label: str, value: int) -> str:
246+
async def _ask_player_for_initials(self, player: Player, award_label: str, value: int, category_name: str) -> str:
245247
"""Show text widget to ask player for initials."""
246248
self.info_log("New high score. Player: %s, award_label: %s"
247249
", Value: %s", player, award_label, value)
@@ -256,7 +258,18 @@ async def _ask_player_for_initials(self, player: Player, award_label: str, value
256258
timeout=self.high_score_config['enter_initials_timeout']
257259
) # type: dict
258260

259-
return event_result["text"] if "text" in event_result else ''
261+
input_initials = event_result["text"] if "text" in event_result else ''
262+
263+
# If no initials were input, some can be randomly chosen from the 'filler_initials' config section
264+
if not input_initials and self.high_score_config["filler_initials"]:
265+
# High scores are stored as an array of [name, score]
266+
existing_initials = [n[0] for n in self.high_scores[category_name]]
267+
unused_initials = [i for i in self.high_score_config["filler_initials"] if i not in existing_initials]
268+
# If there aren't enough to choose something unique, just pick any from the fillers
269+
if not unused_initials:
270+
unused_initials = self.high_score_config["filler_initials"]
271+
input_initials = choice(unused_initials)
272+
return input_initials
260273

261274
async def _show_award_slide(self, player_num, player_name: str, category_name: str, award: str, value: int) -> None:
262275
if not self.high_score_config['award_slide_display_time']:

mpf/platforms/fast/communicators/net_neuron.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def update_switches_from_hw_data(self):
293293
This will silently sync the switch.hw_state. If the logical state changes,
294294
it will process it like any switch change.
295295
"""
296-
for switch in self.machine.switches:
296+
for switch in self.machine.switches.values():
297297
hw_state = self.platform.hw_switch_data[switch.hw_switch.number]
298298

299299
if hw_state != switch.hw_state:

mpf/platforms/fast/fast_exp_board.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
from base64 import b16decode
5+
from binascii import Error as binasciiError
56
from importlib import import_module
67

78
from packaging import version
@@ -176,11 +177,12 @@ def update_leds(self):
176177

177178
try:
178179
self.communicator.send_bytes(b16decode(f'{msg_header}{msg}'), log_msg)
179-
except Exception as e:
180+
except binasciiError as e:
180181
self.log.error(
181182
f"Error decoding the following message for board {breakout_address} : {msg_header}{msg}")
182-
self.log.debug("Attempted update that caused this error: %s", dirty_leds)
183-
raise e
183+
self.log.info("Attempted update that caused this error: %s", dirty_leds)
184+
if not self.config['ignore_led_errors']:
185+
raise e
184186

185187
def set_led_fade(self, rate: int) -> None:
186188
"""Set LED fade rate in ms."""

mpf/platforms/virtual.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,16 @@ async def get_hw_switch_states(self):
105105

106106
if 'virtual_platform_start_active_switches' in self.machine.config:
107107
initial_active_switches = []
108-
for switch in Util.string_to_list(self.machine.config['virtual_platform_start_active_switches']):
109-
if switch not in self.machine.switches:
110-
if " " in switch:
108+
for switch_name in Util.string_to_list(self.machine.config['virtual_platform_start_active_switches']):
109+
if switch_name not in self.machine.switches.keys():
110+
if " " in switch_name:
111111
self.raise_config_error("MPF no longer supports lists separated by space in "
112112
"virtual_platform_start_active_switches. Please separate "
113-
"switches by comma: {}.".format(switch), 1)
113+
"switches by comma: {}.".format(switch_name), 1)
114114
else:
115115
self.raise_config_error("Switch {} used in virtual_platform_start_active_switches was not "
116-
"found in switches section.".format(switch), 1)
117-
initial_active_switches.append(self.machine.switches[switch].hw_switch.number)
116+
"found in switches section.".format(switch_name), 1)
117+
initial_active_switches.append(self.machine.switches[switch_name].hw_switch.number)
118118

119119
for k in self.hw_switches:
120120
if k in initial_active_switches:

mpf/plugins/platform_integration_test_runner.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,16 @@ async def start_game(self, num_players=1):
207207
async def eject_and_plunge_ball(self, plunger_switch_name, plunger_lane_settle_time=2, **kwargs):
208208
"""Shuffle the trough switches and plunger to simulate an eject."""
209209
del kwargs
210-
self.info_log("Ejecting and plunging ball...")
211-
self.set_switch_sync(self.trough_switches[0], 0)
210+
# Find all the trough switches that currently have balls
211+
active_trough_switches = [s for s in self.trough_switches if self.machine.switches[s].state]
212+
assert len(active_trough_switches), "Unable to eject a ball. Trough is empty."
213+
self.info_log("Ejecting and plunging ball from trough %s to plunger switch %s...",
214+
active_trough_switches[-1], plunger_switch_name)
215+
self.set_switch_sync(active_trough_switches[0], 0)
212216
await asyncio.sleep(0.03)
213-
self.set_switch_sync(self.trough_switches[-1], 0)
217+
self.set_switch_sync(active_trough_switches[-1], 0)
214218
await asyncio.sleep(0.1)
215-
self.set_switch_sync(self.trough_switches[0], 1)
219+
self.set_switch_sync(active_trough_switches[0], 1)
216220
await asyncio.sleep(0.25)
217221
await self.set_switch(plunger_switch_name, 1, duration_secs=plunger_lane_settle_time)
218222
await asyncio.sleep(1)
@@ -221,7 +225,11 @@ async def move_ball_from_drain_to_trough(self, **kwargs):
221225
"""Move a ball from the drain device to the trough device."""
222226
del kwargs
223227
drain_switches = self.machine.ball_devices.items_tagged('drain')[0].config.get('ball_switches')
224-
self.set_switch_sync(drain_switches[-1], 0)
228+
self.info_log("Found drain switches: %s of type %s", drain_switches, type(drain_switches))
229+
# If there's only one drain switch it might be a single value, rather than a list
230+
drain_switch = drain_switches if isinstance(drain_switches, str) else drain_switches[-1]
231+
self.info_log("Setting drain switch '%s' to zero", drain_switch)
232+
self.set_switch_sync(drain_switch, 0)
225233
await asyncio.sleep(0.25)
226234
self.set_switch_sync(self.trough_switches[-1], 1)
227235
await asyncio.sleep(0.25)

mpf/tests/machine_files/bonus/modes/bonus/config/bonus.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ mode_settings:
1212
score: 5000
1313
player_score_entry: modes
1414
reset_player_score_entry: False
15+
- event: bonus_undefined_var
16+
score: 5000
17+
skip_if_zero: false
18+
player_score_entry: undefined_var
19+
- event: bonus_static
20+
score: 2000

mpf/tests/machine_files/shots/config/test_shot_groups.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ switches:
1212
number:
1313
switch_4:
1414
number:
15+
switch_5:
16+
number:
17+
switch_6:
18+
number:
1519
s_rotate_l:
1620
number:
1721
s_rotate_r:

mpf/tests/machine_files/shots/modes/base2/config/base2.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ shots:
2020
light: tag1
2121
shot_4:
2222
switch: switch_1
23+
shot_5:
24+
switch: switch_5
25+
shot_6:
26+
switch: switch_6
2327
led_1:
2428
switch: switch_1
2529
show_tokens:

mpf/tests/machine_files/shots/modes/mode1/config/mode1.yaml

+18-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ shots:
2424
mode1_shot_3:
2525
switch: switch_3
2626
profile: mode1_shot_3
27+
mode1_shot_5:
28+
switch: switch_5
29+
profile: mode1_shot_5
30+
mode1_shot_6:
31+
switch: switch_6
32+
profile: mode1_shot_6
2733

2834
shot_profiles:
2935
mode1_shot_2:
@@ -32,10 +38,21 @@ shot_profiles:
3238
- name: mode1_one
3339
- name: mode1_two
3440
- name: mode1_three
35-
mode1_shot_3:
41+
mode1_shot_3: # Test block: True
3642
show: rainbow2
3743
block: True
3844
states:
3945
- name: mode1_one
4046
- name: mode1_two
4147
- name: mode1_three
48+
mode1_shot_5: # Test block: False
49+
show: rainbow2
50+
block: False
51+
states:
52+
- name: mode1_one
53+
- name: mode1_two
54+
mode1_shot_6: # Test block default
55+
show: rainbow2
56+
states:
57+
- name: mode1_one
58+
- name: mode1_two

mpf/tests/test_Bonus.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def test_slam_tilt_in_service(self):
3838
def testBonus(self):
3939
self.mock_event("bonus_ramps")
4040
self.mock_event("bonus_modes")
41+
self.mock_event("bonus_undefined_var")
42+
self.mock_event("bonus_static")
4143
self.mock_event("bonus_subtotal")
4244
self.mock_event("bonus_multiplier")
4345
self.mock_event("bonus_total")
@@ -78,10 +80,14 @@ def testBonus(self):
7880
self.assertEqual(3, self._last_event_kwargs["bonus_ramps"]["hits"])
7981
self.assertEqual(10000, self._last_event_kwargs["bonus_modes"]["score"])
8082
self.assertEqual(2, self._last_event_kwargs["bonus_modes"]["hits"])
81-
self.assertEqual(13000, self._last_event_kwargs["bonus_subtotal"]["score"])
83+
self.assertEqual(0, self._last_event_kwargs["bonus_undefined_var"]["score"])
84+
self.assertEqual(0, self._last_event_kwargs["bonus_undefined_var"]["hits"])
85+
self.assertEqual(2000, self._last_event_kwargs["bonus_static"]["score"])
86+
self.assertEqual(1, self._last_event_kwargs["bonus_static"]["hits"])
87+
self.assertEqual(15000, self._last_event_kwargs["bonus_subtotal"]["score"])
8288
self.assertEqual(5, self._last_event_kwargs["bonus_multiplier"]["multiplier"])
83-
self.assertEqual(65000, self._last_event_kwargs["bonus_total"]["score"])
84-
self.assertEqual(66337, self.machine.game.player.score)
89+
self.assertEqual(75000, self._last_event_kwargs["bonus_total"]["score"])
90+
self.assertEqual(76337, self.machine.game.player.score)
8591

8692
# check resets
8793
self.assertEqual(0, self.machine.game.player.ramps)
@@ -102,10 +108,10 @@ def testBonus(self):
102108
self.assertEqual(0, self._last_event_kwargs["bonus_ramps"]["hits"])
103109
self.assertEqual(10000, self._last_event_kwargs["bonus_modes"]["score"])
104110
self.assertEqual(2, self._last_event_kwargs["bonus_modes"]["hits"])
105-
self.assertEqual(10000, self._last_event_kwargs["bonus_subtotal"]["score"])
111+
self.assertEqual(12000, self._last_event_kwargs["bonus_subtotal"]["score"])
106112
self.assertEqual(5, self._last_event_kwargs["bonus_multiplier"]["multiplier"])
107-
self.assertEqual(50000, self._last_event_kwargs["bonus_total"]["score"])
108-
self.assertEqual(116337, self.machine.game.player.score)
113+
self.assertEqual(60000, self._last_event_kwargs["bonus_total"]["score"])
114+
self.assertEqual(136337, self.machine.game.player.score)
109115

110116
# multiplier should stay the same
111117
self.assertEqual(0, self.machine.game.player.ramps)
@@ -128,6 +134,8 @@ def testBonus(self):
128134
self.mock_event("bonus_start")
129135
self.mock_event("bonus_ramps")
130136
self.mock_event("bonus_modes")
137+
self.mock_event("bonus_undefined_var")
138+
self.mock_event("bonus_static")
131139
self.mock_event("bonus_subtotal")
132140
self.mock_event("bonus_multiplier")
133141
self.mock_event("bonus_total")
@@ -157,6 +165,18 @@ def testBonus(self):
157165
self.assertEventNotCalled('bonus_multiplier')
158166
self.assertEventNotCalled('bonus_total')
159167

168+
self.advance_time_and_run(.5)
169+
self.assertEventCalled('bonus_undefined_var')
170+
self.assertEventNotCalled('bonus_subtotal')
171+
self.assertEventNotCalled('bonus_multiplier')
172+
self.assertEventNotCalled('bonus_total')
173+
174+
self.advance_time_and_run(.5)
175+
self.assertEventCalled('bonus_static')
176+
self.assertEventNotCalled('bonus_subtotal')
177+
self.assertEventNotCalled('bonus_multiplier')
178+
self.assertEventNotCalled('bonus_total')
179+
160180
self.advance_time_and_run(.5)
161181
self.assertEventCalled('bonus_subtotal')
162182
self.assertEventNotCalled('bonus_multiplier')

0 commit comments

Comments
 (0)