feat,wip: 금,쌀 거래장 관련 로직 조정

This commit is contained in:
2022-06-09 01:21:21 +09:00
parent 3784aa09fc
commit f607350442
9 changed files with 572 additions and 190 deletions
@@ -0,0 +1,69 @@
<?php
namespace sammo\API\Auction;
use sammo\Session;
use DateTimeInterface;
use sammo\AuctionUniqueItem;
use sammo\Validator;
use sammo\GameConst;
use sammo\General;
use function sammo\buildItemClass;
class OpenUniqueAuction 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', [
'itemID',
'amount'
])
->rule('int', 'amount')
->rule('min', 'amount', GameConst::$inheritItemUniqueMinPoint)
->rule('keyExists', 'itemID', $availableItems);
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)
{
$itemID = $this->args['itemID'];
$amount = $this->args['amount'];
$generalID = $session->generalID;
$itemObj = buildItemClass($itemID);
$general = General::createGeneralObjFromDB($generalID);
$auctionResult = AuctionUniqueItem::openItemAuction($itemObj, $general, $amount);
if(is_string($auctionResult)) {
return $auctionResult;
}
return [
'result' => true,
'auctionID' => $auctionResult->id,
];
}
}
+121 -22
View File
@@ -12,7 +12,7 @@ use sammo\Enums\InheritanceKey;
use sammo\Enums\RankColumn;
use sammo\Enums\ResourceType;
class Auction
abstract class Auction
{
protected AuctionInfo $info;
@@ -26,22 +26,15 @@ class Auction
public const MIN_EXTENSION_MINUTES_LIMIT_BY_BID = 5;
public const MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY = 5;
static public function openAuction(AuctionInfo $info, General $general): int|string
protected AuctionBidItem|null|false $_highestBid = false;
static protected function openAuction(AuctionInfo $info, General $general): int|string
{
$db = DB::db();
if ($info->id !== null) {
return 'id가 지정되어 있습니다.';
}
$prevAuctionID = $db->queryFirstField(
'SELECT id FROM ng_auction WHERE opener_general_id = %i AND finished = 0 AND `type` = %s',
$general->getID(),
$info->type
);
if ($prevAuctionID !== null) {
return '아직 경매가 끝나지 않았습니다.';
}
$db->insert('ng_auction', $info->toArray());
return $db->insertId();
}
@@ -49,15 +42,31 @@ class Auction
public function getHighestBid(): ?AuctionBidItem
{
$db = DB::db();
$rawHighestBid = $db->queryFirstRow(
'SELECT * FROM ng_auction_bid WHERE auction_id = %i ORDER BY `amount` DESC LIMIT 1',
$this->info->id
);
if($this->_highestBid !== false){
return $this->_highestBid;
}
if (!$this->info->detail->isReverse) {
$rawHighestBid = $db->queryFirstRow(
'SELECT * FROM ng_auction_bid WHERE auction_id = %i ORDER BY `amount` DESC LIMIT 1',
$this->info->id
);
} else {
$rawHighestBid = $db->queryFirstRow(
'SELECT * FROM ng_auction_bid WHERE auction_id = %i ORDER BY `amount` ASC LIMIT 1',
$this->info->id
);
}
if (!$rawHighestBid) {
$this->_highestBid = null;
return null;
}
return AuctionBidItem::fromArray($rawHighestBid);
$highestBid = AuctionBidItem::fromArray($rawHighestBid);
$this->_highestBid = $highestBid;
return $highestBid;
}
public function getMyPrevBid(): ?AuctionBidItem
@@ -89,6 +98,18 @@ class Auction
return $this->info;
}
public function shrinkCloseDate(?DateTimeInterface $date): ?string{
if($date === null){
$date = new DateTimeImmutable();
}
$this->info->closeDate = $date;
$db = DB::db();
$db->update('ng_auction', $this->info->toArrayExcept('id'), 'id = %i', $this->info->id);
return null;
}
public function extendCloseDate(DateTimeInterface $date, bool $force = false): ?string
{
if (!$force) {
@@ -171,6 +192,7 @@ class Auction
if ($highestBid !== null) {
$this->refundBid($highestBid, "{$this->info->title} 경매가 취소되었습니다.");
}
$this->rollbackAuction();
}
$db->update('ng_auction', $this->info->toArrayExcept('id'), 'id = %i', $this->info->id);
@@ -248,7 +270,7 @@ class Auction
return null;
}
public function bid(int $amount, bool $tryExtendCloseDate = false): ?string
protected function _bid(int $amount, bool $tryExtendCloseDate = false): ?string
{
$auctionInfo = $this->info;
$general = $this->general;
@@ -266,10 +288,17 @@ class Auction
return '경매가 아직 시작되지 않았습니다.';
}
if ($auctionInfo->buyImmediatelyAmount !== null && $auctionInfo->buyImmediatelyAmount < $amount) {
return '즉시판매가보다 높을 수 없습니다.';
if (!$auctionInfo->detail->isReverse) {
if ($auctionInfo->buyImmediatelyAmount !== null && $auctionInfo->buyImmediatelyAmount < $amount) {
return '즉시판매가보다 높을 수 없습니다.';
}
} else {
if ($auctionInfo->buyImmediatelyAmount !== null && $auctionInfo->buyImmediatelyAmount > $amount) {
return '즉시판매가보다 낮을 수 없습니다.';
}
}
if ($auctionInfo->reqResource === ResourceType::inheritancePoint) {
return $this->bidInheritPoint($amount, $now, $tryExtendCloseDate);
}
@@ -279,10 +308,17 @@ class Auction
$db = DB::db();
$highestBid = $this->getHighestBid();
if ($highestBid !== null && $amount <= $highestBid->amount) {
return '현재입찰가보다 높게 입찰해야 합니다.';
if (!$auctionInfo->detail->isReverse) {
if ($highestBid !== null && $amount <= $highestBid->amount) {
return '현재입찰가보다 높게 입찰해야 합니다.';
}
} else {
if ($highestBid !== null && $amount >= $highestBid->amount) {
return '현재입찰가보다 낮게 입찰해야 합니다.';
}
}
$myPrevBid = $this->getMyPrevBid();
$morePoint = $amount - ($myPrevBid ? $myPrevBid->amount : 0);
@@ -317,7 +353,7 @@ class Auction
return '입찰에 실패했습니다: DB 오류';
}
$general->setVar($resType->value, $general->getVar($resType->value) - $morePoint);
$general->increaseVar($resType->value, -$morePoint);
if ($myPrevBid === null || ($highestBid !== null && $myPrevBid->id !== $highestBid->id)) {
$this->refundBid($highestBid, "{$auctionInfo->title} 경매에 상회입찰자가 나타났습니다.");
@@ -325,4 +361,67 @@ class Auction
$general->applyDB($db);
return null;
}
public function tryFinish(): ?bool
{
$now = new DateTimeImmutable();
if ($now < $this->info->closeDate) {
return null;
}
//경매를 닫아야한다.
$highestBid = $this->getHighestBid();
if ($highestBid === null) {
$this->closeAuction(true);
return true;
}
if ($highestBid->data->tryExtendCloseDate) {
//연장 요청이 있었다.
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $this->info->turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60
));
if ($this->extendCloseDate($extendedCloseDate) === null) {
return false;
}
}
$bidder = General::createGeneralObjFromDB($highestBid->generalID);
$failReason = $this->finishAuction($highestBid, $bidder);
if ($failReason === null) {
$this->closeAuction();
return true;
}
$staticNation = $bidder->getStaticNation();
$src = new MessageTarget(0, '', 0, 'System', '#000000');
$dest = new MessageTarget(
$bidder->getID(),
$bidder->getName(),
$bidder->getNationID(),
$staticNation['name'],
$staticNation['color'],
GetImageURL($bidder->getVar('imgsvr'), $bidder->getVar('picture'))
);
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$msg = new Message(
Message::MSGTYPE_PRIVATE,
$src,
$dest,
$failReason,
new \DateTime(),
new \DateTime('9999-12-31'),
[]
);
$msg->send(true);
return false;
}
abstract public function bid(int $amount, bool $tryExtendCloseDate = false): ?string;
abstract protected function rollbackAuction(): void;
abstract protected function finishAuction(AuctionBidItem $highestBid, General $bidder): ?string;
}
+223
View File
@@ -0,0 +1,223 @@
<?php
namespace sammo;
use DateTimeImmutable;
use sammo\DTO\AuctionBidItem;
use sammo\DTO\AuctionInfo;
use sammo\DTO\AuctionInfoDetail;
use sammo\Enums\AuctionType;
use sammo\Enums\ResourceType;
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
{
if ($closeTurnCnt < 1 || $closeTurnCnt > 24) {
return '종료기한은 1 ~ 24 턴 이어야 합니다.';
}
if ($amount < self::MIN_AUCTION_AMOUNT || $amount > self::MAX_AUCTION_AMOUNT) {
return '거래량은 ' . self::MIN_AUCTION_AMOUNT . ' ~ ' . self::MAX_AUCTION_AMOUNT . ' 이어야 합니다.';
}
if ($startBidAmount < $amount * 0.5 || $amount * 2 < $startBidAmount) {
return '시작거래가는 50% ~ 200% 이어야 합니다.';
}
if ($finishBidAmount < $amount * 1.1 || $amount * 2 < $finishBidAmount) {
return '즉시거래가는 110% ~ 200% 이어야 합니다.';
}
if ($finishBidAmount < $startBidAmount * 1.1) {
return '즉시거래가는 시작판매가의 110% 이상이어야 합니다.';
}
$hostRes = static::$hostRes;
$hostResName = $hostRes->getName();
$bidderRes = static::$bidderRes;
$minimumRes = static::$hostRes === ResourceType::rice ? GameConst::$generalMinimumRice : GameConst::$generalMinimumGold;
if ($general->getVar($hostRes->value) < $amount + $minimumRes) {
return "기본 {$hostRes->getName()} {$minimumRes}은 거래할 수 없습니다.";
}
$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 '아직 경매가 끝나지 않았습니다.';
}
$now = new \DateTimeImmutable();
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm');
$closeDate = $now->add(TimeUtil::secondsToDateInterval($closeTurnCnt * $turnTerm * 60));
$openResult = static::openAuction(new AuctionInfo(
null,
static::$auctionType,
false,
"$amount",
$general->getId(),
$bidderRes,
$now,
$closeDate,
new AuctionInfoDetail(
"{$hostResName} {$amount} 경매",
$general->getName(),
$amount,
false,
$startBidAmount,
$finishBidAmount,
null,
null
)
), $general);
if (is_string($openResult)) {
return $openResult;
}
$general->increaseVarWithLimit($hostRes->value, -$amount, 0);
$general->applyDB($db);
return new self($openResult, $general);
}
protected function rollbackAuction(): void
{
if ($this->general->getID() === $this->info->openerGeneralID) {
$auctionHost = $this->general;
} else {
$auctionHost = General::createGeneralObjFromDB($this->info->openerGeneralID);
}
$hostRes = static::$hostRes;
$hostResName = $hostRes->getName();
$auctionHost->increaseVar($hostRes->value, $this->info->detail->amount);
$auctionHost->applyDB(DB::db());
$staticNation = $auctionHost->getStaticNation();
$src = new MessageTarget(0, '', 0, 'System', '#000000');
$dest = new MessageTarget(
$auctionHost->getID(),
$auctionHost->getName(),
$auctionHost->getNationID(),
$staticNation['name'],
$staticNation['color'],
GetImageURL($auctionHost->getVar('imgsvr'), $auctionHost->getVar('picture'))
);
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$msg = new Message(
Message::MSGTYPE_PRIVATE,
$src,
$dest,
"{$this->auctionID}{$hostResName} 경매에 입찰이 없어 취소되었습니다.",
new \DateTime(),
new \DateTime('9999-12-31'),
[]
);
$msg->send(true);
}
protected function finishAuction(AuctionBidItem $highestBid, General $bidder): ?string
{
if ($this->general->getID() === $this->info->openerGeneralID) {
$auctionHost = $this->general;
} else {
$auctionHost = General::createGeneralObjFromDB($this->info->openerGeneralID);
}
$highestBid = $this->getHighestBid();
if ($highestBid === null) {
throw new \Exception('입찰자가 없습니다.');
}
if ($this->general->getID() === $highestBid->generalID) {
$bidder = $this->general;
} else {
$bidder = General::createGeneralObjFromDB($highestBid->generalID);
}
$hostRes = static::$hostRes;
$hostResName = $hostRes->getName();
$bidderRes = static::$bidderRes;
$bidderResName = $bidderRes->getName();
$bidAmount = $highestBid->amount;
$auctionAmount = $this->info->detail->amount;
//거래 종료이므로 서로 반대
$josaUlBidder = JosaUtil::pick($bidAmount, '을');
$josaUlHost = JosaUtil::pick($auctionAmount, '을');
$auctionHost->increaseVar($bidderRes->value, $bidAmount);
$bidder->increaseVar($hostRes->value, $auctionAmount);
$auctionID = $this->info->id;
$auctionHost->getLogger()->pushGeneralActionLog(
"{$auctionID}번 거래 <C>성사</>로 {$bidderResName} <C>{$bidAmount}</>{$josaUlBidder} 지불, {$hostResName} <C>{$auctionAmount}</>{$josaUlHost} 획득!",
ActionLogger::EVENT_PLAIN
);
$bidder->getLogger()->pushGeneralActionLog(
"{$auctionID}번 거래 <C>성사</>로 {$hostResName} <C>{$auctionAmount}</>{$josaUlHost} 판매, {$bidderResName} <C>{$bidAmount}</>{$josaUlBidder} 획득!",
ActionLogger::EVENT_PLAIN
);
$josaYiHost = JosaUtil::pick($auctionHost->getName(), '이');
$josaYiBidder = JosaUtil::pick($bidder->getName(), '이');
$josaRo = JosaUtil::pick($bidAmount, '로');
$auctionLog = [];
$auctionLog[] = "{$auctionID}{$hostResName} 경매 <C>성사</> : <Y>{$auctionHost->getName()}</>{$josaYiHost} {$hostResName} <C>{$auctionAmount}</> 판매, <Y>{$bidder->getName()}</>{$josaYiBidder} <C>{$bidAmount}</> 구매";
if ($highestBid->amount === $this->info->detail->finishBidAmount) {
$auctionLog[0] .= ' <M>★ 즉시구매가 거래 ★</>';
} else if ($highestBid->amount === $this->info->detail->startBidAmount) {
$auctionLog[0] .= " <R>★ 최고가 거래 ★</>";
}
pushAuctionLog(array_map(
fn ($log) =>
$auctionHost->getLogger()->formatText($log, ActionLogger::EVENT_PLAIN),
$auctionLog
));
$db = DB::db();
$bidder->applyDB($db);
$auctionHost->applyDB($db);
return null;
}
public function bid(int $amount, bool $_tryExtendCloseDate = true): ?string
{
if ($this->info->openerGeneralID === $this->general->getID()) {
return '자신이 연 경매에 입찰할 수 없습니다.';
}
$result = $this->_bid($amount, true);
if(is_string($result)){
return $result;
}
if($amount === $this->info->detail->finishBidAmount){
//즉구, 1턴 후 지급
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm');
$date = (new DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($turnTerm * 60));
$this->shrinkCloseDate($date);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace sammo;
use sammo\Enums\AuctionType;
use sammo\Enums\ResourceType;
/** 경매에 쌀을 매물로 등록, 입찰자가 금으로 구매 */
class AuctionBuyRice extends AuctionBasicResource
{
static AuctionType $auctionType = AuctionType::SellRice;
static ResourceType $hostRes = ResourceType::rice;
static ResourceType $bidderRes = ResourceType::gold;
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace sammo;
use sammo\DTO\AuctionBidItem;
use sammo\DTO\AuctionInfo;
use sammo\DTO\AuctionInfoDetail;
use sammo\Enums\AuctionType;
use sammo\Enums\ResourceType;
/** 경매에 금을 매물로 등록, 입찰자가 쌀로 판매 */
class AuctionSellRice extends AuctionBasicResource
{
static AuctionType $auctionType = AuctionType::SellRice;
static ResourceType $hostRes = ResourceType::gold;
static ResourceType $bidderRes = ResourceType::rice;
}
+113 -161
View File
@@ -14,7 +14,6 @@ use sammo\RandUtil;
class AuctionUniqueItem extends Auction
{
static public function genObfuscatedName(int $id): string
{
$db = DB::db();
@@ -63,7 +62,7 @@ class AuctionUniqueItem extends Auction
$itemKey = $item->getRawClassName();
$db = DB::db();
$auctionIDonProgress = $db->queryFirstField(
'SELECT `id` FROM ng_auction WHERE `finished` = 0 AND `target` = %s AND `target` = %s',
'SELECT `id` FROM ng_auction WHERE `finished` = 0 AND `type` = %s AND `target` = %s',
AuctionType::UniqueItem->value,
$itemKey
);
@@ -71,6 +70,15 @@ class AuctionUniqueItem extends Auction
return '이미 경매가 진행중입니다.';
}
$prevAuctionID = $db->queryFirstField(
'SELECT id FROM ng_auction WHERE opener_general_id = %i AND finished = 0 AND `type` = %s',
$general->getID(),
AuctionType::UniqueItem->value,
);
if ($prevAuctionID !== null) {
return '아직 경매가 끝나지 않았습니다.';
}
$gameStor = KVStorage::getStorage($db, 'game_env');
$givenUnique = $gameStor->getValue('givenUnique') ?? [];
$givenUniqueCnt = $givenUnique[$itemKey] ?? 0;
@@ -108,7 +116,8 @@ class AuctionUniqueItem extends Auction
new AuctionInfoDetail(
"{$item->getName()} 경매",
static::genObfuscatedName($general->getID()),
1,
false,
$startAmount,
null,
1,
@@ -133,164 +142,9 @@ class AuctionUniqueItem extends Auction
return $auction;
}
private function giveUniqueItem(AuctionBidItem $highestBid, General $bidder): ?string
protected function rollbackAuction(): void
{
$itemKey = $this->info->target;
if ($itemKey === null) {
throw new \Exception('아이템 키가 없습니다.');
}
$itemObj = buildItemClass($itemKey);
$general = $bidder;
$availableItemTypes = [];
$reasons = [];
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
[$startYear, $year] = $gameStor->getValuesAsArray(['startyear', 'year']);
$relYear = $year - $startYear;
$availableEquipUniqueCnt = 1;
foreach (GameConst::$maxUniqueItemLimit as $tmpVals) {
[$targetYear, $targetTrialCnt] = $tmpVals;
if ($relYear < $targetYear) {
break;
}
$availableEquipUniqueCnt = $targetTrialCnt;
}
$availableEquipUniqueCnt = Util::valueFit($availableEquipUniqueCnt, null, count(GameConst::$allItems));
foreach ($general->getItems() as $item) {
if (!$item->isBuyable()) {
$availableEquipUniqueCnt -= 1;
}
}
if ($availableEquipUniqueCnt <= 0) {
return '유니크 아이템 소유 제한 상태입니다. 종료 시간이 연장됩니다.';
}
foreach (GameConst::$allItems as $itemType => $itemList) {
//아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름
if (!key_exists($itemKey, $itemList)) {
continue;
}
$ownItem = $general->getItem($itemType);
if ($ownItem->getRawClassName() == $itemKey) {
//FIXME: 이 경우에는 환불이 되던가 해야함.
$reasons[] = '이미 그 유니크를 가지고 있습니다.';
continue;
}
if (!$ownItem->isBuyable()) {
$reasons[] = '이미 다른 유니크를 가지고 있습니다.';
continue;
}
$availableCnt = $itemList[$itemKey];
$occupiedCnt = $db->queryFirstField('SELECT count(*) FROM general WHERE %b = %s', $itemType, $itemKey);
if ($occupiedCnt >= $availableCnt) {
//FIXME: 이 경우에는 환불이 되던가 해야함.
$reasons[] = '그 유니크는 모두 점유되었습니다.';
continue;
}
$availableItemTypes[] = $itemType;
}
if (!$availableItemTypes) {
return join(' ', $reasons);
}
$itemType = $availableItemTypes[0];
$general->setVar($itemType, $itemKey);
$logger = $general->getLogger();
$nationName = $general->getStaticNation()['name'];
$generalName = $general->getName();
$josaYi = JosaUtil::pick($generalName, '이');
$itemName = $itemObj->getName();
$itemRawName = $itemObj->getRawName();
$josaUl = JosaUtil::pick($itemRawName, '을');
$logger->pushGeneralActionLog("<C>{$itemName}</>{$josaUl} 습득했습니다!");
$logger->pushGeneralHistoryLog("<C>{$itemName}</>{$josaUl} 습득");
$logger->pushGlobalActionLog("<Y>{$generalName}</>{$josaYi} <C>{$itemName}</>{$josaUl} 습득했습니다!");
$logger->pushGlobalHistoryLog("<C><b>【보물발견】</b></><D><b>{$nationName}</b></>의 <Y>{$generalName}</>{$josaYi} <C>{$itemName}</>{$josaUl} 습득했습니다!");
$general->applyDB($db);
$givenUnique = $gameStor->getValue('givenUnique') ?? [];
$givenUnique[$itemKey] = ($givenUnique[$itemKey] ?? 0) + 1;
$gameStor->setValue('givenUnique', $givenUnique);
return null;
}
public function checkCloseDate(): ?bool
{
$now = new DateTimeImmutable();
if ($now < $this->info->closeDate) {
return null;
}
//경매를 닫아야한다.
$highestBid = $this->getHighestBid();
if ($highestBid === null) {
$this->closeAuction();
return true;
}
if ($highestBid->data->tryExtendCloseDate) {
//연장 요청이 있었다.
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $this->info->turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60
));
if ($this->extendCloseDate($extendedCloseDate) === null) {
return false;
}
}
$bidder = General::createGeneralObjFromDB($highestBid->generalID);
$failReason = $this->giveUniqueItem($highestBid, $bidder);
if ($failReason === null) {
$this->closeAuction();
return true;
}
$staticNation = $bidder->getStaticNation();
$src = new MessageTarget(0, '', 0, 'System', '#000000');
$dest = new MessageTarget(
$bidder->getID(),
$bidder->getName(),
$bidder->getNationID(),
$staticNation['name'],
$staticNation['color'],
GetImageURL($bidder->getVar('imgsvr'), $bidder->getVar('picture'))
);
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
$msg = new Message(
Message::MSGTYPE_PRIVATE,
$src,
$dest,
$failReason,
new \DateTime(),
new \DateTime('9999-12-31'),
[]
);
$msg->send(true);
//아이템 제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $this->info->turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60
));
$this->extendCloseDate($extendedCloseDate, true);
return false;
// 유니크 옥션의 개최자는 운영자이므로 할 일이 없다.
}
public function bid(int $amount, bool $tryExtendCloseDate = false): ?string
@@ -349,6 +203,104 @@ class AuctionUniqueItem extends Auction
}
}
return parent::bid($amount, $tryExtendCloseDate);
return $this->_bid($amount, $tryExtendCloseDate);
}
protected function finishAuction(AuctionBidItem $highestBid, General $bidder): ?string
{
$itemKey = $this->info->target;
if ($itemKey === null) {
throw new \Exception('아이템 키가 없습니다.');
}
$itemObj = buildItemClass($itemKey);
$general = $bidder;
$availableItemTypes = [];
$reasons = [];
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
[$startYear, $year] = $gameStor->getValuesAsArray(['startyear', 'year']);
$relYear = $year - $startYear;
$availableEquipUniqueCnt = 1;
foreach (GameConst::$maxUniqueItemLimit as $tmpVals) {
[$targetYear, $targetTrialCnt] = $tmpVals;
if ($relYear < $targetYear) {
break;
}
$availableEquipUniqueCnt = $targetTrialCnt;
}
$availableEquipUniqueCnt = Util::valueFit($availableEquipUniqueCnt, null, count(GameConst::$allItems));
foreach ($general->getItems() as $item) {
if (!$item->isBuyable()) {
$availableEquipUniqueCnt -= 1;
}
}
if ($availableEquipUniqueCnt <= 0) {
//제한에 걸렸다면 자동 연장
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $this->info->turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60
));
$this->extendCloseDate($extendedCloseDate, true);
return '유니크 아이템 소유 제한 상태입니다. 종료 시간이 연장됩니다.';
}
foreach (GameConst::$allItems as $itemType => $itemList) {
//아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름
if (!key_exists($itemKey, $itemList)) {
continue;
}
$ownItem = $general->getItem($itemType);
if ($ownItem->getRawClassName() == $itemKey) {
//FIXME: 이 경우에는 환불이 되던가 해야함.
$reasons[] = '이미 그 유니크를 가지고 있습니다.';
continue;
}
if (!$ownItem->isBuyable()) {
$reasons[] = '이미 다른 유니크를 가지고 있습니다.';
continue;
}
$availableCnt = $itemList[$itemKey];
$occupiedCnt = $db->queryFirstField('SELECT count(*) FROM general WHERE %b = %s', $itemType, $itemKey);
if ($occupiedCnt >= $availableCnt) {
//FIXME: 이 경우에는 환불이 되던가 해야함.
$reasons[] = '그 유니크는 모두 점유되었습니다.';
continue;
}
$availableItemTypes[] = $itemType;
}
if (!$availableItemTypes) {
return join(' ', $reasons);
}
$itemType = $availableItemTypes[0];
$general->setVar($itemType, $itemKey);
$logger = $general->getLogger();
$nationName = $general->getStaticNation()['name'];
$generalName = $general->getName();
$josaYi = JosaUtil::pick($generalName, '이');
$itemName = $itemObj->getName();
$itemRawName = $itemObj->getRawName();
$josaUl = JosaUtil::pick($itemRawName, '을');
$logger->pushGeneralActionLog("<C>{$itemName}</>{$josaUl} 습득했습니다!");
$logger->pushGeneralHistoryLog("<C>{$itemName}</>{$josaUl} 습득");
$logger->pushGlobalActionLog("<Y>{$generalName}</>{$josaYi} <C>{$itemName}</>{$josaUl} 습득했습니다!");
$logger->pushGlobalHistoryLog("<C><b>【보물발견】</b></><D><b>{$nationName}</b></>의 <Y>{$generalName}</>{$josaYi} <C>{$itemName}</>{$josaUl} 습득했습니다!");
$general->applyDB($db);
$givenUnique = $gameStor->getValue('givenUnique') ?? [];
$givenUnique[$itemKey] = ($givenUnique[$itemKey] ?? 0) + 1;
$gameStor->setValue('givenUnique', $givenUnique);
return null;
}
}
+6 -4
View File
@@ -11,11 +11,13 @@ class AuctionInfoDetail extends DTO
public function __construct(
public string $title,
public string $openerName,
public int $minAmount,
public int $amount,
#[NullIsUndefined]
public ?int $buyImmediatelyAmount,
public ?bool $isReverse,
public int $startBidAmount,
#[NullIsUndefined]
public ?int $finishBidAmount,
#[NullIsUndefined]
public ?int $remainCloseDateExtensionCnt,
#[NullIsUndefined]
+8 -2
View File
@@ -1,8 +1,14 @@
<?php
namespace sammo\Enums;
/**
* 입찰자 기준
*/
enum AuctionType: string{
case Gold = 'gold';
case Rice = 'rice';
/** 쌀을 매물로 등록, 금으로 구매 */
case BuyRice = 'buyRice';
/** 금을 매물로 등록, 쌀로 판매 */
case SellRice = 'sellRice';
/** 유미크를 매물로 등록, 유산 포인트로 구매 */
case UniqueItem = 'uniqueItem';
}
+1 -1
View File
@@ -693,7 +693,7 @@ CREATE TABLE `vote_comment` (
# KVStorage에
CREATE TABLE `ng_auction` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`type` ENUM('gold','rice','uniqueItem') NOT NULL COLLATE 'utf8mb4_bin',
`type` ENUM('buyRice','sellRice','uniqueItem') NOT NULL COLLATE 'utf8mb4_bin',
`finished` BIT(1) NOT NULL,
`target` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_bin',
`opener_general_id` INT(11) NOT NULL,