Ejemplo n.º 1
0
class Credits(Mode):

    def mode_init(self):
        self.data_manager = DataManager(self.machine, 'earnings')
        self.earnings = self.data_manager.get_data()

        self.credit_units_per_game = 0
        self.credit_units_inserted = 0
        self.credit_unit = 0
        self.max_credit_units = 0
        self.pricing_tiers = set()
        self.credit_units_for_pricing_tiers = 0

        self.credits_config = self.machine.config['credits']

        if 'credits' in self.config:
            self.credits_config.update(self.config['credits'])

        self.credits_config = self.machine.config_processor.process_config2(
            'credits', self.credits_config, 'credits')

    def mode_start(self, **kwargs):
        self.add_mode_event_handler('enable_free_play',
                                    self.enable_free_play)
        self.add_mode_event_handler('enable_credit_play',
                                    self.enable_credit_play)
        self.add_mode_event_handler('toggle_credit_play',
                                    self.toggle_credit_play)
        self.add_mode_event_handler('slam_tilt',
                                    self.clear_all_credits)

        if self.credits_config['free_play']:
            self.enable_free_play(post_event=False)
        else:
            self._calculate_credit_units()
            self._calculate_pricing_tiers()
            self.enable_credit_play(post_event=False)

    def mode_stop(self, **kwargs):
        self.enable_free_play()

    def _calculate_credit_units(self):
        # "credit units" are how we handle fractional credits (since most
        # pinball machines show credits as fractions instead of decimals).
        # We convert everything to the smallest coin unit and then track
        # how many of those a game takes. So price of $0.75 per game with a
        # quarter slot means a credit unit is 0.25 and the game needs 3 credit
        # units to start. This is all hidden from the player

        # We need to calculate it differently depending on how the coin switch
        # values relate to game cost.

        if self.credits_config['switches']:
            min_currency_value = min(x['value'] for x in
                                     self.credits_config['switches'])
        else:
            min_currency_value = (
                self.credits_config['pricing_tiers'][0]['price'])

        price_per_game = self.credits_config['pricing_tiers'][0]['price']

        if min_currency_value == price_per_game:
            self.credit_unit = min_currency_value

        elif min_currency_value < price_per_game:
            self.credit_unit = price_per_game - min_currency_value
            if self.credit_unit > min_currency_value:
                self.credit_unit = min_currency_value

        elif min_currency_value > price_per_game:
            self.credit_unit = min_currency_value - price_per_game
            if self.credit_unit > price_per_game:
                self.credit_unit = price_per_game

        self.log.debug("Calculated the credit unit to be %s based on a minimum"
                       "currency value of %s and a price per game of %s",
                       self.credit_unit, min_currency_value, price_per_game)

        self.credit_units_per_game = (
            int(self.credits_config['pricing_tiers'][0]['price'] /
                self.credit_unit))

        self.log.debug("Credit units per game: %s", self.credit_units_per_game)

        if self.credits_config['max_credits']:
            self.max_credit_units = (self.credit_units_per_game *
                                     self.credits_config['max_credits'])

    def _calculate_pricing_tiers(self):
        # pricing tiers are calculated with a set of tuples which indicate the
        # credit units for the price break as well as the "bump" in credit
        # units that should be added once that break is passed.

        for pricing_tier in self.credits_config['pricing_tiers']:
            credit_units = pricing_tier['price'] / self.credit_unit
            actual_credit_units = self.credit_units_per_game * pricing_tier['credits']
            bonus = actual_credit_units - credit_units

            self.log.debug("Pricing Tier Bonus. Price: %s, Credits: %s. "
                           "Credit units for this tier: %s, Credit units this "
                           "tier buys: %s, Bonus bump needed: %s",
                           pricing_tier['price'], pricing_tier['credits'],
                           credit_units, actual_credit_units, bonus)

            self.pricing_tiers.add((credit_units, bonus))

    def enable_credit_play(self, post_event=True, **kwargs):

        self.credits_config['free_play'] = False

        if self.machine.is_machine_var('credit_units'):
            credit_units = self.machine.get_machine_var('credit_units')
        else:
            credit_units = 0

        if self.credits_config['persist_credits_while_off_time']:
            self.machine.create_machine_var(name='credit_units',
                                            value=credit_units,
                                            persist=True,
                                            expire_secs=self.credits_config[
                                            'persist_credits_while_off_time'])
        else:
            self.machine.create_machine_var(name='credit_units',
                                            value=credit_units)

        self.machine.create_machine_var('credits_string', ' ')
        self.machine.create_machine_var('credits_value', '0')
        self.machine.create_machine_var('credits_whole_num', 0)
        self.machine.create_machine_var('credits_numerator', 0)
        self.machine.create_machine_var('credits_denominator', 0)
        self._update_credit_strings()

        self._enable_credit_switch_handlers()

        # setup switch handlers

        self.machine.events.add_handler('player_add_request',
                                        self._player_add_request)
        self.machine.events.add_handler('request_to_start_game',
                                        self._request_to_start_game)
        self.machine.events.add_handler('player_add_success',
                                        self._player_add_success)
        self.machine.events.add_handler('mode_game_started',
                                        self._game_ended)
        self.machine.events.add_handler('mode_game_ended',
                                        self._game_started)
        self.machine.events.add_handler('ball_starting',
                                        self._ball_starting)
        if post_event:
            self.machine.events.post('enabling_credit_play')

    def enable_free_play(self, post_event=True, **kwargs):
        self.credits_config['free_play'] = True

        self.machine.events.remove_handler(self._player_add_request)
        self.machine.events.remove_handler(self._request_to_start_game)
        self.machine.events.remove_handler(self._player_add_success)
        self.machine.events.remove_handler(self._game_ended)
        self.machine.events.remove_handler(self._game_started)
        self.machine.events.remove_handler(self._ball_starting)

        self._disable_credit_switch_handlers()

        self._update_credit_strings()

        if post_event:
            self.machine.events.post('enabling_free_play')

    def toggle_credit_play(self, **kwargs):

        if self.credits_config['free_play']:
            self.enable_credit_play()
        else:
            self.enable_free_play()

    def _player_add_request(self):
        if (self.machine.get_machine_var('credit_units') >=
                self.credit_units_per_game):
            self.log.debug("Received request to add player. Request Approved")
            return True

        else:
            self.log.debug("Received request to add player. Request Denied")
            self.machine.events.post("not_enough_credits")
            return False

    def _request_to_start_game(self):
        if (self.machine.get_machine_var('credit_units') >=
                self.credit_units_per_game):
            self.log.debug("Received request to start game. Request Approved")
            return True

        else:
            self.log.debug("Received request to start game. Request Denied")
            self.machine.events.post("not_enough_credits")
            return False

    def _player_add_success(self, **kwargs):
        new_credit_units = (self.machine.get_machine_var('credit_units') -
                self.credit_units_per_game)

        if new_credit_units < 0:
            self.log.warning("Somehow credit units went below 0?!? Resetting "
                             "to 0.")
            new_credit_units = 0

        self.machine.set_machine_var('credit_units', new_credit_units)
        self._update_credit_strings()

    def _enable_credit_switch_handlers(self):
        for switch_settings in self.credits_config['switches']:
            self.machine.switch_controller.add_switch_handler(
                switch_name=switch_settings['switch'].name,
                callback=self._credit_switch_callback,
                callback_kwargs={'value': switch_settings['value'],
                                 'audit_class': switch_settings['type']})

        for switch in self.credits_config['service_credits_switch']:
            self.machine.switch_controller.add_switch_handler(
                switch_name=switch.name,
                callback=self._service_credit_callback)

    def _disable_credit_switch_handlers(self):
        for switch_settings in self.credits_config['switches']:
            self.machine.switch_controller.remove_switch_handler(
                switch_name=switch_settings['switch'].name,
                callback=self._credit_switch_callback)

        for switch in self.credits_config['service_credits_switch']:
            self.machine.switch_controller.remove_switch_handler(
                switch_name=switch.name,
                callback=self._service_credit_callback)

    def _credit_switch_callback(self, value, audit_class):
        self._add_credit_units(credit_units=value/self.credit_unit)
        self._audit(value, audit_class)

    def _service_credit_callback(self):
        self.log.debug("Service Credit Added")
        self.add_credit(price_tiering=False)
        self._audit(1, 'service_credit')

    def _add_credit_units(self, credit_units, price_tiering=True):
        self.log.debug("Adding %s credit_units. Price tiering: %s",
                       credit_units, price_tiering)

        previous_credit_units = self.machine.get_machine_var('credit_units')
        total_credit_units = credit_units + previous_credit_units

        # check for pricing tier
        if price_tiering:
            self.credit_units_for_pricing_tiers += credit_units
            bonus_credit_units = 0
            for tier_credit_units, bonus in self.pricing_tiers:
                if self.credit_units_for_pricing_tiers % tier_credit_units == 0:
                    bonus_credit_units += bonus

            total_credit_units += bonus_credit_units

        max_credit_units = (self.credits_config['max_credits'] *
                            self.credit_units_per_game)

        if max_credit_units and total_credit_units > max_credit_units:
            self.log.debug("Max credits reached")
            self._update_credit_strings()
            self.machine.events.post('max_credits_reached')
            self.machine.set_machine_var('credit_units', max_credit_units)

        if max_credit_units > previous_credit_units:
            self.log.debug("Credit units added")
            self.machine.set_machine_var('credit_units', total_credit_units)
            self._update_credit_strings()
            self.machine.events.post('credits_added')

    def add_credit(self, price_tiering=True):
        """Adds a single credit to the machine.

        Args:
            price_tiering: Boolean which controls whether this credit will be
                eligible for the pricing tier bonuses. Default is True.

        """
        self._add_credit_units(self.credit_units_per_game, price_tiering)

    def _reset_pricing_tier_credits(self):
        if not self.reset_pricing_tier_count_this_game:
            self.log.debug("Resetting pricing tier credit count")
            self.credit_units_for_pricing_tiers = 0
            self.reset_pricing_tier_count_this_game = True

    def _ball_starting(self, **kwargs):
        if self.player.number == 1 and self.player.ball == 2:
            self._reset_pricing_tier_credits()

    def _update_credit_strings(self):
        machine_credit_units = self.machine.get_machine_var('credit_units')
        whole_num = int(floor(machine_credit_units /
                              self.credit_units_per_game))
        numerator = int(machine_credit_units % self.credit_units_per_game)
        denominator = int(self.credit_units_per_game)

        if numerator:
            if whole_num:
                display_fraction = '{} {}/{}'.format(whole_num, numerator,
                                                     denominator)
            else:
                display_fraction = '{}/{}'.format(numerator, denominator)

        else:
            display_fraction = str(whole_num)

        if self.credits_config['free_play']:
            display_string = self.credits_config['free_play_string']
        else:
            display_string = '{} {}'.format(
                self.credits_config['credits_string'], display_fraction)
        self.machine.set_machine_var('credits_string', display_string)
        self.machine.set_machine_var('credits_value', display_fraction)
        self.machine.set_machine_var('credits_whole_num', whole_num)
        self.machine.set_machine_var('credits_numerator', numerator)
        self.machine.set_machine_var('credits_denominator', denominator)

    def _audit(self, value, audit_class):
        if audit_class not in self.earnings:
            self.earnings[audit_class] = dict()
            self.earnings[audit_class]['total_value'] = 0
            self.earnings[audit_class]['count'] = 0

        self.earnings[audit_class]['total_value'] += value
        self.earnings[audit_class]['count'] += 1

        self.data_manager.save_all(data=self.earnings)

    def _game_started(self):
        self.log.debug("Removing credit clearing delays")
        self.delay.remove('clear_fractional_credits')
        self.delay.remove('clear_all_credits')

    def _game_ended(self):
        if self.credits_config['fractional_credit_expiration_time']:
            self.log.debug("Adding delay to clear fractional credits")
            self.delay.add(
                ms=self.credits_config['fractional_credit_expiration_time'],
                callback=self._clear_fractional_credits,
                name='clear_fractional_credits')

        if self.credits_config['credit_expiration_time']:
            self.log.debug("Adding delay to clear credits")
            self.delay.add(
                ms=self.credits_config['credit_expiration_time'],
                callback=self.clear_all_credits,
                name='clear_all_credits')

        self.reset_pricing_tier_count_this_game = False

    def _clear_fractional_credits(self):
        self.log.debug("Clearing fractional credits")

        credit_units = self.machine.get_machine_var('credit_units')
        credit_units -= credit_units % self.credit_units_per_game

        self.machine.set_machine_var('credit_units', credit_units)
        self._update_credit_strings()

    def clear_all_credits(self):
        self.log.debug("Clearing all credits")
        self.machine.set_machine_var('credit_units', 0)
        self._update_credit_strings()




