Esempio n. 1
0
class Friend(object):
    def __init__(self, char_id):
        self.char_id = char_id
        self.char = Char(self.char_id)

        try:
            self.mf = MongoFriend.objects.get(id=self.char_id)
        except DoesNotExist:
            self.mf = MongoFriend(id=self.char_id)
            self.mf.friends = []
            self.mf.pending = []
            self.mf.accepting = []

            self.mf.plunder_gives = []
            self.mf.plunder_gots = []
            self.mf.plunder_senders = []
            self.mf.save()

    def is_friend(self, target_id):
        # 真正的好友,对方接受了我的好友申请的
        return int(target_id) in self.mf.friends

    def is_general_friend(self, target_id):
        # 广义好友,只要是自己或者对方发过好友申请的都算
        target_id = int(target_id)
        return target_id in self.mf.friends or target_id in self.mf.pending or target_id in self.mf.accepting

    @property
    def max_amount(self):
        return VIP_FUNCTION[self.char.mc.vip].friends

    @property
    def cur_amount(self):
        return len(self.mf.friends) + len(self.mf.pending)

    @property
    def real_cur_amount(self):
        return len(self.mf.friends)

    def candidate_list(self, level_diff=FRIEND_CANDIDATE_LEVEL_DIFF):
        # 候选人列表

        # 先从最近活跃用户找,如果没有再通找
        ap = ActivePlayers()
        ap_list = ap.get_list()

        random.shuffle(ap_list)

        char_ids = []
        for c in ap_list:
            if c == self.char_id:
                continue
            if self.is_general_friend(c):
                continue

            char_ids.append(c)
            if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
                break

        if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
            return char_ids

        # 数量不够,补充
        level = self.char.mc.level
        supply_char_ids = get_char_ids_by_level_range(
            level - level_diff,
            level + level_diff,
            exclude_char_ids=[self.char_id])

        for c in supply_char_ids:
            if self.is_general_friend(c):
                continue

            if c in char_ids:
                continue

            char_ids.append(c)
            if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
                break

        return char_ids

    def friends_list(self):
        """
        @return: All Friends List. Format: [(id, status), (id, status)...]
        @rtype: list
        """
        res = []
        for i in self.mf.friends[:]:
            # try:
            #     MongoCharacter.objects.get(id=i)
            # except DoesNotExist:
            #     self.mf.friends.remove(i)
            # else:
            #     res.append((i, FRIEND_OK))
            res.append((i, FRIEND_OK))

        for i in self.mf.pending[:]:
            # try:
            #     MongoCharacter.objects.get(id=i)
            # except DoesNotExist:
            #     self.mf.pending.remove(i)
            # else:
            #     res.append((i, FRIEND_ACK))
            res.append((i, FRIEND_ACK))

        for i in self.mf.accepting[:]:
            # try:
            #     MongoCharacter.objects.get(id=i)
            # except DoesNotExist:
            #     self.mf.accepting.remove(i)
            # else:
            #     res.append((i, FRIEND_APPLY))
            res.append((i, FRIEND_APPLY))

        # self.mf.save()
        return res

    def check_max_amount(self, func_name, raise_exception=True):
        if raise_exception:
            if self.real_cur_amount >= self.max_amount:
                if self.char.mc.vip < VIP_MAX_LEVEL:
                    raise SanguoException(
                        errormsg.FRIEND_FULL, self.char_id, func_name,
                        "friends full. vip current: {0}, max: {1}".format(
                            self.char.mc.vip, VIP_MAX_LEVEL))
                raise SanguoException(
                    errormsg.FRIEND_FULL_FINAL, self.char_id, func_name,
                    "friends full. vip reach max level {0}".format(
                        VIP_MAX_LEVEL))
        else:
            if self.real_cur_amount >= self.max_amount:
                return False
            return True

    def add(self, target_id=None, target_name=None):
        # 发出好友申请
        if not target_id and not target_name:
            raise SanguoException(errormsg.BAD_MESSAGE, self.char_id,
                                  "Friend Add",
                                  "no target_id and no target_name")

        if target_id:
            doc = MongoCharacter._get_collection().find_one({'_id': target_id},
                                                            {'_id': 1})
            if not doc:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND, self.char_id, "Friend Add",
                    "character id {0} not found".format(target_id))
        else:
            doc = MongoCharacter._get_collection().find_one(
                {'name': target_name}, {'_id': 1})
            if not doc:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND, self.char_id, "Friend Add",
                    u"can not found character {0} in server {1}".format(
                        target_name, self.char.mc.server_id))

        cid = doc['_id']

        if cid in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_ALREADY_ADD, self.char_id, "Friend Add",
                "character {0} already has beed added".format(cid))

        self.check_max_amount("Friend Add")

        if cid in self.mf.accepting:
            # 如果要加的好友以前已经给我发过好友申请,那么就是直接接受
            self.accept(cid)
            return

        if cid not in self.mf.pending:
            self.mf.pending.append(cid)
            self.send_new_friend_notify(cid, status=FRIEND_ACK)

        self.mf.save()

        target_char_friend = Friend(cid)
        target_char_friend.someone_add_me(self.char_id)

    def someone_add_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.friends or from_id in self.mf.accepting:
            return

        self.mf.accepting.append(from_id)
        self.mf.save()

        if from_id in self.mf.pending:
            # 对方要加我,但我也给要加对方,那就直接成为好友
            self.accept(from_id)
            return

        self.send_new_friend_notify(from_id, status=FRIEND_APPLY)

    def terminate(self, target_id):
        # 终止好友关系
        target_id = int(target_id)
        if target_id not in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_NOT_OK, self.char_id, "Friend Terminate",
                "character {0} is not friend".format(target_id))

        self.mf.friends.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_terminate_me(self.char_id)

        self.send_remove_friend_notify([target_id])
        self.send_friends_amount_notify()

    def someone_terminate_me(self, from_id):
        from_id = int(from_id)
        if from_id not in self.mf.friends:
            return

        self.mf.friends.remove(from_id)
        self.mf.save()

        self.send_remove_friend_notify([from_id])
        self.send_friends_amount_notify()

    def cancel(self, target_id):
        # 取消好友申请
        target_id = int(target_id)
        if target_id not in self.mf.pending:
            raise SanguoException(
                errormsg.FRIEND_NOT_ACK, self.char_id, "Friend Cancel",
                "not ack for character {0}".format(target_id))

        self.mf.pending.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_cancel_me(self.char_id)

        self.send_remove_friend_notify([target_id])
        # self.send_friends_amount_notify()

    def someone_cancel_me(self, from_id):
        if from_id not in self.mf.accepting:
            return

        self.mf.accepting.remove(from_id)
        self.mf.save()

        self.send_remove_friend_notify([from_id])
        # self.send_friends_amount_notify()

    def accept(self, target_id):
        # 接受对方的好友申请
        target_id = int(target_id)
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST, self.char_id,
                "Friend Accept",
                "character {0} not in accept list".format(target_id))

        def _clean_target_pending(target_id):
            target = Friend(target_id)
            if self.char_id in target.mf.pending:
                target.mf.pending.remove(self.char_id)
                target.mf.save()
                target.send_remove_friend_notify([self.char_id])

        target_char_friend = Friend(target_id)

        # 检查对方好友是否已满
        if not target_char_friend.check_max_amount("Friend Accept By Other",
                                                   raise_exception=False):
            # 对方满了
            # 并且删除此人的申请
            self.mf.accepting.remove(target_id)
            self.mf.save()
            self.send_remove_friend_notify([target_id])

            _clean_target_pending(target_id)

            raise SanguoException(
                errormsg.FRIEND_OTHER_SIDE_IS_FULL, self.char_id,
                "Friend Accept",
                "other side {0} firend is full".format(target_id))

        # 然后检查自己好友是否已满
        try:
            self.check_max_amount("Friend Accept")
        except SanguoException as e:
            # 满了就清空所有申请
            self.send_remove_friend_notify(self.mf.accepting)
            _accepting = self.mf.accepting
            self.mf.accepting = []
            self.mf.save()

            for _acc in _accepting:
                _clean_target_pending(_acc)
            raise e

        self.mf.accepting.remove(target_id)
        self.mf.friends.append(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_accept_me(self.char_id)

        new_friend_got_signal.send(sender=None,
                                   char_id=self.char_id,
                                   new_friend_id=target_id,
                                   total_friends_amount=self.real_cur_amount)

        self.send_update_friend_notify(target_id)
        self.send_friends_amount_notify()

    def someone_accept_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.pending:
            self.mf.pending.remove(from_id)
        if from_id not in self.mf.friends:
            self.mf.friends.append(from_id)
        self.mf.save()

        self.send_new_friend_notify(from_id)

        new_friend_got_signal.send(sender=None,
                                   char_id=self.char_id,
                                   new_friend_id=from_id,
                                   total_friends_amount=self.real_cur_amount)

        self.send_friends_amount_notify()

    def refuse(self, target_id):
        # 拒绝对方的好友申请
        target_id = int(target_id)
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST, self.char_id,
                "Friend Refuse",
                "character {0} not in accept list".format(target_id))

        self.mf.accepting.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_refuse_me(self.char_id)

        self.send_remove_friend_notify([target_id])
        self.send_friends_amount_notify()

    def someone_refuse_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.pending:
            self.mf.pending.remove(from_id)
        self.mf.save()

        from_char_name = get_char_property(from_id, 'name')

        mail = Mail(self.char_id)
        mail.add(MAIL_FRIEND_REFUSE_TITLE,
                 MAIL_FRIEND_REFUSE_CONTENT.format(from_char_name))

        self.send_remove_friend_notify([from_id])
        self.send_friends_amount_notify()

    def _msg_friend(self, msg, fid, status):
        msg.char.MergeFrom(create_character_infomation_message(fid))
        msg.status = status

        if status == FRIEND_OK and fid not in self.mf.plunder_gives:
            msg.can_give_plunder_times = True
        else:
            msg.can_give_plunder_times = False

        if status != FRIEND_OK:
            msg.got_plunder_times_status = MsgFriend.CAN_NOT_GET
        else:
            if fid in self.mf.plunder_senders:
                msg.got_plunder_times_status = MsgFriend.CAN_GET
            elif fid in self.mf.plunder_gots:
                msg.got_plunder_times_status = MsgFriend.ALREADY_GET
            else:
                msg.got_plunder_times_status = MsgFriend.CAN_NOT_GET

    def send_friends_amount_notify(self):
        msg = protomsg.FriendsAmountNotify()
        msg.max_amount = self.max_amount
        msg.cur_amount = self.real_cur_amount
        publish_to_char(self.char_id, pack_msg(msg))

    def send_friends_notify(self):
        msg = protomsg.FriendsNotify()
        for k, v in self.friends_list():
            f = msg.friends.add()
            self._msg_friend(f, k, v)

        publish_to_char(self.char_id, pack_msg(msg))

    def send_new_friend_notify(self, friend_id, status=FRIEND_OK):
        msg = protomsg.NewFriendNotify()
        self._msg_friend(msg.friend, friend_id, status)
        publish_to_char(self.char_id, pack_msg(msg))

    def send_update_friend_notify(self, friend_id, status=FRIEND_OK):
        msg = protomsg.UpdateFriendNotify()
        self._msg_friend(msg.friend, friend_id, status)
        publish_to_char(self.char_id, pack_msg(msg))

    def send_remove_friend_notify(self, friend_ids):
        for i in friend_ids:
            msg = protomsg.RemoveFriendNotify()
            msg.id = i
            publish_to_char(self.char_id, pack_msg(msg))

    # 行军令相关
    def give_plunder_times(self, target_id):
        target_id = int(target_id)
        if target_id not in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_GIVE_PLUNDER_TIMES_NOT_FRIEND, self.char_id,
                "Friend Give Plunder Times",
                "{0} has no friend {1}".format(self.char_id, target_id))

        if target_id in self.mf.plunder_gives:
            raise SanguoException(
                errormsg.FRIEND_GIVE_PLUNDER_TIMES_ALREADY_GIVE, self.char_id,
                "Friend Give Plunder Times",
                "{0} already give to {1}".format(self.char_id, target_id))

        self.mf.plunder_gives.append(target_id)
        self.mf.save()

        self.send_update_friend_notify(target_id)

        f = Friend(target_id)
        f.someone_give_me_plunder_times(self.char_id)

    def someone_give_me_plunder_times(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.plunder_senders:
            return

        self.mf.plunder_senders.append(from_id)
        self.mf.save()

        self.send_update_friend_notify(from_id)

    def get_plunder_times(self, sender_id):
        if sender_id not in self.mf.plunder_senders:
            raise SanguoException(
                errormsg.FRIEND_GET_PLUNDER_TIMES_NOT_EXIST, self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy {1} not give".format(
                    self.char_id, sender_id))

        if sender_id in self.mf.plunder_gots:
            raise SanguoException(
                errormsg.FRIEND_GET_PLUNDER_TIMES_ALREADY_GOT, self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy already got from {1}".
                format(self.char_id, sender_id))

        if len(self.mf.plunder_gots) >= self.max_amount:
            raise SanguoException(
                errormsg.PLUNDER_GET_TIMES_FULL, self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy already reach max friends amount"
                .format(self.char_id))

        self.mf.plunder_senders.remove(sender_id)
        self.mf.plunder_gots.append(sender_id)
        self.mf.save()

        p = Plunder(self.char_id)
        p.change_current_plunder_times(change_value=1, allow_overflow=True)

        self.send_update_friend_notify(sender_id)

    @staticmethod
    def cron_job():
        condition = {
            "$or": [{
                "plunder_gives.0": {
                    "$exists": True
                }
            }, {
                "plunder_gots.0": {
                    "$exists": True
                }
            }]
        }

        friends = MongoFriend._get_collection().find(condition, {'_id': 1})

        updater = {'plunder_gives': [], 'plunder_gots': []}

        MongoFriend._get_collection().update({}, {'$set': updater}, multi=True)

        for f in friends:
            Friend(f['_id']).send_friends_notify()
