diff --git a/hwe/_admin2.php b/hwe/_admin2.php
index 4281e2eb..913f7fac 100644
--- a/hwe/_admin2.php
+++ b/hwe/_admin2.php
@@ -48,54 +48,25 @@ $db = DB::db();
접속제한
블럭회원
-
+ query('SELECT `no`, `name`, npc, `block` FROM general ORDER BY npc, binary(`name`)');
- echo "
-
- |
- 아이템 지급 |
-
-
-
-
-
-
|
diff --git a/hwe/_admin2_submit.php b/hwe/_admin2_submit.php
index fb0b717f..660c204b 100644
--- a/hwe/_admin2_submit.php
+++ b/hwe/_admin2_submit.php
@@ -200,95 +200,6 @@ switch ($btn) {
$msg->send(true);
}
break;
- case "무기지급":
-
- if ($item == 'None') {
- $text = "무기 회수!";
- } else {
- $text = getItemName($item) . " 지급!";
- }
-
- foreach ($genlist as $generalID) {
- $msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
- $msg->send(true);
- }
-
- if ($item === 'None') {
- $db->update('general', [
- 'weapon' => 'None'
- ], '`no` IN %li', $genlist);
- } else {
- $db->update('general', [
- 'weapon' => $item
- ], '`no` IN %li', $genlist, $item);
- }
- break;
- case "책지급":
- if ($item == 'None') {
- $text = "책 회수!";
- } else {
- $text = getItemName($item) . " 지급!";
- }
-
- foreach ($genlist as $generalID) {
- $msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
- $msg->send(true);
- }
-
- if ($item == 'None') {
- $db->update('general', [
- 'book' => 'None'
- ], '`no` IN %li', $genlist);
- } else {
- $db->update('general', [
- 'book' => $item
- ], '`no` IN %li', $genlist);
- }
- break;
- case "말지급":
- if ($item == 'None') {
- $text = "말 회수!";
- } else {
- $text = getItemName($item) . " 지급!";
- }
-
- foreach ($genlist as $generalID) {
- $msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
- $msg->send(true);
- }
-
- if ($item == 'None') {
- $db->update('general', [
- 'horse' => 'None'
- ], '`no` IN %li', $genlist);
- } else {
- $db->update('general', [
- 'horse' => $item
- ], '`no` IN %li', $genlist);
- }
- break;
- case "도구지급":
- if ($item == 'None') {
- $text = "특수도구 회수!";
- } else {
- $text = getItemName($item) . " 지급!";
- }
-
- foreach ($genlist as $generalID) {
- $msg = new Message(Message::MSGTYPE_PRIVATE, $src, MessageTarget::buildQuick($generalID), $text, new \DateTime(), new \DateTime('9999-12-31'), []);
- $msg->send(true);
- }
-
- if ($item == 'None') {
- $db->update('general', [
- 'item' => 'None'
- ], '`no` IN %li', $genlist);
- } else {
- $db->update('general', [
- 'item' => $item
- ], '`no` IN %li AND item < %i', $genlist, $item);
- }
- break;
case "하야입력":
$db->update('general_turn', [
'action' => 'che_하야',
diff --git a/hwe/func.php b/hwe/func.php
index 66774df1..0e7b550b 100644
--- a/hwe/func.php
+++ b/hwe/func.php
@@ -3,6 +3,7 @@
namespace sammo;
use DateTime;
+use Ds\Set;
use sammo\Enums\InheritanceKey;
use sammo\Enums\RankColumn;
use sammo\Event\Action;
@@ -1665,6 +1666,10 @@ function giveRandomUniqueItem(RandUtil $rng, General $general, string $acquireTy
$logger->pushGlobalActionLog("{$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
$logger->pushGlobalHistoryLog("【{$acquireType}】>{$nationName}>의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
+ $givenUnique =$gameStor->getValue('givenUnique') ?? [];
+ $givenUnique[$itemCode] = ($givenUnique[$itemCode] ?? 0) + 1;
+ $gameStor->setValue('givenUnique', $givenUnique);
+
return true;
}
@@ -1866,6 +1871,11 @@ function tryInheritUniqueItem(RandUtil $rng, General $general, string $acquireTy
$general->applyDB($db);
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $givenUnique =$gameStor->getValue('givenUnique') ?? [];
+ $givenUnique[$ownTarget] = ($givenUnique[$ownTarget] ?? 0) + 1;
+ $gameStor->setValue('givenUnique', $givenUnique);
+
//같은 종류의 유니크를 입찰했을 수 있으니 한번 더 검사한다.
tryRollbackInheritUniqueItem($rng, $general);
diff --git a/hwe/sammo/API/Auction/BidUniqueAuction.php b/hwe/sammo/API/Auction/BidUniqueAuction.php
new file mode 100644
index 00000000..9c5c7948
--- /dev/null
+++ b/hwe/sammo/API/Auction/BidUniqueAuction.php
@@ -0,0 +1,65 @@
+ $amount) {
+ if ($amount == 0) {
+ continue;
+ }
+ $availableItems[$itemKey] = $amount;
+ }
+ }
+
+ $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;
+ }
+}
diff --git a/hwe/sammo/API/Auction/OpenUniqueAuction.php b/hwe/sammo/API/Auction/OpenUniqueAuction.php
index 21c6e2ee..ef3928ec 100644
--- a/hwe/sammo/API/Auction/OpenUniqueAuction.php
+++ b/hwe/sammo/API/Auction/OpenUniqueAuction.php
@@ -4,14 +4,12 @@ namespace sammo\API\Auction;
use sammo\Session;
use DateTimeInterface;
-use sammo\Betting;
-use sammo\DB;
-use sammo\DTO\BettingItem;
+use sammo\AuctionUniqueItem;
use sammo\Validator;
use sammo\GameConst;
-use sammo\KVStorage;
-use sammo\UserLogger;
-use sammo\Util;
+use sammo\General;
+
+use function sammo\buildItemClass;
class OpenUniqueAuction extends \sammo\BaseAPI
{
@@ -34,7 +32,7 @@ class OpenUniqueAuction extends \sammo\BaseAPI
])
->rule('int', 'amount')
->rule('min', 'amount', GameConst::$inheritItemUniqueMinPoint)
- ->rule('keyExists', 'item', $availableItems);
+ ->rule('keyExists', 'itemID', $availableItems);
if (!$v->validate()) {
@@ -50,22 +48,22 @@ class OpenUniqueAuction extends \sammo\BaseAPI
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
{
- /** @var int */
- $bettingID = $this->args['bettingID'];
- /** @var int[] */
- $bettingType = $this->args['bettingType'];
- /** @var int */
+ $itemID = $this->args['itemID'];
$amount = $this->args['amount'];
+ $generalID = $session->generalID;
- $bettingHelper = new Betting($bettingID);
- try {
- $bettingHelper->bet($session->generalID, $session->userID, $bettingType, $amount);
- } catch (\Throwable $e) {
- return $e->getMessage();
+ $itemObj = buildItemClass($itemID);
+ $general = General::createGeneralObjFromDB($generalID);
+
+ $auctionResult = AuctionUniqueItem::openItemAuction($itemObj, $general, $amount);
+
+ if(is_string($auctionResult)) {
+ return $auctionResult;
}
return [
- 'result' => true
+ 'result' => true,
+ 'auctionID' => $auctionResult->id,
];
}
}
diff --git a/hwe/sammo/API/General/DropItem.php b/hwe/sammo/API/General/DropItem.php
index 61c82db9..317aea01 100644
--- a/hwe/sammo/API/General/DropItem.php
+++ b/hwe/sammo/API/General/DropItem.php
@@ -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,30 @@ class DropItem extends \sammo\BaseAPI
$logger->pushGeneralActionLog("{$itemName}>{$josaUl} 버렸습니다.");
$nationName = $me->getStaticNation()['name'];
+ $db = DB::db();
if (!$item->isBuyable()) {
$logger->pushGlobalActionLog("{$generalName}>{$josaYi} {$itemName}>{$josaUl} 잃었습니다!");
$logger->pushGlobalHistoryLog("【망실】>{$nationName}>의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 잃었습니다!");
+
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $givenUnique = $gameStor->getValue('givenUnique') ?? [];
+ $itemCode = $item->getRawClassName();
+ if (key_exists($itemCode, $givenUnique)) {
+ $givenUniqueCnt = $givenUnique[$itemCode];
+ $givenUniqueCnt -= 1;
+ if ($givenUniqueCnt <= 0) {
+ unset($givenUnique[$itemCode]);
+ } else {
+ $givenUnique[$itemCode] = $givenUniqueCnt;
+ }
+ } else {
+ //XXX: 처리하지 못한 코드가 있는가?
+ //FIXME: 여기에서 무언가 경고를 내야함
+ }
+ $gameStor->setValue('givenUnique', $givenUnique);
}
- $me->applyDB(DB::db());
+ $me->applyDB($db);
return null;
}
diff --git a/hwe/sammo/Auction.php b/hwe/sammo/Auction.php
index 7284b779..1d609c80 100644
--- a/hwe/sammo/Auction.php
+++ b/hwe/sammo/Auction.php
@@ -317,8 +317,6 @@ class Auction
return '입찰에 실패했습니다: DB 오류';
}
-
-
$general->setVar($resType->value, $general->getVar($resType->value) - $morePoint);
if ($myPrevBid === null || ($highestBid !== null && $myPrevBid->id !== $highestBid->id)) {
diff --git a/hwe/sammo/AuctionUniqueItem.php b/hwe/sammo/AuctionUniqueItem.php
index 21af7aae..c717d08b 100644
--- a/hwe/sammo/AuctionUniqueItem.php
+++ b/hwe/sammo/AuctionUniqueItem.php
@@ -3,6 +3,7 @@
namespace sammo;
use DateTimeImmutable;
+use Ds\Set;
use sammo\DTO\AuctionBidItem;
use sammo\DTO\AuctionInfo;
use sammo\DTO\AuctionInfoDetail;
@@ -59,10 +60,33 @@ class AuctionUniqueItem extends Auction
return '구매할 수 있는 아이템입니다.';
}
+ $itemKey = $item->getRawClassName();
+ $db = DB::db();
+ $auctionIDonProgress = $db->queryFirstField(
+ 'SELECT `id` FROM ng_auction WHERE `finished` = 0 AND `target` = %s AND `target` = %s',
+ AuctionType::UniqueItem->value,
+ $itemKey
+ );
+ if ($auctionIDonProgress !== null) {
+ return '이미 경매가 진행중입니다.';
+ }
+
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $givenUnique = $gameStor->getValue('givenUnique') ?? [];
+ $givenUniqueCnt = $givenUnique[$itemKey] ?? 0;
+ $remainUniqueCnt = 0;
+ foreach (GameConst::$allItems as $itemList) {
+ if (!key_exists($itemKey, $itemList)) {
+ continue;
+ }
+ $remainUniqueCnt += $itemList[$itemKey];
+ }
+ if ($remainUniqueCnt > 1 && $givenUniqueCnt >= $remainUniqueCnt - 1) {
+ return '더이상 이 아이템을 경매로 가져갈 수 없습니다.';
+ }
+
$now = new DateTimeImmutable();
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
$turnTerm = $gameStor->getValue('turnterm');
$closeDate = $now->add(TimeUtil::secondsToDateInterval(
@@ -76,7 +100,7 @@ class AuctionUniqueItem extends Auction
null,
AuctionType::UniqueItem,
false,
- $item->getRawClassName(),
+ $itemKey,
$general->getID(),
ResourceType::inheritancePoint,
$now,
@@ -113,6 +137,9 @@ class AuctionUniqueItem extends Auction
{
$itemKey = $this->info->target;
+ if ($itemKey === null) {
+ throw new \Exception('아이템 키가 없습니다.');
+ }
$itemObj = buildItemClass($itemKey);
$general = $bidder;
$availableItemTypes = [];
@@ -192,6 +219,11 @@ class AuctionUniqueItem extends Auction
$logger->pushGlobalHistoryLog("【보물발견】>{$nationName}>의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
$general->applyDB($db);
+
+ $givenUnique = $gameStor->getValue('givenUnique') ?? [];
+ $givenUnique[$itemKey] = ($givenUnique[$itemKey] ?? 0) + 1;
+ $gameStor->setValue('givenUnique', $givenUnique);
+
return null;
}
@@ -263,7 +295,60 @@ class AuctionUniqueItem extends Auction
public function bid(int $amount, bool $tryExtendCloseDate = false): ?string
{
- //TODO: 입찰 가능 제한확인(다른 경매 진행 등)
+
+ $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
+ ) ?? []);
+
+ $db = DB::db();
+ $rawHighestBids = Util::convertArrayToDict($db->queryFirstRow(
+ 'SELECT * FROM ng_auction_bid WHERE auction_id IN %i ORDER BY `amount` DESC LIMIT 1',
+ $this->info->id
+ ) ?? [], 'auction_id');
+ /** @var array */
+ $highestBids = Util::mapWithKey(
+ fn ($auctionID, $bid) => AuctionBidItem::fromArray($bid),
+ $rawHighestBids
+ );
+
+ $itemCode = $this->info->target;
+ $bidItemTypes = new Set();
+ foreach (GameConst::$allItems as $itemType => $itemList) {
+ if (($itemList[$itemCode] ?? 0) <= 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 parent::bid($amount, $tryExtendCloseDate);
}
}
diff --git a/hwe/sammo/Command/General/che_장비매매.php b/hwe/sammo/Command/General/che_장비매매.php
index 2204dae9..a7faaf89 100644
--- a/hwe/sammo/Command/General/che_장비매매.php
+++ b/hwe/sammo/Command/General/che_장비매매.php
@@ -13,7 +13,8 @@ use \sammo\{
GameConst,
GameUnitConst,
LastTurn,
- Command
+ Command,
+ KVStorage
};
use function \sammo\buildItemClass;
@@ -181,12 +182,28 @@ 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'];
$logger->pushGlobalActionLog("{$generalName}>{$josaYi} {$itemName}>{$josaUl} 판매했습니다!");
$logger->pushGlobalHistoryLog("【판매】>{$nationName}>의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 판매했습니다!");
+
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ $givenUnique = $gameStor->getValue('givenUnique') ?? [];
+ if (key_exists($itemCode, $givenUnique)) {
+ $givenUniqueCnt = $givenUnique[$itemCode];
+ $givenUniqueCnt -= 1;
+ if ($givenUniqueCnt <= 0) {
+ unset($givenUnique[$itemCode]);
+ } else {
+ $givenUnique[$itemCode] = $givenUniqueCnt;
+ }
+ } else {
+ //XXX: 처리하지 못한 코드가 있는가?
+ //FIXME: 여기에서 무언가 경고를 내야함
+ }
+ $gameStor->setValue('givenUnique', $givenUnique);
}
}