listMySelledNfts($account, $type)); foreach ($rows as &$row) { $nftDb = Nft::findNftByOwner($account, $row['token_id']); if (empty($nftDb)) { $nftDb = Nft::getNft($row['token_id']); } // $row['info'] = Nft::toDto($nftDb); // $row['detail'] = Hero::toDtoInfo(Hero::findByTokenId2($row['token_id'])); // if (in_array($row['info']['info']['job'], $job) == false) continue; // if ($row['detail']['hero_lv'] < $lv) continue; // if ($row['detail']['quality'] < $quality) continue; // if ($row['detail']['hero_tili'] < $durability) continue; if (count($search) > 0) { $searchLower = array_map('strtolower', $search); if (!(in_array(strtolower($row['detail']['hero_name']), $searchLower) || in_array(strtolower($row['detail']['token_id']), $searchLower))) continue; } // $row['detail'] = $this->appendChipsInfo($row['detail']); array_push($nfts, $row); } switch ($order_method) { case 1: usort($nfts, $sortByLevel); break; case 2: usort($nfts, $sortByTili); break; case 3: usort($nfts, $sortByStar); break; } } break; case Nft::HERO_TYPE: { $rows = Nft::getNftListByType($account, $type); $rows = array_merge($rows, $this->listMySelledNfts($account, $type)); foreach ($rows as &$row) { $nftDb = Nft::findNftByOwner($account, $row['token_id']); if (empty($nftDb)) { $nftDb = Nft::getNft($row['token_id']); } $row['info'] = Nft::toDto($nftDb); $row['detail'] = Hero::toDtoInfo(Hero::findByTokenId2($row['token_id'])); if (!empty($job)) { if (in_array($row['info']['info']['job'], $job) == false) continue; } if ($row['detail']['hero_lv'] < $lv) continue; if ($row['detail']['quality'] < $quality) continue; if ($row['detail']['hero_tili'] < $durability) continue; if (count($search) > 0) { $searchLower = array_map('strtolower', $search); if (!(in_array(strtolower($row['detail']['hero_name']), $searchLower) || in_array(strtolower($row['detail']['token_id']), $searchLower))) continue; } $row['detail'] = $this->appendChipsInfo($row['detail']); array_push($nfts, $row); } switch ($order_method) { case 1: usort($nfts, $sortByLevel); break; case 2: usort($nfts, $sortByTili); break; case 3: usort($nfts, $sortByStar); break; } } break; case Nft::EQUIP_TYPE: { $rows = Nft::getNftListByType($account, $type); $rows = array_merge($rows, $this->listMySelledNfts($account, $type)); foreach ($rows as &$row) { $nftDb = Nft::findNftByOwner($account, $row['token_id']); if (empty($nftDb)) { $nftDb = Nft::getNft($row['token_id']); } $row['info'] = Nft::toDto($nftDb); $row['detail'] = Gun::toDtoInfo(Gun::findByTokenId2($row['token_id'])); if ($row['detail']['gun_lv'] < $lv) continue; if ($row['detail']['quality'] < $quality) continue; if ($row['detail']['durability'] < $durability) continue; if (count($search) > 0) { $searchLower = array_map('strtolower', $search); if (!(in_array(strtolower($row['detail']['gun_name']), $searchLower) || in_array(strtolower($row['detail']['token_id']), $searchLower))) continue; } $row['detail'] = $this->appendChipsInfo($row['detail']); array_push($nfts, $row); } switch ($order_method) { case 1: usort($nfts, $sortByGunLv); break; case 2: usort($nfts, $sortByDurability); break; case 3: usort($nfts, $sortByStar); break; } } break; case Nft::CHIP_TYPE: { $rows = Nft::getNft1155List($account, $type); $rows = array_merge($rows, $this->listMySelledNfts($account, $type)); foreach ($rows as &$row) { $row['detail'] = Chip::toDto(Chip::getChipByTokenId($row['token_id'])); if (!in_array($row['detail']['chip_type'], $job)) continue; if ($row['detail']['chip_grade'] < $lv) continue; if (count($search) > 0) { $searchLower = array_map('strtolower', $search); if (!(in_array(strtolower($row['detail']['chip_name']), $searchLower) || in_array(strtolower($row['detail']['token_id']), $searchLower))) continue; } array_push($nfts, $row); } switch ($order_method) { case 1: usort($nfts, $sortByGrade); break; case 2: usort($nfts, $sortByPower); break; } } break; case Nft::FRAGMENT_TYPE: { $rows = Nft::getNft1155List($account, $type); $rows = array_merge($rows, $this->listMySelledNfts($account, $type)); foreach ($rows as &$row) { $nftDb = Nft::findNftByOwner($account, $row['token_id']); if (empty($nftDb)) { $nftDb = Nft::getNft($row['token_id']); } $row['detail'] = $this->getNftGameData($nftDb); if (!in_array($row['detail']['type'], $job)) continue; if (count($search) > 0) { $searchLower = array_map('strtolower', $search); if (!(in_array(strtolower($row['detail']['name']), $searchLower) || in_array(strtolower($row['detail']['token_id']), $searchLower))) continue; } array_push($nfts, $row); } usort($nfts, $sortByTokenId); } break; default: { } } return $nfts; } public function listSellNfts() { $account = strtolower(getReqVal('account', '')); $token = getReqVal('token', ''); $start = getReqVal('start', 0); $page_size = getReqVal('page_size', 10); $order_method = getReqVal('order_method', 0); $order_asc = getReqVal('order_asc', 1); $type = getReqVal('type', 1); $job_filters = getReqVal('job_filters', ''); $job_filter_array = explode('|', $job_filters); $search_filters = getReqVal('search_filters', ''); if ($search_filters != '') { $search_filter_array = explode('|', $search_filters); } else { $search_filter_array = array(); } $lv_filter = getReqVal('lv_filter', 0); $quality_filter = getReqVal('quality_filter', 0); $durability_filter = getReqVal('durability_filter', 0); $amount_filter = getReqVal('amount_filter', 0); $amount_filter_array = explode('|', $amount_filter); $price_filter = getReqVal('price_filter', ''); $price_filter_array = explode('|', $price_filter); $job_filter_fn = function ($f) { $str = ''; $arr = array(); foreach ($f as $v) { if (!empty($v)) { array_push($arr, 'c_job=\'' . $v . '\' '); } } if (count($arr) > 0) { $str = implode('OR ', $arr); $str = 'AND (' . $str . ') '; } return $str; }; $price_filter_fn = function ($f) { if (count($f) == 2) { $low = $f[0]; $top = $f[1]; return 'AND s_price>=' . $low . ' AND s_price<=' . $top . ' '; } return ''; }; $amount_filter_fn = function ($f) { if (count($f) == 2) { $low = $f[0]; $top = $f[1]; return 'AND amount>=' . $low . ' AND amount<=' . $top . ' '; } return ''; }; $lv_filter_fn = function ($f) { $f = (int) $f; return 'AND c_lv>=' . $f . ' '; }; $quality_filter_fn = function ($f) { $f = (int) $f; return 'AND c_quality>=' . $f . ' '; }; $durability_filter_fn = function ($f) { $f = (int) $f; return 'AND c_durability>=' . $f . ' '; }; $search_filter_fn = function ($f) { $str = ''; $arr_options = array(); foreach ($f as $v) { if (!empty($v)) { array_push($arr_options, 'c_name=\'' . $v . '\' OR token_id=\'' . $v . '\' '); } } if (count($arr_options) > 0) { $str = implode('OR ', $arr_options); $str = 'AND (' . $str . ') '; } return $str; }; $order_fn = function ($method, $asc) { switch ($method) { case 2: return 'ORDER BY s_price ' . ($asc == 0 ? 'ASC' : 'DESC') . ' '; break; case 3: return 'ORDER BY c_quality ' . ($asc == 0 ? 'ASC' : 'DESC') . ' '; break; case 4: return 'ORDER BY c_lv ' . ($asc == 0 ? 'ASC' : 'DESC') . ' '; break; case 5: return 'ORDER BY c_durability ' . ($asc == 0 ? 'ASC' : 'DESC') . ' '; break; // 所有其他不正常的排序都执行最新上架 case 1: default: return 'ORDER BY createtime ' . ($asc == 0 ? 'ASC' : 'DESC') . ' '; break; } return ''; }; $conn = myself()->_getSelfMysql(); $now = myself()->_getNowTime(); $counts = $conn->execQuery( 'SELECT count(idx) as count FROM t_market_store ' . 'WHERE token_type=:token_type AND (status=0 OR status=3) AND buytime<:nowThat ' . $job_filter_fn($job_filter_array) . $lv_filter_fn($lv_filter) . $quality_filter_fn($quality_filter) . $durability_filter_fn($durability_filter) . $price_filter_fn($price_filter_array) . $amount_filter_fn($amount_filter_array) . $search_filter_fn($search_filter_array) . $order_fn($order_method, $order_asc), array( ':token_type' => $type, ':nowThat' => $now - 3600 * 24, ) ); $total = $counts[0]['count']; $page_end = $start + $page_size; if ($page_end > $total) { $page_end = $total; $start = $total - 1; $start = intval($start / $page_size) * $page_size; if ($start < 0) $start = 0; } $rows = $conn->execQuery( 'SELECT * FROM t_market_store ' . 'WHERE token_type=:token_type AND (status=0 OR status=3) AND buytime<:nowThat ' . $job_filter_fn($job_filter_array) . $lv_filter_fn($lv_filter) . $quality_filter_fn($quality_filter) . $durability_filter_fn($durability_filter) . $price_filter_fn($price_filter_array) . $amount_filter_fn($amount_filter_array) . $search_filter_fn($search_filter_array) . $order_fn($order_method, $order_asc) . 'LIMIT ' . $start . ',' . $page_size, array( ':token_type' => $type, ':nowThat' => $now - 3600 * 24, ) ); $nfts = array(); for ($x = $start; $x < $page_end; $x++) { $row = $rows[$x % $page_size]; $nftDb = Nft::getNft($row['token_id']); if (!$nftDb) { $nftDb = Nft::findNftByOwner($account, $row['token_id']); // 0x768b5faed6dc69816f33377d214ffaf00dcdd0cf if (!$nftDb) { $nftDb = Nft::findNftByOwner('0xfc628dd79137395f3c9744e33b1c5de554d94882', $row['token_id']); if (!$nftDb) { if ($row['item_id']) { } else { myself()->_rspErr(1, 'nft not exists'); return; } } } } $row['info'] = $nftDb ? Nft::toDto($nftDb) : null; $row['detail'] = $nftDb ? $this->getNftGameData($nftDb) : null; array_push($nfts, $row); } $this->_rspData(array( "total" => $total, "start" => $start, "page_size" => $page_size, 'nfts' => $nfts, )); } public function listMyNfts() { $account = strtolower(getReqVal('account', '')); $token = getReqVal('token', ''); $start = getReqVal('start', 0); $page_size = getReqVal('page_size', 10); $order_method = getReqVal('order_method', 0); $order_asc = getReqVal('order_asc', 1); $type = getReqVal('type', 1); $job_filters = getReqVal('job_filters', ''); $address = $this->_getAddress(); if ($address != $account) { $this->_rspErr(1, 'account not match'); return; } if (empty($job_filters)) { $job_filter_array = array(); } else { $job_filter_array = explode('|', $job_filters); } $search_filters = getReqVal('search_filters', ''); if ($search_filters != '') { $search_filter_array = explode('|', $search_filters); } else { $search_filter_array = array(); } $lv_filter = getReqVal('lv_filter', 0); $quality_filter = getReqVal('quality_filter', 0); $durability_filter = getReqVal('durability_filter', 0); $rows = $this->getNftListByAccountAndType($account, $type, $order_method, $order_asc, $job_filter_array, $search_filter_array, $lv_filter, $quality_filter, $durability_filter); $total = count($rows); $page_end = $start + $page_size; if ($page_end > $total) { $page_end = $total; $start = $total - 1; $start = intval($start / $page_size) * $page_size; if ($start < 0) $start = 0; } $nfts = array(); for ($x = $start; $x < $page_end; $x++) { $row = $rows[$x]; // $this->attach_market_selling($row); array_push($nfts, $row); } $this->_rspData(array( "total" => $total, "start" => $start, "page_size" => $page_size, 'nfts' => $nfts, )); } public function sell() { $address = $this->_getAddress(); if (!$address) { $this->_rspErr(1, 'address not found'); return; } $item_id = getReqVal('item_id', ''); if ($item_id != V_ITEM_GOLD) { $this->_rspErr(1, 'only support gold'); return; } $itemMeta = mt\Item::get($item_id); if (!$itemMeta) { $this->_rspErr(1, 'item_id not found'); return; } $s_price = getReqVal('s_price', ''); if (empty($s_price)) { $this->_rspErr(1, 's_price not found'); return; } if (!is_numeric($s_price)) { $this->_rspErr(1, 's_price must be number'); return; } if ($s_price <= 0) { $this->_rspErr(1, 's_price must > 0'); return; } $amount = getReqVal('amount', 1); if (empty($amount)) { $this->_rspErr(1, 'amount not found'); return; } if (!is_numeric($amount)) { $this->_rspErr(1, 'amount must be number'); return; } if ($amount <= 0) { $this->_rspErr(1, 'amount must > 0'); return; } $conn = myself()->_getSelfMysql(); // 检查是否有足够的物品 $costItems = $this->makeCostItems($item_id, $amount); $lackItem = null; if (!$this->_hasEnoughItems($costItems, $lackItem)) { $this->_rspErr(2, $this->_getLackItemErrMsg($lackItem)); return; } $this->_decItems($costItems); { //埋点 $event = [ 'name' => LogService::MARKET_SELL_GOLD, 'val' => $amount ]; LogService::consumeGold($event); } $c_name = $itemMeta['name']; $c_job = 0; $c_lv = 0; $c_quality = $itemMeta['quality']; $c_durability = 0; $c_type = 0; $c_id = $item_id; $r = SqlHelper::insert( $conn, 't_market_store', array( 'token_id' => '', 'item_id' => $item_id, 'status' => 0, 'owner_address' => $address, 'token_type' => 0, 'amount' => $amount, 'createtime' => myself()->_getNowTime(), 'modifytime' => myself()->_getNowTime(), 's_price' => $s_price, 'c_name' => $c_name, 'c_job' => $c_job, 'c_lv' => $c_lv, 'c_quality' => $c_quality, 'c_durability' => $c_durability, 'c_type' => $c_type, 'c_id' => $c_id, ) ); if (!$r) { $this->_rspErr(3, "sell failed"); return; } $lastId = $this->lastInsertId($conn); $order_id = $this->genOrderId($lastId); $test = SqlHelper::update($conn, 't_market_store', array('idx' => $lastId), array('order_id' => $order_id)); if (!$test) { $this->_rspErr(6, "sell failed"); return; } $this->_rspOk(); } private function genOrderId($id) { $order_id_base = date('YmdHis') . "10000000"; $divIdx = phpcommon\bnToStr(gmp_mod($id, 9999999)); $order_id = phpcommon\bnAdd_s($order_id_base, $divIdx); return $order_id; } private function lastInsertId($conn) { $row = $conn->execQueryOne('SELECT LAST_INSERT_ID() as lastId;', array()); return $row['lastId']; } private function makeCostItems($item_id, $num) { $costItems = array( array( 'item_id' => $item_id, 'item_num' => $num ) ); return $costItems; } public function sellCancel() { $idx = getReqVal('idx', ''); $address = $this->_getAddress(); if (!$address) { $this->_rspErr(1, 'address not found'); return; } $goods = $this->getGoodsByIdx($idx); if (!$goods) { $this->_rspErr(1, 'goods not found, idx:' . $idx); return; } if ($goods['owner_address'] != $address) { $this->_rspErr(1, 'not your goods, idx:' . $idx); return; } $conn = $this->_getSelfMysql(); $r = SqlHelper::update( $conn, 't_market_store', array( 'idx' => $idx, ), array( 'status' => 1, 'modifytime' => $this->_getNowTime(), ) ); if ($r) { $items = array( array( 'item_id' => $goods['item_id'], 'item_num' => $goods['amount'], ) ); $awardService = new services\AwardService(); $propertyChgService = new services\PropertyChgService(); $this->_addItems($items, $awardService, $propertyChgService); { //埋点 $event = [ 'name' => LogService::MARKET_CANCEL_SELL_GOLD, 'val' => $goods['amount'] ]; LogService::productGold($event); } $this->_rspData( array( 'idx' => $idx, 'property_chg' => $propertyChgService->toDto(), ) ); } else { $this->_rspErr(1, 'cancel failed'); } } public function sellUpdatePrice() { $idx = getReqVal('idx', ''); $s_price = getReqVal('s_price', ''); if (empty($s_price)) { $this->_rspErr(1, 's_price not found'); return; } if (!is_numeric($s_price)) { $this->_rspErr(1, 's_price must be number'); return; } $address = $this->_getAddress(); if (!$address) { $this->_rspErr(1, 'address not found'); return; } $goods = $this->getGoodsByIdx($idx); if (!$goods) { $this->_rspErr(1, 'goods not found, idx:' . $idx); return; } if ($goods['owner_address'] != $address) { $this->_rspErr(1, 'not your goods, idx:' . $idx); return; } $conn = $this->_getSelfMysql(); $r = SqlHelper::update( $conn, 't_market_store', array( 'idx' => $idx, ), array( 's_price' => $s_price, 'modifytime' => $this->_getNowTime(), ) ); if (!$r) { $this->_rspErr(1, 'update price failed'); return; } $this->_rspOk(); } public function buy() { $address = $this->_getAddress(); if (!$address) { $this->_rspErr(1, 'address not found'); return; } $idx = getReqVal('idx', ''); $s_price = getReqVal('s_price', ''); if (empty($s_price)) { $this->_rspErr(1, 's_price not found'); return; } if (!is_numeric($s_price)) { $this->_rspErr(1, 's_price not number'); return; } $goods = $this->getGoodsByIdx($idx); if (!$goods) { $this->_rspErr(1, 'goods not found, idx:' . $idx); return; } if ($s_price != $goods['s_price']) { $this->_rspErr(1, 'price not match, idx:' . $idx); return; } $response = services\BlockChainService::gameItemMarketBuy( Transaction::BUY_GOODS_FROM_MARKET_ACTION_TYPE, $goods['owner_address'], $goods['s_price'], $goods['item_id'], $goods['amount'] ); // 不再标记购买状态,改为抢单模式,第一个交易成功者获得商品 // if (!$this->markOrderBuying($idx)) { // $this->_rspErr(1, 'buy failed, update order status failed, idx:' . $idx); // return; // } $item_id = $goods['item_id']; $item_count = $goods['amount']; BcOrder::upsert($response['trans_id'], array( 'item_id' => $item_id, 'item_num' => $item_count, 'order_type' => 1, 'price' => $this->Web3PriceLowFormat($goods['s_price']), 'ext_data' => json_encode(array( 'mode' => MARKET_BUY_MODE_NORMAL, 'idx' => $idx, 'order_id' => $goods['order_id'], )), )); $this->_rspData(array( 'block_chain' => $response, )); } private function Web3PriceLowFormat($price) { $bn2 = phpcommon\bnInit('1000000000000000000'); $ret_price = phpcommon\bnDiv($price, $bn2); return phpcommon\bnToStr($ret_price); } private function getNftGameData($nftRowInfo) { $t = $nftRowInfo['token_type']; $token_id = $nftRowInfo['token_id']; switch ($t) { case Nft::HERO_TYPE: { return $this->appendChipsInfo(Hero::toDtoInfo(Hero::findByTokenId2($token_id))); } break; case Nft::EQUIP_TYPE: { return $this->appendChipsInfo(Gun::toDtoInfo(Gun::findByTokenId2($token_id))); } break; case Nft::CHIP_TYPE: { return Chip::toDto(Chip::getChipByTokenId($token_id)); } break; case Nft::FRAGMENT_TYPE: { return Fragment::ToDto($nftRowInfo); } break; default: { } break; } return array('unknown' => 'unknown game data type, cannot find data'); } private function appendChipsInfo($detail) { $detail['chips_info'] = array(); if (!empty($detail['chip_ids'])) { $chips = explode('|', $detail['chip_ids']); foreach ($chips as $chip) { $chip_info = ""; if (!empty($chip)) { $chip_info = Chip::toDto(Chip::getChipByTokenId($chip)); } array_push($detail['chips_info'], $chip_info); } } return $detail; } private function attach_market_selling(&$row) { $conn = myself()->_getSelfMysql(); $rows = $conn->execQuery( 'SELECT * FROM t_market_store ' . 'WHERE token_id=:token_id AND owner_address=:owner_address AND status=:status', array( ':token_id' => $row['token_id'], ':owner_address' => $row['owner_address'], ':status' => 0, ) ); $count = 0; $link_array = array(); foreach ($rows as $r) { $count += $r['amount']; array_push($link_array, $r['o_link']); } $row['o_link'] = implode('|', $link_array); $row['selling'] = $count; } private function listMySelledNfts($account, $type) { // error_log('listMySelledNfts ' . $account . ' ' . $type); $conn = myself()->_getSelfMysql(); $now = myself()->_getNowTime(); $rows = $conn->execQuery( 'SELECT * FROM t_market_store ' . 'WHERE owner_address=:account AND token_type=:token_type AND (status=0 OR status=3) AND buytime<:nowThat ', array( ':account' => $account, ':token_type' => $type, ':nowThat' => $now - 3600 * 24, ) ); return $rows; } private function normalizeWeb3Price($price) { $bn1 = phpcommon\bnInit($price * pow(10, 8)); $bn2 = phpcommon\bnInit('1000000000000000000'); $ret_price = phpcommon\bnDiv(phpcommon\bnMul($bn1, $bn2), pow(10, 8)); // error_log('normalizeWeb3Price: ' . $ret_price . ' ' . $price * pow(10, 8)); return phpcommon\bnToStr($ret_price); } private function getGoodsByIdx($idx) { $row = SqlHelper::selectOne( myself()->_getSelfMysql(), 't_market_store', array('order_id', 'item_id', 'amount', 's_price', 'owner_address', 'status', 'buytime'), array( 'idx' => $idx, ) ); if (!$row) { return null; } if (!$row['item_id']) { return null; } if (!$row['status']==3 && !$row['status']==0) { return null; } $now = myself()->_getNowTime(); if ($row['buytime'] > $now - 3600 * 24) { return null; } return $row; } private function markOrderBuying($idx) { $r = SqlHelper::update( myself()->_getSelfMysql(), 't_market_store', array( 'idx' => $idx, ), array( 'status' => 3, 'buytime' => myself()->_getNowTime(), ) ); if (!$r) { return false; } return true; } }