Esempio n. 2
0
class Friend(object):
    def __init__(self, char_id):
        self.char_id = char_id
        self.char = Char(self.char_id)

        try:
            self.mf = MongoFriend.objects.get(id=self.char_id)
        except DoesNotExist:
            self.mf = MongoFriend(id=self.char_id)
            self.mf.friends = []
            self.mf.pending = []
            self.mf.accepting = []

            self.mf.plunder_gives = []
            self.mf.plunder_gots = []
            self.mf.plunder_senders = []
            self.mf.save()

    def is_friend(self, target_id):
        # 真正的好友,对方接受了我的好友申请的
        return int(target_id) in self.mf.friends

    def is_general_friend(self, target_id):
        # 广义好友,只要是自己或者对方发过好友申请的都算
        target_id = int(target_id)
        return target_id in self.mf.friends or target_id in self.mf.pending or target_id in self.mf.accepting

    @property
    def max_amount(self):
        return VIP_FUNCTION[self.char.mc.vip].friends

    @property
    def cur_amount(self):
        return len(self.mf.friends) + len(self.mf.pending)

    @property
    def real_cur_amount(self):
        return len(self.mf.friends)

    def candidate_list(self, level_diff=FRIEND_CANDIDATE_LEVEL_DIFF):
        # 候选人列表

        # 先从最近活跃用户找,如果没有再通找
        ap = ActivePlayers()
        ap_list = ap.get_list()

        random.shuffle(ap_list)

        char_ids = []
        for c in ap_list:
            if c == self.char_id:
                continue
            if self.is_general_friend(c):
                continue

            char_ids.append(c)
            if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
                break

        if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
            return char_ids

        # 数量不够,补充
        level = self.char.mc.level
        supply_char_ids = get_char_ids_by_level_range(
            level - level_diff,
            level + level_diff,
            exclude_char_ids=[self.char_id])

        for c in supply_char_ids:
            if self.is_general_friend(c):
                continue

            if c in char_ids:
                continue

            char_ids.append(c)
            if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
                break

        return char_ids

    def friends_list(self):
        """
        @return: All Friends List. Format: [(id, status), (id, status)...]
        @rtype: list
        """
        res = []
        for i in self.mf.friends:
            res.append((i, FRIEND_OK))

        for i in self.mf.pending:
            res.append((i, FRIEND_ACK))

        for i in self.mf.accepting:
            res.append((i, FRIEND_APPLY))

        return res

    def check_max_amount(self, func_name):
        if self.cur_amount >= self.max_amount:
            if self.char.mc.vip < VIP_MAX_LEVEL:
                raise SanguoException(
                    errormsg.FRIEND_FULL, self.char_id, func_name,
                    "friends full. vip current: {0}, max: {1}".format(
                        self.char.mc.vip, VIP_MAX_LEVEL))
            raise SanguoException(
                errormsg.FRIEND_FULL_FINAL, self.char_id, func_name,
                "friends full. vip reach max level {0}".format(VIP_MAX_LEVEL))

    def add(self, target_id=None, target_name=None):
        # 发出好友申请
        if not target_id and not target_name:
            raise SanguoException(errormsg.BAD_MESSAGE, self.char_id,
                                  "Friend Add",
                                  "no target_id and no target_name")

        self.check_max_amount("Friend Add")

        if target_id:
            try:
                c = MongoCharacter.objects.get(id=target_id)
            except DoesNotExist:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND, self.char_id, "Friend Add",
                    "character id {0} not found".format(target_id))
        else:
            try:
                c = MongoCharacter.objects.get(name=target_name)
            except DoesNotExist:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND, self.char_id, "Friend Add",
                    u"can not found character {0} in server {1}".format(
                        target_name, self.char.mc.server_id))

        if c.id in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_ALREADY_ADD, self.char_id, "Friend Add",
                "character {0} already has beed added".format(c.id))

        if c.id in self.mf.pending:
            return

        if c.id in self.mf.accepting:
            # 如果要加的好友以前已经给我发过好友申请,那么就是直接接受
            self.accept(c.id)
            return

        self.mf.pending.append(c.id)
        self.mf.save()

        target_char_friend = Friend(c.id)
        target_char_friend.someone_add_me(self.char_id)

        # notify
        msg = protomsg.NewFriendNotify()
        self._msg_friend(msg.friend, c.id, FRIEND_ACK)
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()

    def someone_add_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.accepting or from_id in self.mf.pending or from_id in self.mf.accepting:
            return

        self.mf.accepting.append(from_id)
        self.mf.save()

        msg = protomsg.NewFriendNotify()
        self._msg_friend(msg.friend, from_id, FRIEND_APPLY)
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()

    def terminate(self, target_id):
        # 终止好友关系
        target_id = int(target_id)
        if target_id not in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_NOT_OK, self.char_id, "Friend Terminate",
                "character {0} is not friend".format(target_id))

        self.mf.friends.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_terminate_me(self.char_id)

        msg = protomsg.RemoveFriendNotify()
        msg.id = target_id
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()

    def someone_terminate_me(self, from_id):
        from_id = int(from_id)
        if from_id not in self.mf.friends:
            return

        self.mf.friends.remove(from_id)
        self.mf.save()

        msg = protomsg.RemoveFriendNotify()
        msg.id = from_id
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()

    def cancel(self, target_id):
        # 取消好友申请
        target_id = int(target_id)
        if target_id not in self.mf.pending:
            raise SanguoException(
                errormsg.FRIEND_NOT_ACK, self.char_id, "Friend Cancel",
                "not ack for character {0}".format(target_id))

        self.mf.pending.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_cancel_me(self.char_id)

        msg = protomsg.RemoveFriendNotify()
        msg.id = target_id
        publish_to_char(self.char_id, pack_msg(msg))
        self.send_friends_amount_notify()

    def someone_cancel_me(self, from_id):
        if from_id not in self.mf.accepting:
            return

        self.mf.accepting.remove(from_id)
        self.mf.save()

        msg = protomsg.RemoveFriendNotify()
        msg.id = from_id
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()

    def accept(self, target_id):
        # 接受对方的好友申请
        target_id = int(target_id)
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST, self.char_id,
                "Friend Accept",
                "character {0} not in accept list".format(target_id))

        self.check_max_amount("Friend Accept")

        self.mf.accepting.remove(target_id)
        self.mf.friends.append(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_accept_me(self.char_id)

        self.send_update_friend_notify(target_id)

        new_friend_got_signal.send(sender=None,
                                   char_id=self.char_id,
                                   new_friend_id=target_id,
                                   total_friends_amount=self.real_cur_amount)

        self.send_friends_amount_notify()

    def someone_accept_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.pending:
            self.mf.pending.remove(from_id)
        if from_id not in self.mf.friends:
            self.mf.friends.append(from_id)
        self.mf.save()

        self.send_update_friend_notify(from_id)

        new_friend_got_signal.send(sender=None,
                                   char_id=self.char_id,
                                   new_friend_id=from_id,
                                   total_friends_amount=self.real_cur_amount)

        self.send_friends_amount_notify()

    def refuse(self, target_id):
        # 拒绝对方的好友申请
        target_id = int(target_id)
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST, self.char_id,
                "Friend Refuse",
                "character {0} not in accept list".format(target_id))

        self.mf.accepting.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_refuse_me(self.char_id)

        msg = protomsg.RemoveFriendNotify()
        msg.id = target_id
        publish_to_char(self.char_id, pack_msg(msg))
        self.send_friends_amount_notify()

    def someone_refuse_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.pending:
            self.mf.pending.remove(from_id)
        self.mf.save()

        msg = protomsg.RemoveFriendNotify()
        msg.id = from_id
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()

    def _msg_friend(self, msg, fid, status):
        char_f = Char(fid)
        cache_f = char_f.cacheobj
        msg.id = fid
        msg.name = cache_f.name
        msg.level = cache_f.level
        msg.official = cache_f.official
        if status == FRIEND_OK or status == FRIEND_NOT:
            msg.power = char_f.power

        msg.status = status

        f = Formation(fid)
        in_formation_hero_ids = [h for h in f.in_formation_hero_ids() if h]
        hero_list = [(Hero.cache_obj(hid).power, hid)
                     for hid in in_formation_hero_ids]
        hero_list.sort(key=lambda item: -item[0])
        leader_oid = Hero.cache_obj(hero_list[0][1]).oid

        if status == FRIEND_OK:
            f = Formation(fid)
            msg.formation.extend(f.in_formation_hero_original_ids())

        msg.leader = leader_oid

        if status == FRIEND_OK and fid not in self.mf.plunder_gives:
            msg.can_give_plunder_times = True
        else:
            msg.can_give_plunder_times = False

        if fid in self.mf.plunder_senders:
            msg.got_plunder_times_status = MsgFriend.CAN_GET
        elif fid in self.mf.plunder_gots:
            msg.got_plunder_times_status = MsgFriend.ALREADY_GET
        else:
            msg.got_plunder_times_status = MsgFriend.CAN_NOT_GET

    def send_friends_amount_notify(self):
        msg = protomsg.FriendsAmountNotify()
        msg.max_amount = self.max_amount
        msg.cur_amount = self.cur_amount
        publish_to_char(self.char_id, pack_msg(msg))

    def send_friends_notify(self):
        msg = protomsg.FriendsNotify()
        for k, v in self.friends_list():
            f = msg.friends.add()
            self._msg_friend(f, k, v)

        publish_to_char(self.char_id, pack_msg(msg))

    def send_update_friend_notify(self, friend_id, status=FRIEND_OK):
        msg = protomsg.UpdateFriendNotify()
        self._msg_friend(msg.friend, friend_id, status)
        publish_to_char(self.char_id, pack_msg(msg))

    # 行军令相关
    def give_plunder_times(self, target_id):
        target_id = int(target_id)
        if target_id not in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_GIVE_PLUNDER_TIMES_NOT_FRIEND, self.char_id,
                "Friend Give Plunder Times",
                "{0} has no friend {1}".format(self.char_id, target_id))

        if target_id in self.mf.plunder_gives:
            raise SanguoException(
                errormsg.FRIEND_GIVE_PLUNDER_TIMES_ALREADY_GIVE, self.char_id,
                "Friend Give Plunder Times",
                "{0} already give to {1}".format(self.char_id, target_id))

        self.mf.plunder_gives.append(target_id)
        self.mf.save()

        self.send_update_friend_notify(target_id)

        f = Friend(target_id)
        f.someone_give_me_plunder_times(self.char_id)

    def someone_give_me_plunder_times(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.plunder_senders:
            return

        self.mf.plunder_senders.append(from_id)
        self.mf.save()

        self.send_update_friend_notify(from_id)

    def get_plunder_times(self, sender_id):
        if sender_id not in self.mf.plunder_senders:
            raise SanguoException(
                errormsg.FRIEND_GET_PLUNDER_TIMES_NOT_EXIST, self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy {1} not give".format(
                    self.char_id, sender_id))

        if sender_id in self.mf.plunder_gots:
            raise SanguoException(
                errormsg.FRIEND_GET_PLUNDER_TIMES_ALREADY_GOT, self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy already got from {1}".
                format(self.char_id, sender_id))

        self.mf.plunder_senders.remove(sender_id)
        self.mf.plunder_gots.append(sender_id)
        self.mf.save()

        p = Plunder(self.char_id)
        p.change_current_plunder_times(change_value=1, allow_overflow=True)

        self.send_update_friend_notify(sender_id)

    def daily_plunder_times_reset(self):
        self.mf.plunder_gives = []
        self.mf.plunder_gots = []
        self.mf.save()
        self.send_friends_notify()
