PVPProc.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. <?php
  2. namespace loyalsoft;
  3. /**
  4. * PVPProc 竞技场 战斗模块, 挑战玩家镜像
  5. * @version
  6. * 1.0.0 Created at 2017-6-26. by --gwang
  7. * @author gwang (mail@wanggangzero.cn)
  8. * @copyright ? 2017-6-26, SJZ LoyalSoft Corporation & gwang. All rights reserved.
  9. */
  10. class PVPProc {
  11. //
  12. // <editor-fold defaultstate="collapsed" desc=" 常量 ">
  13. /**
  14. * 挑战记录最大条数
  15. */
  16. const maxLogCount = 49;
  17. /**
  18. * 查找对手数量
  19. */
  20. const matchCount = 5;
  21. /**
  22. * 竞技场赛季起始时间戳
  23. */
  24. const pvpStartTs = 1588521600; # 2020年5月4日 0时0分0秒
  25. /**
  26. * 每个赛季持续时常
  27. */
  28. const pvpSeasonLengt = 86400 * 14; # 2周
  29. /**
  30. * 竞技场初始积分
  31. */
  32. const pvpBaseScore = 1000;
  33. /**
  34. * 竞技场 最大上榜人数
  35. */
  36. const pvpMaxRank = 500;
  37. // </editor-fold>
  38. /**
  39. * [6803] 挑战 - 查询对手信息 等级、头像、昵称、战队信息(言灵师,等级,星级,武器,技能,言灵)
  40. * @param req $req
  41. */
  42. public static function GetChallengeAdversaryInfo($req) {
  43. $targetUID = $req->paras[0]; # 参数: 对手的UID
  44. $uinfo = UserProc::getUserInfo($req->mem, $req->zoneid, $targetUID); # 读取玩家信息
  45. if (null == $uinfo) {
  46. Err(ErrCode::user_no_err);
  47. }
  48. $team = JsonUtil::decode($uinfo->game->heroTeamConfig);
  49. $heros = new \stdClass();
  50. $curTeamId = $team->curUseTeamID;
  51. $arr_nil = array();
  52. var_dump($team->teamDic->$curTeamId);
  53. foreach ($team->teamDic->$curTeamId->heros as $i => $hid) {
  54. if ($hid > 0) {
  55. $n_hid = $hid - 10000;
  56. $heros->$n_hid = $uinfo->game->heros->collectHeros->$hid;
  57. }
  58. }
  59. $adversary = array(# # 拼装玩家信息
  60. 'uid' => $targetUID,
  61. 'name' => my_null_default($uinfo->game->name, ""),
  62. 'level' => my_null_default($uinfo->game->level, 1),
  63. 'headImg' => my_null_default($uinfo->game->img, ""),
  64. // 'skills' => null, # # skills暂时没有实例数据
  65. 'equipment' => array("equipments" => my_null_default($uinfo->game->store->equipment, new \stdClass())), # 武器
  66. 'yanling' => array("items" => my_null_default($uinfo->game->store->yanling, new \stdClass())), # 言灵
  67. 'heros' => my_null_default($heros, new \stdClass()), # # 英雄集合
  68. );
  69. $result = array(# # 拼装返回值
  70. "adversaryInfo" => $adversary
  71. );
  72. return Resp::ok($result);
  73. }
  74. /**
  75. * [6804] 挑战 - 记录挑战结果
  76. * @param req $req
  77. */
  78. static function LogChallengeInfo($req) {
  79. // 参数: 对手uid,对手昵称,对手头像, 对战结果, 胜利者的留言(失败时无法留言"")
  80. list($targetUID, $name, $headImg, $win, $msg) = $req->paras;
  81. $key_mine = MemKey_User::OffensiveLog_zset($req->zoneid, $req->uid);
  82. $key_target = MemKey_User::DefensiveLog_zset($req->zoneid, $targetUID);
  83. $ts = now(); # 记录时间戳
  84. $req->mem->zadd($key_mine, array(# # 组装被挑战对手信息
  85. JsonUtil::encode(array(
  86. 'uid' => my_null_default($targetUID, "-"),
  87. 'name' => my_null_default($name, ""),
  88. 'headImg' => my_null_default($headImg, ""),
  89. 'win' => my_null_default($win, false),
  90. 'msg' => my_null_default($msg, ""),
  91. 'ts' => $ts
  92. )) => $ts));
  93. $req->mem->zremrangebyrank($key_mine, self::maxLogCount, -1); # 保留50条数据
  94. $req->mem->zadd($key_target, array(# # 组装挑战者信息
  95. JsonUtil::encode(array(
  96. 'uid' => $req->uid,
  97. 'name' => $req->userInfo->game->name,
  98. 'headImg' => $req->userInfo->game->img,
  99. 'win' => !my_null_default($win, false),
  100. 'msg' => my_null_default($msg, ""),
  101. 'ts' => $ts
  102. )) => $ts));
  103. $req->mem->zremrangebyrank($key_target, self::maxLogCount, -1); # 保留50条数据
  104. // 暂无发放奖励流程
  105. // 更新每日任务
  106. // 返回
  107. return Resp::ok(); # 返回成功
  108. }
  109. /**
  110. * [6805] 挑战 - 拉取挑战记录
  111. * @param req $req
  112. */
  113. static function GetChagllengeLog($req) {
  114. // 参数:无
  115. $key_off = MemKey_User::OffensiveLog_zset($req->zoneid, $req->uid);
  116. $key_def = MemKey_User::DefensiveLog_zset($req->zoneid, $req->uid);
  117. // 拉取自己的挑战记录
  118. $offLog = $req->mem->zrange($key_off, 0, self::maxLogCount);
  119. $defLog = $req->mem->zrange($key_def, 0, self::maxLogCount);
  120. // Ps. 挑战记录分为2个榜, 且按照时间戳记录,晚于指定时间戳的判定为未读消息,挑战记录最多记录50条
  121. // if (!CommUtil::isPropertyExists($req->userInfo->game->privateState, "lastCheckDefLog")) {
  122. $req->userInfo->game->privateState->lastCheckDefLog_ts = now(); # 记录时间戳
  123. // }
  124. UserProc::updateUserInfo(); # 回写数据
  125. // 记录拉取时间戳(在主界面有个未读消息条数显示, 需要靠最后拉取时间戳对比, 时间戳之后的消息是未读消息)
  126. // 回传数据记录
  127. array_walk($offLog, function (&$i) { # 解码一下
  128. $i = JsonUtil::decode($i);
  129. });
  130. array_walk($defLog, function (&$i) { # 解码一下
  131. $i = JsonUtil::decode($i);
  132. });
  133. return Resp::ok(array(
  134. 'offLog' => $offLog,
  135. 'defLog' => $defLog
  136. ));
  137. }
  138. // <editor-fold defaultstate="collapsed" desc=" 竞技商店 ">
  139. /**
  140. * 王刚 16:23:39 (2020.5.9)
  141. 商店现在的模式定位:
  142. 商店显示所有物品, 刷新时是重置购买/售罄记录
  143. 刘海 16:24:23 (2020.5.9)
  144. 没错
  145. */
  146. /**
  147. * [6820] 竞技商店 主界面
  148. * @param req $req
  149. */
  150. public static function pvpShopMain($req) {
  151. $pvp = new UserPVPModel($req->userInfo->game->pvp); # PVP信息
  152. if ($pvp->shopRefreshTs < now()) { # 检查刷新时间<now => 刷新商品列表
  153. $pvp->shopRefreshTs = now() + glc()->PVP_shop_refresh_interval; # 更新刷新时间
  154. $pvp->curShopItems = GameConfig::pvp_shop(); # 重刷道具
  155. $req->userInfo->game->pvp = $pvp; # 回写
  156. UserProc::updateUserInfo();
  157. }
  158. return Resp::ok($pvp); // 返回
  159. }
  160. /**
  161. * [6821] 竞技 商店 购买道具
  162. * @param req $req
  163. */
  164. public static function pvpShopBuy($req) {
  165. $index = $req->paras[0]; # 参数:道具索引(typeId)
  166. $pvp = new UserPVPModel($req->userInfo->game->pvp); # PVP 数据
  167. my_Assert(CommUtil::isPropertyExists($pvp->curShopItems, $index), ErrCode::err_innerfault); # 没有找到改商品
  168. // isEditor() && $citem = new \sm_pvp_shop();
  169. $citem = $pvp->curShopItems->$index; # 查询物品数据
  170. my_Assert($citem->sealed == 0, ErrCode::pvp_item_soldout); # 防御道具已售罄
  171. my_Assert($citem->pricetype == 5, ErrCode::pay_price_err); # 防御定价异常
  172. my_Assert($pvp->pvpCoins > $citem->price, ErrCode::pvp_coinnotenough); # pvp币不足
  173. $citem->sealed += 1; # 设置已售罄/已购买标志
  174. $pvp->pvpCoins -= $citem->price; # 扣除竞技币
  175. StoreProc::AddMultiItemInStore($req, $citem->goods); // 发放道具
  176. $req->userInfo->game->pvp = $pvp; // 回写数据
  177. UserProc::updateUserInfo();
  178. return Resp::ok(); // 返回
  179. }
  180. /**
  181. * [6822] 竞技 商店 刷新道具
  182. * @param req $req
  183. */
  184. public static function pvpShopRefresh($req) {
  185. // 扣除刷新消耗
  186. $pvp = new UserPVPModel($req->userInfo->game->pvp);
  187. $costCash = glc()->PVP_shop_refresh_cash;
  188. my_Assert(UserGameModel::Consume_Cash($req->userInfo->game, $costCash), ErrCode::notenough_cash_msg);
  189. $pvp->shopRefreshTs = now() + glc()->PVP_shop_refresh_interval; # 更新刷新时间
  190. $pvp->curShopItems = GameConfig::pvp_shop(); # 重刷道具
  191. $req->userInfo->game->pvp = $pvp; # 回写
  192. UserProc::updateUserInfo(); # 回写玩家数据
  193. return Resp::ok($pvp); # 返回
  194. }
  195. /**
  196. * 【移动支付】获取神秘商城物品
  197. * 刷新规则: 根据玩家拥有的英雄、装备、道具等数据进行刷新。(因英雄、道具、装备数量不足以支撑该刷新规则,目前先按照随机刷新做,几率平等)
  198. * @param Req $req
  199. */
  200. public static function m_pay_getDynamic($req) {
  201. $user = $req->userInfo->game;
  202. $userSecretshop = new userSecretshopModel($user->userSecretshop);
  203. // 参数提取
  204. $refreshType = $req->paras[0]; # 刷新类型(参数)0,不刷,1,免费刷,2,钻石刷
  205. switch ($refreshType) {
  206. case 1: # 免费刷
  207. if (now() < $userSecretshop->lastRefreshTs + glc()->secretshop_refresh_interval) { // 检查是否达到免费刷新时间了, 执行自动更新
  208. return Resp::err(ErrCode::pay_secretshopt_freeRefresh_Time);
  209. }
  210. break;
  211. case 2: # 钻石刷
  212. if (glc()->secretshop_refresh_maxtimes <= $userSecretshop->refreshedTimes) { // 检查刷新次数, 已达上限, 返回错误信息
  213. return Resp::err(ErrCode::pay_refresh_times);
  214. } # 可以继续刷新,
  215. $cishu = $userSecretshop->refreshedTimes + 1; # 下次
  216. $amt = GameConfig::secretshop_refresh_getItem($cishu)->price;
  217. if (!UserGameModel::Consume_Cash($user, $amt)) { # 扣除本次所需费用, 余额不足, 返回错误信息
  218. return Resp::err(ErrCode::notenough_cash_msg);
  219. }
  220. $userSecretshop->refreshedTimes++; # 增加当天付费刷新计数
  221. break;
  222. case 0: # 不刷
  223. default : # 默认不刷
  224. // do nothing.
  225. break;
  226. }
  227. if ($refreshType != 0) { # 是否刷新
  228. $err = self::refreshDynamicShopItems($req, $userSecretshop); # 更新物品表
  229. if ($err) {
  230. return Resp::err($err);
  231. }
  232. $user->userSecretshop = $userSecretshop;
  233. $req->userInfo->game = $user;
  234. UserProc::updateUserInfo();
  235. }
  236. // 返回最新物品表
  237. return Resp::ok(array(# # 成功后将最新的玩家数据返回给客户端
  238. 'gold' => $user->gold,
  239. 'tili' => $user->tili,
  240. 'cash' => $user->cash,
  241. 'uss' => $userSecretshop, # # 当前神秘商城数据
  242. ));
  243. }
  244. /**
  245. * 更新神秘商城物品
  246. * @param Req $req
  247. * @param UserSecretshopModel $userSecretshop Description
  248. */
  249. private static function refreshDynamicShopItems($req, &$userSecretshop) {
  250. $userSecretshop->lastRefreshTs = now();
  251. // todo: 这里补完更新物品的函数, // 第一版: 随机
  252. $userSecretshop->currentItems = ObjectInit();
  253. for ($i = 1; $i <= 3; $i++) { # 3种类型的商品
  254. $arr = GameConfig::secretshop_goodsType_getItem($i);
  255. if (count($arr) > 0) {
  256. $err = self::Dice(GameConfig::secretshop_goodsType_getItem($i), 1, $userSecretshop); # 一个种类一次1个物品
  257. if ($err) {
  258. return $err;
  259. }
  260. }
  261. }
  262. return ErrCode::ok;
  263. }
  264. // // </editor-fold>
  265. //
  266. // <editor-fold defaultstate="collapsed" desc=" 竞技场 681x">
  267. //
  268. /**
  269. * 辅助方法:取当前赛季的编号(从赛季起始开始算起)
  270. * @return int
  271. */
  272. public static function GetCurSeasonID() {
  273. $n = ceil((now() - self::pvpStartTs ) / self::pvpSeasonLengt); # 进一取整
  274. return $n;
  275. }
  276. /**
  277. * 辅助方法:取指定赛季的结束时间戳
  278. * @param int $seasonID
  279. * @return int
  280. */
  281. public static function GetSeasonEndTs($seasonID) {
  282. $ts = self::pvpStartTs + $seasonID * self::pvpSeasonLengt; # 从起始点开始 + n个赛季时常
  283. return $ts;
  284. }
  285. /**
  286. * [6810] 竞技场 拉取主界面信息
  287. * @param Req $req
  288. */
  289. static function pvpMainInfo($req) {
  290. $uid = $req->uid; # 快速访问UID
  291. $zoneid = $req->zoneid; # 快速访问zoneid
  292. $pvp = new UserPVPModel($req->userInfo->game->pvp); # 设计玩家pvp数据结构
  293. $seasonId = self::GetCurSeasonID(); # 当前赛季ID
  294. $key = MemKey_GameRun::Game_PVPScoreByZoneSeason_zset($zoneid, $seasonId); # 积分总榜
  295. $score = self::_getScore_by_uid($uid, $key); # 玩家积分
  296. $rank = self::_getRank_by_uid($uid, $key); # 玩家排名
  297. $fPower = HeroProc::CalcUserFightPower($zoneid, $uid, $req->userInfo->game); # 玩家总战力?还是当前防守队伍的战斗力?
  298. $bHasNewLog = true; // todo: 真正查询是否有新战报
  299. $matches = self::getNewMatches($pvp, gMem(), $uid, $zoneid); # 获得新的匹配对手
  300. if ($pvp->haventReward_season > 0 && $pvp->haventReward_season < $seasonId) { # 尚未发放上赛季奖励
  301. $haventKey = MemKey_GameRun::Game_PVPScoreByZoneSeason_zset($zoneid, $pvp->haventReward_season);
  302. // todo:发放上赛季奖励邮件
  303. $rank = self::_getRank_by_uid($uid, $haventKey); # 查询上赛季排名
  304. if ($rank <= self::pvpMaxRank) { # 防御未上榜
  305. foreach (GameConfig::pvp_rankreward() as $cfg) {
  306. $cfg = new \sm_pvp_rankreward();
  307. if ($rank >= $cfg->minRank && $rank <= $cfg->maxRank) { # 找到对应的名次段
  308. EmailProc::SendPvpRankReward_Season($zoneid, $uid, $rank); # 发放奖励邮件(竞技币)
  309. }
  310. }
  311. }
  312. $pvp->haventReward_season = $seasonId; # 更新待发奖赛季
  313. }
  314. if ($pvp->haventReward_tsDay > 0 && $pvp->haventReward_tsDay < tsDay()) { # 尚未发放昨天奖励
  315. $haventKey_day = MemKey_GameRun::Game_PVPScoreByZone_zset_Day($zoneid, $pvp->haventReward_tsDay);
  316. if (!gMem()->exists($haventKey_day) && $pvp->haventReward_tsDay == tsDay() - 1) { # 昨天的积分记录不存在
  317. gMem()->zcopy($key, $haventKey_day); # 复制一份当前积分作为昨天的截止积分榜
  318. } else {
  319. // 不是昨天登录的, 且没有对应的数据 就不再复制当前数据了.直接未上榜处理
  320. }
  321. $rank = self::_getRank_by_uid($uid, $haventKey_day); # 查询上一天排名
  322. if ($rank <= self::pvpMaxRank) { # 防御未上榜
  323. foreach (GameConfig::pvp_rankreward() as $cfg) {
  324. $cfg = new \sm_pvp_rankreward();
  325. if ($rank >= $cfg->minRank && $rank <= $cfg->maxRank) { # 找到对应的名次段
  326. EmailProc::SendPvpRankReward_Lastday($zoneid, $uid, $rank); # 发放奖励邮件(竞技币)
  327. }
  328. }
  329. }
  330. $pvp->haventReward_tsDay = tsDay(); # 更新待发放奖励日期
  331. }
  332. // 组装 返回值结构
  333. $ret = array(
  334. 'score' => $score, # # 自己的积分
  335. 'rank' => $rank, # # 自己的排名
  336. 'pvpCoins' => $pvp->pvpCoins, # # 自己的竞技币
  337. 'fPower' => $fPower, # # 自己的总战力
  338. 'fightTicket' => $pvp->fightTicket, # # 自己的挑战票
  339. 'defTeam' => $pvp->defTeam, # # 自己的防守队伍
  340. 'bHasNewFightLog' => $bHasNewLog, # # 是否有战报刷新
  341. 'matches' => $matches, # # 对手列表
  342. );
  343. return Resp::ok($ret); # 返回
  344. }
  345. /**
  346. * [6811] 竞技场 刷新对手
  347. * @param Req $req
  348. */
  349. static function pvp_Refresh($req) {
  350. // 刷新无花费, 间隔时间3秒(客户端控制得了)
  351. $pvp = new UserPVPModel($req->userInfo->game->pvp);
  352. $ts = now();
  353. my_Assert($pvp->nextRefreshTs < $ts, ErrCode::pvp_refresh_time); # 验证时间间隔
  354. $pvp->curMatches = self::getNewMatches($pvp, $req->uid, $req->zoneid);
  355. $pvp->nextRefreshTs = now(3);
  356. $req->userInfo->game->pvp = $pvp;
  357. UserProc::updateUserInfo(); # 回写数据
  358. $ret = array(
  359. 'curMatches' => $pvp->curMatches # # 当前对手清单
  360. );
  361. return Resp::ok($ret);
  362. }
  363. /**
  364. * [6812] 竞技场 挑战对手xx
  365. * @param Req $req
  366. */
  367. static function pvp_PK($req) {
  368. $uid = $req->uid;
  369. $target_uid = $req->paras[0]; # 对手id
  370. $result = $req->paras[1]; # 胜负结果 0负,1胜
  371. $pvp = $req->userInfo->game->pvp;
  372. if ($pvp->freeFightTickets > 0) { # 有免费挑战票,先扣除免费的
  373. $pvp->freeFightTickets -= 1;
  374. } else {
  375. my_Assert($pvp->fightTicket > 0, ErrCode::pvp_no_tickets); # 防御: 挑战票不足
  376. $pvp->fightTicket -= 1; # 扣除挑战票
  377. }
  378. $season = self::GetCurSeasonID(); # 当前赛季
  379. $key = MemKey_GameRun::Game_PVPScoreByZoneSeason_zset($zoneid, $season); # redis key
  380. $RA = self::_getScore_by_uid($uid, $key); # A的积分
  381. $RB = self::_getScore_by_uid($uid, $key); # B的积分
  382. $EA = 1 / (1 + pow(10, ($RA - $RB) / 400)); # A的胜率期望值
  383. $EB = 1 / (1 + pow(10, ($RB - $RA) / 400)); # B的胜率期望值
  384. $K = 32; # 浮动系数, 暂定为32
  385. $SA = $result; # 我的胜负结果
  386. $SB = ($result == 1) ? 0 : 1; # 对手的胜负结果
  387. $R_A = $RA + $K * ($SA - $EA ); # 我的最终积分
  388. $R_B = $RB + $K * ($SB - $EB); # 对手的最终积分
  389. #
  390. self::_addScore_by_uid($zoneid, $uid, $R_A - $RA); # 更新A的积分
  391. self::_addScore_by_uid($zoneid, $uid, $R_B - $RB); # 更新B的积分
  392. $req->userInfo->game->pvp = $pvp;
  393. UserProc::updateUserInfo(); # 回写数据
  394. $ret = array(# # 组装返回值
  395. 'freeFightTickets' => $pvp->freeFightTickets, # # 自己剩余免费票
  396. 'fightTicket' => $pvp->fightTicket, # # 自己剩余挑战票
  397. 'RA' => $RA, # # 自己挑战之前积分
  398. 'RB' => $RB, # # 对手挑战之前积分
  399. 'R_A' => $R_A, # # 自己最新积分
  400. 'R_B' => $R_B, # # 对手最新积分
  401. );
  402. return Resp::ok($ret); # 返回
  403. }
  404. /**
  405. * [6813] 竞技场 设置防守队伍
  406. * @param req $req
  407. */
  408. public static function pvp_setTeam($req) {
  409. $heros = $req->paras[0]; # para: 新阵容
  410. $pvp = new UserPVPModel($req->userInfo->game->pvp);
  411. if (is_array($heros)) { # 更新阵容
  412. $pvp->defTeam->heros = $heros;
  413. }
  414. $req->userInfo->game->pvp = $pvp;
  415. UserProc::updateUserInfo(); # 回存数据
  416. return Resp::ok($pvp->defTeam); # 返回
  417. }
  418. /**
  419. * [6814] 购买挑战票
  420. * @param Req $req
  421. * @return type
  422. */
  423. static function pvp_buyticket($req) {
  424. $amt = $req->paras[0]; # 购买数量
  425. # 检查并扣除消耗
  426. # 增加挑战票
  427. # 回存数据
  428. # 返回
  429. my_Assert($amt > 0, ErrCode::paras_err); # 数量>0
  430. $pvp = new UserPVPModel($req->userInfo->game->pvp);
  431. $g = glc();
  432. $costCash = $g->PVP_pk_ticket_price * $amt; # 计算消耗钻石
  433. my_Assert($costCash > 0, ErrCode::pvp_ticket_cost_ilegal); # 定价数据异常
  434. if (!UserGameModel::Consume_Cash($req->userInfo->game, $costCash)) { # 扣除钻石失败
  435. return Resp::err(ErrCode::notenough_cash_msg);
  436. }
  437. $pvp->fightTicket += $amt; # 发放挑战票
  438. $req->userInfo->game->pvp = $pvp;
  439. UserProc::updateUserInfo(); # 回写玩家数据
  440. $ret = array(
  441. 'tickets' => $pvp->fightTicket,
  442. 'costCash' => $costCash,
  443. 'userCash' => $req->userInfo->game->cash
  444. );
  445. return Resp::ok($ret); # 返回
  446. }
  447. /**
  448. * [6815] 竞技场 拉取榜单数据
  449. * @param Req $req
  450. */
  451. static function pvp_getRank($req) {
  452. $maxAmt = 10; # 一次最多取10个玩家信息
  453. $zoneid = $req->zoneid;
  454. $index = $req->paras[0]; # 起始(0)
  455. $n = $req->paras[1]; # 数量, (n<=max)
  456. #
  457. if ($n < 1 || $n > $maxAmt) { # 防御非法情况
  458. $n = $maxAmt;
  459. }
  460. $arr = self::getRankPlayers($req->mem, $zoneid, $index - 1, ($index + $n) - 1);
  461. //
  462. $rankId = $index;
  463. $result = ObjectInit();
  464. if (count($arr)) {
  465. foreach ($arr as $key => $value) {// $value 的数据类型是array
  466. $value["uid"] = $key;
  467. $result->$rankId = $value;
  468. $rankId += 1;
  469. }
  470. }
  471. $ret = array(
  472. 'dic' => $result
  473. );
  474. return Resp::ok($ret);
  475. }
  476. /**
  477. * [6816] 竞技场 查看战报
  478. * @param Req $req
  479. */
  480. static function pvp_getFightLogs($req) {
  481. // 提取主动挑战+被挑战记录
  482. // 更新下刷新时间
  483. // 返回
  484. // 参数:无
  485. $key_off = MemKey_User::PVP_OffensiveLog_zset($req->zoneid, $req->uid);
  486. $key_def = MemKey_User::PVP_DefensiveLog_zset($req->zoneid, $req->uid);
  487. // 拉取自己的挑战记录
  488. $offLog = $req->mem->zrange($key_off, 0, self::maxLogCount); # 主动挑战数据
  489. $defLog = $req->mem->zrange($key_def, 0, self::maxLogCount); # 防守数据
  490. // Ps. 挑战记录分为2个榜, 且按照时间戳记录,晚于指定时间戳的判定为未读消息,挑战记录最多记录50条
  491. $pvp = new UserPVPModel($req->userInfo->game->pvp); # 玩家竞技场数据
  492. $pvp->lastCheckDefLog_ts = now(); # 记录时间戳
  493. UserProc::updateUserInfo(); # 回写数据
  494. array_walk($offLog, function (&$i) { # 解码一下
  495. $i = JsonUtil::decode($i);
  496. });
  497. array_walk($defLog, function (&$i) { # 解码一下
  498. $i = JsonUtil::decode($i);
  499. });
  500. return Resp::ok(array(
  501. 'offLog' => $offLog,
  502. 'defLog' => $defLog
  503. ));
  504. return Resp::ok($ret);
  505. }
  506. // ---------------- 辅助函数 -----------------------
  507. // <editor-fold defaultstate="collapsed" desc=" 辅助 函数 ">
  508. /**
  509. * 修改积分
  510. * @param int $zoneid
  511. * @param string $uid
  512. * @param int $addValue 可以是负值
  513. */
  514. private static function _addScore_by_uid($zoneid, $uid, $addValue) {
  515. $mem = gMem();
  516. $seasonId = self::GetCurSeasonID();
  517. $key = MemKey_GameRun::Game_PVPScoreByZoneSeason_zset($zoneid, $seasonId); # 积分榜
  518. $score = $mem->zscore($key, $uid);
  519. if (is_null($score) || $score <= 0) { # 分数异常, 理论上不会出现负分
  520. $score = self::pvpBaseScore; # 新手基础分
  521. $mem->zadd($key, array($uid => $score)); # 重置玩家积分
  522. }
  523. $newscore = $mem->zincrby($key, $uid, $addValue); # 返回最新值
  524. return $newscore;
  525. }
  526. /**
  527. * 竞技场 查询玩家的积分
  528. * @param type $uid
  529. * @return int
  530. */
  531. static function _getScore_by_uid($uid, $key) {
  532. if ("" == $uid) {
  533. CLog::err('"uid" is Null!');
  534. return 10; # 记录错误并返回一个极低分
  535. }
  536. $mem = gMem();
  537. $score = $mem->zscore($key, $uid);
  538. if (is_null($score) || $score <= 0) { # 分数异常
  539. $score = self::pvpBaseScore; # 新手基础分
  540. $mem->zadd($key, array($uid => $score)); # 更新玩家积分
  541. }
  542. return $score;
  543. }
  544. /**
  545. * 竞技场 查询玩家的排名
  546. * @param string $uid
  547. * @param string $key
  548. * @return int
  549. */
  550. static function _getRank_by_uid($uid, $key) {
  551. $rank = self::pvpMaxRank + 1; # 未上榜
  552. if ("" == $uid) {
  553. CLog::err('"uid" is Null!');
  554. return $rank; # 记录错误并返回未上榜
  555. }
  556. $mem = gMem();
  557. $r = $mem->zrevrank($key, $uid);
  558. if (!is_null($r)) {
  559. $rank = $r; # 有名次,更新
  560. }
  561. return $rank; # 返回
  562. }
  563. /**
  564. * 获取对手匹配结果
  565. * @param UserPVPModel $pvp
  566. * @param CRedisutil $mem
  567. * @param type $uid
  568. * @param type $zoneid
  569. */
  570. private static function getNewMatches($pvp, $uid, $zoneid) {
  571. $seasonID = self::GetCurSeasonID();
  572. $key = MemKey_GameRun::Game_PVPScoreByZoneSeason_zset($zoneid, $seasonID); # redis key
  573. $arr = self::findmatcher($zoneid, $key, $uid, $pvp); # 积分榜查找
  574. if (count($arr) < self::matchCount) { # 再不行, 准备机器人吧
  575. CLog::err('PVP刷对手数量不足, 赶快考虑加入机器人.', 'PVP'); // todo: 这里引入gm对手数据,
  576. }
  577. $ret = self::GetPlayerInfosForPVP(gMem(), $zoneid, $arr);
  578. return $ret;
  579. }
  580. /**
  581. * 查找匹配的对手
  582. * @param type $zoneid
  583. * @param type $key
  584. * @param type $uid
  585. * @param UserPVPModel $pvp
  586. * @param type $arr
  587. */
  588. private static function findmatcher($zoneid, $key, $uid, &$pvp, &$arr) {
  589. // 根据匹配规格获得5个对手即可(1. 排除自己, 2. 最好不要出现已经刷到过的对手)
  590. // 低于玩家当前积分 -- 2个 -5%~-20%之间
  591. // 与玩家当前积分基本持平 1个, ±5%之间
  592. // 高于玩家当前积分 -- 2个 +5%~+40%之间
  593. $mem = gMem();
  594. $rank = self::_getRank_by_uid($uid, $key); # 直接查排名
  595. $score = self::_getScore_by_uid($uid, $key); # 查积分
  596. $zlen = $mem->zlen($key); # 数据长度
  597. $i = 0; # 计数器
  598. $b = false; # 上下标志位
  599. // 计算 比自己弱的 上线下线
  600. $bH = ceil($score * (1 - 0.05)); # 低于当前玩家积分5%
  601. $bL = ceil($score * (1 - 0.2)); # 低于当前玩家积分20%
  602. $aL = ceil($score * (1 + 0.05)); # 高于当前玩家积分5%
  603. $aH = ceil($score * (1 + 0.2)); # 高于当前玩家积分20%
  604. $bPlayerUIDs = gMem()->zrevrangebyscore($key, $bH, $bL, true, true, 0, 2); # 取低于玩家积分的uids2个
  605. $mPlayerUIDs = gMem()->zrevrangebyscore($key, $aL, $bH, true, true, 0, 2); # 取玩家相当的uid2个(以防包含玩家自己)
  606. $aPlayerUIDs = gMem()->zrevrangebyscore($key, $aH, $aL, true, true, 0, 2); # 取高于玩家的uids2个
  607. if (array_key_exists($uid, $mPlayerUIDs)) { # 如果积分相近的那一组包含自己
  608. unset($mPlayerUIDs[$uid]); # 剔除掉
  609. } else { # 或者不包含自己
  610. array_pop($mPlayerUIDs); # 那多一个人,踢掉一个
  611. }
  612. $uidWithScore = array_merge($bPlayerUIDs, $mPlayerUIDs, $aPlayerUIDs); # 合并为玩家列表
  613. return $uidWithScore; # 返回uid集合
  614. }
  615. /**
  616. * 获取榜单玩家
  617. * @param Credisutil $mem
  618. * @param int $zoneid
  619. * @param int $start
  620. * @param int $stop
  621. * @return type
  622. */
  623. private static function getRankPlayers($mem, $zoneid, $start, $stop) {
  624. $key = MemKey_GameRun::Game_PVPScoreByZoneSeason_zset($zoneid, self::GetCurSeasonID());
  625. $retUidsWithScore = $mem->zrevrange($key, $start, $stop, true);
  626. return self::GetPlayerInfosForPVP($mem, $zoneid, $retUidsWithScore);
  627. }
  628. /**
  629. * 拉取玩家信息-pvp模块专用信息
  630. * @param CredisUtil $mem
  631. * @param type $zoneid
  632. * @param type $retUidsWithScore
  633. * @return type
  634. */
  635. private static function GetPlayerInfosForPVP($mem, $zoneid, $retUidsWithScore) {
  636. $retUids = array_keys($retUidsWithScore);
  637. $keysOfUserInfo = array_map(function($u)use($zoneid) {
  638. return MemKey_User::Info_hash($zoneid, $u);
  639. }, $retUids);
  640. $arrUserInfos = $mem->getMulti($keysOfUserInfo);
  641. $arr = ArrayInit();
  642. $i = 0;
  643. foreach ($arrUserInfos as $userGameInfo) { # 遍历
  644. isEditor() && $userGameInfo = new UserGameModel; # 语法辅助
  645. $uid = $retUids[$i++]; # 当前UID
  646. $teamConfig = $userGameInfo->pvp->defTeam; # 防守阵容
  647. $heros = new \stdClass(); # 英雄集合
  648. foreach ($teamConfig->heros as $i => $hid) {
  649. if ($hid > 0) {
  650. $n_hid = $hid - 10000;
  651. $heros->$n_hid = $userGameInfo->game->heros->collectHeros->$hid;
  652. }
  653. }
  654. $fpower = HeroProc::CalcUserFightPower($zoneid, $uid, $userGameInfo); # 计算总战力
  655. $adversary = array(# # 拼装玩家信息
  656. 'uid' => $uid,
  657. 'name' => my_null_default($userGameInfo->game->name, ""),
  658. 'level' => my_null_default($userGameInfo->game->level, 1),
  659. 'headImg' => my_null_default($userGameInfo->game->img, ""),
  660. // 'skills' => null, # # skills暂时没有实例数据
  661. 'equipment' => array("equipments" => my_null_default($userGameInfo->game->store->equipment, new \stdClass())), # 武器
  662. 'yanling' => array("items" => my_null_default($userGameInfo->game->store->yanling, new \stdClass())), # 言灵
  663. 'heros' => my_null_default($heros, new \stdClass()), # # 英雄集合
  664. 'score' => $retUidsWithScore[$uid], # # 积分
  665. 'fpower' => $fpower, # # 总战力
  666. );
  667. $arr[$uid] = $adversary;
  668. }
  669. if (count($arr) <= 0) {
  670. $arr = null;
  671. }
  672. return $arr;
  673. }
  674. // </editor-fold>
  675. //
  676. // </editor-fold>
  677. //
  678. }