Files
core/hwe/sammo/Auction.php
T

306 lines
9.2 KiB
PHP

<?php
namespace sammo;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Ds\Map;
use sammo\DTO\AuctionBidItem;
use sammo\DTO\AuctionBidItemData;
use sammo\DTO\AuctionInfo;
use sammo\Enums\InheritanceKey;
use sammo\Enums\RankColumn;
use sammo\Enums\ResourceType;
use sammo\VO\InheritancePointType;
class Auction
{
protected AuctionInfo $info;
public const LAST_AUCTION_ID_KEY = 'last_auction_id';
static public function genNextAuctionID(): int
{
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$gameStor->invalidateCacheValue(self::LAST_AUCTION_ID_KEY);
$auctionID = ($gameStor->getValue(self::LAST_AUCTION_ID_KEY) ?? 0) + 1;
$gameStor->setValue(self::LAST_AUCTION_ID_KEY, $auctionID);
return $auctionID;
}
static public function openAuction(AuctionInfo $info, General $general): ?string
{
$db = DB::db();
$auctionID = $info->id;
$auctionStor = KVStorage::getStorage($db, 'auction');
$openedAuction = $general->getAuxVar('openedAuction') ?? [];
$prevAuctionID = $openedAuction[$info->reqResource->value] ?? null;
if ($prevAuctionID !== null) {
//XXX: 이 구조보다는 차라리 DB에 insert로 넣는 게 나을 수도.
$prevAuction = new Auction($prevAuctionID, $general);
if (!$prevAuction->info->finished) {
return '아직 경매가 끝나지 않았습니다.';
}
unset($openedAuction[$info->reqResource->value]);
$general->setAuxVar('openedAuction', $openedAuction);
}
$auctionStor->setValue("id_{$auctionID}", $info->toArray());
}
public function getHighestBid(): ?AuctionBidItem
{
$db = DB::db();
$rawHighestBid = $db->queryFirstRow(
'SELECT * FROM ng_auction WHERE auction_id = %i ORDER BY `amount` DESC LIMIT 1',
$this->info->id
);
if(!$rawHighestBid){
return null;
}
return AuctionBidItem::fromArray($rawHighestBid);
}
public function getMyPrevBid(): ?AuctionBidItem
{
$db = DB::db();
$rawMyPrevBid = $db->queryFirstRow(
'SELECT * FROM ng_auction WHERE general_id = %i AND auction_id = %i ORDER BY `amount` DESC 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();
$auctionStor = KVStorage::getStorage($db, 'auction');
$rawAuctionInfo = $auctionStor->getValue("id_{$auctionID}");
if ($rawAuctionInfo === null) {
throw new \RuntimeException("해당 경매가 없습니다: {$auctionID}");
}
$this->info = AuctionInfo::fromArray($rawAuctionInfo);
}
public function getInfo(): AuctionInfo
{
return $this->info;
}
public function extendCloseDate(DateTimeInterface $date): void
{
$db = DB::db();
$auctionStor = KVStorage::getStorage($db, 'auction');
$this->info->closeDate = DateTimeImmutable::createFromInterface($date);
$auctionStor->setValue("id_{$this->info->id}", $this->info->toArray());
}
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->generalID) {
$oldBidder = $this->general;
} else {
$oldBidder = General::createGeneralObjFromDB($bidItem->general_id);
}
if($this->info->reqResource === ResourceType::inheritancePoint){
$oldBidder->increaseInheritancePoint(InheritanceKey::previous, $bidItem->amount);
}
else{
$oldBidder->increaseVar($this->info->reqResource->value, $bidItem->amount);
}
$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();
$auctionStor = KVStorage::getStorage($db, 'auction');
$this->info->finished = true;
$openedAuction = $this->general->getAuxVar('openedAuction') ?? [];
if (key_exists($this->info->reqResource->value, $openedAuction)) {
unset($openedAuction[$this->info->reqResource->value]);
$this->general->setAuxVar('openedAuction', $openedAuction);
}
if ($isRollback) {
$highestBid = $this->getHighestBid();
if($highestBid !== null){
$this->refundBid($highestBid, "{$this->info->title} 경매가 취소되었습니다.");
}
}
$auctionID = $this->info->id;
$auctionStor->setValue("id_{$auctionID}", $this->info->toArray());
}
private function bidInheritPoint(int $amount, \DateTimeImmutable $now): void
{
$db = DB::db();
$auctionInfo = $this->info;
$general = $this->general;
$highestBid = $this->getHighestBid();
if ($highestBid !== null && $amount <= $highestBid->amount) {
throw new \RuntimeException('현재입찰가보다 높게 입찰해야 합니다.');
}
$rawMyPrevBid = $db->queryFirstRow(
'SELECT * FROM ng_auction WHERE general_id = %i AND auction_id = %i ORDER BY `amount` DESC LIMIT 1',
$general->getID(),
$auctionInfo->id
);
if ($rawMyPrevBid === null) {
$myPrevBid = null;
} else {
$myPrevBid = AuctionBidItem::fromArray($rawMyPrevBid);
}
$morePoint = $amount - ($myPrevBid ? $myPrevBid->amount : 0);
$currPoint = $general->getInheritancePoint(InheritanceKey::previous);
if ($currPoint === null || $currPoint < $morePoint) {
throw new \RuntimeException('유산포인트가 부족합니다.');
}
//여기서부터 입찰 성공
$newBid = new AuctionBidItem(
null,
$auctionInfo->id,
$general->getVar('owner'),
$general->getID(),
$amount,
$now,
new AuctionBidItemData(
$general->getVar('owner_name'),
$general->getName(),
)
);
$db->insert('ng_auction', $newBid->toArray());
$general->increaseInheritancePoint(InheritanceKey::previous, -$morePoint);
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $morePoint);
if ($myPrevBid === null || ($highestBid !== null && $myPrevBid->id !== $highestBid->id)) {
$this->refundBid($highestBid, "{$auctionInfo->title} 경매에 상회입찰자가 나타났습니다.");
}
$general->applyDB($db);
}
public function bid(int $amount): void
{
$auctionInfo = $this->info;
$general = $this->general;
if ($auctionInfo->finished) {
throw new \RuntimeException('경매가 이미 끝났습니다.');
}
$now = new \DateTimeImmutable();
if ($auctionInfo->closeDate < $now) {
throw new \RuntimeException('경매가 이미 끝났습니다.');
}
if ($auctionInfo->closeDate > $now) {
throw new \RuntimeException('경매가 아직 시작되지 않았습니다.');
}
if ($auctionInfo->buyImmediatelyAmount !== null && $auctionInfo->buyImmediatelyAmount < $amount) {
throw new \RuntimeException('즉시판매가보다 높을 수 없습니다.');
}
if ($auctionInfo->reqResource === ResourceType::inheritancePoint) {
$this->bidInheritPoint($amount, $now);
return;
}
//reqResource는 말 그대로 '구매자가 내야하는 자원'이다.
$db = DB::db();
$highestBid = $this->getHighestBid();
if ($highestBid !== null && $amount <= $highestBid->amount) {
throw new \RuntimeException('현재입찰가보다 높게 입찰해야 합니다.');
}
$myPrevBid = $this->getMyPrevBid();
$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) {
throw new \RuntimeException($resType->getName() . '이 부족합니다.');
}
//여기서부터 입찰 성공
$newBid = new AuctionBidItem(
null,
$auctionInfo->id,
$general->getVar('owner'),
$general->getID(),
$amount,
$now,
new AuctionBidItemData(
$general->getVar('owner_name'),
$general->getName(),
)
);
$db->insert('ng_auction', $newBid->toArray());
$general->setVar($resType->value, $general->getVar($resType->value) - $morePoint);
if ($myPrevBid === null || ($highestBid !== null && $myPrevBid->id !== $highestBid->id)) {
$this->refundBid($highestBid, "{$auctionInfo->title} 경매에 상회입찰자가 나타났습니다.");
}
$general->applyDB($db);
}
}