feat,refac: 경매장 재설계, 유니크 경매장 구현 (#221)
- 경매장을 모든 입찰 기록이 남는 새로운 경매장 로직으로 변경
- ng_auction, ng_auction_bid
- DTO 사용
- 상회입찰 시 개인메시지로 알림
- 기존의 '배경에서 조용히 이루어지는' 유니크 입찰을 공개된 유니크 경매장으로 변경
- 종료 기간 명시
- 종료 기간에 가까워질때 입찰하면 자동 연장
- 최대 연장기간 있음
- 유니크 제한인 경우 24턴 연장
- 중원정세에서 '보물수배'로 알림
Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/221
This commit was merged in pull request #221.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionBuyRice;
|
||||
use sammo\AuctionUniqueItem;
|
||||
use sammo\Validator;
|
||||
use sammo\General;
|
||||
|
||||
class BidBuyRiceAuction extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionSellRice;
|
||||
use sammo\Validator;
|
||||
use sammo\General;
|
||||
|
||||
class BidSellRiceAuction extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionUniqueItem;
|
||||
use sammo\Validator;
|
||||
use sammo\GameConst;
|
||||
use sammo\General;
|
||||
|
||||
class BidUniqueAuction extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->args);
|
||||
$v->rule('required', [
|
||||
'auctionID',
|
||||
'amount',
|
||||
])
|
||||
->rule('int', 'amount')
|
||||
->rule('int', 'auctionID')
|
||||
->rule('boolean', 'extendCloseDate');
|
||||
|
||||
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'];
|
||||
$tryExtendCloseDate = $this->arg['extendCloseDate'] ?? false;
|
||||
|
||||
$generalID = $session->generalID;
|
||||
$general = General::createGeneralObjFromDB($generalID);
|
||||
$auction = new AuctionUniqueItem($auctionID, $general);
|
||||
$result = $auction->bid($amount, $tryExtendCloseDate);
|
||||
|
||||
if (is_string($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionSellRice;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\AuctionBidItem;
|
||||
use sammo\DTO\AuctionInfo;
|
||||
use sammo\Enums\AuctionType;
|
||||
use sammo\Validator;
|
||||
use sammo\General;
|
||||
use sammo\Json;
|
||||
use sammo\TimeUtil;
|
||||
use sammo\Util;
|
||||
|
||||
use function sammo\getAuctionLogRecent;
|
||||
|
||||
class GetActiveResourceAuctionList extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN;
|
||||
}
|
||||
|
||||
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
|
||||
{
|
||||
$db = DB::db();
|
||||
|
||||
$buyRiceList = [];
|
||||
$sellRiceList = [];
|
||||
/** @var AuctionInfo[] */
|
||||
$auctions = array_map(fn ($raw) => AuctionInfo::fromArray($raw), $db->query(
|
||||
'SELECT * FROM `ng_auction` WHERE `type` IN %ls AND `finished` = 0 ORDER BY `close_date` ASC',
|
||||
[
|
||||
AuctionType::BuyRice->value,
|
||||
AuctionType::SellRice->value,
|
||||
]
|
||||
));
|
||||
|
||||
$recentLogs = getAuctionLogRecent(20);
|
||||
|
||||
|
||||
if (!$auctions) {
|
||||
return [
|
||||
'result' => true,
|
||||
'buyRice' => $buyRiceList,
|
||||
'sellRice' => $sellRiceList,
|
||||
'recentLogs' => $recentLogs,
|
||||
'generalID' => $session->generalID,
|
||||
];
|
||||
}
|
||||
|
||||
$auctionIDList = [];
|
||||
foreach ($auctions as $auction) {
|
||||
$auctionIDList[] = $auction->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<int,AuctionBidItem> */
|
||||
$highestBids = Util::mapWithKey(
|
||||
fn ($auctionID, $bid) => AuctionBidItem::fromArray($bid),
|
||||
$rawHighestBids
|
||||
);
|
||||
|
||||
foreach ($auctions as $auction) {
|
||||
$rawAuction = [
|
||||
'id' => $auction->id,
|
||||
'type' => $auction->type->value,
|
||||
'hostGeneralID' => $auction->hostGeneralID,
|
||||
'hostName' => $auction->detail->hostName,
|
||||
'openDate' => TimeUtil::format($auction->openDate, false),
|
||||
'closeDate' => TimeUtil::format($auction->closeDate, false),
|
||||
'amount' => $auction->detail->amount,
|
||||
'startBidAmount' => $auction->detail->startBidAmount,
|
||||
'finishBidAmount' => $auction->detail->finishBidAmount,
|
||||
];
|
||||
|
||||
$highestBid = $highestBids[$rawAuction['id']] ?? null;
|
||||
if ($highestBid === null) {
|
||||
$rawAuction['highestBid'] = null;
|
||||
} else {
|
||||
$rawAuction['highestBid'] = [
|
||||
'amount' => $highestBid->amount,
|
||||
'date' => TimeUtil::format($highestBid->date, false),
|
||||
'generalID' => $highestBid->generalID,
|
||||
'generalName' => $highestBid->aux->generalName,
|
||||
];
|
||||
}
|
||||
|
||||
if ($rawAuction['type'] == AuctionType::BuyRice->value) {
|
||||
$buyRiceList[] = $rawAuction;
|
||||
} else {
|
||||
$sellRiceList[] = $rawAuction;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'buyRice' => $buyRiceList,
|
||||
'sellRice' => $sellRiceList,
|
||||
'recentLogs' => $recentLogs,
|
||||
'generalID' => $session->generalID,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionUniqueItem;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\AuctionBidItem;
|
||||
use sammo\DTO\AuctionInfo;
|
||||
use sammo\Enums\AuctionType;
|
||||
use sammo\TimeUtil;
|
||||
use sammo\Validator;
|
||||
|
||||
class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->args);
|
||||
$v->rule('required', [
|
||||
'auctionID',
|
||||
])
|
||||
->rule('integer', 'auctionID');
|
||||
|
||||
if (!$v->validate()) {
|
||||
return $v->errorStr();
|
||||
}
|
||||
$this->args['auctionID'] = (int)$this->args['auctionID'];
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN;
|
||||
}
|
||||
|
||||
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
|
||||
{
|
||||
$db = DB::db();
|
||||
|
||||
$generalID = $session->generalID;
|
||||
$auctionID = $this->args['auctionID'];
|
||||
|
||||
$rawAuction = $db->queryFirstRow(
|
||||
'SELECT * FROM `ng_auction` WHERE `type` = %s AND `id` = %i',
|
||||
AuctionType::UniqueItem->value,
|
||||
$auctionID
|
||||
);
|
||||
|
||||
if (!$rawAuction) {
|
||||
return '선택한 경매가 없습니다.';
|
||||
}
|
||||
|
||||
$auction = AuctionInfo::fromArray($rawAuction);
|
||||
|
||||
/** @var AuctionBidItem[] */
|
||||
$bidList = array_map(fn ($raw) => AuctionBidItem::fromArray($raw), $db->query(
|
||||
'SELECT * FROM `ng_auction_bid` WHERE `auction_id` = %s ORDER BY `amount` DESC',
|
||||
$auctionID
|
||||
) ?? []);
|
||||
|
||||
$responseBid = [];
|
||||
foreach ($bidList as $bid) {
|
||||
$responseBid[] = [
|
||||
'generalName' => $bid->aux->generalName,
|
||||
'amount' => $bid->amount,
|
||||
'isCallerHighestBidder' => $bid->generalID === $generalID,
|
||||
'date' => TimeUtil::format($bid->date, false),
|
||||
];
|
||||
}
|
||||
|
||||
$obfuscatedName = AuctionUniqueItem::genObfuscatedName($generalID);
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'auction' => [
|
||||
'id' => $auction->id,
|
||||
'finished' => $auction->finished,
|
||||
'title' => $auction->detail->title,
|
||||
'target' => $auction->target,
|
||||
'isCallerHost' => $auction->hostGeneralID === $generalID,
|
||||
'hostName' => $auction->detail->hostName,
|
||||
'closeDate' => TimeUtil::format($auction->closeDate, false),
|
||||
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
|
||||
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false),
|
||||
],
|
||||
'bidList' => $responseBid,
|
||||
'obfuscatedName' => $obfuscatedName,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionUniqueItem;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\AuctionBidItem;
|
||||
use sammo\DTO\AuctionInfo;
|
||||
use sammo\Enums\AuctionType;
|
||||
use sammo\TimeUtil;
|
||||
use sammo\Util;
|
||||
|
||||
class GetUniqueItemAuctionList extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN;
|
||||
}
|
||||
|
||||
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
|
||||
{
|
||||
$db = DB::db();
|
||||
|
||||
$generalID = $session->generalID;
|
||||
|
||||
/** @var AuctionInfo[] */
|
||||
$auctions = array_map(fn($raw)=>AuctionInfo::fromArray($raw), $db->query(
|
||||
'SELECT * FROM `ng_auction` WHERE `type` = %s ORDER BY `close_date` ASC',
|
||||
AuctionType::UniqueItem->value
|
||||
) ?? []);
|
||||
|
||||
$obfuscatedName = AuctionUniqueItem::genObfuscatedName($generalID);
|
||||
|
||||
if(!$auctions){
|
||||
return [
|
||||
'result' => true,
|
||||
'list' => [],
|
||||
'obfuscatedName' => $obfuscatedName,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$auctionIDList = [];
|
||||
foreach ($auctions as $auction) {
|
||||
$auctionIDList[] = $auction->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<int,AuctionBidItem> */
|
||||
$highestBids = Util::mapWithKey(
|
||||
fn ($auctionID, $bid) => AuctionBidItem::fromArray($bid),
|
||||
$rawHighestBids
|
||||
);
|
||||
|
||||
$response = [];
|
||||
foreach ($auctions as $auction) {
|
||||
$auctionID = $auction->id;
|
||||
$highestBid = $highestBids[$auctionID] ?? null;
|
||||
if($highestBid === null){
|
||||
continue;
|
||||
}
|
||||
|
||||
$response[] = [
|
||||
'id' => $auctionID,
|
||||
'finished' => $auction->finished,
|
||||
'title' => $auction->detail->title,
|
||||
'target' => $auction->target,
|
||||
'isCallerHost' => $auction->hostGeneralID === $generalID,
|
||||
'hostName' => $auction->detail->hostName,
|
||||
'closeDate' => TimeUtil::format($auction->closeDate, false),
|
||||
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
|
||||
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false),
|
||||
'highestBid' => [
|
||||
'generalName' => $highestBid->aux->generalName,
|
||||
'amount' => $highestBid->amount,
|
||||
'isCallerHighestBidder' => $highestBid->generalID === $generalID,
|
||||
'date' => $highestBid->date,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'list' => $response,
|
||||
'obfuscatedName' => $obfuscatedName,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionBuyRice;
|
||||
use sammo\DB;
|
||||
use sammo\Validator;
|
||||
use sammo\GameConst;
|
||||
use sammo\General;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Util;
|
||||
|
||||
class OpenBuyRiceAuction 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', [
|
||||
'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);
|
||||
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
[$initYear, $initMonth, $year, $month] = $gameStor->getValuesAsArray(['init_year', 'init_month', 'year', 'month']);
|
||||
$initYearMonth = Util::joinYearMonth($initYear, $initMonth);
|
||||
$yearMonth = Util::joinYearMonth($year, $month);
|
||||
|
||||
if($yearMonth <= $initYearMonth + 3){
|
||||
return '시작 후 3개월이 지나야 경매를 열 수 있습니다.';
|
||||
}
|
||||
|
||||
$auctionResult = AuctionBuyRice::openResourceAuction(
|
||||
$general,
|
||||
$amount,
|
||||
$closeTurnCnt,
|
||||
$startBidAmount,
|
||||
$finishBidAmount
|
||||
);
|
||||
|
||||
if (is_string($auctionResult)) {
|
||||
return $auctionResult;
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'auctionID' => $auctionResult->getInfo()->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionSellRice;
|
||||
use sammo\DB;
|
||||
use sammo\Validator;
|
||||
use sammo\GameConst;
|
||||
use sammo\General;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Util;
|
||||
|
||||
class OpenSellRiceAuction 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', [
|
||||
'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);
|
||||
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
[$initYear, $initMonth, $year, $month] = $gameStor->getValuesAsArray(['init_year', 'init_month', 'year', 'month']);
|
||||
$initYearMonth = Util::joinYearMonth($initYear, $initMonth);
|
||||
$yearMonth = Util::joinYearMonth($year, $month);
|
||||
|
||||
if($yearMonth <= $initYearMonth + 3){
|
||||
return '시작 후 3개월이 지나야 경매를 열 수 있습니다.';
|
||||
}
|
||||
|
||||
$auctionResult = AuctionSellRice::openResourceAuction(
|
||||
$general,
|
||||
$amount,
|
||||
$closeTurnCnt,
|
||||
$startBidAmount,
|
||||
$finishBidAmount
|
||||
);
|
||||
|
||||
if (is_string($auctionResult)) {
|
||||
return $auctionResult;
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'auctionID' => $auctionResult->getInfo()->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Auction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\AuctionUniqueItem;
|
||||
use sammo\DB;
|
||||
use sammo\Validator;
|
||||
use sammo\GameConst;
|
||||
use sammo\General;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Util;
|
||||
|
||||
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);
|
||||
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
[$initYear, $initMonth, $year, $month] = $gameStor->getValuesAsArray(['init_year', 'init_month', 'year', 'month']);
|
||||
$initYearMonth = Util::joinYearMonth($initYear, $initMonth);
|
||||
$yearMonth = Util::joinYearMonth($year, $month);
|
||||
|
||||
if($yearMonth <= $initYearMonth + 3){
|
||||
return '시작 후 3개월이 지나야 경매를 열 수 있습니다.';
|
||||
}
|
||||
|
||||
$auctionResult = AuctionUniqueItem::openItemAuction($itemObj, $general, $amount);
|
||||
|
||||
if(is_string($auctionResult)) {
|
||||
return $auctionResult;
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'auctionID' => $auctionResult->getInfo()->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace sammo\API\General;
|
||||
|
||||
use Ds\Set;
|
||||
use sammo\DB;
|
||||
use sammo\Validator;
|
||||
|
||||
@@ -9,6 +10,7 @@ use sammo\Session;
|
||||
use sammo\GameConst;
|
||||
use sammo\General;
|
||||
use sammo\JosaUtil;
|
||||
use sammo\KVStorage;
|
||||
|
||||
class DropItem extends \sammo\BaseAPI
|
||||
{
|
||||
@@ -56,12 +58,13 @@ class DropItem extends \sammo\BaseAPI
|
||||
$logger->pushGeneralActionLog("<C>{$itemName}</>{$josaUl} 버렸습니다.");
|
||||
|
||||
$nationName = $me->getStaticNation()['name'];
|
||||
$db = DB::db();
|
||||
if (!$item->isBuyable()) {
|
||||
$logger->pushGlobalActionLog("<Y>{$generalName}</>{$josaYi} <C>{$itemName}</>{$josaUl} 잃었습니다!");
|
||||
$logger->pushGlobalHistoryLog("<R><b>【망실】</b></><D><b>{$nationName}</b></>의 <Y>{$generalName}</>{$josaYi} <C>{$itemName}</>{$josaUl} 잃었습니다!");
|
||||
}
|
||||
|
||||
$me->applyDB(DB::db());
|
||||
$me->applyDB($db);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\InheritAction;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\Enums\RankColumn;
|
||||
use sammo\GameConst;
|
||||
use sammo\General;
|
||||
use sammo\KVStorage;
|
||||
use sammo\UserLogger;
|
||||
use sammo\Validator;
|
||||
|
||||
use function sammo\buildItemClass;
|
||||
|
||||
class BuySpecificUnique 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', [
|
||||
'item',
|
||||
'amount',
|
||||
])
|
||||
->rule('int', 'amount')
|
||||
->rule('min', 'amount', GameConst::$inheritItemUniqueMinPoint)
|
||||
->rule('keyExists', 'item', $availableItems);
|
||||
|
||||
if (!$v->validate()) {
|
||||
return $v->errorStr();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSessionMode(): int
|
||||
{
|
||||
//KVStrorage, General.aux 모두 쓰므로 lock;
|
||||
return static::REQ_GAME_LOGIN;
|
||||
}
|
||||
|
||||
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
|
||||
{
|
||||
$itemKey = $this->args['item'];
|
||||
$amount = $this->args['amount'];
|
||||
|
||||
$userID = $session->userID;
|
||||
$generalID = $session->generalID;
|
||||
|
||||
$general = General::createGeneralObjFromDB($generalID);
|
||||
if ($userID != $general->getVar('owner')) {
|
||||
return '로그인 상태가 이상합니다. 다시 로그인해 주세요.';
|
||||
}
|
||||
|
||||
$itemTrials = $general->getAuxVar('inheritUniqueTrial') ?? [];
|
||||
if (key_exists($itemKey, $itemTrials)) {
|
||||
return '이미 입찰한 아이템입니다. 다음 턴에 시도해 주세요.';
|
||||
}
|
||||
|
||||
foreach(GameConst::$allItems as $itemType => $items){
|
||||
if(!key_exists($itemKey, $items)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevItem = $general->getItem($itemType);
|
||||
if(!$prevItem->isBuyable()){
|
||||
return '이미 같은 자리에 유니크를 보유하고 있습니다.';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$db = DB::db();
|
||||
$inheritStor = KVStorage::getStorage($db, "inheritance_{$userID}");
|
||||
$trialStor = KVStorage::getStorage($db, "ut_{$itemKey}");
|
||||
$previousPoint = ($inheritStor->getValue('previous') ?? [0, 0])[0];
|
||||
if ($previousPoint < $amount) {
|
||||
return '충분한 유산 포인트를 가지고 있지 않습니다.';
|
||||
}
|
||||
|
||||
$itemObj = buildItemClass($itemKey);
|
||||
$userLogger = new UserLogger($userID);
|
||||
$userLogger->push("{$amount} 포인트로 유니크 {$itemObj->getName()} 구입 시도", "inheritPoint");
|
||||
$userLogger->flush();
|
||||
|
||||
$itemTrials[$itemKey] = $amount;
|
||||
$general->setAuxVar('inheritUniqueTrial', $itemTrials);
|
||||
$inheritStor->setValue('previous', [$previousPoint - $amount, null]);
|
||||
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $amount);
|
||||
$trialStor->setValue("u{$userID}", [$userID, $generalID, $amount]);
|
||||
$general->applyDB($db);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
<?php
|
||||
|
||||
namespace sammo;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
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;
|
||||
|
||||
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;
|
||||
public const COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY = 1;
|
||||
public const MIN_AUCTION_CLOSE_MINUTES = 30;
|
||||
public const MIN_EXTENSION_MINUTES_PER_BID = 1;
|
||||
public const MIN_EXTENSION_MINUTES_LIMIT_BY_BID = 5;
|
||||
public const MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY = 5;
|
||||
|
||||
protected AuctionBidItem|null|false $_highestBid = false;
|
||||
|
||||
static public function genObfuscatedName(int $id): string
|
||||
{
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
|
||||
$namePool = $gameStor->getValue('obfuscatedNamePool');
|
||||
if ($namePool === null) {
|
||||
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
||||
UniqueConst::$hiddenSeed,
|
||||
)));
|
||||
$namePool = [];
|
||||
foreach (GameConst::$randGenFirstName as $ch0) {
|
||||
foreach (GameConst::$randGenMiddleName as $ch1) {
|
||||
foreach (GameConst::$randGenLastName as $ch2) {
|
||||
$namePool[] = "{$ch0}{$ch1}{$ch2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
$namePool = $rng->shuffle($namePool);
|
||||
$gameStor->setValue('obfuscatedNamePool', $namePool);
|
||||
}
|
||||
|
||||
|
||||
$dupIdx = intdiv($id, count($namePool));
|
||||
$subIdx = $id % count($namePool);
|
||||
if ($dupIdx == 0) {
|
||||
return $namePool[$subIdx];
|
||||
}
|
||||
return "{$namePool[$subIdx]}{$dupIdx}";
|
||||
}
|
||||
|
||||
static protected function openAuction(AuctionInfo $info, General $general): int|string
|
||||
{
|
||||
$db = DB::db();
|
||||
if ($info->id !== null) {
|
||||
return 'id가 지정되어 있습니다.';
|
||||
}
|
||||
|
||||
$db->insert('ng_auction', $info->toArray());
|
||||
return $db->insertId();
|
||||
}
|
||||
|
||||
public function getHighestBid(): ?AuctionBidItem
|
||||
{
|
||||
$db = DB::db();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$highestBid = AuctionBidItem::fromArray($rawHighestBid);
|
||||
$this->_highestBid = $highestBid;
|
||||
return $highestBid;
|
||||
}
|
||||
|
||||
public function getMyPrevBid(): ?AuctionBidItem
|
||||
{
|
||||
$db = DB::db();
|
||||
if (!$this->info->detail->isReverse) {
|
||||
$rawMyPrevBid = $db->queryFirstRow(
|
||||
'SELECT * FROM ng_auction_bid WHERE general_id = %i AND auction_id = %i ORDER BY `amount` DESC LIMIT 1',
|
||||
$this->general->getID(),
|
||||
$this->info->id
|
||||
);
|
||||
} else {
|
||||
$rawMyPrevBid = $db->queryFirstRow(
|
||||
'SELECT * FROM ng_auction_bid WHERE general_id = %i AND auction_id = %i ORDER BY `amount` ASC LIMIT 1',
|
||||
$this->general->getID(),
|
||||
$this->info->id
|
||||
);
|
||||
}
|
||||
if (!$rawMyPrevBid) {
|
||||
return null;
|
||||
}
|
||||
return AuctionBidItem::fromArray($rawMyPrevBid);
|
||||
}
|
||||
|
||||
public function __construct(protected readonly int $auctionID, protected General $general)
|
||||
{
|
||||
$db = DB::db();
|
||||
$rawAuctionInfo = $db->queryFirstRow('SELECT * FROM `ng_auction` WHERE id = %i', $auctionID);
|
||||
if (!$rawAuctionInfo) {
|
||||
throw new \RuntimeException("해당 경매가 없습니다: {$auctionID}");
|
||||
}
|
||||
$this->info = AuctionInfo::fromArray($rawAuctionInfo);
|
||||
$thisAuctionType = static::$auctionType;
|
||||
if ($this->info->type !== $thisAuctionType) {
|
||||
throw new \RuntimeException("잘못된 경매 타입입니다: {$this->info->type->value} != {$thisAuctionType->value}");
|
||||
}
|
||||
}
|
||||
|
||||
public function getInfo(): AuctionInfo
|
||||
{
|
||||
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->toArray('id'), 'id = %i', $this->info->id);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function extendLatestBidCloseDate(?DateTimeInterface $date): ?string
|
||||
{
|
||||
if ($date === null) {
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
$date = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
|
||||
));
|
||||
}
|
||||
else{
|
||||
$date = DateTimeImmutable::createFromInterface($date);
|
||||
}
|
||||
if ($this->info->detail->availableLatestBidCloseDate !== null && $date < $this->info->detail->availableLatestBidCloseDate) {
|
||||
return '기간보다 짧습니다.';
|
||||
}
|
||||
$this->info->detail->availableLatestBidCloseDate = $date;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function extendCloseDate(DateTimeInterface $date, bool $force = false): ?string
|
||||
{
|
||||
if (!$force) {
|
||||
if ($this->info->detail->remainCloseDateExtensionCnt === null) {
|
||||
return '연장할 수 없는 경매입니다.';
|
||||
}
|
||||
if ($this->info->detail->remainCloseDateExtensionCnt === 0) {
|
||||
return '더 이상 연장할 수 없습니다';
|
||||
}
|
||||
if ($this->info->detail->remainCloseDateExtensionCnt > 0) {
|
||||
$this->info->detail->remainCloseDateExtensionCnt--;
|
||||
}
|
||||
}
|
||||
|
||||
if ($date < $this->info->closeDate) {
|
||||
return '종료 기간보다 짧습니다.';
|
||||
}
|
||||
|
||||
$closeDate = DateTimeImmutable::createFromInterface($date);
|
||||
$this->info->closeDate = $closeDate;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function applyDB(): void
|
||||
{
|
||||
$db = DB::db();
|
||||
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
|
||||
}
|
||||
|
||||
public function refundBid(AuctionBidItem $bidItem, string $reason): void
|
||||
{
|
||||
if ($bidItem->auctionID !== $this->info->id) {
|
||||
throw new \RuntimeException('잘못된 경매입니다.');
|
||||
}
|
||||
|
||||
$db = DB::db();
|
||||
if ($bidItem->generalID === $this->general->getID()) {
|
||||
$oldBidder = $this->general;
|
||||
} else {
|
||||
$oldBidder = General::createGeneralObjFromDB($bidItem->generalID);
|
||||
}
|
||||
|
||||
if ($this->info->reqResource === ResourceType::inheritancePoint) {
|
||||
$oldBidder->increaseInheritancePoint(InheritanceKey::previous, $bidItem->amount);
|
||||
$oldBidder->increaseRankVar(RankColumn::inherit_point_spent_dynamic, -$bidItem->amount);
|
||||
} else {
|
||||
$oldBidder->increaseVar($this->info->reqResource->value, $bidItem->amount);
|
||||
}
|
||||
|
||||
if ($oldBidder instanceof DummyGeneral) {
|
||||
return;
|
||||
}
|
||||
|
||||
$staticNation = $oldBidder->getStaticNation();
|
||||
$src = new MessageTarget(0, '', 0, 'System', '#000000');
|
||||
$dest = new MessageTarget(
|
||||
$oldBidder->getID(),
|
||||
$oldBidder->getName(),
|
||||
$oldBidder->getNationID(),
|
||||
$staticNation['name'],
|
||||
$staticNation['color'],
|
||||
GetImageURL($oldBidder->getVar('imgsvr'), $oldBidder->getVar('picture'))
|
||||
);
|
||||
|
||||
//TODO: 전역 알림이 나타나야한다. 일반 메시지보다는 중요하고, 메시지보단 약하게..
|
||||
//TODO: 바로가기를 제공하는 편이 좋을 것 같다.
|
||||
$msg = new Message(
|
||||
Message::MSGTYPE_PRIVATE,
|
||||
$src,
|
||||
$dest,
|
||||
$reason,
|
||||
new DateTime(),
|
||||
new DateTime('9999-12-31'),
|
||||
[]
|
||||
);
|
||||
$oldBidder->applyDB($db);
|
||||
$msg->send(true);
|
||||
}
|
||||
|
||||
public function closeAuction(bool $isRollback = false): void
|
||||
{
|
||||
$db = DB::db();
|
||||
|
||||
$this->info->finished = true;
|
||||
|
||||
if ($isRollback) {
|
||||
$highestBid = $this->getHighestBid();
|
||||
if ($highestBid !== null) {
|
||||
$this->refundBid($highestBid, "{$this->info->id}번 {$this->info->detail->title} 경매가 취소되었습니다.");
|
||||
}
|
||||
$this->rollbackAuction();
|
||||
}
|
||||
|
||||
$db->update('ng_auction', $this->info->toArray('id'), 'id = %i', $this->info->id);
|
||||
}
|
||||
|
||||
private function bidInheritPoint(int $amount, \DateTimeImmutable $now, bool $tryExtendCloseDate): ?string
|
||||
{
|
||||
$db = DB::db();
|
||||
|
||||
$auctionInfo = $this->info;
|
||||
$general = $this->general;
|
||||
|
||||
$highestBid = $this->getHighestBid();
|
||||
if ($highestBid !== null && $amount <= $highestBid->amount) {
|
||||
return '현재입찰가보다 높게 입찰해야 합니다.';
|
||||
}
|
||||
|
||||
$myPrevBid = $this->getMyPrevBid();
|
||||
if ($myPrevBid !== null && $highestBid->no !== $myPrevBid->no) {
|
||||
//이미 환불 받았으니 무효.
|
||||
$myPrevBid = null;
|
||||
}
|
||||
|
||||
$morePoint = $amount - ($myPrevBid ? $myPrevBid->amount : 0);
|
||||
$currPoint = $general->getInheritancePoint(InheritanceKey::previous);
|
||||
if ($currPoint === null || $currPoint < $morePoint) {
|
||||
return '유산포인트가 부족합니다.';
|
||||
}
|
||||
|
||||
$obfuscatedName = static::genObfuscatedName($general->getID());
|
||||
//여기서부터 입찰 성공
|
||||
|
||||
$newBid = new AuctionBidItem(
|
||||
null,
|
||||
$auctionInfo->id,
|
||||
$general->getVar('owner'),
|
||||
$general->getID(),
|
||||
$amount,
|
||||
$now,
|
||||
new AuctionBidItemData(
|
||||
$general->getVar('owner_name'),
|
||||
$obfuscatedName,
|
||||
$tryExtendCloseDate,
|
||||
)
|
||||
);
|
||||
$db->insert('ng_auction_bid', $newBid->toArray());
|
||||
if ($db->affectedRows() == 0) {
|
||||
return '입찰에 실패했습니다: DB 오류';
|
||||
}
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
|
||||
if ($this->info->detail->availableLatestBidCloseDate !== null) {
|
||||
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
|
||||
));
|
||||
|
||||
if ($extendedCloseDate > $this->info->closeDate && $this->info->closeDate < $this->info->detail->availableLatestBidCloseDate) {
|
||||
$this->extendCloseDate(min($extendedCloseDate, $this->info->detail->availableLatestBidCloseDate), true);
|
||||
$this->applyDB();
|
||||
}
|
||||
}
|
||||
|
||||
$general->increaseInheritancePoint(InheritanceKey::previous, -$morePoint);
|
||||
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $morePoint);
|
||||
|
||||
if ($highestBid !== null && $myPrevBid === null) {
|
||||
$this->refundBid($highestBid, "{$auctionInfo->id}번 {$auctionInfo->detail->title}에 상회입찰자가 나타났습니다.");
|
||||
}
|
||||
$general->applyDB($db);
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function _bid(int $amount, bool $tryExtendCloseDate = false): ?string
|
||||
{
|
||||
$auctionInfo = $this->info;
|
||||
$general = $this->general;
|
||||
|
||||
if ($auctionInfo->finished) {
|
||||
return '경매가 이미 끝났습니다.';
|
||||
}
|
||||
|
||||
$now = new \DateTimeImmutable();
|
||||
|
||||
if ($auctionInfo->closeDate < $now) {
|
||||
return '경매가 이미 끝났습니다.';
|
||||
}
|
||||
if ($auctionInfo->openDate > $now) {
|
||||
return '경매가 아직 시작되지 않았습니다.';
|
||||
}
|
||||
|
||||
if (!$auctionInfo->detail->isReverse) {
|
||||
if ($auctionInfo->detail->finishBidAmount !== null && $auctionInfo->detail->finishBidAmount < $amount) {
|
||||
return '즉시판매가보다 높을 수 없습니다.';
|
||||
}
|
||||
} else {
|
||||
if ($auctionInfo->detail->finishBidAmount !== null && $auctionInfo->detail->finishBidAmount > $amount) {
|
||||
return '즉시판매가보다 낮을 수 없습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($auctionInfo->reqResource === ResourceType::inheritancePoint) {
|
||||
return $this->bidInheritPoint($amount, $now, $tryExtendCloseDate);
|
||||
}
|
||||
|
||||
//reqResource는 말 그대로 '구매자가 내야하는 자원'이다.
|
||||
|
||||
$db = DB::db();
|
||||
|
||||
$highestBid = $this->getHighestBid();
|
||||
if (!$auctionInfo->detail->isReverse) {
|
||||
if ($highestBid !== null && $amount <= $highestBid->amount) {
|
||||
return '현재입찰가보다 높게 입찰해야 합니다.';
|
||||
}
|
||||
} else {
|
||||
if ($highestBid !== null && $amount >= $highestBid->amount) {
|
||||
return '현재입찰가보다 낮게 입찰해야 합니다.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$myPrevBid = $this->getMyPrevBid();
|
||||
if ($myPrevBid !== null && $highestBid->no !== $myPrevBid->no) {
|
||||
//이미 환불 받았으니 무효.
|
||||
$myPrevBid = null;
|
||||
}
|
||||
|
||||
$morePoint = $amount - ($myPrevBid ? $myPrevBid->amount : 0);
|
||||
$resType = $auctionInfo->reqResource;
|
||||
$minReqRes = match ($resType) {
|
||||
ResourceType::gold => GameConst::$defaultGold,
|
||||
ResourceType::rice => GameConst::$defaultRice,
|
||||
};
|
||||
|
||||
if ($general->getVar($resType->value) < $morePoint + $minReqRes) {
|
||||
return $resType->getName() . '이 부족합니다.';
|
||||
}
|
||||
|
||||
//여기서부터 입찰 성공
|
||||
|
||||
$newBid = new AuctionBidItem(
|
||||
null,
|
||||
$auctionInfo->id,
|
||||
$general->getVar('owner'),
|
||||
$general->getID(),
|
||||
$amount,
|
||||
$now,
|
||||
new AuctionBidItemData(
|
||||
$general->getVar('owner_name'),
|
||||
$general->getName(),
|
||||
$tryExtendCloseDate,
|
||||
)
|
||||
);
|
||||
|
||||
$db->insert('ng_auction_bid', $newBid->toArray());
|
||||
if ($db->affectedRows() == 0) {
|
||||
return '입찰에 실패했습니다: DB 오류';
|
||||
}
|
||||
|
||||
$general->increaseVar($resType->value, -$morePoint);
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
$extendedCloseDate = $now->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_PER_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_PER_BID) * 60
|
||||
));
|
||||
|
||||
if ($extendedCloseDate > $this->info->closeDate) {
|
||||
$this->extendCloseDate($extendedCloseDate, true);
|
||||
$this->applyDB();
|
||||
}
|
||||
|
||||
if ($highestBid !== null && $myPrevBid === null) {
|
||||
$this->refundBid($highestBid, "{$auctionInfo->id}번 {$auctionInfo->detail->title}에 상회입찰자가 나타났습니다.");
|
||||
}
|
||||
$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->aux->tryExtendCloseDate) {
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
|
||||
//연장 요청이 있었다.
|
||||
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60
|
||||
));
|
||||
|
||||
if ($this->extendCloseDate($extendedCloseDate) === null) {
|
||||
$this->extendLatestBidCloseDate(null);
|
||||
$this->applyDB();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$bidder = General::createGeneralObjFromDB($highestBid->generalID);
|
||||
$failReason = $this->finishAuction($highestBid, $bidder);
|
||||
if ($failReason === null) {
|
||||
$this->closeAuction();
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($bidder instanceof DummyGeneral) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$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): ?string;
|
||||
|
||||
abstract protected function rollbackAuction(): void;
|
||||
abstract protected function finishAuction(AuctionBidItem $highestBid, General $bidder): ?string;
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?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 ResourceType $hostRes;
|
||||
static ResourceType $bidderRes;
|
||||
|
||||
static public function openResourceAuction(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();
|
||||
if (!($general instanceof DummyGeneral)) {
|
||||
$prevAuctionID = $db->queryFirstField(
|
||||
'SELECT id FROM ng_auction WHERE host_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 static($openResult, $general);
|
||||
}
|
||||
|
||||
static public function genDummy(bool $initFullLogger = true): DummyGeneral
|
||||
{
|
||||
$dummyGeneral = new DummyGeneral(false);
|
||||
$dummyGeneral->setVar('name', '상인');
|
||||
$dummyGeneral->setVar('gold', static::MAX_AUCTION_AMOUNT * 10);
|
||||
$dummyGeneral->setVar('rice', static::MAX_AUCTION_AMOUNT * 10);
|
||||
|
||||
if($initFullLogger){
|
||||
$db = DB::db();
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
[$year, $month] = $gameStor->getValuesAsArray(['year', 'month']);
|
||||
$dummyGeneral->initLogger($year, $month);
|
||||
}
|
||||
|
||||
return $dummyGeneral;
|
||||
}
|
||||
|
||||
protected function rollbackAuction(): void
|
||||
{
|
||||
if ($this->general->getID() === $this->info->hostGeneralID) {
|
||||
$auctionHost = $this->general;
|
||||
} else if ($this->info->hostGeneralID == 0) {
|
||||
$auctionHost = $this->genDummy();
|
||||
} else {
|
||||
$auctionHost = General::createGeneralObjFromDB($this->info->hostGeneralID);
|
||||
}
|
||||
|
||||
$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->hostGeneralID) {
|
||||
$auctionHost = $this->general;
|
||||
} else if ($this->info->hostGeneralID == 0) {
|
||||
$auctionHost = $this->genDummy();
|
||||
} else {
|
||||
$auctionHost = General::createGeneralObjFromDB($this->info->hostGeneralID);
|
||||
}
|
||||
|
||||
$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(), '이');
|
||||
|
||||
$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) =>
|
||||
$bidder->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 = false): ?string
|
||||
{
|
||||
if ($this->info->hostGeneralID === $this->general->getID()) {
|
||||
return '자신이 연 경매에 입찰할 수 없습니다.';
|
||||
}
|
||||
$result = $this->_bid($amount, $tryExtendCloseDate);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace sammo;
|
||||
|
||||
use sammo\Enums\AuctionType;
|
||||
use sammo\Enums\ResourceType;
|
||||
|
||||
/** 경매에 쌀을 매물로 등록, 입찰자가 금으로 구매 */
|
||||
class AuctionBuyRice extends AuctionBasicResource
|
||||
{
|
||||
static AuctionType $auctionType = AuctionType::BuyRice;
|
||||
static ResourceType $hostRes = ResourceType::rice;
|
||||
static ResourceType $bidderRes = ResourceType::gold;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace sammo;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
namespace sammo;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Ds\Set;
|
||||
use sammo\DTO\AuctionBidItem;
|
||||
use sammo\DTO\AuctionInfo;
|
||||
use sammo\DTO\AuctionInfoDetail;
|
||||
use sammo\Enums\AuctionType;
|
||||
use sammo\Enums\InheritanceKey;
|
||||
use sammo\Enums\ResourceType;
|
||||
use sammo\RandUtil;
|
||||
|
||||
class AuctionUniqueItem extends Auction
|
||||
{
|
||||
const COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT = 24;
|
||||
|
||||
static AuctionType $auctionType = AuctionType::UniqueItem;
|
||||
|
||||
static public function openItemAuction(BaseItem $item, General $general, int $startAmount): self|string
|
||||
{
|
||||
if ($startAmount < GameConst::$inheritItemUniqueMinPoint) {
|
||||
return '최소 경매 금액은 ' . GameConst::$inheritItemUniqueMinPoint . '입니다.';
|
||||
}
|
||||
|
||||
if ($general->getInheritancePoint(InheritanceKey::previous) < $startAmount) {
|
||||
return '경매를 시작할 포인트가 부족합니다.';
|
||||
}
|
||||
|
||||
if ($item->isBuyable()) {
|
||||
return '구매할 수 있는 아이템입니다.';
|
||||
}
|
||||
|
||||
$itemKey = $item->getRawClassName();
|
||||
$db = DB::db();
|
||||
$auctionIDonProgress = $db->queryFirstField(
|
||||
'SELECT `id` FROM ng_auction WHERE `finished` = 0 AND `type` = %s AND `target` = %s',
|
||||
AuctionType::UniqueItem->value,
|
||||
$itemKey
|
||||
);
|
||||
if ($auctionIDonProgress !== null) {
|
||||
return '이미 경매가 진행중입니다.';
|
||||
}
|
||||
|
||||
$prevAuctionID = $db->queryFirstField(
|
||||
'SELECT id FROM ng_auction WHERE host_general_id = %i AND finished = 0 AND `type` = %s',
|
||||
$general->getID(),
|
||||
AuctionType::UniqueItem->value,
|
||||
);
|
||||
if ($prevAuctionID !== null) {
|
||||
return '아직 경매가 끝나지 않았습니다.';
|
||||
}
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
|
||||
$now = new DateTimeImmutable();
|
||||
|
||||
[$turnTerm, $year, $month] = $gameStor->getValuesAsArray(['turnterm', 'year', 'month']);
|
||||
|
||||
$closeDate = $now->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_AUCTION_CLOSE_MINUTES, $turnTerm * static::COEFF_AUCTION_CLOSE_MINUTES) * 60
|
||||
));
|
||||
$availableLatestBidCloseDate = $closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_LIMIT_BY_BID, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_BY_BID) * 60
|
||||
));
|
||||
|
||||
$info = new AuctionInfo(
|
||||
null,
|
||||
AuctionType::UniqueItem,
|
||||
false,
|
||||
$itemKey,
|
||||
$general->getID(),
|
||||
ResourceType::inheritancePoint,
|
||||
$now,
|
||||
$closeDate,
|
||||
new AuctionInfoDetail(
|
||||
"{$item->getName()} 경매",
|
||||
static::genObfuscatedName($general->getID()),
|
||||
1,
|
||||
false,
|
||||
$startAmount,
|
||||
null,
|
||||
1,
|
||||
$availableLatestBidCloseDate,
|
||||
)
|
||||
);
|
||||
|
||||
$auctionID = static::openAuction($info, $general);
|
||||
if (!is_int($auctionID)) {
|
||||
return $auctionID;
|
||||
}
|
||||
$auction = new static($auctionID, $general);
|
||||
try {
|
||||
$auction->bid($startAmount, false);
|
||||
} catch (\Exception $e) {
|
||||
//실패해선 안된다.
|
||||
$msg = $e->getMessage();
|
||||
$auction->closeAuction();
|
||||
return "경매를 시작했지만, 첫 입찰에 실패했습니다: {$msg}";
|
||||
}
|
||||
|
||||
$itemName = $item->getName();
|
||||
$josaRa = JosaUtil::pick($item->getRawName(), '라');
|
||||
|
||||
$logger = new ActionLogger(0, 0, $year, $month);
|
||||
$logger->pushGlobalHistoryLog("<C><b>【보물수배】</b></>누군가가 <C>{$itemName}</>{$josaRa}는 보물을 구한다는 소문이 들려옵니다.");
|
||||
$logger->flush();
|
||||
|
||||
return $auction;
|
||||
}
|
||||
|
||||
protected function rollbackAuction(): void
|
||||
{
|
||||
// 유니크 옥션의 개최자는 운영자이므로 할 일이 없다.
|
||||
}
|
||||
|
||||
public function bid(int $amount, bool $tryExtendCloseDate): ?string
|
||||
{
|
||||
|
||||
$db = DB::db();
|
||||
/** @var AuctionInfo[] */
|
||||
$openUniqueAuctions = array_map(fn ($raw) => AuctionInfo::fromArray($raw), $db->query(
|
||||
'SELECT * FROM `ng_auction` WHERE `finished` = 0 AND `type`= %s',
|
||||
AuctionType::UniqueItem->value
|
||||
) ?? []);
|
||||
|
||||
$auctionIDList = [];
|
||||
foreach($openUniqueAuctions as $auction){
|
||||
$auctionIDList[] = $auction->id;
|
||||
}
|
||||
$db = DB::db();
|
||||
$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<int,AuctionBidItem> */
|
||||
$highestBids = Util::mapWithKey(
|
||||
fn ($auctionID, $bid) => AuctionBidItem::fromArray($bid),
|
||||
$rawHighestBids
|
||||
);
|
||||
|
||||
$itemCode = $this->info->target;
|
||||
|
||||
if($itemCode === null){
|
||||
throw new \Exception('아이템 코드가 없습니다.');
|
||||
}
|
||||
|
||||
$bidItemTypes = new Set();
|
||||
foreach (GameConst::$allItems as $itemType => $itemList) {
|
||||
if (key_exists($itemCode, $itemList) && $itemList[$itemCode] <= 0) {
|
||||
continue;
|
||||
}
|
||||
$bidItemTypes->add($itemType);
|
||||
}
|
||||
|
||||
foreach ($openUniqueAuctions as $auction) {
|
||||
$auctionID = $auction->id;
|
||||
if (!isset($highestBids[$auctionID])) {
|
||||
continue;
|
||||
}
|
||||
if ($auctionID === $this->auctionID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bid = $highestBids[$auctionID];
|
||||
if ($bid->generalID !== $this->general->getID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemCodeComp = $auction->target;
|
||||
|
||||
foreach (GameConst::$allItems as $itemType => $itemList) {
|
||||
if (($itemList[$itemCodeComp] ?? 0) <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($bidItemTypes->contains($itemType)) {
|
||||
return '1순위 입찰자인 경매중에 같은 부위가 있습니다.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
$turnTerm = $gameStor->getValue('turnterm');
|
||||
//제한에 걸렸다면 자동 연장
|
||||
$extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval(
|
||||
max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $turnTerm * static::COEFF_EXTENSION_MINUTES_LIMIT_UNIQUE_CNT) * 60
|
||||
));
|
||||
|
||||
$this->extendCloseDate($extendedCloseDate, true);
|
||||
$this->extendLatestBidCloseDate(null);
|
||||
$this->applyDB();
|
||||
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} 습득했습니다!");
|
||||
|
||||
$userLogger = new UserLogger($general->getVar('owner'));
|
||||
$userLogger->push(sprintf("유니크 %s 경매로 %d 포인트 사용", $itemName, $highestBid->amount), "inheritPoint");
|
||||
|
||||
$general->applyDB($db);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,6 @@ use \sammo\{
|
||||
use \sammo\Constraint\Constraint;
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_NPC능동 extends Command\GeneralCommand{
|
||||
static protected $actionName = 'NPC능동';
|
||||
|
||||
@@ -98,7 +96,6 @@ class che_NPC능동 extends Command\GeneralCommand{
|
||||
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
}
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -15,8 +15,6 @@ use \sammo\Constraint\Constraint;
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
use sammo\Enums\InheritanceKey;
|
||||
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_모반시도 extends Command\GeneralCommand{
|
||||
static protected $actionName = '모반시도';
|
||||
|
||||
@@ -99,7 +97,6 @@ class che_모반시도 extends Command\GeneralCommand{
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->increaseInheritancePoint(InheritanceKey::active_action, 1);
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
$lordGeneral->applyDB($db);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ use sammo\Enums\InheritanceKey;
|
||||
|
||||
use function sammo\DeleteConflict;
|
||||
use function sammo\refreshNationStaticInfo;
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_방랑 extends Command\GeneralCommand{
|
||||
static protected $actionName = '방랑';
|
||||
@@ -125,7 +124,6 @@ class che_방랑 extends Command\GeneralCommand{
|
||||
refreshNationStaticInfo();
|
||||
$general->increaseInheritancePoint(InheritanceKey::active_action, 1);
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -21,8 +21,6 @@ use \sammo\Constraint\Constraint;
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
use sammo\Enums\InheritanceKey;
|
||||
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_선양 extends Command\GeneralCommand
|
||||
{
|
||||
static protected $actionName = '선양';
|
||||
@@ -139,7 +137,6 @@ class che_선양 extends Command\GeneralCommand
|
||||
$general->increaseInheritancePoint(InheritanceKey::active_action, 1);
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
$destGeneral->applyDB($db);
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ use \sammo\{
|
||||
use \sammo\Constraint\Constraint;
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_소집해제 extends Command\GeneralCommand{
|
||||
static protected $actionName = '소집해제';
|
||||
|
||||
@@ -82,7 +80,6 @@ class che_소집해제 extends Command\GeneralCommand{
|
||||
$general->addDedication($ded);
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -13,8 +13,6 @@ use \sammo\{
|
||||
use \sammo\Constraint\Constraint;
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_요양 extends Command\GeneralCommand{
|
||||
static protected $actionName = '요양';
|
||||
|
||||
@@ -71,7 +69,6 @@ class che_요양 extends Command\GeneralCommand{
|
||||
$general->addDedication($ded);
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -13,7 +13,8 @@ use \sammo\{
|
||||
GameConst,
|
||||
GameUnitConst,
|
||||
LastTurn,
|
||||
Command
|
||||
Command,
|
||||
KVStorage
|
||||
};
|
||||
|
||||
use function \sammo\buildItemClass;
|
||||
@@ -181,7 +182,7 @@ class che_장비매매 extends Command\GeneralCommand
|
||||
$general->onArbitraryAction($general, $rng, '장비매매', '판매', ['itemCode' => $itemCode]);
|
||||
$general->setItem($itemType, null);
|
||||
|
||||
if(!$itemObj->isBuyable()){
|
||||
if (!$itemObj->isBuyable()) {
|
||||
$generalName = $general->getName();
|
||||
$josaYi = JosaUtil::pick($generalName, '이');
|
||||
$nationName = $general->getStaticNation()['name'];
|
||||
|
||||
@@ -12,7 +12,6 @@ use \sammo\Command;
|
||||
use \sammo\Json;
|
||||
|
||||
use function \sammo\searchDistance;
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
use sammo\CityConst;
|
||||
@@ -214,7 +213,6 @@ class che_첩보 extends Command\GeneralCommand
|
||||
$general->increaseVar('leadership_exp', 1);
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -15,8 +15,6 @@ use \sammo\Constraint\ConstraintHelper;
|
||||
use sammo\CityConst;
|
||||
use sammo\Enums\InheritanceKey;
|
||||
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_하야 extends Command\GeneralCommand{
|
||||
static protected $actionName = '하야';
|
||||
|
||||
@@ -118,7 +116,6 @@ class che_하야 extends Command\GeneralCommand{
|
||||
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -20,7 +20,6 @@ use sammo\Event\EventHandler;
|
||||
|
||||
use function sammo\refreshNationStaticInfo;
|
||||
use function sammo\deleteNation;
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class che_해산 extends Command\GeneralCommand{
|
||||
static protected $actionName = '해산';
|
||||
@@ -99,7 +98,6 @@ class che_해산 extends Command\GeneralCommand{
|
||||
$oldGeneral->setVar('makelimit', 12);
|
||||
$oldGeneral->applyDB($db);
|
||||
}
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
// 이벤트 핸들러 동작
|
||||
|
||||
@@ -12,7 +12,6 @@ use \sammo\LastTurn;
|
||||
use \sammo\Command;
|
||||
|
||||
use function \sammo\searchDistance;
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
use sammo\CityConst;
|
||||
@@ -296,7 +295,6 @@ class che_화계 extends Command\GeneralCommand
|
||||
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
return false;
|
||||
}
|
||||
@@ -330,7 +328,6 @@ class che_화계 extends Command\GeneralCommand
|
||||
$general->increaseRankVar(RankColumn::firenum, 1);
|
||||
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
|
||||
$general->checkStatChange();
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB($db);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -7,8 +7,6 @@ use \sammo\JosaUtil;
|
||||
use \sammo\LastTurn;
|
||||
use \sammo\DB;
|
||||
|
||||
use function sammo\tryRollbackInheritUniqueItem;
|
||||
|
||||
class 휴식 extends Command\GeneralCommand{
|
||||
static protected $actionName = '휴식';
|
||||
|
||||
@@ -41,7 +39,6 @@ class 휴식 extends Command\GeneralCommand{
|
||||
$logger->pushGeneralActionLog("아무것도 실행하지 않았습니다. <1>$date</>");
|
||||
|
||||
$this->setResultTurn(new LastTurn());
|
||||
tryRollbackInheritUniqueItem($rng, $general);
|
||||
$general->applyDB(DB::db());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
use sammo\DTO\Attr\Convert;
|
||||
use sammo\DTO\Attr\JsonString;
|
||||
use sammo\DTO\Attr\NullIsUndefined;
|
||||
use sammo\DTO\Attr\RawName;
|
||||
use sammo\DTO\Converter\DateTimeConverter;
|
||||
|
||||
class AuctionBidItem extends DTO
|
||||
{
|
||||
public function __construct(
|
||||
#[NullIsUndefined]
|
||||
public ?int $no,
|
||||
#[RawName('auction_id')]
|
||||
public int $auctionID,
|
||||
public ?int $owner,
|
||||
|
||||
#[RawName('general_id')]
|
||||
public int $generalID,
|
||||
|
||||
public int $amount,
|
||||
|
||||
#[Convert(DateTimeConverter::class)]
|
||||
public \DateTimeImmutable $date,
|
||||
#[JsonString]
|
||||
public AuctionBidItemData $aux,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
use sammo\DTO\Attr\NullIsUndefined;
|
||||
|
||||
class AuctionBidItemData extends DTO
|
||||
{
|
||||
public function __construct(
|
||||
#[NullIsUndefined]
|
||||
public ?string $ownerName,
|
||||
public string $generalName,
|
||||
#[NullIsUndefined]
|
||||
public ?bool $tryExtendCloseDate,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
use sammo\DTO\Attr\Convert;
|
||||
use sammo\DTO\Attr\JsonString;
|
||||
use sammo\DTO\Attr\NullIsUndefined;
|
||||
use sammo\DTO\Attr\RawName;
|
||||
use sammo\DTO\Converter\DateTimeConverter;
|
||||
use sammo\Enums\AuctionType;
|
||||
use sammo\Enums\ResourceType;
|
||||
|
||||
class AuctionInfo extends DTO
|
||||
{
|
||||
public function __construct(
|
||||
#[NullIsUndefined]
|
||||
public ?int $id,
|
||||
public AuctionType $type,
|
||||
public bool $finished,
|
||||
public ?string $target,
|
||||
#[RawName('host_general_id')]
|
||||
public int $hostGeneralID,
|
||||
#[RawName('req_resource')]
|
||||
public ResourceType $reqResource,
|
||||
|
||||
#[RawName('open_date')]
|
||||
#[Convert(DateTimeConverter::class)]
|
||||
public \DateTimeImmutable $openDate,
|
||||
#[RawName('close_date')]
|
||||
#[Convert(DateTimeConverter::class)]
|
||||
public \DateTimeImmutable $closeDate,
|
||||
|
||||
#[JsonString]
|
||||
public AuctionInfoDetail $detail,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
use sammo\DTO\Attr\Convert;
|
||||
use sammo\DTO\Attr\NullIsUndefined;
|
||||
use sammo\DTO\Converter\DateTimeConverter;
|
||||
|
||||
class AuctionInfoDetail extends DTO
|
||||
{
|
||||
public function __construct(
|
||||
public string $title,
|
||||
public string $hostName,
|
||||
public int $amount,
|
||||
#[NullIsUndefined]
|
||||
public ?bool $isReverse,
|
||||
|
||||
public int $startBidAmount,
|
||||
#[NullIsUndefined]
|
||||
public ?int $finishBidAmount,
|
||||
#[NullIsUndefined]
|
||||
public ?int $remainCloseDateExtensionCnt,
|
||||
#[NullIsUndefined]
|
||||
#[Convert(DateTimeConverter::class)]
|
||||
public ?\DateTimeImmutable $availableLatestBidCloseDate,
|
||||
) {
|
||||
}
|
||||
}
|
||||
+40
-28
@@ -4,67 +4,79 @@ namespace sammo;
|
||||
|
||||
use sammo\Enums\InheritanceKey;
|
||||
|
||||
class DummyGeneral extends General{
|
||||
public function __construct(bool $initLogger=true){
|
||||
class DummyGeneral extends General
|
||||
{
|
||||
public function __construct(bool $initLogger = true)
|
||||
{
|
||||
$raw = [
|
||||
'no'=>0,
|
||||
'name'=>'Dummy',
|
||||
'npc'=>3,
|
||||
'city'=>0,
|
||||
'nation'=>0,
|
||||
'officer_level'=>0,
|
||||
'crewtype'=>-1,
|
||||
'turntime'=>'2012-03-04 05:06:07.000000',
|
||||
'experience'=>0,
|
||||
'dedication'=>0,
|
||||
'gold'=>0,
|
||||
'rice'=>0,
|
||||
'leadership'=>10,
|
||||
'strength'=>10,
|
||||
'intel'=>10,
|
||||
'no' => 0,
|
||||
'name' => 'Dummy',
|
||||
'npc' => 3,
|
||||
'city' => 0,
|
||||
'nation' => 0,
|
||||
'officer_level' => 0,
|
||||
'crewtype' => -1,
|
||||
'turntime' => '2012-03-04 05:06:07.000000',
|
||||
'experience' => 0,
|
||||
'dedication' => 0,
|
||||
'gold' => 0,
|
||||
'rice' => 0,
|
||||
'leadership' => 10,
|
||||
'strength' => 10,
|
||||
'intel' => 10,
|
||||
'imgsvr' => 0,
|
||||
'picture' => 'default.jpg',
|
||||
];
|
||||
|
||||
$this->raw = $raw;
|
||||
|
||||
$this->resultTurn = new LastTurn();
|
||||
|
||||
if($initLogger){
|
||||
if ($initLogger) {
|
||||
$this->initLogger(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function getBattleInitSkillTriggerList(WarUnit $unit):?WarUnitTriggerCaller{
|
||||
public function getBattleInitSkillTriggerList(WarUnit $unit): ?WarUnitTriggerCaller
|
||||
{
|
||||
return new WarUnitTriggerCaller();
|
||||
}
|
||||
|
||||
public function getBattlePhaseSkillTriggerList(WarUnit $unit):?WarUnitTriggerCaller{
|
||||
public function getBattlePhaseSkillTriggerList(WarUnit $unit): ?WarUnitTriggerCaller
|
||||
{
|
||||
return new WarUnitTriggerCaller();
|
||||
}
|
||||
|
||||
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
||||
public function onCalcStat(General $general, string $statName, $value, $aux = null)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function onCalcOpposeStat(General $general, string $statName, $value, $aux=null){
|
||||
public function onCalcOpposeStat(General $general, string $statName, $value, $aux = null)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getInheritancePoint(InheritanceKey $key, &$aux = null, bool $forceCalc = false): int|float{
|
||||
public function getInheritancePoint(InheritanceKey $key, &$aux = null, bool $forceCalc = false): int|float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setInheritancePoint(InheritanceKey $key, $value, $aux = null){
|
||||
public function setInheritancePoint(InheritanceKey $key, $value, $aux = null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public function increaseInheritancePoint(InheritanceKey $key, $value, $aux = null){
|
||||
public function increaseInheritancePoint(InheritanceKey $key, $value, $aux = null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
function applyDB($db):bool{
|
||||
if($this->logger){
|
||||
function applyDB($db): bool
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->initLogger($this->logger->getYear(), $this->logger->getMonth());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace sammo\Enums;
|
||||
|
||||
/**
|
||||
* 입찰자 기준
|
||||
*/
|
||||
enum AuctionType: string{
|
||||
/** 쌀을 매물로 등록, 금으로 구매 */
|
||||
case BuyRice = 'buyRice';
|
||||
/** 금을 매물로 등록, 쌀로 판매 */
|
||||
case SellRice = 'sellRice';
|
||||
/** 유미크를 매물로 등록, 유산 포인트로 구매 */
|
||||
case UniqueItem = 'uniqueItem';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace sammo\Enums;
|
||||
|
||||
enum ResourceType: string
|
||||
{
|
||||
case gold = 'gold';
|
||||
case rice = 'rice';
|
||||
case inheritancePoint = 'inheritPoint';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return match($this){
|
||||
ResourceType::gold => '금',
|
||||
ResourceType::rice => '쌀',
|
||||
ResourceType::inheritancePoint => '유산 포인트',
|
||||
};
|
||||
}
|
||||
}
|
||||
+1
-17
@@ -610,23 +610,7 @@ class General implements iAction
|
||||
$refundPoint += GameConst::$inheritItemRandomPoint;
|
||||
}
|
||||
|
||||
$itemTrials = $this->getAuxVar('inheritUniqueTrial') ?? [];
|
||||
foreach (array_keys($itemTrials) as $itemKey) {
|
||||
$trialStor = KVStorage::getStorage($db, "ut_{$itemKey}");
|
||||
$ownTrial = $trialStor->getValue("u{$userID}");
|
||||
|
||||
$itemObj = buildItemClass($itemKey);
|
||||
$itemName = $itemObj->getName();
|
||||
|
||||
if (!$ownTrial) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[,, $amount] = $ownTrial;
|
||||
$trialStor->deleteValue("u{$userID}");
|
||||
$userLogger->push("사망으로 {$itemName} 입찰에 사용한 {$amount} 포인트 반환", "inheritPoint");
|
||||
$refundPoint += $amount;
|
||||
}
|
||||
//TODO: 경매 최우선 입찰자인경우 반환
|
||||
|
||||
if ($this->getAuxVar('inheritSpecificSpecialWar')) {
|
||||
$this->setAuxVar('inheritSpecificSpecialWar', null);
|
||||
|
||||
Reference in New Issue
Block a user