From 4b436b149954bc6d1ecd6aae2300a8c9e3322da4 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Mon, 6 Jun 2022 23:52:52 +0900 Subject: [PATCH] =?UTF-8?q?feat,wip:=20=EA=B2=BD=EB=A7=A4=EC=9E=A5=20API?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hwe/func_history.php | 4 +- hwe/sammo/API/Auction/BidBuyRiceAuction.php | 51 +++++++++ hwe/sammo/API/Auction/BidSellRiceAuction.php | 50 +++++++++ hwe/sammo/API/Auction/BidUniqueAuction.php | 12 --- .../Auction/GetActiveResourceAuctionList.php | 101 ++++++++++++++++++ hwe/sammo/API/Auction/OpenBuyRiceAuction.php | 39 ++++--- hwe/sammo/API/Auction/OpenSellRiceAuction.php | 84 +++++++++++++++ hwe/sammo/API/Auction/OpenUniqueAuction.php | 2 +- hwe/sammo/Auction.php | 8 +- hwe/sammo/AuctionBasicResource.php | 45 +++++--- hwe/sammo/AuctionSellRice.php | 3 - hwe/sammo/AuctionUniqueItem.php | 24 ++++- hwe/sql/schema.sql | 2 +- 13 files changed, 372 insertions(+), 53 deletions(-) create mode 100644 hwe/sammo/API/Auction/BidBuyRiceAuction.php create mode 100644 hwe/sammo/API/Auction/BidSellRiceAuction.php create mode 100644 hwe/sammo/API/Auction/GetActiveResourceAuctionList.php create mode 100644 hwe/sammo/API/Auction/OpenSellRiceAuction.php diff --git a/hwe/func_history.php b/hwe/func_history.php index fd5241e4..687e9e52 100644 --- a/hwe/func_history.php +++ b/hwe/func_history.php @@ -90,8 +90,8 @@ function pushAuctionLog($log) { pushRawFileLog(__DIR__."/logs/".UniqueConst::$serverID."/_auctionlog.txt", $log); } -function getAuctionLogRecent(int $count) { - return join('
', array_reverse(getFormattedFileLogRecent(__DIR__."/logs/".UniqueConst::$serverID."/_auctionlog.txt", $count, 300))); +function getAuctionLogRecent(int $count): array { + return array_reverse(getRawFileLogRecent(__DIR__."/logs/".UniqueConst::$serverID."/_auctionlog.txt", $count, 300)); } //DB-based diff --git a/hwe/sammo/API/Auction/BidBuyRiceAuction.php b/hwe/sammo/API/Auction/BidBuyRiceAuction.php new file mode 100644 index 00000000..8c8b1043 --- /dev/null +++ b/hwe/sammo/API/Auction/BidBuyRiceAuction.php @@ -0,0 +1,51 @@ +args); + $v->rule('required', [ + 'auctionID', + 'amount', + ]) + ->rule('int', 'amount') + ->rule('int', 'auctionID'); + + if (!$v->validate()) { + return $v->errorStr(); + } + return null; + } + + public function getRequiredSessionMode(): int + { + return static::REQ_GAME_LOGIN; + } + + public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + $auctionID = $this->args['auctionID']; + $amount = $this->args['amount']; + + $generalID = $session->generalID; + $general = General::createGeneralObjFromDB($generalID); + $auction = new AuctionBuyRice($auctionID, $general); + $result = $auction->bid($amount, true); + + if (is_string($result)) { + return $result; + } + + return null; + } +} diff --git a/hwe/sammo/API/Auction/BidSellRiceAuction.php b/hwe/sammo/API/Auction/BidSellRiceAuction.php new file mode 100644 index 00000000..3acb49fa --- /dev/null +++ b/hwe/sammo/API/Auction/BidSellRiceAuction.php @@ -0,0 +1,50 @@ +args); + $v->rule('required', [ + 'auctionID', + 'amount', + ]) + ->rule('int', 'amount') + ->rule('int', 'auctionID'); + + if (!$v->validate()) { + return $v->errorStr(); + } + return null; + } + + public function getRequiredSessionMode(): int + { + return static::REQ_GAME_LOGIN; + } + + public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + $auctionID = $this->args['auctionID']; + $amount = $this->args['amount']; + + $generalID = $session->generalID; + $general = General::createGeneralObjFromDB($generalID); + $auction = new AuctionSellRice($auctionID, $general); + $result = $auction->bid($amount, true); + + if (is_string($result)) { + return $result; + } + + return null; + } +} diff --git a/hwe/sammo/API/Auction/BidUniqueAuction.php b/hwe/sammo/API/Auction/BidUniqueAuction.php index 9c5c7948..725bae84 100644 --- a/hwe/sammo/API/Auction/BidUniqueAuction.php +++ b/hwe/sammo/API/Auction/BidUniqueAuction.php @@ -9,22 +9,10 @@ use sammo\Validator; use sammo\GameConst; use sammo\General; -use function sammo\buildItemClass; - class BidUniqueAuction extends \sammo\BaseAPI { public function validateArgs(): ?string { - $availableItems = []; - foreach (GameConst::$allItems as $items) { - foreach ($items as $itemKey => $amount) { - if ($amount == 0) { - continue; - } - $availableItems[$itemKey] = $amount; - } - } - $v = new Validator($this->args); $v->rule('required', [ 'auctionID', diff --git a/hwe/sammo/API/Auction/GetActiveResourceAuctionList.php b/hwe/sammo/API/Auction/GetActiveResourceAuctionList.php new file mode 100644 index 00000000..c7057341 --- /dev/null +++ b/hwe/sammo/API/Auction/GetActiveResourceAuctionList.php @@ -0,0 +1,101 @@ +query( + 'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` != 0 ORDER BY `close_date` DESC', + [ + AuctionType::BuyRice->value, + AuctionType::SellRice->value, + ] + ); + + $auctionIDList = []; + foreach ($rawAuctions as $rawAuction) { + $auctionIDList[] = $rawAuction['id']; + } + + $rawHighestBids = Util::convertArrayToDict($db->query( + 'SELECT bid.* FROM `ng_auction_bid` bid INNER JOIN ( + SELECT `auction_id`, MAX(`amount`) as `max_amount` + FROM `ng_auction_bid` + WHERE `auction_id` IN %li + GROUP BY `auction_id` + ORDER BY `amount` + ) AS max_bid + ON bid.`auction_id` = max_bid.`auction_id` AND bid.`amount` = max_bid.`max_amount`', + $auctionIDList, + ) ?? [], 'auction_id'); + /** @var array */ + $highestBids = Util::mapWithKey( + fn ($auctionID, $bid) => AuctionBidItem::fromArray($bid), + $rawHighestBids + ); + + foreach ($rawAuctions as $rawAuction) { + $detail = Json::decode($rawAuction['detail']); + unset($rawAuction['detail']); + $rawAuction = array_merge($rawAuction, $detail); + + $highestBid = $highestBids[$rawAuction['id']] ?? null; + if($highestBid === null){ + $rawAuction['highestBid'] = null; + } + else { + $rawAuction['hightestBid'] = [ + 'amount' => $highestBid->amount, + 'date' => TimeUtil::format($highestBid->date, false), + 'generalID' => $highestBid->generalID, + 'generalName' => $highestBid->data->generalName, + ]; + } + + if ($rawAuction['type'] == AuctionType::BuyRice->value) { + $buyRiceList[] = $rawAuction; + } else { + $sellRiceList[] = $rawAuction; + } + } + + $recentLogs = getAuctionLogRecent(20); + + return [ + 'result' => true, + 'buyRice' => $buyRiceList, + 'sellRice' => $sellRiceList, + 'recentLogs' => $recentLogs + ]; + } +} diff --git a/hwe/sammo/API/Auction/OpenBuyRiceAuction.php b/hwe/sammo/API/Auction/OpenBuyRiceAuction.php index ef3928ec..c51d128c 100644 --- a/hwe/sammo/API/Auction/OpenBuyRiceAuction.php +++ b/hwe/sammo/API/Auction/OpenBuyRiceAuction.php @@ -4,13 +4,11 @@ namespace sammo\API\Auction; use sammo\Session; use DateTimeInterface; -use sammo\AuctionUniqueItem; +use sammo\AuctionBuyRice; use sammo\Validator; use sammo\GameConst; use sammo\General; -use function sammo\buildItemClass; - class OpenUniqueAuction extends \sammo\BaseAPI { public function validateArgs(): ?string @@ -27,12 +25,17 @@ class OpenUniqueAuction extends \sammo\BaseAPI $v = new Validator($this->args); $v->rule('required', [ - 'itemID', - 'amount' + 'amount', + 'closeTurnCnt', + 'startBidAmount', + 'finishBidAmount', ]) ->rule('int', 'amount') - ->rule('min', 'amount', GameConst::$inheritItemUniqueMinPoint) - ->rule('keyExists', 'itemID', $availableItems); + ->rule('int', 'closeTurnCnt') + ->rule('min', 'amount', 100) + ->rule('max', 'amount', 10000) + ->rule('int', 'startBidAmount') + ->rule('int', 'finishBidAmount'); if (!$v->validate()) { @@ -48,22 +51,34 @@ class OpenUniqueAuction extends \sammo\BaseAPI public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) { - $itemID = $this->args['itemID']; + /** @var int */ $amount = $this->args['amount']; + /** @var int */ + $closeTurnCnt = $this->args['closeTurnCnt']; + + /** @var int */ + $startBidAmount = $this->args['startBidAmount']; + /** @var int */ + $finishBidAmount = $this->args['finishBidAmount']; $generalID = $session->generalID; - $itemObj = buildItemClass($itemID); $general = General::createGeneralObjFromDB($generalID); - $auctionResult = AuctionUniqueItem::openItemAuction($itemObj, $general, $amount); + $auctionResult = AuctionBuyRice::openResourceAuction( + $general, + $amount, + $closeTurnCnt, + $startBidAmount, + $finishBidAmount + ); - if(is_string($auctionResult)) { + if (is_string($auctionResult)) { return $auctionResult; } return [ 'result' => true, - 'auctionID' => $auctionResult->id, + 'auctionID' => $auctionResult->getInfo()->id, ]; } } diff --git a/hwe/sammo/API/Auction/OpenSellRiceAuction.php b/hwe/sammo/API/Auction/OpenSellRiceAuction.php new file mode 100644 index 00000000..deeabb44 --- /dev/null +++ b/hwe/sammo/API/Auction/OpenSellRiceAuction.php @@ -0,0 +1,84 @@ + $amount) { + if ($amount == 0) { + continue; + } + $availableItems[$itemKey] = $amount; + } + } + + $v = new Validator($this->args); + $v->rule('required', [ + 'amount', + 'closeTurnCnt', + 'startBidAmount', + 'finishBidAmount', + ]) + ->rule('int', 'amount') + ->rule('int', 'closeTurnCnt') + ->rule('min', 'amount', 100) + ->rule('max', 'amount', 10000) + ->rule('int', 'startBidAmount') + ->rule('int', 'finishBidAmount'); + + + if (!$v->validate()) { + return $v->errorStr(); + } + return null; + } + + public function getRequiredSessionMode(): int + { + return static::REQ_GAME_LOGIN; + } + + public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + /** @var int */ + $amount = $this->args['amount']; + /** @var int */ + $closeTurnCnt = $this->args['closeTurnCnt']; + + /** @var int */ + $startBidAmount = $this->args['startBidAmount']; + /** @var int */ + $finishBidAmount = $this->args['finishBidAmount']; + $generalID = $session->generalID; + + $general = General::createGeneralObjFromDB($generalID); + + $auctionResult = AuctionSellRice::openResourceAuction( + $general, + $amount, + $closeTurnCnt, + $startBidAmount, + $finishBidAmount + ); + + if (is_string($auctionResult)) { + return $auctionResult; + } + + return [ + 'result' => true, + 'auctionID' => $auctionResult->getInfo()->id, + ]; + } +} diff --git a/hwe/sammo/API/Auction/OpenUniqueAuction.php b/hwe/sammo/API/Auction/OpenUniqueAuction.php index ef3928ec..1a70ec1e 100644 --- a/hwe/sammo/API/Auction/OpenUniqueAuction.php +++ b/hwe/sammo/API/Auction/OpenUniqueAuction.php @@ -63,7 +63,7 @@ class OpenUniqueAuction extends \sammo\BaseAPI return [ 'result' => true, - 'auctionID' => $auctionResult->id, + 'auctionID' => $auctionResult->getInfo()->id, ]; } } diff --git a/hwe/sammo/Auction.php b/hwe/sammo/Auction.php index e4b923f3..94fdb3e7 100644 --- a/hwe/sammo/Auction.php +++ b/hwe/sammo/Auction.php @@ -8,6 +8,7 @@ use DateTimeInterface; use sammo\DTO\AuctionBidItem; use sammo\DTO\AuctionBidItemData; use sammo\DTO\AuctionInfo; +use sammo\Enums\AuctionType; use sammo\Enums\InheritanceKey; use sammo\Enums\RankColumn; use sammo\Enums\ResourceType; @@ -17,6 +18,8 @@ abstract class Auction protected AuctionInfo $info; + static AuctionType $auctionType; + public const COEFF_AUCTION_CLOSE_MINUTES = 24; public const COEFF_EXTENSION_MINUTES_PER_BID = 1 / 6; public const COEFF_EXTENSION_MINUTES_LIMIT_BY_BID = 1; @@ -91,6 +94,9 @@ abstract class Auction throw new \RuntimeException("해당 경매가 없습니다: {$auctionID}"); } $this->info = AuctionInfo::fromArray($rawAuctionInfo); + if($this->info->type !== $this->auctionType){ + throw new \RuntimeException("잘못된 경매 타입입니다: {$this->info->type} != {$this->auctionType}"); + } } public function getInfo(): AuctionInfo @@ -420,7 +426,7 @@ abstract class Auction return false; } - abstract public function bid(int $amount, bool $tryExtendCloseDate = false): ?string; + abstract public function bid(int $amount, bool $tryExtendCloseDate): ?string; abstract protected function rollbackAuction(): void; abstract protected function finishAuction(AuctionBidItem $highestBid, General $bidder): ?string; diff --git a/hwe/sammo/AuctionBasicResource.php b/hwe/sammo/AuctionBasicResource.php index 9dd1d7c9..6fbf7420 100644 --- a/hwe/sammo/AuctionBasicResource.php +++ b/hwe/sammo/AuctionBasicResource.php @@ -13,11 +13,10 @@ abstract class AuctionBasicResource extends Auction { const MIN_AUCTION_AMOUNT = 100; const MAX_AUCTION_AMOUNT = 10000; - static AuctionType $auctionType; static ResourceType $hostRes; static ResourceType $bidderRes; - static public function openBuyRiceAuction(General $general, int $amount, int $closeTurnCnt, int $startBidAmount, int $finishBidAmount): self|string + static public function openResourceAuction(General $general, int $amount, int $closeTurnCnt, int $startBidAmount, int $finishBidAmount): self|string { if ($closeTurnCnt < 1 || $closeTurnCnt > 24) { return '종료기한은 1 ~ 24 턴 이어야 합니다.'; @@ -44,15 +43,18 @@ abstract class AuctionBasicResource extends Auction } $db = DB::db(); - $prevAuctionID = $db->queryFirstField( - 'SELECT id FROM ng_auction WHERE opener_general_id = %i AND finished = 0 AND `type` IN %ls', - $general->getID(), - [AuctionType::BuyRice->value, AuctionType::SellRice->value], - ); - if ($prevAuctionID !== null) { - return '아직 경매가 끝나지 않았습니다.'; + if (!($general instanceof DummyGeneral)) { + $prevAuctionID = $db->queryFirstField( + 'SELECT id FROM ng_auction WHERE opener_general_id = %i AND finished = 0 AND `type` IN %ls', + $general->getID(), + [AuctionType::BuyRice->value, AuctionType::SellRice->value], + ); + if ($prevAuctionID !== null) { + return '아직 경매가 끝나지 않았습니다.'; + } } + $now = new \DateTimeImmutable(); $gameStor = KVStorage::getStorage($db, 'game_env'); $turnTerm = $gameStor->getValue('turnterm'); @@ -89,10 +91,21 @@ abstract class AuctionBasicResource extends Auction return new self($openResult, $general); } + static public function genDummy(): DummyGeneral + { + $dummyGeneral = new DummyGeneral(true); + $dummyGeneral->setVar('name', '상인'); + $dummyGeneral->setVar('gold', static::MAX_AUCTION_AMOUNT * 10); + $dummyGeneral->setVar('rice', static::MAX_AUCTION_AMOUNT * 10); + return $dummyGeneral; + } + protected function rollbackAuction(): void { if ($this->general->getID() === $this->info->openerGeneralID) { $auctionHost = $this->general; + } else if ($this->info->openerGeneralID == 0) { + $auctionHost = $this->genDummy(); } else { $auctionHost = General::createGeneralObjFromDB($this->info->openerGeneralID); } @@ -132,6 +145,8 @@ abstract class AuctionBasicResource extends Auction { if ($this->general->getID() === $this->info->openerGeneralID) { $auctionHost = $this->general; + } else if ($this->info->openerGeneralID == 0) { + $auctionHost = $this->genDummy(); } else { $auctionHost = General::createGeneralObjFromDB($this->info->openerGeneralID); } @@ -174,7 +189,6 @@ abstract class AuctionBasicResource extends Auction $josaYiHost = JosaUtil::pick($auctionHost->getName(), '이'); $josaYiBidder = JosaUtil::pick($bidder->getName(), '이'); - $josaRo = JosaUtil::pick($bidAmount, '로'); $auctionLog = []; $auctionLog[] = "{$auctionID}번 {$hostResName} 경매 성사 : {$auctionHost->getName()}{$josaYiHost} {$hostResName} {$auctionAmount} 판매, {$bidder->getName()}{$josaYiBidder} {$bidAmount} 구매"; @@ -188,7 +202,7 @@ abstract class AuctionBasicResource extends Auction pushAuctionLog(array_map( fn ($log) => - $auctionHost->getLogger()->formatText($log, ActionLogger::EVENT_PLAIN), + $bidder->getLogger()->formatText($log, ActionLogger::EVENT_PLAIN), $auctionLog )); @@ -199,18 +213,18 @@ abstract class AuctionBasicResource extends Auction return null; } - public function bid(int $amount, bool $_tryExtendCloseDate = true): ?string + public function bid(int $amount, bool $tryExtendCloseDate = false): ?string { if ($this->info->openerGeneralID === $this->general->getID()) { return '자신이 연 경매에 입찰할 수 없습니다.'; } - $result = $this->_bid($amount, true); + $result = $this->_bid($amount, $tryExtendCloseDate); - if(is_string($result)){ + if (is_string($result)) { return $result; } - if($amount === $this->info->detail->finishBidAmount){ + if ($amount === $this->info->detail->finishBidAmount) { //즉구, 1턴 후 지급 $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); @@ -218,6 +232,5 @@ abstract class AuctionBasicResource extends Auction $date = (new DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($turnTerm * 60)); $this->shrinkCloseDate($date); } - } } diff --git a/hwe/sammo/AuctionSellRice.php b/hwe/sammo/AuctionSellRice.php index 98e9dd45..5f87ab5e 100644 --- a/hwe/sammo/AuctionSellRice.php +++ b/hwe/sammo/AuctionSellRice.php @@ -2,9 +2,6 @@ namespace sammo; -use sammo\DTO\AuctionBidItem; -use sammo\DTO\AuctionInfo; -use sammo\DTO\AuctionInfoDetail; use sammo\Enums\AuctionType; use sammo\Enums\ResourceType; diff --git a/hwe/sammo/AuctionUniqueItem.php b/hwe/sammo/AuctionUniqueItem.php index 2a64755e..516f211e 100644 --- a/hwe/sammo/AuctionUniqueItem.php +++ b/hwe/sammo/AuctionUniqueItem.php @@ -14,6 +14,9 @@ use sammo\RandUtil; class AuctionUniqueItem extends Auction { + + static AuctionType $auctionType = AuctionType::UniqueItem; + static public function genObfuscatedName(int $id): string { $db = DB::db(); @@ -131,7 +134,7 @@ class AuctionUniqueItem extends Auction } $auction = new static($auctionID, $general); try { - $auction->bid($startAmount); + $auction->bid($startAmount, false); } catch (\Exception $e) { //실패해선 안된다. $msg = $e->getMessage(); @@ -147,7 +150,7 @@ class AuctionUniqueItem extends Auction // 유니크 옥션의 개최자는 운영자이므로 할 일이 없다. } - public function bid(int $amount, bool $tryExtendCloseDate = false): ?string + public function bid(int $amount, bool $tryExtendCloseDate): ?string { $db = DB::db(); @@ -157,10 +160,21 @@ class AuctionUniqueItem extends Auction AuctionType::UniqueItem->value ) ?? []); + $auctionIDList = []; + foreach($openUniqueAuctions as $auction){ + $auctionIDList[] = $auction->id; + } $db = DB::db(); - $rawHighestBids = Util::convertArrayToDict($db->queryFirstRow( - 'SELECT * FROM ng_auction_bid WHERE auction_id IN %i ORDER BY `amount` DESC LIMIT 1', - $this->info->id + $rawHighestBids = Util::convertArrayToDict($db->query( + 'SELECT bid.* FROM `ng_auction_bid` bid INNER JOIN ( + SELECT `auction_id`, MAX(`amount`) as `max_amount` + FROM `ng_auction_bid` + WHERE `auction_id` IN %li + GROUP BY `auction_id` + ORDER BY `amount` + ) AS max_bid + ON bid.`auction_id` = max_bid.`auction_id` AND bid.`amount` = max_bid.`max_amount`', + $auctionIDList, ) ?? [], 'auction_id'); /** @var array */ $highestBids = Util::mapWithKey( diff --git a/hwe/sql/schema.sql b/hwe/sql/schema.sql index 9a153abf..ef37427b 100644 --- a/hwe/sql/schema.sql +++ b/hwe/sql/schema.sql @@ -721,7 +721,7 @@ CREATE TABLE `ng_auction_bid` ( PRIMARY KEY (`no`), UNIQUE INDEX `by_general` (`general_id`, `auction_id`, `amount`), UNIQUE INDEX `by_owner` (`owner`, `auction_id`, `amount`), - INDEX `by_amount` (`auction_id`, `amount`, `date`), + UNIQUE INDEX `by_amount` (`auction_id`, `amount`), CONSTRAINT `aux` CHECK (json_valid(`aux`)) ) COLLATE='utf8mb4_general_ci'