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'