def new_party(self,
                  creator,
                  player_limit=None,
                  has_leader=True,
                  add_creator=True,
                  tc=False):
        if creator.party and add_creator:
            raise PartyError(
                self._f('The player is part of another party.', tc=tc))
        if player_limit is None:
            player_limit = self.server.config['playerlimit']
        area = creator.area
        leader = {creator} if has_leader and add_creator else set()
        # Check if there are any party slots remaining
        if len(self.parties) == 10**self.pid_length - 10**(self.pid_length -
                                                           1) + 1:
            raise PartyError('The server has reached its party limit.')

        # Generate a party ID
        while True:
            pid = random.randint(10**(self.pid_length - 1),
                                 10**self.pid_length)
            if pid not in self.parties:
                break

        party = self.Party(self.server, pid, area, player_limit, leader)
        self.parties[pid] = party

        if add_creator:
            party.add_invite(creator)
            party.add_member(creator)

        return party
 def add_invite(self, member, tc=False):
     if member in self.invite_list:
         raise PartyError(
             self._f('The player is already in the party invite list.'))
     if member in self.members:
         raise PartyError(
             self._f('The player is already a member of this party.'))
     self.invite_list.add(member)
        def remove_leader(self, leader, tc=False):
            if not self.is_member(leader):
                raise PartyError(
                    self._f('The player is not part of the party.', tc=tc))
            if leader not in self.leaders:
                raise PartyError(
                    self._f('The player is not a leader of the party.', tc=tc))

            self.leaders.remove(leader)
        def add_leader(self, new_leader, tc=False):
            if not self.is_member(new_leader):
                raise PartyError(
                    self._f('The player is not part of the party.', tc=tc))
            if new_leader in self.leaders:
                raise PartyError(
                    self._f('The player is already a leader of the party.',
                            tc=tc))

            self.leaders.add(new_leader)
 def get_party_id(self, party):
     if isinstance(party, PartyManager.Party):
         if party.pid not in self.parties:
             raise PartyError('This party does not exist.')
         return party.pid
     if isinstance(party, str):
         if party.isdigit():
             if int(party) not in self.parties:
                 raise PartyError('This party does not exist.')
             return int(party)
     if isinstance(party, int):
         if party not in self.parties:
             raise PartyError('This party does not exist.')
         return party
     raise PartyError('Invalid party ID.')
    def split_party(self, party, members1, members2):
        party = self.get_party(party)  # Assert that it is a party
        common = members1.intersection(members2)
        if common:
            raise PartyError(
                'Invalid party split: {} would belong in two parties.'.format(
                    ', '.join([c.id for c in common])))
        orphaned = party.members - (members1.union(members2))
        if orphaned:
            raise PartyError(
                'Invalid party split: {} would be left out of the parties.'.
                format(', '.join([c.id for c in orphaned])))

        _, _, old_pl, old_leaders, _ = party.get_details()
        if not members1 or not members2:
            raise PartyError(
                'Invalid party split: One of the new parties would be empty.')

        self.disband_party(party)
        creator1 = random.choice(tuple(members1))
        creator2 = random.choice(tuple(members2))

        party1 = self.new_party(creator1,
                                player_limit=old_pl,
                                has_leader=(old_leaders is not None),
                                add_creator=False)
        party2 = self.new_party(creator2,
                                player_limit=old_pl,
                                has_leader=(old_leaders is not None),
                                add_creator=False)

        for member in members1:
            party1.add_invite(member)
            party1.add_member(member)
            if member in old_leaders:
                party1.add_leader(member)

        for member in members2:
            party2.add_invite(member)
            party2.add_member(member)
            if member in old_leaders:
                party2.add_leader(member)
        def add_member(self, member, tc=False):
            if self.is_member(member):
                raise PartyError(
                    self._f('The player is already part of this party.',
                            tc=tc))
            if self.player_limit and len(self.members) == self.player_limit:
                raise PartyError(self._f('The party is full.', tc=tc))
            if member.party:
                raise PartyError(
                    self._f('The player is part of another party.', tc=tc))
            if not self.is_invited(member) and not member.is_staff():
                raise PartyError(
                    self._f('The player is not part of the party invite list.',
                            tc=tc))
            if member.area != self.area:
                raise PartyError(
                    self._f('The player is not in the same area as the party.',
                            tc=tc))

            self.members.add(member)
            self.invite_list.discard(member)
            member.party = self
    def fork_party(self, party, remainers, leavers):
        # Put a Brexit joke here
        # Jerm70: Because we needed to split the Brexit vote to stop the damn plebs from leaving

        party = self.get_party(party)  # Assert that it is a party
        common = remainers.intersection(leavers)
        if common:
            raise PartyError(
                'Invalid party split: {} would belong in two parties.'.format(
                    ', '.join([c.id for c in common])))
        orphaned = party.members - (remainers.union(leavers))
        if orphaned:
            raise PartyError(
                'Invalid party split: {} would be left out of the parties.'.
                format(', '.join([c.id for c in orphaned])))

        _, _, old_pl, old_leaders, _ = party.get_details()
        if not remainers or not leavers:
            raise PartyError(
                'Invalid party split: One of the new parties would be empty.')

        leave_creator = random.choice(tuple(leavers))

        leave_party = self.new_party(leave_creator,
                                     player_limit=old_pl,
                                     has_leader=(old_leaders is not None),
                                     add_creator=False)

        for member in leavers:
            party.remove_member(member)
            leave_party.add_invite(member)
            leave_party.add_member(member)
            if member in old_leaders:
                leave_party.add_leader(member)

        return leave_party
        def remove_member(self, member, tc=False):
            if not self.is_member(member):
                raise PartyError(
                    self._f('The player is not part of this party.', tc=tc))

            self.members.remove(member)
            self.leaders.discard(member)
            member.party = None

            # Check if empty party and if so, disband it
            if not self.members:
                self.server.party_manager.disband_party(self)
            # Otherwise, check if there are no more leaders left, and if so, choose a new one
            elif not self.leaders:
                new_leader = self.get_random_member()
                self.add_leader(new_leader)
    def move_party(self, party, initiator, new_area):
        ini_name = initiator.displayname  # Backup in case initiator's char changes.
        movers = self.check_move_party(party, initiator, new_area)
        moving, staying = movers[True], movers[False]

        if not moving and staying:
            raise PartyError('No one was able to move.')

        if moving and not staying:
            # Everyone moves case
            for (member, new_char) in moving.items():
                if member is initiator:
                    mes = 'You started moving your party.'
                else:
                    mes = '{} started moving your party.'.format(ini_name)
                member.send_ooc(mes)
                member.change_area(new_area,
                                   ignore_checks=True,
                                   change_to=new_char)

            party.area.remove_party(party)
            new_area.add_party(party)
            party.area = new_area
            party.check_lights()
            return

        # Some people move, some stay behind case
        """
        If initiator is not sneaking
        1. Visible who moved
        2. Visible who stayed as they were not allowed
        3. Sneaked who stayed
        Party ID is assigned to the formed party that contains initiator

        If initiator is sneaking
        1. Sneaked who moved
        2. Sneaked who stayed as they were not allowed
        3. Visible who stayed (keeps party ID)
        """
        split = list()
        split.append({c: i for c, i in moving.items()})  # Guaranteed non-empty
        split.append({c: i for c, i in staying.items() if c.is_visible})
        split.append({c: i for c, i in staying.items() if not c.is_visible})
        s = lambda x: set(split[x].keys())
        parties = [None, None, None
                   ]  # Store party divisions, assumes only parties[0] moves

        if initiator.is_visible:
            # Assumes parties[og_party_id] contains the initiator
            og_party_id = 0 if initiator in moving else 1

            # Note that split[og_party_id] is guaranteed to be non-empty
            # and so is split[1-og_party_id].union(split[2])
            # Just think about the cases og_party_id = 0 and og_party_id = 1 and
            # it will all make sense
            party_2 = self.fork_party(party, s(og_party_id),
                                      s(1 - og_party_id).union(s(2)))

            if split[1 - og_party_id] and split[2]:
                party_3 = self.fork_party(party_2, s(1 - og_party_id), s(2))
            else:
                party_3 = None

            parties[
                og_party_id] = party  # Convenient hack for parties[0] requirement!
            parties[1 - og_party_id] = party_2
            parties[2] = party_3

            # With this logic
            # *party holds s(og_party_id)
            # *party_2 holds s(1-og_party_id)
            # *party_3 holds s(2), which is members that are staying but not visible.

            for (member, new_char) in split[0].items():
                if initiator == member:
                    msg = 'You started moving your party.'
                else:
                    msg = '{} started moving your party.'.format(ini_name)
                member.send_ooc(msg)
                member.change_area(new_area,
                                   ignore_checks=True,
                                   change_to=new_char)
                if split[
                        1]:  # Announce split only if visible people were left behind.
                    member.send_ooc('Your party was split.')

            for (member, _) in split[1].items():
                if initiator == member:
                    msg = 'You started moving your party but you were unable to move.'
                else:
                    msg = (
                        '{} started moving your party but you were unable to move.'
                        .format(ini_name))
                member.send_ooc(msg)
                member.send_ooc('Your party was split.')

            for (member, _) in split[2].items():
                msg = 'Your party started moving so you decided to break away from them.'
                msg += (' The ones who were left behind formed a new party {}.'
                        .format(member.get_party().get_id()))
                member.send_ooc(msg)

        else:
            # Case initiator is sneaking
            # Assumes the original party ID stays for one of the resultant parties that stays
            og_party_id = 1 if split[1] else 2

            # Note that split[og_party_id] is guaranteed to be non-empty
            # and so is split[2-og_party_id].union(split[2])
            # Just think about the cases og_party_id = 0 and og_party_id = 1 and
            # it will all make sense

            party_2 = self.fork_party(party, s(og_party_id),
                                      s(3 - og_party_id).union(s(0)))
            if split[3 - og_party_id] and split[0]:
                party_3 = self.fork_party(party_2, s(0), s(3 - og_party_id))
            else:
                party_3 = None

            # With this logic
            # *party holds s(og_party_id)
            # *party_2 conveniently only holds s(0)
            # *party_3 holds s(3-og_party_id) if needed
            # Then party_2 is the party that moves, so satisfying that parties[0] moves is easy
            parties[0] = party_2
            parties[1] = party
            parties[2] = party_3

            for (member, new_char) in split[0].items():
                if initiator == member:
                    msg = 'You started moving the sneaked members of your party.'
                else:
                    msg = '{} started moving the sneaked members of your party.'.format(
                        ini_name)
                member.send_ooc(msg)
                member.change_area(new_area,
                                   ignore_checks=True,
                                   change_to=new_char)
                if split[2]:
                    member.send_ooc('Your sneaked party was split.')

            for (member, _) in split[1].items():
                # Deliberately empty, do not announce anything to these people
                pass

            for (member, _) in split[2].items():
                if initiator == member:
                    msg = (
                        'You started moving the sneaked members of your party but you were '
                        'unable to move.')
                else:
                    msg = (
                        '{} started moving the sneaked members of your party but you were '
                        'unable to move.'.format(ini_name))
                member.send_ooc(msg)
                member.send_ooc('Your sneaked party was split.')

        # parties[0] is the party that moved, so update its area status
        parties[0].area.remove_party(parties[0])
        new_area.add_party(parties[0])
        parties[0].area = new_area
        # For parties[0] (the party that moves), check light status
        # parties[1] and parties[2] did not move, so do not check their lights.
        parties[0].check_lights()

        # Announce staff members of the split
        end_parties = ", ".join(
            [str(p.get_id()) for p in parties if p is not None])
        initiator.send_ooc_others(
            "(X) {}'s party {} was split into these parties: {}".format(
                ini_name, parties[og_party_id].get_id(), end_parties),
            is_zstaff_flex=True)
 def get_parties(self):
     if not self.parties:
         raise PartyError('No parties exist.')
     return self.parties.values()
 def remove_invite(self, member, tc=False):
     if member not in self.invite_list:
         raise PartyError(
             self._f('The player is not in the party invite list.'))
     self.invite_list.discard(member)