Skip to content

Commit

Permalink
Merge pull request #1834 from missionpinball/dev
Browse files Browse the repository at this point in the history
MPF 0.57.3 Release
  • Loading branch information
avanwinkle authored Sep 15, 2024
2 parents 8f5cfd8 + 7a96651 commit 76585ea
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 47 deletions.
2 changes: 1 addition & 1 deletion mpf/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"""

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

__short_version__ = '0.57'
Expand Down
3 changes: 3 additions & 0 deletions mpf/config_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,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|
Expand Down Expand Up @@ -835,6 +836,7 @@ high_score:
award_slide_display_time: single|ms|4s
categories: dict|str:list|
defaults: dict|str:list|None
filler_initials: list|str|None
enter_initials_timeout: single|secs|20s
reverse_sort: list|str|None
reset_high_scores_events: list|event_handler|high_scores_reset,factory_reset
Expand Down Expand Up @@ -1111,6 +1113,7 @@ mpf:
core_modules: ignore
config_players: ignore
device_modules: ignore
min_mpf_version: single|str|None
plugins: ignore
platforms: ignore
paths: ignore
Expand Down
17 changes: 16 additions & 1 deletion mpf/core/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import threading
from typing import Any, Callable, Dict, List, Set, Optional

from packaging import version
from pkg_resources import iter_entry_points

from mpf._version import __version__
Expand Down Expand Up @@ -368,6 +369,16 @@ def _validate_config(self) -> None:
self.validate_machine_config_section('machine')
self.validate_machine_config_section('game')
self.validate_machine_config_section('mpf')
self._validate_version()

def _validate_version(self):
if not self.config['mpf']['min_mpf_version']:
return
min_version = version.parse(self.config['mpf']['min_mpf_version'])
mpf_version = version.parse(__version__)
if mpf_version < min_version:
raise AssertionError(f'MPF version mismatch. MPF version {mpf_version} found but game config '
f'requires at least {min_version} ')

def validate_machine_config_section(self, section: str) -> None:
"""Validate a config section."""
Expand Down Expand Up @@ -685,7 +696,11 @@ def run(self) -> None:
if not self.initialize_mpf():
return

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

def stop_with_exception(self, exception) -> None:
Expand Down
9 changes: 4 additions & 5 deletions mpf/modes/bonus/code/bonus.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,10 @@ def _bonus_next_item(self):
self._subtotal()
return

# 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']):
Expand Down
19 changes: 16 additions & 3 deletions mpf/modes/high_score/code/high_score.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Contains the High Score mode code."""
import asyncio
from random import choice

from mpf.core.async_mode import AsyncMode
from mpf.core.player import Player
Expand Down Expand Up @@ -195,7 +196,8 @@ async def _run(self) -> None:
# ask player for initials if we do not know them
if not player.initials:
try:
player.initials = await self._ask_player_for_initials(player, award_names[i], value)
player.initials = await self._ask_player_for_initials(player, award_names[i],
value, category_name)
except asyncio.TimeoutError:
del new_list[i]
# no entry when the player missed the timeout
Expand Down Expand Up @@ -241,7 +243,7 @@ def _assign_vars(self, category_name, player):
return var_dict

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

return event_result["text"] if "text" in event_result else ''
input_initials = event_result["text"] if "text" in event_result else ''

# If no initials were input, some can be randomly chosen from the 'filler_initials' config section
if not input_initials and self.high_score_config["filler_initials"]:
# High scores are stored as an array of [name, score]
existing_initials = [n[0] for n in self.high_scores[category_name]]
unused_initials = [i for i in self.high_score_config["filler_initials"] if i not in existing_initials]
# If there aren't enough to choose something unique, just pick any from the fillers
if not unused_initials:
unused_initials = self.high_score_config["filler_initials"]
input_initials = choice(unused_initials)
return input_initials

