class GPlayerMgr(object): """ 联合进程使用的总角色管理类 """ _rpc_name_ = 'rpc_player_mgr' _rpc_attr_pre = 'rc_' TIME_OUT = 0.1 def __init__(self): setattr(Game, self._rpc_name_, self) self.max_players = config.max_players self.logic_players = config.logic_players self._users = {} self._players = {} self._sub_mgrs = {} self.mgr2addrs = {} self.logons = TimeMemCache(size=10000, default_timeout=2, name='rpc_player_mgr.logons') # self.names = TimeMemCache(size=10000, default_timeout=1*30) #缓存保证不重名 self.name2pids = {} self.pid2names = {} self._name_lock = RLock() self._new_player_lock = RLock() self.names4ad = {} self.overload_time = 0 self._debug_ips = None self._debug_status = False self._area_url = None self._area_legal = None import app from game.base import msg_define app.sub(msg_define.MSG_START, self.start) def start(self): self._svr = new_stream_server(config.player_addr, self._on_client_accept) self.sns_client = SNSClient(*config.logon_url) from game.base.msg_define import MSG_RES_RELOAD Game.res_mgr.sub(MSG_RES_RELOAD, self.load) Game.mail_mgr.start_expire_mail() self.load() def load(self): self._area_url = None self._area_legal = None op_lis = Game.rpc_status_mgr.get_config(GF_AREA_URLS) if not op_lis: return self._area_url = tuple(op_lis[:3]) self._area_legal = op_lis[-1] data = Game.rpc_status_mgr.get(constant.STATUS_DEBUG_FT) if data: self._debug_ips = data['ip'].split(",") self._debug_status = bool(data['status']) def _on_client_accept(self, sock, addr): """ 处理玩家登陆请求 """ log.debug(u'client发起连接(%s)', addr) _rpc = ClientRpc(sock, addr, self) _rpc.call_link_rpc = True _rpc.start() sleep(120) _rpc.stop()#该链接只用于登录接口,之后断开 def on_close(self, rpcobj): pass def reg_sub_mgr(self, rpc_sub_mgr, sub_name, addr, _proxy=True): log.info('reg_player_mgr:%s', sub_name) self._sub_mgrs[sub_name] = [rpc_sub_mgr, set()] self.mgr2addrs[sub_name] = addr return sub_name def unreg_sub_mgr(self, sub_mgr_id, _no_result=True): log.info('unreg_player_mgr:%s', sub_mgr_id) self._sub_mgrs.pop(sub_mgr_id, None) self.mgr2addrs.pop(sub_mgr_id, None) def get_sub_mgr(self, pid): """ 获取玩家所在进程的player_mgr对象 """ mid = self._players.get(pid) if not mid: return 0, None sub_mgr_ids = self._sub_mgrs.get(mid) if not sub_mgr_ids: return 0, None return mid, sub_mgr_ids[0] @wrap_pickle_result def get_sub_game(self, pid): """ 获取玩家所在进程的game对象 """ mid, sub_mgr = self.get_sub_mgr(pid) if not mid: return addr = self.mgr2addrs.get(mid) return get_obj(addr, 'game') @grpc_monitor def pre_add(self, pid, uid): """ 玩家预备登陆,为防止重复,先踢在线的玩家 """ old_mid, sub_mgr = self.get_sub_mgr(pid) if sub_mgr: sub_mgr.del_player_by_id(pid) self.delete(old_mid, pid, uid) return 1 if not uid in self._users: return 1 mid, _ = self._users[uid] sub_mgr = self._sub_mgrs[mid] sub_mgr[0].del_user_by_id(uid) return 1 @grpc_monitor def add(self, mid, pid, name, rid, uid): """ 玩家登陆,防止在短时间内重复登录 """ self._add_name_id(pid, name, rid) if self.logons.get(pid): log.info(u'禁止玩家(%s-%s)短时登录', pid, name) return False self.logons.set(pid, 1) self._users[uid] = mid, pid self._players[pid] = mid self._sub_mgrs[mid][1].add(pid) self.safe_pub(MSG_LOGON, pid) return True @grpc_monitor def delete(self, mid, pid, uid): """ sub_mgr调用,通知玩家退出 """ self._users.pop(uid, None) self._players.pop(pid, None) if mid in self._sub_mgrs: pids = self._sub_mgrs[mid][1] if pid in pids: pids.remove(pid) self.safe_pub(MSG_LOGOUT, pid) @property def count(self): return len(self._players) def get_count(self): return self.count def have(self, pid): return pid in self._players def _add_name_id(self, pid, name, rid): if pid in self.pid2names: return self.name2pids[name] = pid self.pid2names[pid] = (name, rid) def change_name(self, pid, name, rid): """ 改名 """ if pid not in self.pid2names: return False, errcode.EC_VALUE self.del_player(pid) self.name2pids[name] = pid self.pid2names[pid] = (name, rid) return True, None # def valid_name(self, name): # """ 检查角色名是否没重复,可以用来新建角色 """ # with self._name_lock: # pid = self.names.get(name) # if pid is not None: # return False # rs = Game.rpc_store.values(TN_PLAYER, ['name'], dict(name=name)) # if rs: # pid = rs[0]['id'] # self.names.set(name, pid) # return False # self.names.set(name, 0) # return True def get_id_by_name(self, name): """ 根据名称获取对应玩家id """ try: return self.name2pids[name] except KeyError: pid_rid = PlayerData.name_to_id(name) if pid_rid is None: return self._add_name_id(pid_rid[0], name, pid_rid[1]) return pid_rid[0] def get_name_by_id(self, pid, rid=0): """ 查询pid, rid: 1 一起查询主配将id return: name, name, rid """ try: if rid: return self.pid2names[pid] return self.pid2names[pid][0] except KeyError: name_rid = PlayerData.id_to_name(pid) if name_rid is None: return self._add_name_id(pid, name_rid[0], name_rid[1]) if rid: return name_rid return name_rid[0] def get_names(self, ids): """ 获取玩家名列表 """ rs = {} for i in ids: n = self.get_name_by_id(i) if n is None: continue rs[i] = n return rs def get_name_rids(self, ids): """ 获取玩家名rid列表 """ rs = {} for i in ids: n = self.get_name_by_id(i, rid=1) if n is None: continue rs[i] = n return rs def get_player_infos(self, pids, CBE=0): """ 获取玩家信息 返回: onlines, {pid:(name, rid, level, CBE)} """ onlines = self.get_online_ids(pids) rs = {} #等级 if CBE: levels = self.exec_players_func(onlines, _get_level_CBE, has_result=1, _pickle=True) else: levels = self.exec_players_func(onlines, _get_level, has_result=1, _pickle=True) off_ids = set(pids).difference(levels.keys()) if off_ids: off_lvs = PlayerData.get_players_levels(off_ids, CBE=CBE) if off_lvs: levels.update(off_lvs) for pid in pids: n_rid = self.get_name_by_id(pid, rid=1) if not n_rid: continue if CBE: lv, cbe = levels.get(pid, (1, 0)) rs[pid] = (n_rid[0], n_rid[1], lv, cbe) else: rs[pid] = (n_rid[0], n_rid[1], levels.get(pid, 1)) return onlines, rs def get_pids_by_level(self, level, start_chapter=False): """ 获取大于等于指定等级的所有在线pid """ all_pids = [] for sub_mgr in self._sub_mgrs.itervalues(): player_mgr = sub_mgr[0] pids = player_mgr.get_partpids_by_level(level, start_chapter) all_pids.extend(pids) return all_pids def get_player_detail(self, pids, cols): """ 返回玩家详细信息 """ return PlayerData.get_players_values(pids, cols) def get_user_detail_by_playername(self, name, *argv, **kw): """ 返回玩家详细信息 """ return PlayerData.userinfo_from_name(name, *argv, **kw) @grpc_monitor def get_onlines(self, start, end, name=0, rid=0): """ 返回在线玩家列表, 返回 根据传入的参数: 默认: [pid, ....] name=1: [(pid, name), ....] rid=1: [(pid, name, rid), ...] level=1: [(pid, name, rid, level), ...] """ rs = [] for index, pid in enumerate(self._players.iterkeys()): if index < start: continue if index >= end: break if name or rid: n_rid = self.get_name_by_id(pid, rid=1) if rid: rs.append((pid, n_rid[0], n_rid[1])) elif name: rs.append((pid, n_rid[0])) else: rs.append(pid) return rs @grpc_monitor def get_online_ids(self, pids=None, random_num=None): """ 返回在线的玩家id列表, pids: 查询的玩家列表,返回在线的ids random:整形, 随机选择random个pid返回 """ if random_num is not None: if len(self._players) <= random_num: return self._players.keys() return random.sample(self._players, random_num) if not pids: return self._players.keys() return [pid for pid in pids if pid in self._players] @grpc_monitor def new_player(self, uid, name, rid): """ 创建玩家对象,防止重名 """ with self._new_player_lock: if self.get_id_by_name(name) is not None: return pid, data = PlayerData.new_player(uid, name, rid) self._add_name_id(pid, name, rid) return data def del_player(self, pid): name = self.pid2names.pop(pid, None) self.name2pids.pop(name, None) def _new_user(self, t, user, pwd, UDID, DT, MAC, DEV, VER): u = dict(sns=t, name=user, pwd=pwd, UDID=UDID, DT=DT, MAC=MAC, DEV=DEV, VER=VER, tNew=int(time.time())) u = User(adict=u) u.save(Game.rpc_store) return u def _get_login_params(self, user, uid, sns_type=0): """ 选择一个逻辑进程,返回登录用参数 """ if uid in self._users:#重复登陆 mid, pid = self._users.get(uid) self.pre_add(pid, uid) self._users.pop(uid, None) key = uuid() rs, address = self._login_sub(user, uid, key, sns_type) return dict(uid=uid, key=key, time=int(time.time()), ip=address[0], port=address[1]) @wrap_wait4init @grpc_monitor def rc_login(self, user, pwd, UDID, DT, MAC='', DEV='', VER='', **kw): """ 用户登录请求 """ log.debug(u'收到用户登录请求:%s, %s, %s, %s, %s, %s, %s, %s', user, pwd, UDID, DT, MAC, DEV, VER, kw) resp_f = 'login' if self.count >= self.max_players: return pack_msg(resp_f, 0, err=language.STR_PLAYER_3) if not user:#游客登录 u = Game.rpc_store.query_loads(TN_USER, dict(UDID=UDID, name='')) else: #检查user,pwd是否正确,返回uid u = Game.rpc_store.query_loads(TN_USER, dict(name=user)) if not u: #不存在自动增加 u = self._new_user(SNS_NONE, user, pwd, UDID, DT, MAC, DEV, VER) else: u = u[0] u = User(adict=u) if u.data.pwd != pwd: return pack_msg(resp_f, 0, err=language.LOGIN_PWD_ERROR) params = self._get_login_params(user, u.data.id) return pack_msg(resp_f, 1, data=params) def bindSNS(self, uid, t, sid, session): """ 绑定平台账号 """ rs, data = self.sns_client.login(t, sid, session) if not rs: return 0, data u = Game.rpc_store.load(TN_USER, uid) if not u: return 0, errcode.EC_NOFOUND Game.rpc_store.update(TN_USER, uid, dict(name=sid)) return 1, None def area_legal(self): """登陆时区域是不是合法""" if not self._area_url or not self._area_legal: #未配置则所有人合法登陆 return True rpc = client_rpc.get_cur_rpc() data = urllib.urlencode({'ip':rpc.addr[0], 'check':str(0)}) host, port, url = self._area_url try: area = tools.http_post_ex(host, port, url, params=data, timeout=GPlayerMgr.TIME_OUT) area = json.loads(area) country = area['country'] log.debug('area_legal:%s in %s', rpc.addr[0], country) return not country or country in self._area_legal except BaseException as e: log.warn("area_legal error:%s", e) return True def is_debug_time(self): """ 测试期间只允许特定IP登陆 """ if not self._debug_status or not self._debug_ips: return True rpc = client_rpc.get_cur_rpc() ip = rpc.addr[0] if ip in self._debug_ips: return True log.info('during debug time in_ip:%s forbid :%s', self._debug_ips, ip) return False @wrap_wait4init @grpc_monitor def rc_loginSNS(self, t, sid, session, UDID, DT, MAC='', DEV='', VER='', **kw): """ 平台登录接口 """ resp_f = 'loginSNS' if not self.area_legal(): return pack_msg(resp_f, 0, err=errcode.EC_LOGIN_AREA_ERR) if not self.is_debug_time(): return pack_msg(resp_f, 0, err=errcode.EC_LOGIN_DEBUG_TIME) log.debug(u'平台(%s)用户登录请求:%s, %s, %s, %s, %s, %s, %s, %s', t, sid, session, UDID, DT, MAC, DEV, VER, kw) if self.count >= self.max_players: return pack_msg(resp_f, 0, err=errcode.EC_TEAM_ROLE_FULL) if not sid and t not in SNS_LOGINS:#游客登录 return pack_msg(resp_f, 0, err=errcode.EC_VALUE) #u = Game.rpc_store.query_loads(TN_USER, dict(UDID=UDID, name='')) else: rs, data = self.sns_client.login(t, sid, session) if not rs: return pack_msg(resp_f, 0, err=data) if data:#login返回sid sid = data u = UserData.user_by_sns(t, sid) if not u: #不存在自动增加 u = self._new_user(t, sid, '', UDID, DT, MAC, DEV, VER) else: u = u[0] u = User(adict=u) #如果mac地址不同,记录 if u.data.UDID != UDID or u.data.DT != DT or \ u.data.DEV != DEV or \ u.data.MAC != MAC or u.data.VER != VER: def _log_mac(): if 1: #强制保存更新信息, not u.data.MAC: u.data.UDID = UDID u.data.DT = DT u.data.MAC = MAC u.data.DEV = DEV u.data.VER = VER u.save(Game.rpc_store) else: self.glog(PM_MAC, u=u.data.id, UDID=UDID, MAC=MAC, DEV=DEV, VER=VER) spawn(_log_mac) params = self._get_login_params(sid, u.data.id, t) log.debug(u'loginSNS finish:%s', params) return pack_msg(resp_f, 1, data=params) def glog(self, type, **kw): kw['t'] = type Game.glog.log(kw) def _login_sub(self, user_name, uid, key, sns_type): """ 选取subgame """ sub_ids = None while not sub_ids: sub_ids = self._sub_mgrs.keys() if sub_ids: break log.info('_login_sub wait game init') sleep(1) sub_ids.sort() for sub_mgr_id in sub_ids: mgr, pids = self._sub_mgrs[sub_mgr_id] if len(pids) < self.logic_players: rs, address = mgr.logon(user_name, uid, key, sns_type) return rs, address #如果全部都满人,随机选择 sub_id = random.choice(sub_ids) mgr, count = self._sub_mgrs[sub_id] rs, address = mgr.logon(user_name, uid, key, sns_type) return rs, address @grpc_monitor def distribute(self, func_name, pids, *args, **kw): """ 分发方法调用到各个自进程: func_name: SubPlayerMgr中的方法名 pids: 玩家id列表, pids=None广播所有玩家 """ if pids is not None: pids = set(pids) for mid, (mgr, mids) in self._sub_mgrs.items(): if not mids: continue if pids is None: mpids = None else: mpids = list(pids.intersection(mids)) func = getattr(mgr, func_name) try: func(mpids, *args, **kw) except Exception as err: log.log_except('distribute error:%s(%s, %s)', func_name, args, kw) @wrap_distribute def player_mails(self, pids, mids, _no_result=True): """ 玩家收到新的邮件 """ @wrap_distribute def player_send_msg(self, pids, msg, _no_result=True): """ 广播消息给玩家 pids:None 时广播所有在线玩家 """ @wrap_pickle_result def get_rpc_player(self, pid): mid, sub_mgr = self.get_sub_mgr(pid) if not mid: return addr = self.mgr2addrs.get(mid) proxy = SubPlayerMgr.cls_get_player_proxy(pid, addr=addr, local=0) return proxy @wrap_pickle_result @grpc_monitor def get_rpc_players(self, pids): """ 获取rpc_player列表,用于少量玩家操作 """ rs = [] for pid in pids: proxy = self.get_rpc_player(pid) if proxy: rs.append(proxy) return rs @grpc_monitor def exec_players_func(self, pids, func, has_result=False, _pickle=True): """ 在玩家所在的逻辑进程执行方法,用于一般的玩家操作 func: 定义: def func(player) """ pids = set(pids) rs = {} for mid, (mgr, mids) in self._sub_mgrs.iteritems(): mpids = pids.intersection(mids) if not mpids: continue mpids = tuple(mpids) if has_result: sub_rs = mgr.exec_players_func(mpids, func, _pickle=True) rs.update(sub_rs) else: mgr.exec_players_func(mpids, func, _pickle=True, _no_result=True) return rs def overload(self, m): """ 启动压力测试m分钟 """ self.overload_time = time.time() + m * 60 log.warn('overload start to %s', datetime.datetime.fromtimestamp(self.overload_time)) def is_overload(self): """ 是否处于压力测试状态 """ return time.time() < self.overload_time def set_debug_data(self, ips, status): """ 设置测试的数据 """ self._debug_ips = ips self._debug_status = bool(status) self._players.keys() #状态开启了 if not self._debug_status: return pids = self.get_online_ids() for pid in pids: rpc = self.get_rpc_player(pid) if rpc and rpc.get_ip() not in self._debug_ips: rpc.debug_kick()
class SubPlayerMgr(BaseGameMgr, DictExport): """ 逻辑进程使用的角色管理类 """ _rpc_name_ = 'player_mgr' #定时保存时间 5分钟 _SAVE_TIME_ = 60 * 5 def __init__(self, game): BaseGameMgr.__init__(self, game) self._svr = None self._keys = TimeMemCache(size=1000, default_timeout=5*60, name='player_mgr._keys') self.users = {} self.players = {} self.others = TimeMemCache(size=1000, default_timeout=(self._SAVE_TIME_-1), name='player_mgr.others') self._game.reg_obj(self) self._loop_task = None def _rpc_mgr_init(self, rpc_mgr): if not self._game: return self.key = rpc_mgr.reg_sub_mgr(self, self._game.name, self._game.get_addr(), _proxy=True) def start(self): self._svr = new_stream_server_by_ports('0.0.0.0', config.player_ports, self._on_client_accept) self.address = config.inet_ip, self._svr.address[1] self.key = Game.rpc_player_mgr.reg_sub_mgr(self, self._game.name, self._game.get_addr(), _proxy=True) Game.sub_rpc_mgr_init(Game.rpc_player_mgr, self._rpc_mgr_init) self._loop_task = spawn(self._loop) def stop(self): if not BaseGameMgr.stop(self): return if self._svr: self._svr.stop() self._svr = None if self._loop_task: self._loop_task.kill(block=False) self._loop_task= None spawns(lambda u: u.logout(), [(u,) for u in self.users.itervalues()]) if not Game.parent_stoped: Game.rpc_player_mgr.unreg_sub_mgr(self.key, _no_result=True) def _on_client_accept(self, sock, addr): log.debug(u'client发起连接(%s)', addr) user = User() user.start(self, sock, addr) user.wait_for_init() def _loop(self): """ 定时保存等处理 """ stime = 30 while 1: sleep(stime) try: for pid in self.players.keys(): p = self.players.get(pid) if p is None or not p.logined: continue #是否需要定时保存 if p.save_time + self._SAVE_TIME_ <= time.time(): p.save() except: log.log_except() @property def global_count(self): """ 全服在线玩家总数 """ return Game.rpc_player_mgr.get_count() @property def count(self): return len(self.users) @classmethod def cls_get_player_proxy(cls, pid, addr=None, local=1): if addr is None: addr = Game.get_addr() if local: proxy = DictItemProxy(cls._rpc_name_, dict_name='players', key=pid, addr=addr) else: proxy = get_proxy_by_addr(addr, cls._rpc_name_, DictItemProxy) proxy.dict_name = 'players' proxy.key = pid return proxy def get_player_proxy(self, pid, check=True): if check and pid not in self.players: raise ValueError('player id(%d) not in player_mgr' % pid) return self.cls_get_player_proxy(pid) def logon(self, user_name, uid, key, sns_type): """ logon服务器发来的用户登录请求 """ if self._game.stoped: return False, '' #log.debug('subPlayerMgr.logon:%s, %s, %s', user_name, uid, key) self._keys.set(uid, (uid, key, sns_type)) return True, self.address def check_logon(self, uid, key): """ 检查角色登录情况, 返回: 成功(uid, sns_type) 失败(False, 0) """ v = self._keys.delete(uid) #log.debug('subPlayerMgr.check_logon:%s, %s', user_name, v) if v is not None and v[1] == key: return v[0], v[2] return False, 0 def add_user(self, user): self.users[user.data.id] = user def del_user(self, user): return self.users.pop(user.data.id, None) def del_user_by_id(self, uid): """ 全局管理器调用,强制玩家退出 """ user = self.users.get(uid) if not user: return user.logout() def add_player(self, player): """ 玩家进入游戏 """ pid = player.data.id rs = Game.rpc_player_mgr.add(self.key, pid, player.data.name, player.data.rid, player.data.uid) if not rs: return False self.players[pid] = player log.debug('sub_player_mgr.add_player:%s', pid) return True def logon_player(self, player): self.safe_pub(MSG_LOGON, player) def logoned_player(self, player): """ 已经发送初始化数据给前端,触发已登陆消息,其他模块可以正常发消息给前端 """ self.safe_pub(MSG_LOGONED, player) def del_player(self, player): """ 玩家退出 """ pid = player.data.id if pid not in self.players: return log.debug('sub_player_mgr.del_player:%s', pid) assert self.players[pid] == player, 'player != p' self.players.pop(pid) Game.rpc_player_mgr.delete(self.key, pid, player.data.uid) self.safe_pub(MSG_LOGOUT, player) def del_player_by_id(self, pid): """ 玩家退出,由全局管理器调用 """ player = self.players.get(pid) if player is None: return player.user.logout() def get_player(self, pid): return self.players.get(pid) def send_msg_to_all(self, msg): data = prepare_send(msg) for user in self.users.itervalues(): user.send_msg(data) def iter_players(self, pids): if pids is None: pids = self.players.keys() for pid in pids: p = self.players.get(pid) if p is None: continue yield pid, p def player_mails(self, pids, mids): """ 玩家接受邮件 """ for pid, p in self.iter_players(pids): mid = mids.get(pid) if not mid: continue try: p.mail.recv_mails(mid) except: log.log_except() def get_partpids_by_level(self, level, start_chapter=False): """ 获取大于等于指定等级的所有玩家 start_chapter 是否包括初章""" pids = [] for pid, player in self.iter_players(None): if player.data.level >= level: if start_chapter and player.data.chapter == constant.CHATER_START: continue pids.append(pid) return pids def player_send_msg(self, pids, msg): data = prepare_send(msg) for pid, p in self.iter_players(pids): p.send_msg(data) def exec_players_func(self, pids, func, _pickle=True): """ 在玩家所在的逻辑进程执行方法,用于一般的玩家操作 """ rs = {} for pid, p in self.iter_players(pids): try: rs[pid] = func(p) except Exception: log.log_except() return rs def look(self, pid): """ 查看其它玩家信息 """ return self._get_other(pid) def _get_other(self, pid, cache=1): """ 获取其它玩家信息 """ if cache: v = self.others.get(pid) if v is not None: return v p = self.get_player(pid) if not p: p = self._game.rpc_player_mgr.get_rpc_player(pid) if not p:#不在线 p = Player.load_player(pid) # p = OtherPlayer.new(pid) if not p: v = 0, errcode.EC_NOFOUND else: #先计算出战斗力 p.init() v = 1, p.look() self.others.set(pid, v) return v
class ReportMgr(object): """ 战报处理类 """ _rpc_name_ = 'rpc_report_mgr' def __init__(self): setattr(Game, self._rpc_name_, self) self.cache = TimeMemCache(default_timeout=10, name='report_mgr.cache') self.thread_pool = GlobalThreadPool(max_pool_size=5) self.report_base_dir = None self.report_base_url = None def init(self, url, dir): self.report_base_dir = dir self.report_base_url = url if not os.path.exists(self.report_base_dir): log.info('mkdir report_base_dir:%s', self.report_base_dir) os.makedirs(self.report_base_dir) def get_cur_path(self): return strftime(fmt='%Y%m') @grpc_monitor def save(self, type, pids, news, url=None): """ 写战报: pids: 相关玩家id列表 news:战报数据 url:可以指定url, 路径列表如['tbox', 'file_name'] 返回: 战报id """ if url is None: url = (self.get_cur_path(), md5(news).hexdigest()) #目录 path = os.path.join(self.report_base_dir, *url[:-1]) if not os.path.exists(path): os.makedirs(path) file_path = os.path.join(path, url[-1]) self.thread_pool.addTask(self._save_data, (file_path, news)) #处理url return self._save_message(type, '/'.join(url), pids) def _save_data(self, url, news): """ 保存战报 """ #保存数据 with open(url, 'w') as file: file.writelines(news) def _save_message(self, type, url, pids): """ 保存战报信息 """ #保存战报信息 message = {} message[REPORT_TYPE] = type message[REPORT_URL] = url message[REPORT_PIDS] = pids message[REPORT_CT] = int(time.time()) id = Game.rpc_store.insert(TN_F_REPORT, message) self.cache.set(id, (id, message)) return id @grpc_monitor def get_url(self, id): """ 获取战报绝对地址url """ #获取战报信息 _id, v = self.cache.get(id, (None, None)) if v is None: #从数据库读取数据 querys = dict(id=id) values = Game.rpc_store.query_loads(TN_F_REPORT, querys) if not values: return False, None v = values[0] self.cache.set(id, (id, v)) url = '%s/%s' % (self.report_base_url, v[REPORT_URL]) return True, url
class StatusMgr(object): """ 单服状态管理类 """ _rpc_name_ = 'rpc_status_mgr' def __init__(self): setattr(Game, self._rpc_name_, self) self.cache = TimeMemCache(default_timeout=10, name='status_mgr.cache') self._slock = RLock() def _get(self, key): querys = dict(key=key) values = Game.rpc_store.query_loads(TN_STATUS, querys) if values: return values[0]['id'], values[0]['value'] return None, None def get(self, key, default=None): """ 获取服状态 """ _id, v = self.cache.get(key, (None, None)) if v is not None: return v with self._slock: _id, v = self._get(key) if _id is None: return default self.cache.set(key, (_id, v)) return v def _set(self, id, key, value): if id is None: id = Game.rpc_store.insert(TN_STATUS, dict(key=key, value=value)) self.cache.set(key, (id, value)) else: self.cache.set(key, (id, value)) Game.rpc_store.save(TN_STATUS, dict(id=id, key=key, value=value)) return id def set(self, key, value, orig_value=None): """ 设置服状态, orig_value=None 强制覆盖 失败返回False """ with self._slock: _id, v = self.cache.get(key, (None, None)) if v is not None and orig_value is not None and orig_value != v: return False if v is None or _id is None: _id, v = self._get(key) if v is not None and orig_value is not None and v != orig_value: return False self._set(_id, key, value) return True def update_dict(self, key, adict): """更新 值 是字典类型的数据""" with self._slock: _id, v = self._get(key) if v is None: v = adict else: v.update(adict) self._set(_id, key, v) def _add(self, key, num=None): """ num=None时,清零 """ with self._slock: _id, v = self.cache.get(key, (None, None)) if _id is not None: v += num if num is not None else -v self.cache.set(key, (_id, v)) else: _id, v = self._get(key) if v is None: v = 0 v += num if num is not None else -v self._set(_id, key, v) def inc(self, key, num=1): """ key对应的值+1 """ spawn(self._add, key, num) def dec(self, key, num=-1): spawn(self._add, key, num) def zero(self, key): """ 清零 """ spawn(self._add, key) def get_config(self, key, default=None): """ 获取gconfig全局配置 """ return Game.rpc_res_store.get_config(key, default=default)
class ArenaMgr(object): """ 竞技场管理类 """ _rpc_name_ = 'rpc_arena_mgr' PLAYER_TIMEOUT = 60 * 0.1 BOT_RANK = 1000 #机器人预留排名数 def __init__(self): self.arenas = TimeMemCache( size=CACHE_SIZE, default_timeout=CACHE_TIME) #玩家信息 {pid:PlayerArena} self.rivals = TimeMemCache( size=CACHE_SIZE, default_timeout=CACHE_TIME) # {pid:[name, rid]} self.rewards = HitMemCache(size=CACHE_SIZE) self.bots = {} self._lock = RLock() self.stoped = True self._reward_task = None self._auto_start_task = None self.clear() app.sub(MSG_START, self.start) self._active_mul = 1 self.sorts = {} #数据库中保存,方便修改 {sort:id} def _get_status(self): status = Game.rpc_status_mgr.get(ARENA_STATUS) if status is None: self.arena_status = DEFAULT_STATUS return DEFAULT_STATUS return status def _set_status(self, value): Game.rpc_status_mgr.set(ARENA_STATUS, value) arena_status = property(_get_status, _set_status) def _get_is_start(self): """ 竞技场是否开启 """ return self.arena_status['status'] def _set_is_start(self, value): arena_status = self.arena_status arena_status['status'] = int(bool(value)) self.arena_status = arena_status #竞技场是否处于启动状态 is_start = property(_get_is_start, _set_is_start) def _get_reward_time(self): """ 获取上一次奖励时间 """ return self.arena_status['rt'] def _set_reward_time(self, value): arena_status = self.arena_status arena_status['rt'] = int(value) self.arena_status = arena_status reward_time = property(_get_reward_time, _set_reward_time) def clear(self): self.ranks = [] #排行榜 [pid, ...] self.pid2ranks = {} # {pid:rk} def reload(self): """ 重新加载资源数据 """ self.rewards.clear() init_setting() def start(self, init=False): if not self.stoped: return init_setting() if not self.is_start: #开启自动启动线程 if self._auto_start_task is None: self._auto_start_task = spawn_later(0, self._auto_start) return log.debug(u'arena_mgr start!') spawn(self.lood_save) Game.setting_mgr.sub(MSG_RES_RELOAD, self.reload) if init: self.init() else: self.load() self.arena_reward() Game.chat_mgr.sys_send(language.ARENA_START) self.stoped = False def stop(self): if self.stoped: return self.stoped = True self.save() if self._reward_task: self._reward_task.kill(block=False) self._reward_task = None def _auto_start(self): global arena_auto_start, arena_level sleep_times = 60 * 5 log.info('arena auto_start(%s) running', arena_auto_start) while 1: sleep(sleep_times) c = PlayerData.count({FN_PLAYER_LEVEL: {FOP_GTE: arena_level}}) if c >= arena_auto_start: log.info('arena auto start:%d', c) self.init() self.is_start = True self.start() self._auto_start_task = None return def lood_save(self): """ 保存 """ while 1: #保存排名 sleep(SAVE_RANK_TIME) self.save() def save(self): """ 保存 """ #保存排名 log.debug('------arena-rank-save---------') if not self.ranks: return num = len(self.ranks) / SAVE_PER_LEN + 1 for i in xrange(num): start = i * SAVE_PER_LEN end = start + SAVE_PER_LEN pids = self.ranks[start:end] sort = i + 1 if sort in self.sorts: dic = dict(id=self.sorts[sort], sort=sort, pids=pids) Game.rpc_store.save(TN_ARENA_RANKS, dic) else: dic = dict(id=sort, sort=sort, pids=pids) insert_id = Game.rpc_store.insert(TN_ARENA_RANKS, dic) self.sorts[sort] = insert_id log.debug('-----arena-rank-save--ok-----') @wrap_lock def init(self): """ 初始化竞技场数据:根据30级以上的玩家战斗力排名,得到竞技场排名 """ log.warn(u"初始化竞技场") self.clear() #删除记录 Game.rpc_store.deletes(TN_ARENA_RANKS) Game.rpc_store.deletes(TN_P_ARENA) #Game.rpc_store.insert(TN_ARENA_RANK, {FN_ID:0, FN_NEXT:END}) #根据30级以上玩家战斗力排名 CBEs = [(i[FN_P_ATTR_PID], i.get(FN_P_ATTR_CBE, index)) for index, i in enumerate(PlayerAttr.get_CBE_ranks())] levels = PlayerData.get_players_levels(None) #插入机器人 for i in xrange(self.BOT_RANK): self.update_rank(END, END - (i + 1)) #真实玩家 for pid, CBE in CBEs: if pid not in levels or levels[pid] < arena_level: continue self.update_rank(END, pid) @wrap_lock def load(self): """ 使用新方法的加载 """ self.clear() ranks = Game.rpc_store.load_all(TN_ARENA_RANKS) if ranks is None: return rank_sort = sorted(ranks, key=lambda dic: dic[KEY_SORT]) for rank in rank_sort: self.ranks.extend(rank[KEY_PIDS]) for index, pid in enumerate(self.ranks): rk = index + 1 self.pid2ranks[pid] = rk def get_rank(self, pid): return self.pid2ranks.get(pid) def get_pid(self, rank): if rank < 1 or rank > len(self.ranks): return 0 return self.ranks[rank - 1] def update_rank(self, rank, pid): """ 更新排名 """ orank = self.pid2ranks.get(pid, END) assert rank <= orank, ValueError('update_rank error:%s <= %s' % (rank, orank)) #内存数据修改, rank 必须 <= orank if rank == END: self.ranks.append(pid) self.pid2ranks[pid] = len(self.ranks) else: self.ranks.pop(orank - 1) self.ranks.insert(rank - 1, pid) for index, pid in enumerate(self.ranks[rank - 1:orank]): self.pid2ranks[pid] = rank + index def init_player(self, pid): """ 加载并返回玩家信息 """ parena = self.arenas.get(pid, add_time=CACHE_TIME) if parena is None: parena = PlayerArena(pid) self.arenas.set(pid, parena) data = Game.rpc_store.load(TN_P_ARENA, pid) if data is not None: parena.update(data) parena.pass_day() else: parena.modify() if pid not in self.pid2ranks: with self._lock: self.update_rank(END, pid) return parena def _get_rivals(self, pids): """ 批量获取对手信息 """ rs = {} ids = [] for i in pids: rival = self.rivals.get(i) if rival: rs[i] = rival elif is_bot(i): bot = self.get_bot(i) rs[i] = bot._bot_rival else: ids.append(i) if ids: onlines, nids = Game.rpc_player_mgr.get_player_infos(ids, CBE=1) #nids = Game.rpc_player_mgr.get_name_rids(ids) self.rivals.update(nids) rs.update(nids) return rs get_rival_infos = _get_rivals def get_name(self, pid): rival = self.rivals.get(pid) if rival: return rival[0] else: rs = self._get_rivals([pid]) return rs[pid][0] def get_rivals(self, parena): """ 获取对手列表 """ base_rk = parena.rank base_pid = parena.data.id ranks = arena_ranks(base_rk) ids = [] rks = [] for r in ranks[1:]: if ranks[0] == RANK_PREV_R: rk = base_rk - int(r) elif ranks[0] == RANK_PREV_EQ: rk = int(r) else: raise ValueError('ARENA_RANK error:%s' % ranks) rks.append(rk) pid = self.get_pid(rk) if pid: ids.append(pid) if base_pid not in ids: rks.append(base_rk) ids.append(base_pid) rivals = self._get_rivals(ids) rs = [] for index, i in enumerate(ids): rival = rivals.get(i) if not rival: continue CBE = 0 if rival[IPI_IDX_CBE] is None else rival[IPI_IDX_CBE] rs.append( dict(rk=rks[index], pid=i, n=rival[IPI_IDX_NAME], rid=rival[IPI_IDX_RID], level=rival[IPI_IDX_LEVEL], CBE=CBE)) return rs def get_bot(self, bid): assert is_bot(bid), ValueError('bid(%s) is not bot' % bid) try: return self.bots[bid] except KeyError: #从数据库中随机选择某等级区间的玩家作为基础数据 key = 'arena_bot_pids' pids = Game.gcaches.get(key) if not pids: pids = PlayerData.get_level_pids(arena_level, arena_level + 10) Game.gcaches.set(key, pids, timeout=60 * 5) pid = random.choice(pids) bot = Player.load_player(pid) data = bot.data data.id = bid data.name = Game.res_mgr.get_random_name() bot._bot_rival = (data.name, data.rid, data.level, bot.play_attr.CBE) self.bots[bid] = bot return bot def look_bot(self, bid): """ 获取机器人信息 result:rs, info """ bot = self.get_bot(bid) try: return 1, bot._bot_look except AttributeError: bot._bot_look = bot.look() return 1, bot._bot_look @grpc_monitor @wrap_arena def enter(self, pid, vip, parena=None): """ 玩家进入竞技场获取相关信息 """ msg_dict = parena.enter(vip, self._active_mul) msg_dict['rk'] = self.get_rank(pid) msg_dict['rivals'] = self.get_rivals(parena) return 1, msg_dict @grpc_monitor @wrap_arena def start_arena(self, pid, rival_id, parena=None): """ 开始挑战排位,获取被挑战者数据, rk:挑战的玩家id """ return parena.start_arena(rival_id) @grpc_monitor @wrap_arena def buy_arena(self, pid, c, coin2, parena=None): """ 购买次数 """ cost = c * arena_coin if cost > coin2: return 0, errcode.EC_COST_ERR c = parena.buy(c) return 1, (c, cost) @grpc_monitor @wrap_arena_rival @wrap_lock def end_arena(self, pid, rid, is_ok, fp_id, vip, parena=None, rival=None, gm=False): """ 结束挑战,更新信息 """ if not gm and (not rid or rid != parena.rival_id): return 0, errcode.EC_VALUE if not gm and not parena.cost_arena(): return 0, errcode.EC_COST_ERR parena.rival_id = None return self._end_arena(is_ok, parena, rival, fp_id, vip) def _end_arena(self, is_ok, parena, rival, fp_id, vip): """ 挑战胜利 挑战类型: 1=挑战胜利, 2=挑战失败, 3=被挑战胜利, 4=被挑战失败 """ pid, rpid = parena.data.id, rival.data.id pname, rname = self.get_name(pid), self.get_name(rpid) parena_rank = self.get_rank(pid) rival_rank = self.get_rank(rpid) if is_ok: if rival_rank < parena_rank: #排位调整 self.update_rank(rival_rank, pid) parena_rank, rival_rank = rival_rank, rival_rank + 1 #竞技场第一名广播事件 if parena_rank == RANK_FIRST: self.safe_pub(MSG_AREA_RANKFIRST, pid, pname, rpid, rname) #奖励 coin, train = arena_rw_succ coin, train = parena.reward(vip, coin, train, self._active_mul) lt_p, lt_r = LT_SUCC, LT_B_FAIL else: #奖励 coin, train = arena_rw_fail coin, train = parena.reward(vip, coin, train, self._active_mul) lt_p, lt_r = LT_FAIL, LT_B_SUCC #战报 parena.add_log(lt_p, rpid, rname, parena_rank, fp_id) rival.add_log(lt_r, pid, pname, rival_rank, fp_id) return 1, (coin, train) def set_active_reward(self, mul): """活动奖励的设置""" self._active_mul = mul #@property #def next_reward_times(self): # """ 到下一次奖励的秒数 # :rtype : int # """ # reward_time = self.reward_time # zero_time = zero_day_time() # for index, weekday in enumerate(arena_rw_weekdays): # t = week_time(weekday, zero=1) # delay_time = t - zero_time # if delay_time < -60:#前几天 # continue # if delay_time < 60: # if not is_pass_day(reward_time):#今天刚奖励完 # continue # return 1 # return int(t - time.time())+2 #计算可用23:59:59发送奖励必须往后移一点否则一秒会执行多次 # #返回下周奖励时间 # return int(week_time(arena_rw_weekdays[0], zero=1, delta=1) - time.time()) # #raise ValueError('next_reward_times:%s' % str(arena_rw_weekdays)) @property def next_reward_time(self): """ 到下一次奖励的具体时间 :rtype : int """ reward_time = self.reward_time zero_time = zero_day_time() for index, weekday in enumerate(arena_rw_weekdays): t = week_time(weekday, zero=1) delay_time = t - zero_time if delay_time < 0: #前几天 continue if delay_time == 0: if reward_time == t: #今天刚奖励完 continue return t #返回下周奖励时间 return week_time(arena_rw_weekdays[0], zero=1, delta=1) def arena_reward(self): """ 定时发放奖励 """ #安装定时器 #t = self.next_reward_times next_time = self.next_reward_time times = next_time - time.time() if next_time > time.time() else 0 times += 10 log.info('arena_reward after %s times', times) self._reward_task = spawn_later(times, self._reward_nomal, next_time) def _reward_nomal(self, r_t): """ 正常的发放奖励 """ global arena_rewards self.reward_time = r_t #下一个定时发放 if not self.stoped: self.arena_reward() self._reward() def reward_active(self): """ 活动的发放奖励 """ if not self.stoped: self._reward() return log.error('%s is stop _reward_active is not send', self) def _reward(self): """ 发放奖励 """ log.info(u'竞技场排名:%s', self.ranks[:500]) #log.info('arena_ranks:%s', self.pid2ranks) mail_mgr = Game.mail_mgr rw_mail = Game.res_mgr.reward_mails.get(RW_MAIL_ARENA) levels = {} lv_count = 30 #批量读取等级 global arena_level ranks = self.ranks[:] log.info('arena_reward:%s', len(ranks)) for index, pid in enumerate(ranks): rank = index + 1 pids = ranks[index:index + lv_count] if pid not in levels: #批量获取等级 self._get_rivals(pids) rs = PlayerData.get_players_levels(pids) if rs: levels.update(rs) if pid not in levels: #玩家不存在 continue rs, items, rid = self.get_reward(rank, levels[pid]) if not rs: break #content = rw_mail.content % dict(name=self.get_name(pid), rank=rank) content = rw_mail.content % dict(rank=rank) mail_mgr.send_mails(pid, MAIL_REWARD, rw_mail.title, RW_MAIL_ARENA, items, param=content, rid=rid) @grpc_monitor def get_reward(self, rk, level): """ 获取排行榜奖励内容 """ key = (rk, level) v = self.rewards.get(key) if v: return v rid = arena_rewards(rk) if not rid: v = (0, errcode.EC_NOFOUND, 0) else: reward = Game.reward_mgr.get(rid) items = reward.reward(dict(level=level, rank=rk)) v = (1, items, rid) self.rewards.set(key, v) return v @grpc_monitor def get_ranks(self, start, end): """ 获取排名列表 """ #rs = [pid for pid in self.ranks if not is_bot(pid)] #return rs[start:end] return self.ranks[start:end] def gm_add_count(self, pid, c): parena = self.init_player(pid) parena.buy(c) def gm_get_rid_byrank(self, rank): """ gm听过竞技场获取排名 """ if rank > len(self.ranks): return return self.ranks[rank - 1] def gm_change_rank(self, pid, rid, fp_id, vip): """ gm改变排行榜等级 """ self.end_arena(pid, rid, 1, fp_id, vip, gm=True)
class SubPlayerMgr(BaseGameMgr, DictExport): """ 逻辑进程使用的角色管理类 """ _rpc_name_ = 'player_mgr' #定时保存时间 5分钟 _SAVE_TIME_ = 60 * 5 def __init__(self, game): BaseGameMgr.__init__(self, game) self._svr = None self._keys = TimeMemCache(size=1000, default_timeout=5 * 60, name='player_mgr._keys') self.users = {} self.players = {} self.others = TimeMemCache(size=1000, default_timeout=(self._SAVE_TIME_ - 1), name='player_mgr.others') self._game.reg_obj(self) self._loop_task = None def _rpc_mgr_init(self, rpc_mgr): if not self._game: return self.key = rpc_mgr.reg_sub_mgr(self, self._game.name, self._game.get_addr(), _proxy=True) def start(self): self._svr = new_stream_server_by_ports('0.0.0.0', config.player_ports, self._on_client_accept) self.address = config.inet_ip, self._svr.address[1] self.key = Game.rpc_player_mgr.reg_sub_mgr(self, self._game.name, self._game.get_addr(), _proxy=True) Game.sub_rpc_mgr_init(Game.rpc_player_mgr, self._rpc_mgr_init) self._loop_task = spawn(self._loop) def stop(self): if not BaseGameMgr.stop(self): return if self._svr: self._svr.stop() self._svr = None if self._loop_task: self._loop_task.kill(block=False) self._loop_task = None spawns(lambda u: u.logout(), [(u, ) for u in self.users.itervalues()]) if not Game.parent_stoped: Game.rpc_player_mgr.unreg_sub_mgr(self.key, _no_result=True) def _on_client_accept(self, sock, addr): log.debug(u'client发起连接(%s)', addr) user = User() user.start(self, sock, addr) user.wait_for_init() def _loop(self): """ 定时保存等处理 """ stime = 30 while 1: sleep(stime) try: for pid in self.players.keys(): p = self.players.get(pid) if p is None or not p.logined: continue #是否需要定时保存 if p.save_time + self._SAVE_TIME_ <= time.time(): p.save() except: log.log_except() @property def global_count(self): """ 全服在线玩家总数 """ return Game.rpc_player_mgr.get_count() @property def count(self): return len(self.users) @classmethod def cls_get_player_proxy(cls, pid, addr=None, local=1): if addr is None: addr = Game.get_addr() if local: proxy = DictItemProxy(cls._rpc_name_, dict_name='players', key=pid, addr=addr) else: proxy = get_proxy_by_addr(addr, cls._rpc_name_, DictItemProxy) proxy.dict_name = 'players' proxy.key = pid return proxy def get_player_proxy(self, pid, check=True): if check and pid not in self.players: raise ValueError('player id(%d) not in player_mgr' % pid) return self.cls_get_player_proxy(pid) def logon(self, user_name, uid, key, sns_type): """ logon服务器发来的用户登录请求 """ if self._game.stoped: return False, '' #log.debug('subPlayerMgr.logon:%s, %s, %s', user_name, uid, key) self._keys.set(uid, (uid, key, sns_type)) return True, self.address def check_logon(self, uid, key): """ 检查角色登录情况, 返回: 成功(uid, sns_type) 失败(False, 0) """ v = self._keys.delete(uid) #log.debug('subPlayerMgr.check_logon:%s, %s', user_name, v) if v is not None and v[1] == key: return v[0], v[2] return False, 0 def add_user(self, user): self.users[user.data.id] = user def del_user(self, user): return self.users.pop(user.data.id, None) def del_user_by_id(self, uid): """ 全局管理器调用,强制玩家退出 """ user = self.users.get(uid) if not user: return user.logout() def add_player(self, player): """ 玩家进入游戏 """ pid = player.data.id rs = Game.rpc_player_mgr.add(self.key, pid, player.data.name, player.data.rid, player.data.uid) if not rs: return False self.players[pid] = player log.debug('sub_player_mgr.add_player:%s', pid) return True def logon_player(self, player): self.safe_pub(MSG_LOGON, player) def logoned_player(self, player): """ 已经发送初始化数据给前端,触发已登陆消息,其他模块可以正常发消息给前端 """ self.safe_pub(MSG_LOGONED, player) def del_player(self, player): """ 玩家退出 """ pid = player.data.id if pid not in self.players: return log.debug('sub_player_mgr.del_player:%s', pid) assert self.players[pid] == player, 'player != p' self.players.pop(pid) Game.rpc_player_mgr.delete(self.key, pid, player.data.uid) self.safe_pub(MSG_LOGOUT, player) def del_player_by_id(self, pid): """ 玩家退出,由全局管理器调用 """ player = self.players.get(pid) if player is None: return player.user.logout() def get_player(self, pid): return self.players.get(pid) def send_msg_to_all(self, msg): data = prepare_send(msg) for user in self.users.itervalues(): user.send_msg(data) def iter_players(self, pids): if pids is None: pids = self.players.keys() for pid in pids: p = self.players.get(pid) if p is None: continue yield pid, p def player_mails(self, pids, mids): """ 玩家接受邮件 """ for pid, p in self.iter_players(pids): mid = mids.get(pid) if not mid: continue try: p.mail.recv_mails(mid) except: log.log_except() def get_partpids_by_level(self, level, start_chapter=False): """ 获取大于等于指定等级的所有玩家 start_chapter 是否包括初章""" pids = [] for pid, player in self.iter_players(None): if player.data.level >= level: if start_chapter and player.data.chapter == constant.CHATER_START: continue pids.append(pid) return pids def player_send_msg(self, pids, msg): data = prepare_send(msg) for pid, p in self.iter_players(pids): p.send_msg(data) def exec_players_func(self, pids, func, _pickle=True): """ 在玩家所在的逻辑进程执行方法,用于一般的玩家操作 """ rs = {} for pid, p in self.iter_players(pids): try: rs[pid] = func(p) except Exception: log.log_except() return rs def look(self, pid): """ 查看其它玩家信息 """ return self._get_other(pid) def _get_other(self, pid, cache=1): """ 获取其它玩家信息 """ if cache: v = self.others.get(pid) if v is not None: return v p = self.get_player(pid) if not p: p = self._game.rpc_player_mgr.get_rpc_player(pid) if not p: #不在线 p = Player.load_player(pid) # p = OtherPlayer.new(pid) if not p: v = 0, errcode.EC_NOFOUND else: #先计算出战斗力 p.init() v = 1, p.look() self.others.set(pid, v) return v
class GPlayerMgr(object): """ 联合进程使用的总角色管理类 """ _rpc_name_ = 'rpc_player_mgr' _rpc_attr_pre = 'rc_' TIME_OUT = 0.1 def __init__(self): setattr(Game, self._rpc_name_, self) self.max_players = config.max_players self.logic_players = config.logic_players self._users = {} self._players = {} self._sub_mgrs = {} self.mgr2addrs = {} self.logons = TimeMemCache(size=10000, default_timeout=2, name='rpc_player_mgr.logons') # self.names = TimeMemCache(size=10000, default_timeout=1*30) #缓存保证不重名 self.name2pids = {} self.pid2names = {} self._name_lock = RLock() self._new_player_lock = RLock() self.names4ad = {} self.overload_time = 0 self._debug_ips = None self._debug_status = False self._area_url = None self._area_legal = None import app from game.base import msg_define app.sub(msg_define.MSG_START, self.start) def start(self): self._svr = new_stream_server(config.player_addr, self._on_client_accept) self.sns_client = SNSClient(*config.logon_url) from game.base.msg_define import MSG_RES_RELOAD Game.res_mgr.sub(MSG_RES_RELOAD, self.load) Game.mail_mgr.start_expire_mail() self.load() def load(self): self._area_url = None self._area_legal = None op_lis = Game.rpc_status_mgr.get_config(GF_AREA_URLS) if not op_lis: return self._area_url = tuple(op_lis[:3]) self._area_legal = op_lis[-1] data = Game.rpc_status_mgr.get(constant.STATUS_DEBUG_FT) if data: self._debug_ips = data['ip'].split(",") self._debug_status = bool(data['status']) def _on_client_accept(self, sock, addr): """ 处理玩家登陆请求 """ log.debug(u'client发起连接(%s)', addr) _rpc = ClientRpc(sock, addr, self) _rpc.call_link_rpc = True _rpc.start() sleep(120) _rpc.stop() #该链接只用于登录接口,之后断开 def on_close(self, rpcobj): pass def reg_sub_mgr(self, rpc_sub_mgr, sub_name, addr, _proxy=True): log.info('reg_player_mgr:%s', sub_name) self._sub_mgrs[sub_name] = [rpc_sub_mgr, set()] self.mgr2addrs[sub_name] = addr return sub_name def unreg_sub_mgr(self, sub_mgr_id, _no_result=True): log.info('unreg_player_mgr:%s', sub_mgr_id) self._sub_mgrs.pop(sub_mgr_id, None) self.mgr2addrs.pop(sub_mgr_id, None) def get_sub_mgr(self, pid): """ 获取玩家所在进程的player_mgr对象 """ mid = self._players.get(pid) if not mid: return 0, None sub_mgr_ids = self._sub_mgrs.get(mid) if not sub_mgr_ids: return 0, None return mid, sub_mgr_ids[0] @wrap_pickle_result def get_sub_game(self, pid): """ 获取玩家所在进程的game对象 """ mid, sub_mgr = self.get_sub_mgr(pid) if not mid: return addr = self.mgr2addrs.get(mid) return get_obj(addr, 'game') @grpc_monitor def pre_add(self, pid, uid): """ 玩家预备登陆,为防止重复,先踢在线的玩家 """ old_mid, sub_mgr = self.get_sub_mgr(pid) if sub_mgr: sub_mgr.del_player_by_id(pid) self.delete(old_mid, pid, uid) return 1 if not uid in self._users: return 1 mid, _ = self._users[uid] sub_mgr = self._sub_mgrs[mid] sub_mgr[0].del_user_by_id(uid) return 1 @grpc_monitor def add(self, mid, pid, name, rid, uid): """ 玩家登陆,防止在短时间内重复登录 """ self._add_name_id(pid, name, rid) if self.logons.get(pid): log.info(u'禁止玩家(%s-%s)短时登录', pid, name) return False self.logons.set(pid, 1) self._users[uid] = mid, pid self._players[pid] = mid self._sub_mgrs[mid][1].add(pid) self.safe_pub(MSG_LOGON, pid) return True @grpc_monitor def delete(self, mid, pid, uid): """ sub_mgr调用,通知玩家退出 """ self._users.pop(uid, None) self._players.pop(pid, None) if mid in self._sub_mgrs: pids = self._sub_mgrs[mid][1] if pid in pids: pids.remove(pid) self.safe_pub(MSG_LOGOUT, pid) @property def count(self): return len(self._players) def get_count(self): return self.count def have(self, pid): return pid in self._players def _add_name_id(self, pid, name, rid): if pid in self.pid2names: return self.name2pids[name] = pid self.pid2names[pid] = (name, rid) def change_name(self, pid, name, rid): """ 改名 """ if pid not in self.pid2names: return False, errcode.EC_VALUE self.del_player(pid) self.name2pids[name] = pid self.pid2names[pid] = (name, rid) return True, None # def valid_name(self, name): # """ 检查角色名是否没重复,可以用来新建角色 """ # with self._name_lock: # pid = self.names.get(name) # if pid is not None: # return False # rs = Game.rpc_store.values(TN_PLAYER, ['name'], dict(name=name)) # if rs: # pid = rs[0]['id'] # self.names.set(name, pid) # return False # self.names.set(name, 0) # return True def get_id_by_name(self, name): """ 根据名称获取对应玩家id """ try: return self.name2pids[name] except KeyError: pid_rid = PlayerData.name_to_id(name) if pid_rid is None: return self._add_name_id(pid_rid[0], name, pid_rid[1]) return pid_rid[0] def get_name_by_id(self, pid, rid=0): """ 查询pid, rid: 1 一起查询主配将id return: name, name, rid """ try: if rid: return self.pid2names[pid] return self.pid2names[pid][0] except KeyError: name_rid = PlayerData.id_to_name(pid) if name_rid is None: return self._add_name_id(pid, name_rid[0], name_rid[1]) if rid: return name_rid return name_rid[0] def get_names(self, ids): """ 获取玩家名列表 """ rs = {} for i in ids: n = self.get_name_by_id(i) if n is None: continue rs[i] = n return rs def get_name_rids(self, ids): """ 获取玩家名rid列表 """ rs = {} for i in ids: n = self.get_name_by_id(i, rid=1) if n is None: continue rs[i] = n return rs def get_player_infos(self, pids, CBE=0): """ 获取玩家信息 返回: onlines, {pid:(name, rid, level, CBE)} """ onlines = self.get_online_ids(pids) rs = {} #等级 if CBE: levels = self.exec_players_func(onlines, _get_level_CBE, has_result=1, _pickle=True) else: levels = self.exec_players_func(onlines, _get_level, has_result=1, _pickle=True) off_ids = set(pids).difference(levels.keys()) if off_ids: off_lvs = PlayerData.get_players_levels(off_ids, CBE=CBE) if off_lvs: levels.update(off_lvs) for pid in pids: n_rid = self.get_name_by_id(pid, rid=1) if not n_rid: continue if CBE: lv, cbe = levels.get(pid, (1, 0)) rs[pid] = (n_rid[0], n_rid[1], lv, cbe) else: rs[pid] = (n_rid[0], n_rid[1], levels.get(pid, 1)) return onlines, rs def get_pids_by_level(self, level, start_chapter=False): """ 获取大于等于指定等级的所有在线pid """ all_pids = [] for sub_mgr in self._sub_mgrs.itervalues(): player_mgr = sub_mgr[0] pids = player_mgr.get_partpids_by_level(level, start_chapter) all_pids.extend(pids) return all_pids def get_player_detail(self, pids, cols): """ 返回玩家详细信息 """ return PlayerData.get_players_values(pids, cols) def get_user_detail_by_playername(self, name, *argv, **kw): """ 返回玩家详细信息 """ return PlayerData.userinfo_from_name(name, *argv, **kw) @grpc_monitor def get_onlines(self, start, end, name=0, rid=0): """ 返回在线玩家列表, 返回 根据传入的参数: 默认: [pid, ....] name=1: [(pid, name), ....] rid=1: [(pid, name, rid), ...] level=1: [(pid, name, rid, level), ...] """ rs = [] for index, pid in enumerate(self._players.iterkeys()): if index < start: continue if index >= end: break if name or rid: n_rid = self.get_name_by_id(pid, rid=1) if rid: rs.append((pid, n_rid[0], n_rid[1])) elif name: rs.append((pid, n_rid[0])) else: rs.append(pid) return rs @grpc_monitor def get_online_ids(self, pids=None, random_num=None): """ 返回在线的玩家id列表, pids: 查询的玩家列表,返回在线的ids random:整形, 随机选择random个pid返回 """ if random_num is not None: if len(self._players) <= random_num: return self._players.keys() return random.sample(self._players, random_num) if not pids: return self._players.keys() return [pid for pid in pids if pid in self._players] @grpc_monitor def new_player(self, uid, name, rid): """ 创建玩家对象,防止重名 """ with self._new_player_lock: if self.get_id_by_name(name) is not None: return pid, data = PlayerData.new_player(uid, name, rid) self._add_name_id(pid, name, rid) return data def del_player(self, pid): name = self.pid2names.pop(pid, None) self.name2pids.pop(name, None) def _new_user(self, t, user, pwd, UDID, DT, MAC, DEV, VER): u = dict(sns=t, name=user, pwd=pwd, UDID=UDID, DT=DT, MAC=MAC, DEV=DEV, VER=VER, tNew=int(time.time())) u = User(adict=u) u.save(Game.rpc_store) return u def _get_login_params(self, user, uid, sns_type=0): """ 选择一个逻辑进程,返回登录用参数 """ if uid in self._users: #重复登陆 mid, pid = self._users.get(uid) self.pre_add(pid, uid) self._users.pop(uid, None) key = uuid() rs, address = self._login_sub(user, uid, key, sns_type) return dict(uid=uid, key=key, time=int(time.time()), ip=address[0], port=address[1]) @wrap_wait4init @grpc_monitor def rc_login(self, user, pwd, UDID, DT, MAC='', DEV='', VER='', **kw): """ 用户登录请求 """ log.debug(u'收到用户登录请求:%s, %s, %s, %s, %s, %s, %s, %s', user, pwd, UDID, DT, MAC, DEV, VER, kw) resp_f = 'login' if self.count >= self.max_players: return pack_msg(resp_f, 0, err=language.STR_PLAYER_3) if not user: #游客登录 u = Game.rpc_store.query_loads(TN_USER, dict(UDID=UDID, name='')) else: #检查user,pwd是否正确,返回uid u = Game.rpc_store.query_loads(TN_USER, dict(name=user)) if not u: #不存在自动增加 u = self._new_user(SNS_NONE, user, pwd, UDID, DT, MAC, DEV, VER) else: u = u[0] u = User(adict=u) if u.data.pwd != pwd: return pack_msg(resp_f, 0, err=language.LOGIN_PWD_ERROR) params = self._get_login_params(user, u.data.id) return pack_msg(resp_f, 1, data=params) def bindSNS(self, uid, t, sid, session): """ 绑定平台账号 """ rs, data = self.sns_client.login(t, sid, session) if not rs: return 0, data u = Game.rpc_store.load(TN_USER, uid) if not u: return 0, errcode.EC_NOFOUND Game.rpc_store.update(TN_USER, uid, dict(name=sid)) return 1, None def area_legal(self): """登陆时区域是不是合法""" if not self._area_url or not self._area_legal: #未配置则所有人合法登陆 return True rpc = client_rpc.get_cur_rpc() data = urllib.urlencode({'ip': rpc.addr[0], 'check': str(0)}) host, port, url = self._area_url try: area = tools.http_post_ex(host, port, url, params=data, timeout=GPlayerMgr.TIME_OUT) area = json.loads(area) country = area['country'] log.debug('area_legal:%s in %s', rpc.addr[0], country) return not country or country in self._area_legal except BaseException as e: log.warn("area_legal error:%s", e) return True def is_debug_time(self): """ 测试期间只允许特定IP登陆 """ if not self._debug_status or not self._debug_ips: return True rpc = client_rpc.get_cur_rpc() ip = rpc.addr[0] if ip in self._debug_ips: return True log.info('during debug time in_ip:%s forbid :%s', self._debug_ips, ip) return False @wrap_wait4init @grpc_monitor def rc_loginSNS(self, t, sid, session, UDID, DT, MAC='', DEV='', VER='', **kw): """ 平台登录接口 """ resp_f = 'loginSNS' if not self.area_legal(): return pack_msg(resp_f, 0, err=errcode.EC_LOGIN_AREA_ERR) if not self.is_debug_time(): return pack_msg(resp_f, 0, err=errcode.EC_LOGIN_DEBUG_TIME) log.debug(u'平台(%s)用户登录请求:%s, %s, %s, %s, %s, %s, %s, %s', t, sid, session, UDID, DT, MAC, DEV, VER, kw) if self.count >= self.max_players: return pack_msg(resp_f, 0, err=errcode.EC_TEAM_ROLE_FULL) if not sid and t not in SNS_LOGINS: #游客登录 return pack_msg(resp_f, 0, err=errcode.EC_VALUE) #u = Game.rpc_store.query_loads(TN_USER, dict(UDID=UDID, name='')) else: rs, data = self.sns_client.login(t, sid, session) if not rs: return pack_msg(resp_f, 0, err=data) if data: #login返回sid sid = data u = UserData.user_by_sns(t, sid) if not u: #不存在自动增加 u = self._new_user(t, sid, '', UDID, DT, MAC, DEV, VER) else: u = u[0] u = User(adict=u) #如果mac地址不同,记录 if u.data.UDID != UDID or u.data.DT != DT or \ u.data.DEV != DEV or \ u.data.MAC != MAC or u.data.VER != VER: def _log_mac(): if 1: #强制保存更新信息, not u.data.MAC: u.data.UDID = UDID u.data.DT = DT u.data.MAC = MAC u.data.DEV = DEV u.data.VER = VER u.save(Game.rpc_store) else: self.glog(PM_MAC, u=u.data.id, UDID=UDID, MAC=MAC, DEV=DEV, VER=VER) spawn(_log_mac) params = self._get_login_params(sid, u.data.id, t) log.debug(u'loginSNS finish:%s', params) return pack_msg(resp_f, 1, data=params) def glog(self, type, **kw): kw['t'] = type Game.glog.log(kw) def _login_sub(self, user_name, uid, key, sns_type): """ 选取subgame """ sub_ids = None while not sub_ids: sub_ids = self._sub_mgrs.keys() if sub_ids: break log.info('_login_sub wait game init') sleep(1) sub_ids.sort() for sub_mgr_id in sub_ids: mgr, pids = self._sub_mgrs[sub_mgr_id] if len(pids) < self.logic_players: rs, address = mgr.logon(user_name, uid, key, sns_type) return rs, address #如果全部都满人,随机选择 sub_id = random.choice(sub_ids) mgr, count = self._sub_mgrs[sub_id] rs, address = mgr.logon(user_name, uid, key, sns_type) return rs, address @grpc_monitor def distribute(self, func_name, pids, *args, **kw): """ 分发方法调用到各个自进程: func_name: SubPlayerMgr中的方法名 pids: 玩家id列表, pids=None广播所有玩家 """ if pids is not None: pids = set(pids) for mid, (mgr, mids) in self._sub_mgrs.items(): if not mids: continue if pids is None: mpids = None else: mpids = list(pids.intersection(mids)) func = getattr(mgr, func_name) try: func(mpids, *args, **kw) except Exception as err: log.log_except('distribute error:%s(%s, %s)', func_name, args, kw) @wrap_distribute def player_mails(self, pids, mids, _no_result=True): """ 玩家收到新的邮件 """ @wrap_distribute def player_send_msg(self, pids, msg, _no_result=True): """ 广播消息给玩家 pids:None 时广播所有在线玩家 """ @wrap_pickle_result def get_rpc_player(self, pid): mid, sub_mgr = self.get_sub_mgr(pid) if not mid: return addr = self.mgr2addrs.get(mid) proxy = SubPlayerMgr.cls_get_player_proxy(pid, addr=addr, local=0) return proxy @wrap_pickle_result @grpc_monitor def get_rpc_players(self, pids): """ 获取rpc_player列表,用于少量玩家操作 """ rs = [] for pid in pids: proxy = self.get_rpc_player(pid) if proxy: rs.append(proxy) return rs @grpc_monitor def exec_players_func(self, pids, func, has_result=False, _pickle=True): """ 在玩家所在的逻辑进程执行方法,用于一般的玩家操作 func: 定义: def func(player) """ pids = set(pids) rs = {} for mid, (mgr, mids) in self._sub_mgrs.iteritems(): mpids = pids.intersection(mids) if not mpids: continue mpids = tuple(mpids) if has_result: sub_rs = mgr.exec_players_func(mpids, func, _pickle=True) rs.update(sub_rs) else: mgr.exec_players_func(mpids, func, _pickle=True, _no_result=True) return rs def overload(self, m): """ 启动压力测试m分钟 """ self.overload_time = time.time() + m * 60 log.warn('overload start to %s', datetime.datetime.fromtimestamp(self.overload_time)) def is_overload(self): """ 是否处于压力测试状态 """ return time.time() < self.overload_time def set_debug_data(self, ips, status): """ 设置测试的数据 """ self._debug_ips = ips self._debug_status = bool(status) self._players.keys() #状态开启了 if not self._debug_status: return pids = self.get_online_ids() for pid in pids: rpc = self.get_rpc_player(pid) if rpc and rpc.get_ip() not in self._debug_ips: rpc.debug_kick()