Esempio n. 3
0
class Friend(object):
    def __init__(self, char_id):
        self.char_id = char_id
        self.char = Char(self.char_id)

        try:
            self.mf = MongoFriend.objects.get(id=self.char_id)
        except DoesNotExist:
            self.mf = MongoFriend(id=self.char_id)
            self.mf.friends = []
            self.mf.pending = []
            self.mf.accepting = []

            self.mf.plunder_gives = []
            self.mf.plunder_gots = []
            self.mf.plunder_senders = []
            self.mf.save()

    def is_friend(self, target_id):
        # 真正的好友,对方接受了我的好友申请的
        return int(target_id) in self.mf.friends

    def is_general_friend(self, target_id):
        # 广义好友,只要是自己或者对方发过好友申请的都算
        target_id = int(target_id)
        return target_id in self.mf.friends or target_id in self.mf.pending or target_id in self.mf.accepting


    @property
    def max_amount(self):
        return VIP_FUNCTION[self.char.mc.vip].friends

    @property
    def cur_amount(self):
        return len(self.mf.friends) + len(self.mf.pending)

    @property
    def real_cur_amount(self):
        return len(self.mf.friends)


    def candidate_list(self, level_diff=FRIEND_CANDIDATE_LEVEL_DIFF):
        # 候选人列表

        # 先从最近活跃用户找,如果没有再通找
        ap = ActivePlayers()
        ap_list = ap.get_list()

        random.shuffle(ap_list)

        char_ids = []
        for c in ap_list:
            if c == self.char_id:
                continue
            if self.is_general_friend(c):
                continue

            char_ids.append(c)
            if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
                break

        if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
            return char_ids


        # 数量不够,补充
        level = self.char.mc.level
        supply_char_ids = get_char_ids_by_level_range(level-level_diff, level+level_diff, exclude_char_ids=[self.char_id])

        for c in supply_char_ids:
            if self.is_general_friend(c):
                continue

            if c in char_ids:
                continue

            char_ids.append(c)
            if len(char_ids) >= FRIEND_CANDIDATE_LIST_AMOUNT:
                break

        return char_ids


    def friends_list(self):
        """
        @return: All Friends List. Format: [(id, status), (id, status)...]
        @rtype: list
        """
        res = []
        for i in self.mf.friends[:]:
            # try:
            #     MongoCharacter.objects.get(id=i)
            # except DoesNotExist:
            #     self.mf.friends.remove(i)
            # else:
            #     res.append((i, FRIEND_OK))
            res.append((i, FRIEND_OK))

        for i in self.mf.pending[:]:
            # try:
            #     MongoCharacter.objects.get(id=i)
            # except DoesNotExist:
            #     self.mf.pending.remove(i)
            # else:
            #     res.append((i, FRIEND_ACK))
            res.append((i, FRIEND_ACK))

        for i in self.mf.accepting[:]:
            # try:
            #     MongoCharacter.objects.get(id=i)
            # except DoesNotExist:
            #     self.mf.accepting.remove(i)
            # else:
            #     res.append((i, FRIEND_APPLY))
            res.append((i, FRIEND_APPLY))

        # self.mf.save()
        return res

    def check_max_amount(self, func_name, raise_exception=True):
        if raise_exception:
            if self.real_cur_amount >= self.max_amount:
                if self.char.mc.vip < VIP_MAX_LEVEL:
                    raise SanguoException(
                        errormsg.FRIEND_FULL,
                        self.char_id,
                        func_name,
                        "friends full. vip current: {0}, max: {1}".format(self.char.mc.vip, VIP_MAX_LEVEL)
                    )
                raise SanguoException(
                    errormsg.FRIEND_FULL_FINAL,
                    self.char_id,
                    func_name,
                    "friends full. vip reach max level {0}".format(VIP_MAX_LEVEL)
                )
        else:
            if self.real_cur_amount >= self.max_amount:
                return False
            return True



    def add(self, target_id=None, target_name=None):
        # 发出好友申请
        if not target_id and not target_name:
            raise SanguoException(
                errormsg.BAD_MESSAGE,
                self.char_id,
                "Friend Add",
                "no target_id and no target_name"
            )

        if target_id:
            doc = MongoCharacter._get_collection().find_one({'_id': target_id}, {'_id': 1})
            if not doc:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND,
                    self.char_id,
                    "Friend Add",
                    "character id {0} not found".format(target_id)
                )
        else:
            doc = MongoCharacter._get_collection().find_one({'name': target_name}, {'_id': 1})
            if not doc:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND,
                    self.char_id,
                    "Friend Add",
                    u"can not found character {0} in server {1}".format(target_name, self.char.mc.server_id)
                )

        cid = doc['_id']

        if cid in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_ALREADY_ADD,
                self.char_id,
                "Friend Add",
                "character {0} already has beed added".format(cid)
            )

        self.check_max_amount("Friend Add")

        if cid in self.mf.accepting:
            # 如果要加的好友以前已经给我发过好友申请,那么就是直接接受
            self.accept(cid)
            return

        if cid not in self.mf.pending:
            self.mf.pending.append(cid)
            self.send_new_friend_notify(cid, status=FRIEND_ACK)

        self.mf.save()

        target_char_friend = Friend(cid)
        target_char_friend.someone_add_me(self.char_id)



    def someone_add_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.friends or from_id in self.mf.accepting:
            return

        self.mf.accepting.append(from_id)
        self.mf.save()

        if from_id in self.mf.pending:
            # 对方要加我,但我也给要加对方,那就直接成为好友
            self.accept(from_id)
            return

        self.send_new_friend_notify(from_id, status=FRIEND_APPLY)


    def terminate(self, target_id):
        # 终止好友关系
        target_id = int(target_id)
        if target_id not in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_NOT_OK,
                self.char_id,
                "Friend Terminate",
                "character {0} is not friend".format(target_id)
            )

        self.mf.friends.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_terminate_me(self.char_id)

        self.send_remove_friend_notify([target_id])
        self.send_friends_amount_notify()


    def someone_terminate_me(self, from_id):
        from_id = int(from_id)
        if from_id not in self.mf.friends:
            return

        self.mf.friends.remove(from_id)
        self.mf.save()

        self.send_remove_friend_notify([from_id])
        self.send_friends_amount_notify()


    def cancel(self, target_id):
        # 取消好友申请
        target_id = int(target_id)
        if target_id not in self.mf.pending:
            raise SanguoException(
                errormsg.FRIEND_NOT_ACK,
                self.char_id,
                "Friend Cancel",
                "not ack for character {0}".format(target_id)
            )

        self.mf.pending.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_cancel_me(self.char_id)

        self.send_remove_friend_notify([target_id])
        # self.send_friends_amount_notify()

    def someone_cancel_me(self, from_id):
        if from_id not in self.mf.accepting:
            return

        self.mf.accepting.remove(from_id)
        self.mf.save()

        self.send_remove_friend_notify([from_id])
        # self.send_friends_amount_notify()


    def accept(self, target_id):
        # 接受对方的好友申请
        target_id = int(target_id)
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST,
                self.char_id,
                "Friend Accept",
                "character {0} not in accept list".format(target_id)
            )


        def _clean_target_pending(target_id):
            target = Friend(target_id)
            if self.char_id in target.mf.pending:
                target.mf.pending.remove(self.char_id)
                target.mf.save()
                target.send_remove_friend_notify([self.char_id])

        target_char_friend = Friend(target_id)

        # 检查对方好友是否已满
        if not target_char_friend.check_max_amount("Friend Accept By Other", raise_exception=False):
            # 对方满了
            # 并且删除此人的申请
            self.mf.accepting.remove(target_id)
            self.mf.save()
            self.send_remove_friend_notify([target_id])

            _clean_target_pending(target_id)

            raise SanguoException(
                errormsg.FRIEND_OTHER_SIDE_IS_FULL,
                self.char_id,
                "Friend Accept",
                "other side {0} firend is full".format(target_id)
            )


        # 然后检查自己好友是否已满
        try:
            self.check_max_amount("Friend Accept")
        except SanguoException as e:
            # 满了就清空所有申请
            self.send_remove_friend_notify(self.mf.accepting)
            _accepting = self.mf.accepting
            self.mf.accepting = []
            self.mf.save()

            for _acc in _accepting:
                _clean_target_pending(_acc)
            raise e

        self.mf.accepting.remove(target_id)
        self.mf.friends.append(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_accept_me(self.char_id)


        new_friend_got_signal.send(
            sender=None,
            char_id=self.char_id,
            new_friend_id=target_id,
            total_friends_amount=self.real_cur_amount
        )

        self.send_update_friend_notify(target_id)
        self.send_friends_amount_notify()



    def someone_accept_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.pending:
            self.mf.pending.remove(from_id)
        if from_id not in self.mf.friends:
            self.mf.friends.append(from_id)
        self.mf.save()

        self.send_new_friend_notify(from_id)

        new_friend_got_signal.send(
            sender=None,
            char_id=self.char_id,
            new_friend_id=from_id,
            total_friends_amount=self.real_cur_amount
        )

        self.send_friends_amount_notify()


    def refuse(self, target_id):
        # 拒绝对方的好友申请
        target_id = int(target_id)
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST,
                self.char_id,
                "Friend Refuse",
                "character {0} not in accept list".format(target_id)
            )

        self.mf.accepting.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_refuse_me(self.char_id)

        self.send_remove_friend_notify([target_id])
        self.send_friends_amount_notify()


    def someone_refuse_me(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.pending:
            self.mf.pending.remove(from_id)
        self.mf.save()

        from_char_name = get_char_property(from_id, 'name')

        mail = Mail(self.char_id)
        mail.add(
            MAIL_FRIEND_REFUSE_TITLE,
            MAIL_FRIEND_REFUSE_CONTENT.format(from_char_name)
        )

        self.send_remove_friend_notify([from_id])
        self.send_friends_amount_notify()


    def _msg_friend(self, msg, fid, status):
        msg.char.MergeFrom(create_character_infomation_message(fid))
        msg.status = status

        if status == FRIEND_OK and fid not in self.mf.plunder_gives:
            msg.can_give_plunder_times = True
        else:
            msg.can_give_plunder_times = False


        if status != FRIEND_OK:
            msg.got_plunder_times_status = MsgFriend.CAN_NOT_GET
        else:
            if fid in self.mf.plunder_senders:
                msg.got_plunder_times_status = MsgFriend.CAN_GET
            elif fid in self.mf.plunder_gots:
                msg.got_plunder_times_status = MsgFriend.ALREADY_GET
            else:
                msg.got_plunder_times_status = MsgFriend.CAN_NOT_GET



    def send_friends_amount_notify(self):
        msg = protomsg.FriendsAmountNotify()
        msg.max_amount = self.max_amount
        msg.cur_amount = self.real_cur_amount
        publish_to_char(self.char_id, pack_msg(msg))


    def send_friends_notify(self):
        msg = protomsg.FriendsNotify()
        for k, v in self.friends_list():
            f = msg.friends.add()
            self._msg_friend(f, k, v)

        publish_to_char(self.char_id, pack_msg(msg))


    def send_new_friend_notify(self, friend_id, status=FRIEND_OK):
        msg = protomsg.NewFriendNotify()
        self._msg_friend(msg.friend, friend_id, status)
        publish_to_char(self.char_id, pack_msg(msg))

    def send_update_friend_notify(self, friend_id, status=FRIEND_OK):
        msg = protomsg.UpdateFriendNotify()
        self._msg_friend(msg.friend, friend_id, status)
        publish_to_char(self.char_id, pack_msg(msg))

    def send_remove_friend_notify(self, friend_ids):
        for i in friend_ids:
            msg = protomsg.RemoveFriendNotify()
            msg.id = i
            publish_to_char(self.char_id, pack_msg(msg))


    # 行军令相关
    def give_plunder_times(self, target_id):
        target_id = int(target_id)
        if target_id not in self.mf.friends:
            raise SanguoException(
                errormsg.FRIEND_GIVE_PLUNDER_TIMES_NOT_FRIEND,
                self.char_id,
                "Friend Give Plunder Times",
                "{0} has no friend {1}".format(self.char_id, target_id)
            )

        if target_id in self.mf.plunder_gives:
            raise SanguoException(
                errormsg.FRIEND_GIVE_PLUNDER_TIMES_ALREADY_GIVE,
                self.char_id,
                "Friend Give Plunder Times",
                "{0} already give to {1}".format(self.char_id, target_id)
            )

        self.mf.plunder_gives.append(target_id)
        self.mf.save()

        self.send_update_friend_notify(target_id)

        f = Friend(target_id)
        f.someone_give_me_plunder_times(self.char_id)


    def someone_give_me_plunder_times(self, from_id):
        from_id = int(from_id)
        if from_id in self.mf.plunder_senders:
            return

        self.mf.plunder_senders.append(from_id)
        self.mf.save()

        self.send_update_friend_notify(from_id)

    def get_plunder_times(self, sender_id):
        if sender_id not in self.mf.plunder_senders:
            raise SanguoException(
                errormsg.FRIEND_GET_PLUNDER_TIMES_NOT_EXIST,
                self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy {1} not give".format(self.char_id, sender_id)
            )

        if sender_id in self.mf.plunder_gots:
            raise SanguoException(
                errormsg.FRIEND_GET_PLUNDER_TIMES_ALREADY_GOT,
                self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy already got from {1}".format(self.char_id, sender_id)
            )

        if len(self.mf.plunder_gots) >= self.max_amount:
            raise SanguoException(
                errormsg.PLUNDER_GET_TIMES_FULL,
                self.char_id,
                "Friend Get Plunder Times",
                "{0} try to get plunder times, buy already reach max friends amount".format(self.char_id)
            )


        self.mf.plunder_senders.remove(sender_id)
        self.mf.plunder_gots.append(sender_id)
        self.mf.save()

        p = Plunder(self.char_id)
        p.change_current_plunder_times(change_value=1, allow_overflow=True)

        self.send_update_friend_notify(sender_id)


    @staticmethod
    def cron_job():
        condition = {"$or": [
            {"plunder_gives.0": {"$exists": True}},
            {"plunder_gots.0": {"$exists": True}}
        ]}

        friends = MongoFriend._get_collection().find(condition, {'_id': 1})

        updater = {
            'plunder_gives': [],
            'plunder_gots': []
        }

        MongoFriend._get_collection().update({}, {'$set': updater}, multi=True)

        for f in friends:
            Friend(f['_id']).send_friends_notify()
Esempio n. 4
0
class Friend(object):
    def __init__(self, char_id):
        self.char_id = char_id
        self.char = Char(self.char_id)

        try:
            self.mf = MongoFriend.objects.get(id=self.char_id)
        except DoesNotExist:
            self.mf = MongoFriend(id=self.char_id)
            self.mf.friends = {}
            self.mf.accepting = []
            self.mf.save()

    def is_friend(self, target_id):
        # 真正的好友,对方接受了我的好友申请的
        t = str(target_id)
        return t in self.mf.friends and self.mf.friends[t] == FRIEND_OK

    def is_general_friend(self, target_id):
        # 广义好友,只要是自己或者对方发过好友申请的都算
        return str(target_id) in self.mf.friends or target_id in self.mf.accepting


    @property
    def max_amount(self):
        return VIP_FUNCTION[self.char.mc.vip].friends

    @property
    def cur_amount(self):
        fs = self.mf.friends
        return len(fs)

    @property
    def real_cur_amount(self):
        fs = self.mf.friends.values()
        amount = 0
        for f in fs:
            if f == FRIEND_OK:
                amount += 1
        return amount


    def candidate_list(self, level_diff=FRIEND_CANDIDATE_LEVEL_DIFF):
        # 候选人列表
        level = self.char.mc.level
        char_ids = get_char_ids_by_level_range(self.char.mc.server_id, level-level_diff, level+level_diff)

        res = []
        for c in char_ids:
            if c == self.char_id:
                continue
            if self.is_general_friend(c):
                continue

            res.append(c)
            if len(res) >= 5:
                break

        return res

    def friends_list(self):
        """
        @return: All Friends List. Format: [(id, status), (id, status)...]
        @rtype: list
        """
        res = []
        fs = self.mf.friends
        for k, v in fs.iteritems():
            res.append((int(k), v))

        for i in self.mf.accepting:
            res.append((i, FRIEND_APPLY))

        return res

    def check_max_amount(self, func_name):
        if self.cur_amount >= self.max_amount:
            if self.char.mc.vip < VIP_MAX_LEVEL:
                raise SanguoException(
                    errormsg.FRIEND_FULL,
                    self.char_id,
                    func_name,
                    "friends full. vip current: {0}, max: {1}".format(self.char.mc.vip, VIP_MAX_LEVEL)
                )
            raise SanguoException(
                errormsg.FRIEND_FULL_FINAL,
                self.char_id,
                func_name,
                "friends full. vip reach max level {0}".format(VIP_MAX_LEVEL)
            )



    def add(self, target_id=None, target_name=None):
        if not target_id and not target_name:
            raise SanguoException(
                errormsg.BAD_MESSAGE,
                self.char_id,
                "Friend Add",
                "no target_id and no target_name"
            )

        self.check_max_amount("Friend Add")

        if target_id:
            try:
                c = MongoCharacter.objects.get(id=target_id)
            except DoesNotExist:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND,
                    self.char_id,
                    "Friend Add",
                    "character id {0} not found".format(target_id)
                )
        else:
            try:
                c = MongoCharacter.objects.get(name=target_name, server_id=self.char.mc.server_id)
            except DoesNotExist:
                raise SanguoException(
                    errormsg.CHARACTER_NOT_FOUND,
                    self.char_id,
                    "Friend Add",
                    u"can not found character {0} in server {1}".format(target_name, self.char.mc.server_id)
                )

        if str(c.id) in self.mf.friends:
            if self.mf.friends[str(c.id)] == FRIEND_OK:
                raise SanguoException(
                    errormsg.FRIEND_ALREADY_ADD,
                    self.char_id,
                    "Friend Add",
                    "character {0} already has beed added".format(c.id)
                )
            return

        self.mf.friends[str(c.id)] = FRIEND_ACK
        self.mf.save()

        target_char_friend = Friend(c.id)
        target_char_friend.someone_add_me(self.char_id)

        # notify
        msg = protomsg.NewFriendNotify()
        self._msg_friend(msg.friend, c.id, FRIEND_ACK)
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()


    def someone_add_me(self, from_id):
        if from_id in self.mf.accepting or str(from_id) in self.mf.friends:
            return

        self.mf.accepting.append(from_id)
        self.mf.save()

        msg = protomsg.NewFriendNotify()
        self._msg_friend(msg.friend, from_id, FRIEND_APPLY)
        publish_to_char(self.char_id, pack_msg(msg))


    def terminate(self, target_id):
        t = str(target_id)
        if t not in self.mf.friends or self.mf.friends[t] != FRIEND_OK:
            raise SanguoException(
                errormsg.FRIEND_NOT_OK,
                self.char_id,
                "Friend Terminate",
                "character {0} is not friend".format(target_id)
            )

        self.mf.friends.pop(t)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_terminate_me(self.char_id)


        msg = protomsg.RemoveFriendNotify()
        msg.id = target_id
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()


    def someone_terminate_me(self, from_id):
        t = str(from_id)
        if t not in self.mf.friends:
            return

        self.mf.friends.pop(t)
        self.mf.save()

        msg = protomsg.RemoveFriendNotify()
        msg.id = from_id
        publish_to_char(self.char_id, pack_msg(msg))

        self.send_friends_amount_notify()


    def cancel(self, target_id):
        t = str(target_id)
        if t not in self.mf.friends or self.mf.friends[t] != FRIEND_ACK:
            raise SanguoException(
                errormsg.FRIEND_NOT_ACK,
                self.char_id,
                "Friend Cancel",
                "not ack for character {0}".format(target_id)
            )

        self.mf.friends.pop(t)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_cancel_me(self.char_id)

        msg = protomsg.RemoveFriendNotify()
        msg.id = target_id
        publish_to_char(self.char_id, pack_msg(msg))
        self.send_friends_amount_notify()

    def someone_cancel_me(self, from_id):
        if from_id not in self.mf.accepting:
            return

        self.mf.accepting.remove(from_id)
        self.mf.save()

        msg = protomsg.RemoveFriendNotify()
        msg.id = from_id
        publish_to_char(self.char_id, pack_msg(msg))


    def accept(self, target_id):
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST,
                self.char_id,
                "Friend Accept",
                "character {0} not in accept list".format(target_id)
            )

        self.check_max_amount("Friend Accept")

        self.mf.accepting.remove(target_id)
        self.mf.friends[str(target_id)] = FRIEND_OK

        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_accept_me(self.char_id)

        msg = protomsg.UpdateFriendNotify()
        self._msg_friend(msg.friend, target_id, FRIEND_OK)
        publish_to_char(self.char_id, pack_msg(msg))


    def someone_accept_me(self, from_id):
        self.mf.friends[str(from_id)] = FRIEND_OK
        self.mf.save()

        achievement = Achievement(self.char_id)
        achievement.trig(27, self.real_cur_amount)

        msg = protomsg.UpdateFriendNotify()
        self._msg_friend(msg.friend, from_id, FRIEND_OK)
        publish_to_char(self.char_id, pack_msg(msg))


    def refuse(self, target_id):
        if target_id not in self.mf.accepting:
            raise SanguoException(
                errormsg.FRIEND_NOT_IN_ACCEPT_LIST,
                self.char_id,
                "Friend Refuse",
                "character {0} not in accept list".format(target_id)
            )

        self.mf.accepting.remove(target_id)
        self.mf.save()

        target_char_friend = Friend(target_id)
        target_char_friend.someone_refuse_me(self.char_id)

        msg = protomsg.RemoveFriendNotify()
        msg.id = target_id
        publish_to_char(self.char_id, pack_msg(msg))


    def someone_refuse_me(self, from_id):
        self.mf.friends.pop(str(from_id))
        self.mf.save()

        msg = protomsg.RemoveFriendNotify()
        msg.id = from_id
        publish_to_char(self.char_id, pack_msg(msg))


    def _msg_friend(self, msg, fid, status):
        char_f = Char(fid)
        cache_f = char_f.cacheobj
        msg.id = fid
        msg.name = cache_f.name
        msg.level = cache_f.level
        msg.official = cache_f.official
        if status == FRIEND_OK or status == FRIEND_NOT:
            msg.power = char_f.power

        msg.status = status

        f = Formation(fid)
        in_formation_hero_ids = [h for h in f.in_formation_hero_ids() if h]
        hero_list = [(Hero.cache_obj(hid).power, hid) for hid in in_formation_hero_ids]
        hero_list.sort(key=lambda item: -item[0])
        leader_oid = Hero.cache_obj(hero_list[0][1]).oid

        if status == FRIEND_OK:
            f = Formation(fid)
            msg.formation.extend(f.in_formation_hero_original_ids())

        msg.leader = leader_oid

    def send_friends_amount_notify(self):
        msg = protomsg.FriendsAmountNotify()
        msg.max_amount = self.max_amount
        msg.cur_amount = self.cur_amount
        publish_to_char(self.char_id, pack_msg(msg))


    def send_friends_notify(self):
        msg = protomsg.FriendsNotify()
        for k, v in self.friends_list():
            f = msg.friends.add()
            self._msg_friend(f, k, v)

        publish_to_char(self.char_id, pack_msg(msg))