async def _show_award_slide(self, player_num, player_name: str, category_name: str, award: str, value: int) -> None:
if not self.high_score_config['award_slide_display_time']:
Expand Down
2 changes: 1 addition & 1 deletion mpf/platforms/fast/communicators/net_neuron.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 5 additions & 3 deletions mpf/platforms/fast/fast_exp_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down
12 changes: 6 additions & 6 deletions mpf/platforms/virtual.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 13 additions & 5 deletions mpf/plugins/platform_integration_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,16 @@ async def start_game(self, num_players=1):
async def eject_and_plunge_ball(self, plunger_switch_name, plunger_lane_settle_time=2, **kwargs):
"""Shuffle the trough switches and plunger to simulate an eject."""
del kwargs
self.info_log("Ejecting and plunging ball...")
self.set_switch_sync(self.trough_switches[0], 0)
# Find all the trough switches that currently have balls
active_trough_switches = [s for s in self.trough_switches if self.machine.switches[s].state]
assert len(active_trough_switches), "Unable to eject a ball. Trough is empty."
self.info_log("Ejecting and plunging ball from trough %s to plunger switch %s...",
active_trough_switches[-1], plunger_switch_name)
self.set_switch_sync(active_trough_switches[0], 0)
await asyncio.sleep(0.03)
self.set_switch_sync(self.trough_switches[-1], 0)
self.set_switch_sync(active_trough_switches[-1], 0)
await asyncio.sleep(0.1)
self.set_switch_sync(self.trough_switches[0], 1)
self.set_switch_sync(active_trough_switches[0], 1)
await asyncio.sleep(0.25)
await self.set_switch(plunger_switch_name, 1, duration_secs=plunger_lane_settle_time)
await asyncio.sleep(1)
Expand All @@ -221,7 +225,11 @@ async def move_ball_from_drain_to_trough(self, **kwargs):
"""Move a ball from the drain device to the trough device."""
del kwargs
drain_switches = self.machine.ball_devices.items_tagged('drain')[0].config.get('ball_switches')
self.set_switch_sync(drain_switches[-1], 0)
self.info_log("Found drain switches: %s of type %s", drain_switches, type(drain_switches))
# If there's only one drain switch it might be a single value, rather than a list
drain_switch = drain_switches if isinstance(drain_switches, str) else drain_switches[-1]
self.info_log("Setting drain switch '%s' to zero", drain_switch)
self.set_switch_sync(drain_switch, 0)
await asyncio.sleep(0.25)
self.set_switch_sync(self.trough_switches[-1], 1)
await asyncio.sleep(0.25)
6 changes: 6 additions & 0 deletions mpf/tests/machine_files/bonus/modes/bonus/config/bonus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ mode_settings:
score: 5000
player_score_entry: modes
reset_player_score_entry: False
- event: bonus_undefined_var
score: 5000
skip_if_zero: false
player_score_entry: undefined_var
- event: bonus_static
score: 2000
4 changes: 4 additions & 0 deletions mpf/tests/machine_files/shots/config/test_shot_groups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ switches:
number:
switch_4:
number:
switch_5:
number:
switch_6:
number:
s_rotate_l:
number:
s_rotate_r:
Expand Down
4 changes: 4 additions & 0 deletions mpf/tests/machine_files/shots/modes/base2/config/base2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 18 additions & 1 deletion mpf/tests/machine_files/shots/modes/mode1/config/mode1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
32 changes: 26 additions & 6 deletions mpf/tests/test_Bonus.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def test_slam_tilt_in_service(self):
def testBonus(self):
self.mock_event("bonus_ramps")
self.mock_event("bonus_modes")
self.mock_event("bonus_undefined_var")
self.mock_event("bonus_static")
self.mock_event("bonus_subtotal")
self.mock_event("bonus_multiplier")
self.mock_event("bonus_total")
Expand Down Expand Up @@ -78,10 +80,14 @@ def testBonus(self):
self.assertEqual(3, self._last_event_kwargs["bonus_ramps"]["hits"])
self.assertEqual(10000, self._last_event_kwargs["bonus_modes"]["score"])
self.assertEqual(2, self._last_event_kwargs["bonus_modes"]["hits"])
self.assertEqual(13000, self._last_event_kwargs["bonus_subtotal"]["score"])
self.assertEqual(0, self._last_event_kwargs["bonus_undefined_var"]["score"])
self.assertEqual(0, self._last_event_kwargs["bonus_undefined_var"]["hits"])
self.assertEqual(2000, self._last_event_kwargs["bonus_static"]["score"])
self.assertEqual(1, self._last_event_kwargs["bonus_static"]["hits"])
self.assertEqual(15000, self._last_event_kwargs["bonus_subtotal"]["score"])
self.assertEqual(5, self._last_event_kwargs["bonus_multiplier"]["multiplier"])
self.assertEqual(65000, self._last_event_kwargs["bonus_total"]["score"])
self.assertEqual(66337, self.machine.game.player.score)
self.assertEqual(75000, self._last_event_kwargs["bonus_total"]["score"])
self.assertEqual(76337, self.machine.game.player.score)

# check resets
self.assertEqual(0, self.machine.game.player.ramps)
Expand All @@ -102,10 +108,10 @@ def testBonus(self):
self.assertEqual(0, self._last_event_kwargs["bonus_ramps"]["hits"])
self.assertEqual(10000, self._last_event_kwargs["bonus_modes"]["score"])
self.assertEqual(2, self._last_event_kwargs["bonus_modes"]["hits"])
self.assertEqual(10000, self._last_event_kwargs["bonus_subtotal"]["score"])
self.assertEqual(12000, self._last_event_kwargs["bonus_subtotal"]["score"])
self.assertEqual(5, self._last_event_kwargs["bonus_multiplier"]["multiplier"])
self.assertEqual(50000, self._last_event_kwargs["bonus_total"]["score"])
self.assertEqual(116337, self.machine.game.player.score)
self.assertEqual(60000, self._last_event_kwargs["bonus_total"]["score"])
self.assertEqual(136337, self.machine.game.player.score)

# multiplier should stay the same
self.assertEqual(0, self.machine.game.player.ramps)
Expand All @@ -128,6 +134,8 @@ def testBonus(self):
self.mock_event("bonus_start")
self.mock_event("bonus_ramps")
self.mock_event("bonus_modes")
self.mock_event("bonus_undefined_var")
self.mock_event("bonus_static")
self.mock_event("bonus_subtotal")
self.mock_event("bonus_multiplier")
self.mock_event("bonus_total")
Expand Down Expand Up @@ -157,6 +165,18 @@ def testBonus(self):
self.assertEventNotCalled('bonus_multiplier')
self.assertEventNotCalled('bonus_total')

self.advance_time_and_run(.5)
self.assertEventCalled('bonus_undefined_var')
self.assertEventNotCalled('bonus_subtotal')
self.assertEventNotCalled('bonus_multiplier')
self.assertEventNotCalled('bonus_total')

self.advance_time_and_run(.5)
self.assertEventCalled('bonus_static')
self.assertEventNotCalled('bonus_subtotal')
self.assertEventNotCalled('bonus_multiplier')
self.assertEventNotCalled('bonus_total')

self.advance_time_and_run(.5)
self.assertEventCalled('bonus_subtotal')
self.assertEventNotCalled('bonus_multiplier')
Expand Down
Loading

0 comments on commit 76585ea

Please sign in to comment.