# The MIT License (MIT)

# Copyright (c) 2013-2015 Brian Madden and Gabe Knuth

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Ejemplo n.º 2
0
class Auditor(object):

    def __init__(self, machine):
        """Base class for the auditor.

        Args:
            machine: A refence to the machine controller object.
        """

        if 'auditor' not in machine.config:
            machine.log.debug('"Auditor:" section not found in machine '
                              'configuration, so the auditor will not be '
                              'used.')
            return

        self.log = logging.getLogger('Auditor')
        self.machine = machine

        self.machine.auditor = self
        self.switchnames_to_audit = set()

        self.enabled = False
        """Attribute that's viewed by other system components to let them know
        they should send auditing events. Set this via the enable() and
        disable() methods.
        """

        self.data_manager = DataManager(self.machine, 'audits')

        self.machine.events.add_handler('init_phase_4', self._initialize)

    def __repr__(self):
        return '<Auditor>'

    def _initialize(self):
        # Initializes the auditor. We do this separate from __init__() since
        # we need everything else to be setup first.

        config = '''
                    save_events: list|ball_ended
                    audit: list|None
                    events: list|None
                    player: list|None
                    num_player_top_records: int|10
                    '''

        self.config = Config.process_config(config,
                                            self.machine.config['auditor'])

        self.current_audits = self.data_manager.get_data()

        if not self.current_audits:
            self.current_audits = dict()

        # Make sure we have all the sections we need in our audit dict
        if 'switches' not in self.current_audits:
            self.current_audits['switches'] = dict()

        if 'events' not in self.current_audits:
            self.current_audits['events'] = dict()

        if 'player' not in self.current_audits:
            self.current_audits['player'] = dict()

        # Make sure we have all the switches in our audit dict
        for switch in self.machine.switches:
            if (switch.name not in self.current_audits['switches'] and
                    'no_audit' not in switch.tags):
                self.current_audits['switches'][switch.name] = 0

        # build the list of switches we should audit
        self.switchnames_to_audit = {x.name for x in self.machine.switches
                                     if 'no_audit' not in x.tags}

        # Make sure we have all the player stuff in our audit dict
        if 'player' in self.config['audit']:
            for item in self.config['player']:
                if item not in self.current_audits['player']:
                    self.current_audits['player'][item] = dict()
                    self.current_audits['player'][item]['top'] = list()
                    self.current_audits['player'][item]['average'] = 0
                    self.current_audits['player'][item]['total'] = 0

        # Register for the events the auditor needs to do its job
        self.machine.events.add_handler('game_starting', self.enable)
        self.machine.events.add_handler('game_ended', self.disable)
        if 'player' in self.config['audit']:
            self.machine.events.add_handler('game_ending', self.audit_player)

        # Enable the shots monitor
        Shot.monitor_enabled = True
        self.machine.register_monitor('shots', self.audit_shot)

        # Add the switches monitor
        self.machine.switch_controller.add_monitor(self.audit_switch)

    def audit(self, audit_class, event, **kwargs):
        """Called to log an auditable event.

        Args:
            audit_class: A string of the section we want this event to be
            logged to.
            event: A string name of the event we're auditing.
            **kawargs: Not used, but included since some of the audit events
                might include random kwargs.
        """

        if audit_class not in self.current_audits:
            self.current_audits[audit_class] = dict()

        if event not in self.current_audits[audit_class]:
            self.current_audits[audit_class][event] = 0

        self.current_audits[audit_class][event] += 1

    def audit_switch(self, switch_name, state):
        if state and switch_name in self.switchnames_to_audit:
            self.audit('switches', switch_name)

    def audit_shot(self, name, profile, state):
        self.audit('shots', name)

    def audit_event(self, eventname, **kwargs):
        """Registered as an event handlers to log an event to the audit log.

        Args:
            eventname: The string name of the event.
            **kwargs, not used, but included since some types of events include
                kwargs.
        """

        self.current_audits['events'][eventname] += 1

    def audit_player(self, **kwargs):
        """Called to write player data to the audit log. Typically this is only
        called at the end of a game.

        Args:
            **kwargs, not used, but included since some types of events include
                kwargs.
        """
        for item in self.config['player']:
            for player in self.machine.game.player_list:

                self.current_audits['player'][item]['top'] = (
                    self._merge_into_top_list(
                        player[item],
                        self.current_audits['player'][item]['top'],
                        self.config['num_player_top_records']))

                self.current_audits['player'][item]['average'] = (
                    ((self.current_audits['player'][item]['total'] *
                      self.current_audits['player'][item]['average']) +
                     self.machine.game.player[item]) /
                    (self.current_audits['player'][item]['total'] + 1))

                self.current_audits['player'][item]['total'] += 1

    def _merge_into_top_list(self, new_item, current_list, num_items):
        # takes a list of top integers and a new item and merges the new item
        # into the list, then trims it based on the num_items specified
        current_list.append(new_item)
        current_list.sort(reverse=True)
        return current_list[0:num_items]

    def enable(self, **kwags):
        """Enables the auditor.

        This method lets you enable the auditor so it only records things when
        you want it to. Typically this is called at the beginning of a game.

        Args:
            **kwargs: No function here. They just exist to allow this method
                to be registered as a handler for events that might contain
                keyword arguments.

        """
        if self.enabled:
            return  # this will happen if we get a mid game restart

        self.log.debug("Enabling the Auditor")
        self.enabled = True

        # Register for the events we're auditing
        if 'events' in self.config['audit']:
            for event in self.config['events']:
                self.machine.events.add_handler(event,
                                                self.audit_event,
                                                eventname=event,
                                                priority=2)
                # Make sure we have an entry in our audit file for this event
                if event not in self.current_audits['events']:
                    self.current_audits['events'][event] = 0

        for event in self.config['save_events']:
            self.machine.events.add_handler(event, self._save_audits,
                                            priority=0)

    def _save_audits(self, delay_secs=3):
        self.data_manager.save_all(data=self.current_audits,
                                   delay_secs=delay_secs)

    def disable(self, **kwargs):
        """Disables the auditor."""
        self.log.debug("Disabling the Auditor")
        self.enabled = False

        # remove switch and event handlers
        self.machine.events.remove_handler(self.audit_event)
        self.machine.events.remove_handler(self._save_audits)

        for switch in self.machine.switches:
            if 'no_audit' not in switch.tags:
                self.machine.switch_controller.remove_switch_handler(
                    switch.name, self.audit_switch, 1, 0)