diff --git a/.phan/config.php b/.phan/config.php
index d7f23571..b808b9ed 100644
--- a/.phan/config.php
+++ b/.phan/config.php
@@ -45,7 +45,6 @@ return [
'hwe/api.php',
'hwe/a_traffic.php',
'hwe/battle_simulator.php',
- 'hwe/b_auction.php',
'hwe/b_battleCenter.php',
'hwe/b_betting.php',
'hwe/v_chiefCenter.php',
@@ -60,7 +59,6 @@ return [
'hwe/v_processing.php',
'hwe/b_tournament.php',
'hwe/b_troop.php',
- 'hwe/c_auction.php',
'hwe/c_tournament.php',
'hwe/func_auction.php',
'hwe/func_command.php',
diff --git a/hwe/_119_b.php b/hwe/_119_b.php
index 9e18f21f..84158b73 100644
--- a/hwe/_119_b.php
+++ b/hwe/_119_b.php
@@ -50,9 +50,9 @@ switch ($btn) {
$db->update('general', [
'turntime' => $db->sqleval('DATE_SUB(turntime, INTERVAL %i MINUTE)', $minute)
], true);
- $db->update('auction', [
- 'expire' => $db->sqleval('DATE_SUB(expire, INTERVAL %i MINUTE)', $minute)
- ], true);
+ $db->update('ng_auction', [
+ 'close_date' => $db->sqleval('DATE_SUB(close_date, INTERVAL %i MINUTE)', $minute)
+ ], 'finished = 0');
if ($locked) {
unlock();
}
@@ -78,9 +78,9 @@ switch ($btn) {
$db->update('general', [
'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute)
], true);
- $db->update('auction', [
- 'expire' => $db->sqleval('DATE_ADD(expire, INTERVAL %i MINUTE)', $minute)
- ], true);
+ $db->update('ng_auction', [
+ 'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute)
+ ], 'finished = 0');
if ($locked) {
unlock();
}
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 "
- ";
- $generalList = $db->query('SELECT `no`, `name`, npc, `block` FROM general ORDER BY npc, binary(`name`)');
-
- foreach ($generalList as $general) {
- $style = "style=;";
- if ($general['block'] > 0) {
- $style .= "background-color:red;";
- }
-
- $npcColor = getNPCColor($general['npc']);
- if($npcColor !== null){
- $style .= "color:{$npcColor};";
- }
-
- echo "
- {$general['name']} ";
- }
-
- echo "
-
-
- 아이템 지급
-
- ";
- foreach (GameConst::$allItems as $itemCategories) {
- foreach ($itemCategories as $item => $cnt) {
- if ($cnt == 0) {
- continue;
+ foreach ($generalList as $general) {
+ $style = "style=;";
+ if ($general['block'] > 0) {
+ $style .= "background-color:red;";
}
- $itemObj = buildItemClass($item);
- if ($itemObj->isBuyable()) {
- continue;
+
+ $npcColor = getNPCColor($general['npc']);
+ if ($npcColor !== null) {
+ $style .= "color:{$npcColor};";
}
+
+ echo "{$general['name']} ";
}
- }
- for ($i = 0; $i < 27; $i++) {
- echo "
- {$i} ";
- }
- ?>
+ ?>
-
-
-
-
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/b_auction.php b/hwe/b_auction.php
deleted file mode 100644
index 6fd02248..00000000
--- a/hwe/b_auction.php
+++ /dev/null
@@ -1,306 +0,0 @@
-setReadOnly();
-$userID = Session::getUserID();
-
-$db = DB::db();
-$gameStor = KVStorage::getStorage($db, 'game_env');
-
-increaseRefresh("거래장", 2);
-
-$me = $db->queryFirstRow('SELECT no,special,con,turntime from general where owner=%i', $userID);
-
-$con = checkLimit($me['con']);
-if ($con >= 2) {
- printLimitMsg($me['turntime']);
- exit();
-}
-
-$tradeCount = $db->queryFirstField('SELECT count(no) FROM auction WHERE no1=%i', $me['no']);
-$bidCount = $db->queryFirstField('SELECT count(no) FROM auction where no2=%i', $me['no']);
-
-$btCount = $tradeCount + $bidCount;
-
-if ($session->userGrade >= 5 || $btCount < 1) {
- $btn = "submit";
-} else {
- $btn = "hidden";
-}
-
-if ($msg == "") {
- $msg = "-";
-}
-if ($msg2 == "") {
- $msg2 = "-";
-}
-?>
-
-
-
-
- = UniqueConst::$serverName ?>: 거래장
-
-
-
- = WebUtil::printJS('../d_shared/common_path.js') ?>
- = WebUtil::printCSS('../d_shared/common.css') ?>
-
- = WebUtil::printDist('ts', ['common']) ?>
-
-
-
-
-
-
-
-
-
-
-
- 최 근 기 록
-
-
-
-
- = getAuctionLogRecent(20) ?>
-
-
-
-
- 도 움 말
-
-
-
-
-
- ㆍ판매거래는 거래자가 판매할 물품을 거래하면, 구입을 희망하는 사람이 현재가보다 높게 입찰하여 구입하는 방식입니다.
- ㆍHint ) 쌀이 귀한 경우는 입찰자가 많아서 자연스레 단가가 오르게 됩니다. (해당 물품을 사려는 가격이 오름)
- ㆍHint ) 쌀이 흔한 경우는 초기 가격을 낮게 책정해야 판매가 가능할 겁니다.
- ㆍ구매거래는 거래자가 구입할 물품을 거래하면, 판매를 희망하는 사람이 현재가보다 낮게 입찰하여 판매하는 방식입니다.
- ㆍHint ) 쌀이 흔한 경우는 입찰자가 많아서 자연스레 단가가 내리게 됩니다. (해당 물품을 팔려는 가격이 내림)
- ㆍHint ) 쌀이 귀한 경우는 초기 가격을 높게 책정해야 구입이 가능할 겁니다.
- ㆍ마감임박때 입찰하는 경우 입찰후 1턴 후로 종료시간이 연장됩니다.
- ㆍ즉시구매가로 입찰하는 경우 입찰후 1턴 후로 종료시간이 결정됩니다.
- ㆍ악용 방지를 위해 50% ~ 200%의 가격에서 거래시작이 가능합니다.
- ㆍ악용 방지를 위해 즉시판매가는 110% 이상, 즉시구매가는 90% 이하의 시세로 가능합니다.
- ㆍ악용 방지를 위해 즉시판매가는 시작판매가의 110% 이상, 즉시구매가는 시작구매가의 90% 이하로 가능합니다.
- ㆍ1인당 도합 1건의 거래와 입찰이 가능합니다.
- ㆍ기본금쌀 1000은 거래에 사용되지 못합니다.
- ㆍ유찰될 때는 거래 과실자에게 거래금의 1%가 벌금으로 부과됩니다.
- ㆍ10단위로 거래가 가능합니다. 1자리는 반올림 처리 됩니다.
- ㆍ★ 최고가 거래 ★ 혹은 ★ 최저가 거래 ★ 는 암거래 및 악용의 가능성이니 감시 부탁드립니다.
- ㆍ거래와 입찰은 취소가 불가능하니 주의하세요!
- ㆍHint ) 단가는 금/쌀로 쌀1을 거래하기 위한 금의 양입니다.
- ㆍHint ) 단가가 높으면(>1.00) 쌀이 비싸므로 판매가 이득입니다.
- ㆍHint ) 단가가 낮으면(<1.00) 금이 비싸므로 구매가 이득입니다.
- ㆍ즐거운 거래!
-
-
-
-
- = closeButton() ?>
-
-
- = banner() ?>
-
-
-
-
-
\ No newline at end of file
diff --git a/hwe/c_auction.php b/hwe/c_auction.php
deleted file mode 100644
index c4f6c517..00000000
--- a/hwe/c_auction.php
+++ /dev/null
@@ -1,247 +0,0 @@
-rule('integer', [
- 'amount',
- 'cost',
- 'topv',
- 'value',
- 'term',
- 'sel'
-]);
-
-$btn = Util::getPost('btn');
-$amount = Util::getPost('amount', 'int');
-$cost = Util::getPost('cost', 'int');
-$topv = Util::getPost('topv', 'int');
-$value = Util::getPost('value', 'int');
-$term = Util::getPost('term', 'int');
-$sel = Util::getPost('sel', 'int');
-
-$msg = '';
-$msg2 = '';
-
-
-//로그인 검사
-$session = Session::requireGameLogin()->setReadOnly();
-$userID = Session::getUserID();
-
-$db = DB::db();
-$gameStor = KVStorage::getStorage($db, 'game_env');
-
-increaseRefresh("입찰", 1);
-
-$turnterm = $gameStor->turnterm;
-
-$me = $db->queryFirstRow('SELECT no,name,gold,rice,special from general where owner=%i', $userID);
-
-$tradeCount = $db->queryFirstField('SELECT count(no) FROM auction WHERE no1=%i', $me['no']);
-$bidCount = $db->queryFirstField('SELECT count(no) FROM auction WHERE no2=%i', $me['no']);
-
-$btCount = $tradeCount + $bidCount;
-
-$unit = $turnterm * 60;
-
-$amount = Util::round($amount, -1);
-$cost = Util::round($cost, -1);
-$topv = Util::round($topv, -1);
-$value = Util::round($value, -1);
-if ($term > 24) {
- $term = 24;
-}
-
-$valid = 1;
-if ($session->userGrade >= 5 || $btCount < 1) {
-} else {
- $msg = "ㆍ더이상 등록할 수 없습니다. ";
- $msg2 = "ㆍ더이상 등록할 수 없습니다. ";
- $valid = 0;
- $btn = "hidden";
-}
-
-if ($btn == "판매") {
- if ($term < 0 || $term > 24) {
- $msg = "ㆍ종료기한은 1 ~ 24 턴 이어야 합니다. ";
- $valid = 0;
- }
- if ($amount < 100 || $amount > 10000) {
- $msg = "ㆍ거래량은 100 ~ 10000 이어야 합니다. ";
- $valid = 0;
- }
- if ($cost > $amount * 2 || $cost * 2 < $amount) {
- $msg = "ㆍ시작판매가는 50% ~ 200% 이어야 합니다. ";
- $valid = 0;
- }
- if ($topv * 10 < $amount * 11 || $topv > $amount * 2) {
- $msg = "ㆍ즉시판매가는 110% ~ 200% 이어야 합니다. ";
- $valid = 0;
- }
- if ($topv * 10 < $cost * 11) {
- $msg = "ㆍ즉시판매가는 시작판매가의 110% 이상이어야 합니다. ";
- $valid = 0;
- }
- if ($amount > $me['rice'] - GameConst::$defaultRice) {
- $msg = "ㆍ기본 군량 ".GameConst::$defaultRice."은 거래할 수 없습니다. ";
- $valid = 0;
- }
- if ($valid == 1) {
- $msg = "ㆍ등록 성공. ";
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit * $term);
- $db->insert('auction', [
- 'type'=>0,
- 'no1'=>$me['no'],
- 'name1'=>$me['name'],
- 'amount'=>$amount,
- 'cost'=>$cost,
- 'value'=>$cost,
- 'topv'=>$topv,
- 'expire'=>$date
- ]);
- }
-} elseif ($btn == "구매시도") {
- $auction = $db->queryFirstRow('SELECT no2,value,topv,expire,amount FROM auction WHERE no=%i LIMIT 1', $sel);
-
- if ($value == $auction['topv']) {
- $valid = 2;
- }
- if (!$auction) {
- $msg = "ㆍ종료된 거래입니다. ";
- $valid = 0;
- }
- if ($auction['no2'] > 0 && $value <= $auction['value']) {
- $msg = "ㆍ현재판매가보다 높게 입찰해야 합니다. ";
- $valid = 0;
- }
- if ($value < $auction['value']) {
- $msg = "ㆍ현재판매가보다 높게 입찰해야 합니다. ";
- $valid = 0;
- }
- if ($value > $auction['topv']) {
- $msg = "ㆍ즉시판매가보다 높을 수 없습니다. ";
- $valid = 0;
- }
- if ($value > $me['gold'] - GameConst::$defaultGold) {
- $msg = "ㆍ기본 자금 ".GameConst::$defaultGold."은 거래할 수 없습니다. ";
- $valid = 0;
- }
- if ($valid == 1) {
- $msg = "ㆍ입찰 성공. 거래완료는 빨라도 현재로부터 1턴 뒤입니다.";
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit);
- if ($auction['expire'] > $date) {
- $date = $auction['expire'];
- }
-
- $db->update('auction', [
- 'value'=>$value,
- 'no2'=>$me['no'],
- 'name2'=>$me['name'],
- 'expire'=>$date,
- ], 'no=%i', $sel);
- } elseif ($valid == 2) {
- $msg = "ㆍ즉시판매 성공. 거래완료는 빨라도 현재로부터 1턴 뒤입니다.";
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit);
-
- $db->update('auction', [
- 'value'=>$value,
- 'no2'=>$me['no'],
- 'name2'=>$me['name'],
- 'expire'=>$date,
- ], 'no=%i', $sel);
- }
-} elseif ($btn == "구매") {
- if ($term < 0 || $term > 24) {
- $msg2 = "ㆍ종료기한은 1 ~ 24 턴 이어야 합니다. ";
- $valid = 0;
- }
- if ($amount < 100 || $amount > 10000) {
- $msg2 = "ㆍ거래량은 100 ~ 10000 이어야 합니다. ";
- $valid = 0;
- }
- if ($cost > $amount * 2 || $cost * 2 < $amount) {
- $msg2 = "ㆍ시작구매가는 50% ~ 200% 이어야 합니다. ";
- $valid = 0;
- }
- if ($topv < $amount * 0.5 || $topv > $amount * 0.9) {
- $msg2 = "ㆍ즉시구매가는 50% ~ 90% 이어야 합니다. ";
- $valid = 0;
- }
- if ($topv > $cost * 0.9) {
- $msg2 = "ㆍ즉시구매가는 시작구매가의 90% 이하이어야 합니다. ";
- $valid = 0;
- }
- if ($cost > $me['gold'] - GameConst::$defaultGold) {
- $msg2 = "ㆍ기본 자금 ".GameConst::$defaultGold."은 거래할 수 없습니다. ";
- $valid = 0;
- }
- if ($valid == 1) {
- $msg2 = "ㆍ등록 성공. ";
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit * $term);
-
- $db->insert('auction', [
- 'type'=>1,
- 'no1'=>$me['no'],
- 'name1'=>$me['name'],
- 'amount'=>$amount,
- 'cost'=>$cost,
- 'value'=>$cost,
- 'topv'=>$topv,
- 'expire'=>$date
- ]);
- }
-} elseif ($btn == "판매시도") {
- $auction = $db->queryFirstRow('SELECT no2,value,topv,expire,amount FROM auction WHERE no=%i LIMIT 1', $sel);
-
- if ($value == $auction['topv']) {
- $valid = 2;
- }
- if (!$auction) {
- $msg2 = "ㆍ종료된 거래입니다. ";
- $valid = 0;
- }
- if ($auction['no2'] > 0 && $value >= $auction['value']) {
- $msg2 = "ㆍ현재구매가보다 낮게 입찰해야 합니다. ";
- $valid = 0;
- }
- if ($value > $auction['value']) {
- $msg2 = "ㆍ현재구매가보다 낮게 입찰해야 합니다. ";
- $valid = 0;
- }
- if ($value < $auction['topv']) {
- $msg2 = "ㆍ즉시구매가보다 낮을 수 없습니다. ";
- $valid = 0;
- }
- if ($value > $me['rice'] - GameConst::$defaultRice) {
- $msg2 = "ㆍ기본 군량 ".GameConst::$defaultRice."은 거래할 수 없습니다. ";
- $valid = 0;
- }
- if ($valid == 1) {
- $msg2 = "ㆍ입찰 성공. 거래완료는 빨라도 현재로부터 1턴 뒤입니다.";
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit);
- if ($auction['expire'] > $date) {
- $date = $auction['expire'];
- }
-
- $db->update('auction', [
- 'value'=>$value,
- 'no2'=>$me['no'],
- 'name2'=>$me['name'],
- 'expire'=>$date,
- ], 'no=%i', $sel);
- } elseif ($valid == 2) {
- $msg2 = "ㆍ즉시구매 성공. 거래완료는 빨라도 현재로부터 1턴 뒤입니다.";
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit);
-
- $db->update('auction', [
- 'value'=>$value,
- 'no2'=>$me['no'],
- 'name2'=>$me['name'],
- 'expire'=>$date,
- ], 'no=%i', $sel);
- }
-}
-
-Submit("b_auction.php", $msg, $msg2);
diff --git a/hwe/func.php b/hwe/func.php
index 66774df1..e464a532 100644
--- a/hwe/func.php
+++ b/hwe/func.php
@@ -3,6 +3,8 @@
namespace sammo;
use DateTime;
+use Ds\Set;
+use sammo\Enums\AuctionType;
use sammo\Enums\InheritanceKey;
use sammo\Enums\RankColumn;
use sammo\Event\Action;
@@ -1204,9 +1206,9 @@ function checkDelay()
$db->update('general', [
'turntime' => $db->sqleval('DATE_ADD(turntime, INTERVAL %i MINUTE)', $minute)
], 'turntime<=DATE_ADD(turntime, INTERVAL %i MINUTE)', $term);
- $db->update('auction', [
- 'expire' => $db->sqleval('DATE_ADD(expire, INTERVAL %i MINUTE)', $minute)
- ], true);
+ $db->update('ng_auction', [
+ 'close_date' => $db->sqleval('DATE_ADD(close_date, INTERVAL %i MINUTE)', $minute)
+ ], 'finished = 0');
}
}
@@ -1557,6 +1559,7 @@ function CheckHall($no)
function giveRandomUniqueItem(RandUtil $rng, General $general, string $acquireType): bool
{
$db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
//아이템 습득 상황
$availableUnique = [];
@@ -1588,6 +1591,18 @@ function giveRandomUniqueItem(RandUtil $rng, General $general, string $acquireTy
}
}
+ $auctionItems = $db->queryFirstColumn(
+ 'SELECT `target` FROM `ng_auction` WHERE `type` = %s AND `finished` = 0',
+ AuctionType::UniqueItem->value
+ );
+ foreach ($auctionItems as $itemCode) {
+ if (key_exists($itemCode, $occupiedUnique)) {
+ $occupiedUnique[$itemCode]++;
+ } else {
+ $occupiedUnique[$itemCode] = 1;
+ }
+ }
+
foreach ($db->queryAllLists('SELECT namespace, count(*) as cnt FROM `storage` WHERE namespace LIKE "ut_%" GROUP BY namespace') as [$uniqueNS, $cnt]) {
$itemCode = substr($uniqueNS, 3);
$itemClass = buildItemClass($itemCode);
@@ -1631,10 +1646,7 @@ function giveRandomUniqueItem(RandUtil $rng, General $general, string $acquireTy
return false;
}
-
-
if ($general->getAuxVar('inheritRandomUnique')) {
- $gameStor = KVStorage::getStorage($db, 'game_env');
[$year, $month, $initYear, $initMonth] = $gameStor->getValuesAsArray(['year', 'month', 'init_year', 'init_month']);
$relMonthByInit = Util::joinYearMonth($year, $month) - Util::joinYearMonth($initYear, $initMonth);
@@ -1668,210 +1680,6 @@ function giveRandomUniqueItem(RandUtil $rng, General $general, string $acquireTy
return true;
}
-function rollbackInheritUniqueTrial(General $general, string $itemKey, string $reason)
-{
-
- $ownerID = $general->getVar('owner');
-
- $db = DB::db();
-
- $itemTrials = $general->getAuxVar('inheritUniqueTrial');
- LogText("선택유니크 롤백:{$ownerID}", [$itemKey, $itemTrials]);
- unset($itemTrials[$itemKey]);
- if (count($itemTrials) == 0) {
- $itemTrials = null;
- }
- $general->setAuxVar('inheritUniqueTrial', $itemTrials);
-
-
- $trialStor = KVStorage::getStorage($db, "ut_{$itemKey}");
- $ownTrial = $trialStor->getValue("u{$ownerID}");
-
- $itemObj = buildItemClass($itemKey);
- $itemName = $itemObj->getName();
-
- if ($ownTrial) {
- //두 값이 general, KVStorage 둘다 있고, 이중에선 KVStorage 값을 기준으로 하자 따르자
- [,, $amount] = $ownTrial;
- $trialStor->deleteValue("u{$ownerID}");
- $general->increaseInheritancePoint(InheritanceKey::previous, $amount);
- $general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, -$amount);
- LogText("선택유니크 롤백포인트:{$ownerID}", $amount);
-
- $userLogger = new UserLogger($ownerID);
- $userLogger->push("{$itemName} 입찰에 사용한 {$amount} 포인트 반환", "inheritPoint");
- }
-
- //메시지
-
- $staticNation = $general->getStaticNation();
-
- $unlimited = new \DateTime('9999-12-31');
- $src = new MessageTarget(0, '', 0, 'System', '#000000');
- $dest = new MessageTarget($general->getID(), $general->getName(), $general->getNationID(), $staticNation['name'], $staticNation['color'], GetImageURL($general->getVar('imgsvr'), $general->getVar('picture')));
- $josaUl = JosaUtil::pick($itemName, '을');
- $msg = new Message(
- Message::MSGTYPE_PRIVATE,
- $src,
- $dest,
- "{$itemName}{$josaUl} 얻지 못했습니다. {$reason}",
- new DateTime(),
- $unlimited,
- []
- );
-
- $general->applyDB($db);
- $msg->send(true);
-}
-
-function tryRollbackInheritUniqueItem(RandUtil $rng, General $general): void
-{
- tryInheritUniqueItem($rng, $general, 'Rollback', true);
-}
-
-function tryInheritUniqueItem(RandUtil $rng, General $general, string $acquireType = '아이템', bool $justRollback = false): bool
-{
- $ownerID = $general->getVar('owner');
- if (!$ownerID) {
- LogText("선택유니크 실패???: {$ownerID}", $general->getName());
- return false;
- }
-
- $itemTrials = $general->getAuxVar('inheritUniqueTrial') ?? [];
- arsort($itemTrials);
- LogText("선택유니크항목: {$ownerID}", $itemTrials);
-
- $db = DB::db();
-
- $ownTarget = null;
- $ownType = null;
-
- foreach ($itemTrials as $itemKey => $amount) {
- $availableItemTypes = [];
- $reasons = [];
- foreach (GameConst::$allItems as $itemType => $itemList) {
- //아직은 그런 경우는 없지만 동일 유니크를 여러 부위에 장착할 수 있을지도 모름
- if (!key_exists($itemKey, $itemList)) {
- continue;
- }
-
- $ownItem = $general->getItem($itemType);
- if ($ownItem->getRawClassName() == $itemKey) {
- $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) {
- $reasons[] = '그 유니크는 모두 점유되었습니다.';
- continue;
- }
- $availableItemTypes[] = $itemType;
- }
-
- if (!$availableItemTypes) {
- rollbackInheritUniqueTrial($general, $itemKey, join(' ', $reasons));
- continue;
- }
- $reasons = [];
-
- $itemType = $rng->choice($availableItemTypes);
-
- $trialStor = KVStorage::getStorage($db, "ut_{$itemKey}"); //혹시 itemKey의 크기가 37이 넘을 수 있을까?
- $anyTrials = $trialStor->getAll();
- if (!$anyTrials) {
- //순서가 꼬였던 모양, 실제 값은 storage를 우선시하자
- rollbackInheritUniqueTrial($general, $itemKey, '절차상의 오류입니다.');
- continue;
- }
-
- //XXX: 정렬할 필요 없지 않나?
- usort($anyTrials, function ($lhsTrial, $rhsTrial) {
- [,, $lhsAmount] = $lhsTrial;
- [,, $rhsAmount] = $rhsTrial;
- return $rhsAmount <=> $lhsAmount; //큰 값이 앞에 오도록
- });
-
- LogText("선택유니크상태 {$ownerID} {$itemKey}", $anyTrials);
-
- //공동 1등인데 본인이 있을 수도 있다.
- [,, $topAmount] = $anyTrials[0];
- if ($amount < $topAmount) {
- $compAmount = $topAmount / $amount;
- if ($compAmount > 2.0) {
- $compText = '엄청난 차이로 ';
- } else if ($compAmount > 1.2) {
- $compText = '큰 차이로 ';
- } else if ($compAmount > 1.05) {
- $compText = '';
- } else {
- $compText = '아슬아슬한 차이로 ';
- }
- rollbackInheritUniqueTrial($general, $itemKey, "{$compText}상위 입찰한 장수가 있습니다.");
- continue;
- }
-
- //내가 1위다
- if ($ownTarget !== null) {
- //이미 다른 아이템을 얻기로 되어있다.
- continue;
- }
- $ownTarget = $itemKey;
- $ownType = $itemType;
- }
- unset($itemKey);
- unset($itemType);
-
- if ($ownTarget === null) {
- return false;
- }
- if ($justRollback) {
- return false;
- }
-
- LogText("선택유니크획득{$ownerID}", $ownTarget);
-
- $trialStor = KVStorage::getStorage($db, "ut_{$ownTarget}");
- $trialStor->deleteValue("u{$ownerID}");
-
- //rollbackInheritUniqueTrial 과정 때문에 새로 받아와야함
- $itemTrials = $general->getAuxVar('inheritUniqueTrial');
- unset($itemTrials[$ownTarget]);
- $general->setAuxVar('inheritUniqueTrial', $itemTrials);
-
- $nationName = $general->getStaticNation()['name'];
- $generalName = $general->getName();
- $josaYi = JosaUtil::pick($generalName, '이');
- $itemObj = buildItemClass($ownTarget);
- $itemName = $itemObj->getName();
- $itemRawName = $itemObj->getRawName();
- $josaUl = JosaUtil::pick($itemRawName, '을');
-
-
- $general->setVar($ownType, $ownTarget);
-
-
- $logger = $general->getLogger();
-
- $logger->pushGeneralActionLog("{$itemName}>{$josaUl} 습득했습니다!");
- $logger->pushGeneralHistoryLog("{$itemName}>{$josaUl} 습득");
- $logger->pushGlobalActionLog("{$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
- $logger->pushGlobalHistoryLog("【{$acquireType}】 >{$nationName} >의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
-
- $general->applyDB($db);
-
- //같은 종류의 유니크를 입찰했을 수 있으니 한번 더 검사한다.
- tryRollbackInheritUniqueItem($rng, $general);
-
- return true;
-}
-
function tryUniqueItemLottery(RandUtil $rng, General $general, string $acquireType = '아이템'): bool
{
$db = DB::db();
@@ -1917,18 +1725,9 @@ function tryUniqueItemLottery(RandUtil $rng, General $general, string $acquireTy
$userLogger = new UserLogger($general->getVar('owner'));
$userLogger->push(sprintf("유니크를 얻을 공간이 없어 %d 포인트 반환", GameConst::$inheritItemRandomPoint), "inheritPoint");
}
- tryRollbackInheritUniqueItem($rng, $general);
return false;
}
- $inheritUnique = $general->getAuxVar('inheritUniqueTrial');
- if ($acquireType != '설문조사' && $inheritUnique && count($inheritUnique) && $availableBuyUnique) {
- $trialResult = tryInheritUniqueItem($rng, $general, $acquireType);
- if ($trialResult) {
- return true;
- }
- }
-
$scenario = $gameStor->scenario;
$genCount = $db->queryFirstField('SELECT count(*) FROM general WHERE npc<2');
diff --git a/hwe/func_auction.php b/hwe/func_auction.php
index 6f4f5ca2..c4fc2d6d 100644
--- a/hwe/func_auction.php
+++ b/hwe/func_auction.php
@@ -1,247 +1,90 @@
getValues(['startyear', 'year', 'month', 'turnterm']);
-
- $unit = 60 * $admin['turnterm'];
// 장수들 평금,평쌀
- $general = $db->queryFirstRow('SELECT avg(gold) as gold, avg(rice) as rice,max(gold) as maxgold from general where npc<2');
+ [$avgGold, $avgRice] = $db->queryFirstList('SELECT avg(gold), avg(rice) from general where npc<2');
+ $avgGold = Util::valueFit($avgGold, 1000, 20000);
+ $avgRice = Util::valueFit($avgRice, 1000, 20000);
- if($general['gold'] < 1000) { $general['gold'] = 1000; }
- if($general['gold'] > 20000) { $general['gold'] = 20000; }
- if($general['rice'] < 1000) { $general['rice'] = 1000; }
- if($general['rice'] > 20000) { $general['rice'] = 20000; }
+ $neutralAuctionCnt = Util::convertPairArrayToDict($db->queryAllLists(
+ 'SELECT `type`, count(*) FROM ng_auction WHERE `type` IN %ls AND `host_general_id`=0 GROUP BY `type`',
+ [AuctionType::BuyRice->value, AuctionType::SellRice->value],
+ ));
+
+ $neutralbuyRiceCnt = $neutralAuctionCnt[AuctionType::BuyRice->value];
- $count = $db->queryFirstField('SELECT count(*) FROM auction WHERE type=0 AND no1=0');
- $count += 5;
// 판매건 등록
- if(Util::randBool(1/$count)) {
+ if ($rng->nextBool(1 / ($neutralbuyRiceCnt + 5))) {
//평균 쌀의 5% ~ 25%
- $mul = rand() % 5 + 1;
- $amount = $general['rice'] / 20 * $mul;
- $cost = $general['gold'] / 20 * 0.9 * $mul;
+ $mul = $rng->nextRangeInt(1, 5);
+ $amount = $avgRice / 20 * $mul;
+ $cost = $avgGold / 20 * 0.9 * $mul;
$topv = $amount * 2;
- if($cost <= $amount*0.8) { $cost = $amount*0.8; }
- if($cost >= $amount*1.2) { $cost = $amount*1.2; }
+ $cost = Util::valueFit($cost, $amount * 0.8, $amount * 1.2);
$amount = Util::round($amount, -1);
$cost = Util::round($cost, -1);
$topv = Util::round($topv, -1);
- $term = 3 + rand() % 10;
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit * $term);
- $db->insert('auction', [
- 'type'=>0,
- 'no1'=>0,
- 'name1'=>'ⓝ상인',
- 'amount'=>$amount,
- 'cost'=>$cost,
- 'value'=>$cost,
- 'topv'=>$topv,
- 'expire'=>$date
- ]);
+ $term = $rng->nextRangeInt(3, 12);
+ $dummyGeneral = AuctionBasicResource::genDummy();
+ AuctionBuyRice::openResourceAuction($dummyGeneral, $amount, $term, $cost, $topv);
}
- $count = $db->queryFirstField('SELECT count(*) FROM auction WHERE type=1 AND no1=0');
- $count += 5;
+ $neutralSellRiceCnt = $neutralAuctionCnt[AuctionType::SellRice->value];
// 구매건 등록
- if(Util::randBool(1/$count)) {
+ if ($rng->nextBool(1 / ($neutralSellRiceCnt + 5))) {
//평균 쌀의 5% ~ 25%
- $mul = Util::randRangeInt(1, 5);
- $amount = $general['rice'] / 20 * $mul;
- $cost = $general['gold'] / 20 * 1.1 * $mul;
- $topv = $amount * 0.5;
- if($cost <= $amount*0.8) { $cost = $amount*0.8; }
- if($cost >= $amount*1.2) { $cost = $amount*1.2; }
+ $mul = $rng->nextRangeInt(1, 5);
+ $amount = $avgGold / 20 * $mul;
+ $cost = $avgRice / 20 * 1.1 * $mul;
+ $topv = $amount * 2;
+ $cost = Util::valueFit($cost, $amount * 0.8, $amount * 1.2);
$amount = Util::round($amount, -1);
$cost = Util::round($cost, -1);
$topv = Util::round($topv, -1);
- $term = Util::randRangeInt(3, 12);
- $date = date("Y-m-d H:i:s", strtotime(date("Y-m-d H:i:s")) + $unit * $term);
- $db->insert('auction', [
- 'type'=>1,
- 'no1'=>0,
- 'name1'=>'ⓝ상인',
- 'amount'=>$amount,
- 'cost'=>$cost,
- 'value'=>$cost,
- 'topv'=>$topv,
- 'expire'=>$date
- ]);
+ $term = $rng->nextRangeInt(3, 12);
+ $dummyGeneral = AuctionBasicResource::genDummy();
+ AuctionSellRice::openResourceAuction($dummyGeneral, $amount, $term, $cost, $topv);
}
}
-function processAuction() {
+function processAuction()
+{
$db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
- $date = TimeUtil::now();
- [$year, $month] = $gameStor->getValuesAsArray(['year', 'month']);
+ $now = TimeUtil::now();
- $admin = $gameStor->getValues(['year', 'month']);
+ $auctionList = $db->queryAllLists(
+ 'SELECT id, `type` FROM ng_auction WHERE `close_date` <= %s AND finished = 0',
+ $now
+ );
- foreach($db->query('SELECT * from auction where expire<=%s', $date) as $auction){
- $josaYi1 = JosaUtil::pick($auction['name1'], '이');
- $josaYi2 = JosaUtil::pick($auction['name2'], '이');
-
- // 쌀 처리
- if($auction['no2'] == 0) {
- // 상인건수가 아닌것만 출력
- if($auction['no1'] != 0) {
- $traderID = $db->queryFirstField('SELECT no FROM general WHERE no=%i', $auction['no1']);
- $logger = new ActionLogger($traderID, 0, $year, $month);
- $logger->pushGeneralActionLog("입찰자 부재로 {$auction['no']}번 거래 유찰>!", ActionLogger::EVENT_PLAIN);
- $logger->flush();
-
- $auctionLog = [];
- if($auction['type'] == 0) {
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $auctionLog[] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 판매 유찰> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 판매, 그러나 입찰자 부재";
- } else {
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $auctionLog[] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 구매> 유찰> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 구매, 그러나 입찰자 부재";
- }
-
- pushAuctionLog($auctionLog);
- }
- continue;
- }
-
- if($auction['no1'] == 0) {
- $trader = [
- 'no'=>0,
- 'name'=>'ⓝ상인',
- 'gold'=>99999,
- 'rice'=>99999
- ];
- } else {
- $trader = $db->queryFirstRow('SELECT no,name,gold,rice from general where no=%i', $auction['no1']);
- }
-
- $bidder = $db->queryFirstRow('SELECT no,name,gold,rice from general where no=%i', $auction['no2']);
-
- $traderLogger = new ActionLogger($trader['no'], 0, $year, $month, false);
- $bidderLogger = new ActionLogger($bidder['no'], 0, $year, $month, false);
-
- $auctionLog = [];
-
-
- //판매거래
- if($auction['type'] == 0) {
- if($auction['amount'] > $trader['rice'] - 1000) {
- $gold = Util::round($auction['value'] * 0.01);
- $db->update('general', [
- 'gold'=>Util::valueFit($trader['gold'] - $gold, 0)
- ], 'no=%i', $trader['no']);
-
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $josaRo = JosaUtil::pick($auction['value'], '로');
- $traderLogger->pushGeneralActionLog("판매자의 군량 부족으로 {$auction['no']}번 거래 유찰>! 벌금 {$gold}>", ActionLogger::EVENT_PLAIN);
- $bidderLogger->pushGeneralActionLog("판매자의 군량 부족으로 {$auction['no']}번 거래 유찰>!", ActionLogger::EVENT_PLAIN);
- $auctionLog[0] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 판매 유찰> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 판매, {$auction['name2']}>{$josaYi2} 금 {$auction['value']}>{$josaRo} 입찰, 그러나 판매자 군량부족, 벌금 {$gold}>";
- } elseif($auction['value'] > $bidder['gold'] - 1000) {
- $gold = Util::round($auction['value'] * 0.01);
- $db->update('general', [
- 'gold'=>Util::valueFit($bidder['gold'] - $gold, 0)
- ], 'no=%i', $bidder['no']);
-
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $josaRo = JosaUtil::pick($auction['value'], '로');
- $traderLogger->pushGeneralActionLog("입찰자의 자금 부족으로 {$auction['no']}번 거래 유찰>!", ActionLogger::EVENT_PLAIN);
- $bidderLogger->pushGeneralActionLog("입찰자의 자금 부족으로 {$auction['no']}번 거래 유찰>! 벌금 {$gold}>", ActionLogger::EVENT_PLAIN);
- $auctionLog[0] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 판매 유찰> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 판매, {$auction['name2']}>{$josaYi2} 금 {$auction['value']}>{$josaRo} 입찰, 그러나 입찰자 자금부족, 벌금 {$gold}>";
- } else {
- $josaUlGold = JosaUtil::pick($auction['value'], '을');
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $josaRo = JosaUtil::pick($auction['value'], '로');
- $traderLogger->pushGeneralActionLog("{$auction['no']}번 거래 성사>로 쌀 {$auction['amount']}>{$josaUlRice} 판매, 금 {$auction['value']}>{$josaUlGold} 획득!", ActionLogger::EVENT_PLAIN);
- $bidderLogger->pushGeneralActionLog("{$auction['no']}번 거래 성사>로 금 {$auction['value']}>{$josaUlGold} 지불, 쌀 {$auction['amount']}>{$josaUlRice} 구입!", ActionLogger::EVENT_PLAIN);
- $auctionLog[0] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 판매 성사> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 판매, {$auction['name2']}>{$josaYi2} 금 {$auction['value']}>{$josaRo} 구매";
- if($auction['value'] >= $auction['amount'] * 2) {
- $auctionLog[0] .= " ★ 최고가 거래 ★>";
- } elseif($auction['value'] >= $auction['topv']) {
- $auctionLog[0] .= " ★ 즉시구매가 거래 ★>";
- } elseif($auction['value'] * 2 <= $auction['amount']) {
- $auctionLog[0] .= " ★ 최저가 거래 ★>";
-
- }
-
- $db->update('general', [
- 'gold'=>$db->sqleval('gold + %i', $auction['value']),
- 'rice'=>$db->sqleval('rice - %i', $auction['amount']),
- ], 'no=%i', $auction['no1']);
- $db->update('general', [
- 'gold'=>$db->sqleval('gold - %i', $auction['value']),
- 'rice'=>$db->sqleval('rice + %i', $auction['amount']),
- ], 'no=%i', $auction['no2']);
- }
- pushAuctionLog($auctionLog);
- //구매거래
- } else {
- if($auction['amount'] > $bidder['rice'] - 1000) {
- $gold = Util::round($auction['value'] * 0.01);
- $db->update('general', [
- 'gold'=>Util::valueFit($bidder['gold'] - $gold, 0)
- ], 'no=%i', $bidder['no']);
-
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $josaRo = JosaUtil::pick($auction['value'], '로');
- $traderLogger->pushGeneralActionLog("입찰자의 군량 부족으로 {$auction['no']}번 거래 유찰>!", ActionLogger::EVENT_PLAIN);
- $bidderLogger->pushGeneralActionLog("입찰자의 군량 부족으로 {$auction['no']}번 거래 유찰>! 벌금 {$gold}>", ActionLogger::EVENT_PLAIN);
- $auctionLog[0] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 구매> 유찰> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 구매, {$auction['name2']}>{$josaYi2} 금 {$auction['value']}>{$josaRo} 입찰, 그러나 입찰자 군량부족, 벌금 {$gold}>";
- } elseif($auction['value'] > $trader['gold'] - 1000) {
- $gold = Util::round($auction['value'] * 0.01);
- $db->update('general', [
- 'gold'=>Util::valueFit($trader['gold'] - $gold, 0)
- ], 'no=%i', $trader['no']);
-
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $josaRo = JosaUtil::pick($auction['value'], '로');
- $traderLogger->pushGeneralActionLog("구매자의 자금 부족으로 {$auction['no']}번 거래 유찰>! 벌금 {$gold}>", ActionLogger::EVENT_PLAIN);
- $bidderLogger->pushGeneralActionLog("구매자의 자금 부족으로 {$auction['no']}번 거래 유찰>!", ActionLogger::EVENT_PLAIN);
- $auctionLog[0] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 구매> 유찰> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 구매, {$auction['name2']}>{$josaYi2} 금 {$auction['value']}>{$josaRo} 입찰, 그러나 구매자 자금부족, 벌금 {$gold}>";
- } else {
- $josaUlGold = JosaUtil::pick($auction['value'], '을');
- $josaUlRice = JosaUtil::pick($auction['amount'], '을');
- $josaRo = JosaUtil::pick($auction['value'], '로');
- $traderLogger->pushGeneralActionLog(
- "{$auction['no']}번 거래 성사>로 금 {$auction['value']}>{$josaUlGold} 지불, 쌀 {$auction['amount']}>{$josaUlRice} 구입!", ActionLogger::EVENT_PLAIN
- );
- $bidderLogger->pushGeneralActionLog("{$auction['no']}번 거래 성사>로 쌀 {$auction['amount']}>{$josaUlRice} 판매, 금 {$auction['value']}>{$josaUlGold} 획득!", ActionLogger::EVENT_PLAIN);
- $auctionLog[0] = "◆>{$admin['year']}년 {$admin['month']}월, {$auction['no']}번 구매> 성사> : {$auction['name1']}>{$josaYi1} 쌀 {$auction['amount']}>{$josaUlRice} 구매, {$auction['name2']}>{$josaYi2} 금 {$auction['value']}>{$josaRo} 판매";
- if($auction['value'] >= $auction['amount'] * 2) {
- $auctionLog[0] .= " ★ 최고가 거래 ★>";
- } elseif($auction['value'] * 2 <= $auction['amount']) {
- $auctionLog[0] .= " ★ 최저가 거래 ★>";
- } elseif($auction['value'] <= $auction['topv']) {
- $auctionLog[0] .= " ★ 즉시구매가 거래 ★>";
- }
-
- $db->update('general', [
- 'gold'=>$db->sqleval('gold - %i', $auction['value']),
- 'rice'=>$db->sqleval('rice + %i', $auction['amount']),
- ], 'no=%i', $auction['no1']);
- $db->update('general', [
- 'gold'=>$db->sqleval('gold + %i', $auction['value']),
- 'rice'=>$db->sqleval('rice - %i', $auction['amount']),
- ], 'no=%i', $auction['no2']);
- }
- $traderLogger->flush();
- $bidderLogger->flush();
- pushAuctionLog($auctionLog);
- }
-
- $traderLogger->flush();
- $bidderLogger->flush();
+ if (!$auctionList) {
+ return;
}
- $db->delete('auction', 'expire <= %s', $date);
+ $dummyGeneral = AuctionBasicResource::genDummy();
+ foreach ($auctionList as [$auctionID, $rawAuctionType]) {
+ $auctionType = AuctionType::from($rawAuctionType);
+ if ($auctionType === AuctionType::BuyRice) {
+ $auction = new AuctionBuyRice($auctionID, $dummyGeneral);
+ } else if ($auctionType === AuctionType::SellRice) {
+ $auction = new AuctionSellRice($auctionID, $dummyGeneral);
+ } else if ($auctionType === AuctionType::UniqueItem) {
+ $auction = new AuctionUniqueItem($auctionID, $dummyGeneral);
+ } else {
+ throw new \Exception('Unknown auction type');
+ }
+ $auction->tryFinish();
+ }
}
-
diff --git a/hwe/func_gamerule.php b/hwe/func_gamerule.php
index 0c7cc630..1331f69e 100644
--- a/hwe/func_gamerule.php
+++ b/hwe/func_gamerule.php
@@ -510,7 +510,7 @@ function postUpdateMonthly(RandUtil $rng)
//토너먼트 개시
triggerTournament($rng);
// 시스템 거래건 등록
- registerAuction();
+ registerAuction($rng);
//전방설정
foreach (getAllNationStaticInfo() as $nation) {
if ($nation['level'] <= 0) {
diff --git a/hwe/func_history.php b/hwe/func_history.php
index fd5241e4..687e9e52 100644
--- a/hwe/func_history.php
+++ b/hwe/func_history.php
@@ -90,8 +90,8 @@ function pushAuctionLog($log) {
pushRawFileLog(__DIR__."/logs/".UniqueConst::$serverID."/_auctionlog.txt", $log);
}
-function getAuctionLogRecent(int $count) {
- return join(' ', array_reverse(getFormattedFileLogRecent(__DIR__."/logs/".UniqueConst::$serverID."/_auctionlog.txt", $count, 300)));
+function getAuctionLogRecent(int $count): array {
+ return array_reverse(getRawFileLogRecent(__DIR__."/logs/".UniqueConst::$serverID."/_auctionlog.txt", $count, 300));
}
//DB-based
diff --git a/hwe/index.php b/hwe/index.php
index a8fc6416..be5f5c2e 100644
--- a/hwe/index.php
+++ b/hwe/index.php
@@ -88,7 +88,7 @@ $color = "cyan";
$serverName = UniqueConst::$serverName;
$serverCnt = $gameStor->server_cnt;
-$auctionCount = $db->queryFirstField('SELECT count(`no`) FROM auction');
+$auctionCount = $db->queryFirstField('SELECT count(*) FROM ng_auction WHERE finished = 0');
$isTournamentActive = $gameStor->tournament > 0;
$isTournamentApplicationOpen = $gameStor->tournament == 1;
$isBettingActive = $gameStor->tournament == 6;
diff --git a/hwe/j_install.php b/hwe/j_install.php
index 43301a3d..9a3124ba 100644
--- a/hwe/j_install.php
+++ b/hwe/j_install.php
@@ -4,8 +4,6 @@ namespace sammo;
include "lib.php";
include "func.php";
-WebUtil::requireAJAX();
-
$session = Session::requireLogin([])->setReadOnly();
if(!class_exists('\\sammo\\DB')){
diff --git a/hwe/sammo/API/Auction/BidBuyRiceAuction.php b/hwe/sammo/API/Auction/BidBuyRiceAuction.php
new file mode 100644
index 00000000..8c8b1043
--- /dev/null
+++ b/hwe/sammo/API/Auction/BidBuyRiceAuction.php
@@ -0,0 +1,51 @@
+args);
+ $v->rule('required', [
+ 'auctionID',
+ 'amount',
+ ])
+ ->rule('int', 'amount')
+ ->rule('int', 'auctionID');
+
+ if (!$v->validate()) {
+ return $v->errorStr();
+ }
+ return null;
+ }
+
+ public function getRequiredSessionMode(): int
+ {
+ return static::REQ_GAME_LOGIN;
+ }
+
+ public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
+ {
+ $auctionID = $this->args['auctionID'];
+ $amount = $this->args['amount'];
+
+ $generalID = $session->generalID;
+ $general = General::createGeneralObjFromDB($generalID);
+ $auction = new AuctionBuyRice($auctionID, $general);
+ $result = $auction->bid($amount, true);
+
+ if (is_string($result)) {
+ return $result;
+ }
+
+ return null;
+ }
+}
diff --git a/hwe/sammo/API/Auction/BidSellRiceAuction.php b/hwe/sammo/API/Auction/BidSellRiceAuction.php
new file mode 100644
index 00000000..2448a08f
--- /dev/null
+++ b/hwe/sammo/API/Auction/BidSellRiceAuction.php
@@ -0,0 +1,50 @@
+args);
+ $v->rule('required', [
+ 'auctionID',
+ 'amount',
+ ])
+ ->rule('int', 'amount')
+ ->rule('int', 'auctionID');
+
+ if (!$v->validate()) {
+ return $v->errorStr();
+ }
+ return null;
+ }
+
+ public function getRequiredSessionMode(): int
+ {
+ return static::REQ_GAME_LOGIN;
+ }
+
+ public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
+ {
+ $auctionID = $this->args['auctionID'];
+ $amount = $this->args['amount'];
+
+ $generalID = $session->generalID;
+ $general = General::createGeneralObjFromDB($generalID);
+ $auction = new AuctionSellRice($auctionID, $general);
+ $result = $auction->bid($amount, true);
+
+ if (is_string($result)) {
+ return $result;
+ }
+
+ return null;
+ }
+}
diff --git a/hwe/sammo/API/Auction/BidUniqueAuction.php b/hwe/sammo/API/Auction/BidUniqueAuction.php
new file mode 100644
index 00000000..725bae84
--- /dev/null
+++ b/hwe/sammo/API/Auction/BidUniqueAuction.php
@@ -0,0 +1,53 @@
+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/GetActiveResourceAuctionList.php b/hwe/sammo/API/Auction/GetActiveResourceAuctionList.php
new file mode 100644
index 00000000..d0d42dc2
--- /dev/null
+++ b/hwe/sammo/API/Auction/GetActiveResourceAuctionList.php
@@ -0,0 +1,123 @@
+ 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 */
+ $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,
+ ];
+ }
+}
diff --git a/hwe/sammo/API/Auction/GetUniqueItemAuctionDetail.php b/hwe/sammo/API/Auction/GetUniqueItemAuctionDetail.php
new file mode 100644
index 00000000..2051ec30
--- /dev/null
+++ b/hwe/sammo/API/Auction/GetUniqueItemAuctionDetail.php
@@ -0,0 +1,91 @@
+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,
+ ];
+ }
+}
diff --git a/hwe/sammo/API/Auction/GetUniqueItemAuctionList.php b/hwe/sammo/API/Auction/GetUniqueItemAuctionList.php
new file mode 100644
index 00000000..5937581e
--- /dev/null
+++ b/hwe/sammo/API/Auction/GetUniqueItemAuctionList.php
@@ -0,0 +1,105 @@
+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 */
+ $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,
+ ];
+ }
+}
diff --git a/hwe/sammo/API/Auction/OpenBuyRiceAuction.php b/hwe/sammo/API/Auction/OpenBuyRiceAuction.php
new file mode 100644
index 00000000..ae2b74c8
--- /dev/null
+++ b/hwe/sammo/API/Auction/OpenBuyRiceAuction.php
@@ -0,0 +1,97 @@
+ $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,
+ ];
+ }
+}
diff --git a/hwe/sammo/API/Auction/OpenSellRiceAuction.php b/hwe/sammo/API/Auction/OpenSellRiceAuction.php
new file mode 100644
index 00000000..efb38972
--- /dev/null
+++ b/hwe/sammo/API/Auction/OpenSellRiceAuction.php
@@ -0,0 +1,97 @@
+ $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,
+ ];
+ }
+}
diff --git a/hwe/sammo/API/Auction/OpenUniqueAuction.php b/hwe/sammo/API/Auction/OpenUniqueAuction.php
new file mode 100644
index 00000000..40def74d
--- /dev/null
+++ b/hwe/sammo/API/Auction/OpenUniqueAuction.php
@@ -0,0 +1,82 @@
+ $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,
+ ];
+ }
+}
diff --git a/hwe/sammo/API/General/DropItem.php b/hwe/sammo/API/General/DropItem.php
index 61c82db9..21c2728c 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,13 @@ 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} 잃었습니다!");
}
- $me->applyDB(DB::db());
+ $me->applyDB($db);
return null;
}
diff --git a/hwe/sammo/API/InheritAction/BuySpecificUnique.php b/hwe/sammo/API/InheritAction/BuySpecificUnique.php
deleted file mode 100644
index 3b3f7071..00000000
--- a/hwe/sammo/API/InheritAction/BuySpecificUnique.php
+++ /dev/null
@@ -1,104 +0,0 @@
- $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;
- }
-}
diff --git a/hwe/sammo/Auction.php b/hwe/sammo/Auction.php
new file mode 100644
index 00000000..349855f8
--- /dev/null
+++ b/hwe/sammo/Auction.php
@@ -0,0 +1,524 @@
+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;
+}
diff --git a/hwe/sammo/AuctionBasicResource.php b/hwe/sammo/AuctionBasicResource.php
new file mode 100644
index 00000000..783d3ff4
--- /dev/null
+++ b/hwe/sammo/AuctionBasicResource.php
@@ -0,0 +1,246 @@
+ 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}번 거래 성사>로 {$bidderResName} {$bidAmount}>{$josaUlBidder} 지불, {$hostResName} {$auctionAmount}>{$josaUlHost} 획득!",
+ ActionLogger::EVENT_PLAIN
+ );
+ $bidder->getLogger()->pushGeneralActionLog(
+ "{$auctionID}번 거래 성사>로 {$hostResName} {$auctionAmount}>{$josaUlHost} 판매, {$bidderResName} {$bidAmount}>{$josaUlBidder} 획득!",
+ ActionLogger::EVENT_PLAIN
+ );
+
+ $josaYiHost = JosaUtil::pick($auctionHost->getName(), '이');
+ $josaYiBidder = JosaUtil::pick($bidder->getName(), '이');
+
+ $auctionLog = [];
+ $auctionLog[] = "{$auctionID}번 {$hostResName} 경매 성사> : {$auctionHost->getName()}>{$josaYiHost} {$hostResName} {$auctionAmount}> 판매, {$bidder->getName()}>{$josaYiBidder} {$bidAmount}> 구매";
+
+
+ if ($highestBid->amount === $this->info->detail->finishBidAmount) {
+ $auctionLog[0] .= ' ★ 즉시구매가 거래 ★>';
+ } else if ($highestBid->amount === $this->info->detail->startBidAmount) {
+ $auctionLog[0] .= " ★ 최고가 거래 ★>";
+ }
+
+ 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;
+ }
+}
diff --git a/hwe/sammo/AuctionBuyRice.php b/hwe/sammo/AuctionBuyRice.php
new file mode 100644
index 00000000..6b864265
--- /dev/null
+++ b/hwe/sammo/AuctionBuyRice.php
@@ -0,0 +1,14 @@
+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("【보물수배】 >누군가가 {$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 */
+ $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("{$itemName}>{$josaUl} 습득했습니다!");
+ $logger->pushGeneralHistoryLog("{$itemName}>{$josaUl} 습득");
+ $logger->pushGlobalActionLog("{$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
+ $logger->pushGlobalHistoryLog("【보물수배】 >{$nationName} >의 {$generalName}>{$josaYi} {$itemName}>{$josaUl} 습득했습니다!");
+
+ $userLogger = new UserLogger($general->getVar('owner'));
+ $userLogger->push(sprintf("유니크 %s 경매로 %d 포인트 사용", $itemName, $highestBid->amount), "inheritPoint");
+
+ $general->applyDB($db);
+
+ return null;
+ }
+}
diff --git a/hwe/sammo/Command/General/che_NPC능동.php b/hwe/sammo/Command/General/che_NPC능동.php
index 70815485..7ee07fc3 100644
--- a/hwe/sammo/Command/General/che_NPC능동.php
+++ b/hwe/sammo/Command/General/che_NPC능동.php
@@ -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;
diff --git a/hwe/sammo/Command/General/che_모반시도.php b/hwe/sammo/Command/General/che_모반시도.php
index 0be42d50..c5e0d005 100644
--- a/hwe/sammo/Command/General/che_모반시도.php
+++ b/hwe/sammo/Command/General/che_모반시도.php
@@ -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);
diff --git a/hwe/sammo/Command/General/che_방랑.php b/hwe/sammo/Command/General/che_방랑.php
index 74a559be..786f7464 100644
--- a/hwe/sammo/Command/General/che_방랑.php
+++ b/hwe/sammo/Command/General/che_방랑.php
@@ -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;
diff --git a/hwe/sammo/Command/General/che_선양.php b/hwe/sammo/Command/General/che_선양.php
index 922dcc48..6fc4f3f3 100644
--- a/hwe/sammo/Command/General/che_선양.php
+++ b/hwe/sammo/Command/General/che_선양.php
@@ -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);
diff --git a/hwe/sammo/Command/General/che_소집해제.php b/hwe/sammo/Command/General/che_소집해제.php
index 6d474a4b..f4a9b84f 100644
--- a/hwe/sammo/Command/General/che_소집해제.php
+++ b/hwe/sammo/Command/General/che_소집해제.php
@@ -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;
diff --git a/hwe/sammo/Command/General/che_요양.php b/hwe/sammo/Command/General/che_요양.php
index b316240e..8b06217f 100644
--- a/hwe/sammo/Command/General/che_요양.php
+++ b/hwe/sammo/Command/General/che_요양.php
@@ -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;
diff --git a/hwe/sammo/Command/General/che_장비매매.php b/hwe/sammo/Command/General/che_장비매매.php
index 2204dae9..a786c8d2 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,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'];
diff --git a/hwe/sammo/Command/General/che_첩보.php b/hwe/sammo/Command/General/che_첩보.php
index 5102d1aa..1f3c72c2 100644
--- a/hwe/sammo/Command/General/che_첩보.php
+++ b/hwe/sammo/Command/General/che_첩보.php
@@ -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;
diff --git a/hwe/sammo/Command/General/che_하야.php b/hwe/sammo/Command/General/che_하야.php
index 5cbcbd69..b00aea20 100644
--- a/hwe/sammo/Command/General/che_하야.php
+++ b/hwe/sammo/Command/General/che_하야.php
@@ -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;
diff --git a/hwe/sammo/Command/General/che_해산.php b/hwe/sammo/Command/General/che_해산.php
index 48389b07..c3dd00f2 100644
--- a/hwe/sammo/Command/General/che_해산.php
+++ b/hwe/sammo/Command/General/che_해산.php
@@ -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);
// 이벤트 핸들러 동작
diff --git a/hwe/sammo/Command/General/che_화계.php b/hwe/sammo/Command/General/che_화계.php
index 0b2cd114..ce06a2b2 100644
--- a/hwe/sammo/Command/General/che_화계.php
+++ b/hwe/sammo/Command/General/che_화계.php
@@ -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;
diff --git a/hwe/sammo/Command/General/휴식.php b/hwe/sammo/Command/General/휴식.php
index ecdb0b83..8dcba62c 100644
--- a/hwe/sammo/Command/General/휴식.php
+++ b/hwe/sammo/Command/General/휴식.php
@@ -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;
}
diff --git a/hwe/sammo/DTO/AuctionBidItem.php b/hwe/sammo/DTO/AuctionBidItem.php
new file mode 100644
index 00000000..d91d77f4
--- /dev/null
+++ b/hwe/sammo/DTO/AuctionBidItem.php
@@ -0,0 +1,31 @@
+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;
}
-}
\ No newline at end of file
+}
diff --git a/hwe/sammo/Enums/AuctionType.php b/hwe/sammo/Enums/AuctionType.php
new file mode 100644
index 00000000..e0a7e39d
--- /dev/null
+++ b/hwe/sammo/Enums/AuctionType.php
@@ -0,0 +1,14 @@
+ '금',
+ ResourceType::rice => '쌀',
+ ResourceType::inheritancePoint => '유산 포인트',
+ };
+ }
+}
diff --git a/hwe/sammo/General.php b/hwe/sammo/General.php
index cb9bab77..c5a5257c 100644
--- a/hwe/sammo/General.php
+++ b/hwe/sammo/General.php
@@ -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);
diff --git a/hwe/scss/auction.scss b/hwe/scss/auction.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/hwe/sql/reset.sql b/hwe/sql/reset.sql
index 7cc725cc..6bc7fb73 100644
--- a/hwe/sql/reset.sql
+++ b/hwe/sql/reset.sql
@@ -32,9 +32,6 @@ DROP TABLE IF EXISTS ng_diplomacy;
# 토너먼트 테이블 삭제
DROP TABLE IF EXISTS tournament;
-# 거래 테이블 삭제
-DROP TABLE IF EXISTS auction;
-
# 통계 테이블 삭제
DROP TABLE IF EXISTS statistic;
@@ -66,4 +63,7 @@ ENGINE=Aria;
DROP TABLE IF EXISTS ng_betting;
DROP TABLE IF EXISTS vote;
-DROP TABLE IF EXISTS vote_comment;
\ No newline at end of file
+DROP TABLE IF EXISTS vote_comment;
+
+DROP TABLE IF EXISTS `ng_auction`;
+DROP TABLE IF EXISTS `ng_auction_bid`;
\ No newline at end of file
diff --git a/hwe/sql/schema.sql b/hwe/sql/schema.sql
index 5a459bf6..5d300bbb 100644
--- a/hwe/sql/schema.sql
+++ b/hwe/sql/schema.sql
@@ -441,23 +441,6 @@ CREATE TABLE `tournament` (
INDEX `grp` (`grp`, `grp_no`)
) COLLATE = 'utf8mb4_general_ci' ENGINE = Aria;
##############################
-## 거래 테이블
-##############################
-create table `auction` (
- `no` int(6) not null auto_increment,
- `type` int(6) default 0,
- `no1` int(6) default 0,
- `name1` varchar(64) default '-',
- `amount` int(6) default 0,
- `cost` int(6) default 0,
- `value` int(6) default 0,
- `topv` int(6) default 0,
- `no2` int(6) default 0,
- `name2` varchar(64) default '-',
- `expire` datetime,
- PRIMARY KEY (`no`)
-) COLLATE = 'utf8mb4_general_ci' ENGINE = Aria;
-##############################
## 통계 테이블
##############################
CREATE TABLE `statistic` (
@@ -684,4 +667,45 @@ CREATE TABLE `vote_comment` (
`date` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `by_vote` (`vote_id`)
-) COLLATE = 'utf8mb4_general_ci' ENGINE = Aria;
\ No newline at end of file
+) COLLATE = 'utf8mb4_general_ci' ENGINE = Aria;
+
+
+##############################
+## 거래장 / 경매장
+##############################
+# 경매 객체는 KVStorage에 저장
+CREATE TABLE `ng_auction` (
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `type` ENUM('buyRice','sellRice','uniqueItem') NOT NULL COLLATE 'utf8mb4_bin',
+ `finished` BIT(1) NOT NULL,
+ `target` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_bin',
+ `host_general_id` INT(11) NOT NULL,
+ `req_resource` ENUM('gold','rice','inheritPoint') NOT NULL COLLATE 'utf8mb4_bin',
+ `open_date` DATETIME NOT NULL,
+ `close_date` DATETIME NOT NULL,
+ `detail` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `by_close` (`finished`, `type`, `close_date`) USING BTREE,
+ INDEX `by_general_id` (`host_general_id`, `type`, `finished`) USING BTREE,
+ CONSTRAINT `detail` CHECK (json_valid(`detail`))
+)
+COLLATE='utf8mb4_general_ci'
+ENGINE=Aria
+;
+
+CREATE TABLE `ng_auction_bid` (
+ `no` INT(11) NOT NULL AUTO_INCREMENT,
+ `auction_id` INT(11) NOT NULL,
+ `owner` INT(11) NULL DEFAULT NULL,
+ `general_id` INT(11) NOT NULL,
+ `amount` INT(11) NOT NULL,
+ `date` DATETIME NOT NULL,
+ `aux` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin',
+ PRIMARY KEY (`no`),
+ UNIQUE INDEX `by_general` (`general_id`, `auction_id`, `amount`),
+ UNIQUE INDEX `by_owner` (`owner`, `auction_id`, `amount`),
+ UNIQUE INDEX `by_amount` (`auction_id`, `amount`),
+ CONSTRAINT `aux` CHECK (json_valid(`aux`))
+)
+COLLATE='utf8mb4_general_ci'
+ENGINE = Aria;
\ No newline at end of file
diff --git a/hwe/templates/commandButton.php b/hwe/templates/commandButton.php
index 13341621..12f163e4 100644
--- a/hwe/templates/commandButton.php
+++ b/hwe/templates/commandButton.php
@@ -16,5 +16,5 @@
=$btnBegin??''?>'>감 찰 부 =$btnEnd??''?>
=$btnBegin??''?>유산 관리 =$btnEnd??''?>
=$btnBegin??''?>내 정보&설정 =$btnEnd??''?>
-=$btnBegin??''?>거 래 장 =$btnEnd??''?>
+=$btnBegin??''?>거 래 장 =$btnEnd??''?>
=$btnBegin??''?>베 팅 장 =$btnEnd??''?>
\ No newline at end of file
diff --git a/hwe/ts/PageAuction.vue b/hwe/ts/PageAuction.vue
new file mode 100644
index 00000000..b0dc4b07
--- /dev/null
+++ b/hwe/ts/PageAuction.vue
@@ -0,0 +1,32 @@
+
+
+
+ 금/쌀
+ 유니크
+
+
+
+
+
+
+
+
diff --git a/hwe/ts/PageInheritPoint.vue b/hwe/ts/PageInheritPoint.vue
index 2bfef428..e97ef8f2 100644
--- a/hwe/ts/PageInheritPoint.vue
+++ b/hwe/ts/PageInheritPoint.vue
@@ -62,12 +62,12 @@
>
- 구입
+ 구입
-
유니크 입찰
+
유니크 경매
유니크 선택
@@ -89,14 +89,14 @@
얻고자 하는 유니크 아이템을 포인트를 걸어 입찰합니다. 최고 포인트인 경우 다음 턴에 유니크를 얻습니다.
+ >얻고자 하는 유니크 아이템으로 경매를 시작합니다. 24턴 동안 진행됩니다.
- 구입
+ 경매 시작
@@ -107,7 +107,7 @@
@@ -209,6 +209,7 @@ import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
import { SammoAPI } from "./SammoAPI";
import type { inheritBuffType } from "./defs/API/InheritAction";
import * as JosaUtil from '@/util/JosaUtil';
+import { BButton } from "bootstrap-vue-3";
type InheritanceType =
| "previous"
@@ -376,6 +377,7 @@ export default defineComponent({
components: {
TopBackBar,
NumberInputWithInfo,
+ BButton,
},
data() {
const inheritBuff = {} as Record
;
@@ -519,7 +521,7 @@ export default defineComponent({
//TODO: 페이지 새로고침 필요없이 하도록
location.reload();
},
- async buySpecificUnique() {
+ async openUniqueItemAuction() {
if(this.specificUnique === null){
alert("유니크를 선택해주세요.");
return;
@@ -544,8 +546,8 @@ export default defineComponent({
}
try {
- await SammoAPI.InheritAction.BuySpecificUnique({
- item: this.specificUnique,
+ await SammoAPI.Auction.OpenUniqueAuction({
+ itemID: this.specificUnique,
amount,
});
} catch (e) {
@@ -554,7 +556,7 @@ export default defineComponent({
return;
}
- alert("성공했습니다.");
+ alert("성공했습니다. 경매장을 확인해주세요.");
//TODO: 페이지 새로고침 필요없이 하도록
location.reload();
},
diff --git a/hwe/ts/SammoAPI.ts b/hwe/ts/SammoAPI.ts
index 041102cf..39f9b9a7 100644
--- a/hwe/ts/SammoAPI.ts
+++ b/hwe/ts/SammoAPI.ts
@@ -1,7 +1,16 @@
import type { Args } from "./processing/args";
import {
- callSammoAPI, extractHttpMethod, GET, PATCH, POST, PUT,
- type APITail, type APICallT, type RawArgType, type ValidResponse, type InvalidResponse
+ callSammoAPI,
+ extractHttpMethod,
+ GET,
+ PATCH,
+ POST,
+ PUT,
+ type APITail,
+ type APICallT,
+ type RawArgType,
+ type ValidResponse,
+ type InvalidResponse,
} from "./util/callSammoAPI";
export type { ValidResponse, InvalidResponse };
import { APIPathGen, NumVar, StrVar } from "./util/APIPathGen.js";
@@ -12,168 +21,243 @@ import type { inheritBuffType } from "./defs/API/InheritAction";
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse } from "./defs/API/Nation";
import type { UploadImageResponse } from "./defs/API/Misc";
import type { GeneralLogType, GetGeneralLogResponse, JoinArgs } from "./defs/API/General";
-import type { GetConstResponse, GetCurrentHistoryResponse, GetDiplomacyResponse, GetHistoryResponse } from "./defs/API/Global";
+import type {
+ GetConstResponse,
+ GetCurrentHistoryResponse,
+ GetDiplomacyResponse,
+ GetHistoryResponse,
+} from "./defs/API/Global";
import type { CachedMapResult, GeneralListResponse, ItemTypeKey, MapResult } from "./defs";
import type { VoteDetailResult, VoteListResult } from "./defs/API/Vote";
+import type { ActiveResourceAuctionList, OpenAuctionResponse, UniqueItemAuctionDetail, UniqueItemAuctionList } from "./defs/API/Auction";
const apiRealPath = {
- Betting: {
- Bet: PUT as APICallT<{
- bettingID: number,
- bettingType: number[],
- amount: number,
- }>,
- GetBettingDetail: NumVar('betting_id',
- GET as APICallT
- ),
- GetBettingList: GET as APICallT<{
- req?: 'bettingNation' | 'tournament'
- }, BettingListResponse>,
- },
- Command: {
- GetReservedCommand: GET as APICallT,
- PushCommand: PUT as APICallT<{
- amount: number
- }>,
- RepeatCommand: PUT as APICallT<{
- amount: number
- }>,
- ReserveCommand: PUT as APICallT<{
- turnList: number[],
- action: string,
- arg?: Args
- }, ReserveCommandResponse>,
- ReserveBulkCommand: PUT as APICallT<{
- turnList: number[],
- action: string,
- arg?: Args
- }[], ReserveBulkCommandResponse>,
- },
- General: {
- Join: POST as APICallT,
- GetGeneralLog: GET as APICallT<{
- reqType: GeneralLogType,
- reqTo?: number
- }, GetGeneralLogResponse>,
- DropItem: PUT as APICallT<{
- itemType: ItemTypeKey
- }>
- },
- Global: {
- GeneralList: GET as APICallT,
- GeneralListWithToken: GET as APICallT,
- GetConst: GET as APICallT,
- GetHistory: StrVar('serverID')(
- NumVar('year',
- NumVar('month', GET as APICallT
- ))),
- GetCurrentHistory: GET as APICallT,
- GetMap: GET as APICallT<{
- neutralView?: 0 | 1,
- showMe?: 0 | 1,
- }, MapResult>,
- GetCachedMap: GET as APICallT,
- GetDiplomacy: GET as APICallT,
- ExecuteEngine: POST as APICallT,
- },
- InheritAction: {
- BuyHiddenBuff: PUT as APICallT<{
- type: inheritBuffType,
- level: number
- }>,
- BuyRandomUnique: PUT as APICallT,
- BuySpecificUnique: PUT as APICallT<{
- item: string,
- amount: number,
- }>,
- ResetSpecialWar: PUT as APICallT,
- ResetTurnTime: PUT as APICallT,
- SetNextSpecialWar: PUT as APICallT<{
- type: string,
- }>,
- },
- Misc: {
- UploadImage: POST as APICallT<{
- imageData: string,
- }, UploadImageResponse>
- },
- NationCommand: {
- GetReservedCommand: GET as APICallT,
- PushCommand: PUT as APICallT<{
- amount: number
- }>,
- RepeatCommand: PUT as APICallT<{
- amount: number
- }>,
- ReserveCommand: PUT as APICallT<{
- turnList: number[],
- action: string,
- arg?: Args
- }, ReserveCommandResponse>,
- ReserveBulkCommand: PUT as APICallT<{
- turnList: number[],
- action: string,
- arg?: Args
- }[], ReserveBulkCommandResponse>,
- },
- Nation: {
- GeneralList: GET as APICallT,
- SetNotice: PUT as APICallT<{
- msg: string,
- }>,
- SetScoutMsg: PUT as APICallT<{
- msg: string,
- }>,
- SetBill: PATCH as APICallT<{
- amount: number,
- }>,
- SetRate: PATCH as APICallT<{
- amount: number,
- }>,
- SetSecretLimit: PATCH as APICallT<{
- amount: number,
- }>,
- SetBlockWar: PATCH as APICallT<{
- value: boolean,
- }, SetBlockWarResponse>,
- SetBlockScout: PATCH as APICallT<{
- value: boolean,
- }>,
- GetGeneralLog: GET as APICallT<{
- generalID: number,
- reqType: GeneralLogType,
- reqTo?: number
- }, GetGeneralLogResponse>
- },
- Vote: {
- AddComment: POST as APICallT<{
- voteID: number,
- text: string,
- }>,
- GetVoteList: GET as APICallT,
- GetVoteDetail: GET as APICallT<{
- voteID: number
- }, VoteDetailResult>,
- NewVote: POST as APICallT<{
- title: string,
- multipleOptions?: number,
- endDate?: string,
- options: string[],
- keepOldVote?: boolean,
- }>,
- Vote: POST as APICallT<{
- voteID: number,
- selection: number[],
- }, ValidResponse & {wonLottery: boolean}>,
- }
+ Auction: {
+ BidBuyRiceAuction: PUT as APICallT<{
+ auctionID: number;
+ amount: number;
+ }>,
+ BidSellRiceAuction: PUT as APICallT<{
+ auctionID: number;
+ amount: number;
+ }>,
+ GetActiveResourceAuctionList: GET as APICallT,
+ OpenBuyRiceAuction: POST as APICallT<
+ {
+ amount: number;
+ closeTurnCnt: number;
+ startBidAmount: number;
+ finishBidAmount: number;
+ },
+ OpenAuctionResponse
+ >,
+ OpenSellRiceAuction: POST as APICallT<
+ {
+ amount: number;
+ closeTurnCnt: number;
+ startBidAmount: number;
+ finishBidAmount: number;
+ },
+ OpenAuctionResponse
+ >,
+
+ BidUniqueAuction: PUT as APICallT<{
+ auctionID: number;
+ amount: number;
+ }>,
+ GetUniqueItemAuctionDetail: GET as APICallT<{
+ auctionID: number;
+ }, UniqueItemAuctionDetail>,
+ GetUniqueItemAuctionList: GET as APICallT,
+ OpenUniqueAuction: POST as APICallT<{
+ itemID: string,
+ amount: number,
+ }, OpenAuctionResponse>,
+ },
+ Betting: {
+ Bet: PUT as APICallT<{
+ bettingID: number;
+ bettingType: number[];
+ amount: number;
+ }>,
+ GetBettingDetail: NumVar("betting_id", GET as APICallT),
+ GetBettingList: GET as APICallT<
+ {
+ req?: "bettingNation" | "tournament";
+ },
+ BettingListResponse
+ >,
+ },
+ Command: {
+ GetReservedCommand: GET as APICallT,
+ PushCommand: PUT as APICallT<{
+ amount: number;
+ }>,
+ RepeatCommand: PUT as APICallT<{
+ amount: number;
+ }>,
+ ReserveCommand: PUT as APICallT<
+ {
+ turnList: number[];
+ action: string;
+ arg?: Args;
+ },
+ ReserveCommandResponse
+ >,
+ ReserveBulkCommand: PUT as APICallT<
+ {
+ turnList: number[];
+ action: string;
+ arg?: Args;
+ }[],
+ ReserveBulkCommandResponse
+ >,
+ },
+ General: {
+ Join: POST as APICallT,
+ GetGeneralLog: GET as APICallT<
+ {
+ reqType: GeneralLogType;
+ reqTo?: number;
+ },
+ GetGeneralLogResponse
+ >,
+ DropItem: PUT as APICallT<{
+ itemType: ItemTypeKey;
+ }>,
+ },
+ Global: {
+ GeneralList: GET as APICallT,
+ GeneralListWithToken: GET as APICallT,
+ GetConst: GET as APICallT,
+ GetHistory: StrVar("serverID")(NumVar("year", NumVar("month", GET as APICallT))),
+ GetCurrentHistory: GET as APICallT,
+ GetMap: GET as APICallT<
+ {
+ neutralView?: 0 | 1;
+ showMe?: 0 | 1;
+ },
+ MapResult
+ >,
+ GetCachedMap: GET as APICallT,
+ GetDiplomacy: GET as APICallT,
+ ExecuteEngine: POST as APICallT,
+ },
+ InheritAction: {
+ BuyHiddenBuff: PUT as APICallT<{
+ type: inheritBuffType;
+ level: number;
+ }>,
+ BuyRandomUnique: PUT as APICallT,
+ ResetSpecialWar: PUT as APICallT,
+ ResetTurnTime: PUT as APICallT,
+ SetNextSpecialWar: PUT as APICallT<{
+ type: string;
+ }>,
+ },
+ Misc: {
+ UploadImage: POST as APICallT<
+ {
+ imageData: string;
+ },
+ UploadImageResponse
+ >,
+ },
+ NationCommand: {
+ GetReservedCommand: GET as APICallT,
+ PushCommand: PUT as APICallT<{
+ amount: number;
+ }>,
+ RepeatCommand: PUT as APICallT<{
+ amount: number;
+ }>,
+ ReserveCommand: PUT as APICallT<
+ {
+ turnList: number[];
+ action: string;
+ arg?: Args;
+ },
+ ReserveCommandResponse
+ >,
+ ReserveBulkCommand: PUT as APICallT<
+ {
+ turnList: number[];
+ action: string;
+ arg?: Args;
+ }[],
+ ReserveBulkCommandResponse
+ >,
+ },
+ Nation: {
+ GeneralList: GET as APICallT,
+ SetNotice: PUT as APICallT<{
+ msg: string;
+ }>,
+ SetScoutMsg: PUT as APICallT<{
+ msg: string;
+ }>,
+ SetBill: PATCH as APICallT<{
+ amount: number;
+ }>,
+ SetRate: PATCH as APICallT<{
+ amount: number;
+ }>,
+ SetSecretLimit: PATCH as APICallT<{
+ amount: number;
+ }>,
+ SetBlockWar: PATCH as APICallT<
+ {
+ value: boolean;
+ },
+ SetBlockWarResponse
+ >,
+ SetBlockScout: PATCH as APICallT<{
+ value: boolean;
+ }>,
+ GetGeneralLog: GET as APICallT<
+ {
+ generalID: number;
+ reqType: GeneralLogType;
+ reqTo?: number;
+ },
+ GetGeneralLogResponse
+ >,
+ },
+ Vote: {
+ AddComment: POST as APICallT<{
+ voteID: number;
+ text: string;
+ }>,
+ GetVoteList: GET as APICallT,
+ GetVoteDetail: GET as APICallT<
+ {
+ voteID: number;
+ },
+ VoteDetailResult
+ >,
+ NewVote: POST as APICallT<{
+ title: string;
+ multipleOptions?: number;
+ endDate?: string;
+ options: string[];
+ keepOldVote?: boolean;
+ }>,
+ Vote: POST as APICallT<
+ {
+ voteID: number;
+ selection: number[];
+ },
+ ValidResponse & { wonLottery: boolean }
+ >,
+ },
} as const;
export const SammoAPI = APIPathGen(apiRealPath, (path: string[], tail: APITail, pathParam) => {
- const method = extractHttpMethod(tail);
- return (args?: RawArgType, returnError?: boolean) => {
- if (returnError) {
- return callSammoAPI(method, path.join('/'), args, pathParam, true);
- }
- return callSammoAPI(method, path.join('/'), args, pathParam);
- };
-});
\ No newline at end of file
+ const method = extractHttpMethod(tail);
+ return (args?: RawArgType, returnError?: boolean) => {
+ if (returnError) {
+ return callSammoAPI(method, path.join("/"), args, pathParam, true);
+ }
+ return callSammoAPI(method, path.join("/"), args, pathParam);
+ };
+});
diff --git a/hwe/ts/build_exports.json b/hwe/ts/build_exports.json
index 7c47de8c..20065ecc 100644
--- a/hwe/ts/build_exports.json
+++ b/hwe/ts/build_exports.json
@@ -22,6 +22,7 @@
"common": "common_deprecated.ts"
},
"ingame_vue": {
+ "v_auction": "v_auction.ts",
"v_inheritPoint": "v_inheritPoint.ts",
"v_board": "v_board.ts",
"v_cachedMap": "v_cachedMap",
diff --git a/hwe/ts/components/AuctionResource.vue b/hwe/ts/components/AuctionResource.vue
new file mode 100644
index 00000000..e3848aee
--- /dev/null
+++ b/hwe/ts/components/AuctionResource.vue
@@ -0,0 +1,289 @@
+
+
+
거래장
+
쌀 구매
+
+
번호
+
판매자
+
물품
+
수량
+
시작 구매가
+
현재 구매가
+
즉시 구매가
+
단가
+
구매 예정자
+
거래 종료
+
+
+
{{ auction.id }}
+
{{ auction.hostName }}
+
쌀
+
{{ auction.amount }}
+
{{ auction.startBidAmount }}
+
{{ auction.highestBid ? auction.highestBid.amount : "-" }}
+
{{ auction.finishBidAmount }}
+
{{ auction.highestBid ? (auction.highestBid.amount / auction.amount).toFixed(2) : "-" }}
+
{{ auction.highestBid ? auction.highestBid.generalName : "-" }}
+
{{ auction.closeDate }}
+
+
+
{{ selectedBuyRiceAuction.id }}번 쌀 {{ selectedBuyRiceAuction.amount }} 경매에
+
+
+
+
입찰
+
+
+
쌀 판매
+
+
번호
+
판매자
+
물품
+
수량
+
시작 구매가
+
현재 구매가
+
즉시 구매가
+
단가
+
구매 예정자
+
거래 종료
+
+
+
{{ auction.id }}
+
{{ auction.hostName }}
+
금
+
{{ auction.amount }}
+
{{ auction.startBidAmount }}
+
{{ auction.highestBid ? auction.highestBid.amount : "-" }}
+
{{ auction.finishBidAmount }}
+
{{ auction.highestBid ? (auction.highestBid.amount / auction.amount).toFixed(2) : "-" }}
+
{{ auction.highestBid ? auction.highestBid.generalName : "-" }}
+
{{ auction.closeDate }}
+
+
+
{{ selectedSellRiceAuction.id }}번 금 {{ selectedSellRiceAuction.amount }} 경매에
+
+
+
+
입찰
+
+
+
경매 등록
+
+
+ 매물
+
+ 쌀
+
+ 금
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 등록
+
+
+
+
+
+
diff --git a/hwe/ts/components/AuctionUniqueItem.vue b/hwe/ts/components/AuctionUniqueItem.vue
new file mode 100644
index 00000000..d1ecc705
--- /dev/null
+++ b/hwe/ts/components/AuctionUniqueItem.vue
@@ -0,0 +1,155 @@
+
+
+
유니크 경매장
+
내 가명: {{ obfuscatedName }}
+
+ 상세
+
+
경매 번호
+
{{ currentAuction.auction.id }}
+
+
경매명
+
{{ currentAuction.auction.title }}
+
+
주최자(익명)
+
{{ currentAuction.auction.hostName }}
+
+
종료시점
+
{{ currentAuction.auction.closeDate }}
+
+
최대지연
+
{{ currentAuction.auction.availableLatestBidCloseDate }}
+
+ 입찰자 목록
+
+
+
{{ bidder.generalName }}
+
{{ bidder.amount }}
+
{{ bidder.date }}
+
+ 입찰하기
+
+
+
+
목록
+
+
{{ auction.id }}
+
{{ auction.finished ? "종료됨" : "진행중" }}
+
{{ auction.title }}
+
{{ auction.hostName }}
+
{{ auction.closeDate }}
+
{{ auction.remainCloseDateExtensionCnt > 0 ? "남음" : "소진" }}
+
{{ auction.highestBid.generalName }}
+
{{ auction.highestBid.amount }}
+
+
+
+
+
diff --git a/hwe/ts/defs/API/Auction.ts b/hwe/ts/defs/API/Auction.ts
new file mode 100644
index 00000000..9ea32194
--- /dev/null
+++ b/hwe/ts/defs/API/Auction.ts
@@ -0,0 +1,64 @@
+import type { ValidResponse } from "@/util/callSammoAPI";
+
+export type BasicResourceAuctionBidder = {
+ amount: number;
+ date: string;
+ generalID: number;
+ generalName: string;
+};
+
+export type BasicResourceAuctionInfo = {
+ id: number;
+ type: "buyRice" | "sellRice";
+ hostGeneralID: number;
+ hostName: string;
+ openDate: string;
+ closeDate: string;
+ amount: number;
+ startBidAmount: number;
+ finishBidAmount: number;
+ highestBid: BasicResourceAuctionBidder;
+};
+
+export type UniqueItemAuctionBidder = {
+ generalName: string;
+ amount: number;
+ isCallerHighestBidder: boolean;
+ date: string;
+};
+
+export type UniqueItemAuctionInfo = {
+ id: number;
+ finished: boolean;
+ title: string;
+ target: string;
+ isCallerHost: boolean;
+ hostName: string;
+ closeDate: string;
+ remainCloseDateExtensionCnt: number;
+ availableLatestBidCloseDate: string;
+};
+
+export type ActiveResourceAuctionList = ValidResponse & {
+ buyRice: BasicResourceAuctionInfo[];
+ sellRice: BasicResourceAuctionInfo[];
+ recentLogs: string[];
+ generalID: number;
+};
+
+export type UniqueItemAuctionList = ValidResponse & {
+ list: (UniqueItemAuctionInfo & {
+ highestBid: UniqueItemAuctionBidder;
+ })[];
+ obfuscatedName: string;
+};
+
+export type UniqueItemAuctionDetail = ValidResponse & {
+ auction: UniqueItemAuctionInfo;
+ bidList: UniqueItemAuctionBidder[];
+ obfuscatedName: string;
+};
+
+export type OpenAuctionResponse = ValidResponse & {
+ auctionID: number;
+};
diff --git a/hwe/ts/v_auction.ts b/hwe/ts/v_auction.ts
new file mode 100644
index 00000000..a17a0102
--- /dev/null
+++ b/hwe/ts/v_auction.ts
@@ -0,0 +1,14 @@
+import "@scss/auction.scss";
+import { createApp } from 'vue'
+import PageAuction from '@/PageAuction.vue';
+import BootstrapVue3, { BToastPlugin } from 'bootstrap-vue-3'
+import { auto500px } from "./util/auto500px";
+import { insertCustomCSS } from "./util/customCSS";
+import { htmlReady } from "./util/htmlReady";
+
+auto500px();
+
+htmlReady(() => {
+ insertCustomCSS();
+ });
+createApp(PageAuction).use(BootstrapVue3).use(BToastPlugin).mount('#app')
\ No newline at end of file
diff --git a/hwe/v_auction.php b/hwe/v_auction.php
new file mode 100644
index 00000000..2184eb43
--- /dev/null
+++ b/hwe/v_auction.php
@@ -0,0 +1,39 @@
+setReadOnly();
+$userID = Session::getUserID();
+
+$db = DB::db();
+$gameStor = KVStorage::getStorage($db, 'game_env');
+
+$me = $db->queryFirstRow('SELECT no, nation, officer_level, permission, con, turntime, belong, penalty FROM general WHERE owner=%i', $userID);
+
+?>
+
+
+
+
+ = UniqueConst::$serverName ?>: 경매장
+
+
+
+ = WebUtil::printJS('../d_shared/common_path.js', true) ?>
+ = WebUtil::printStaticValues([
+ 'staticValues' => [
+ 'serverID' => UniqueConst::$serverID,
+ 'serverNick' => DB::prefix(),
+ 'turnterm' => $gameStor->turnterm,
+ ]
+ ]) ?>
+ = WebUtil::printDist('vue', 'v_auction', true) ?>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/sammo/APIHelper.php b/src/sammo/APIHelper.php
index 2b74f7e4..8e1bca15 100644
--- a/src/sammo/APIHelper.php
+++ b/src/sammo/APIHelper.php
@@ -98,10 +98,10 @@ class APIHelper
}
Json::die($result, $setCache ? 0 : Json::NO_CACHE);
} catch (\Exception $e) {
- Json::dieWithReason($e->getMessage());
+ Json::dieWithReason($e->getMessage()."\n".$e->getTraceAsString());
} catch (\Throwable $e) {
logExceptionByCustomHandler($e, false);
- Json::dieWithReason($e->getMessage());
+ Json::dieWithReason($e->getMessage()."\n".$e->getTraceAsString());
} catch (mixed $e) {
Json::dieWithReason(strval($e));
}
diff --git a/src/sammo/DTO/Converter/DefaultConverter.php b/src/sammo/DTO/Converter/DefaultConverter.php
index 49011ea5..27451cdf 100644
--- a/src/sammo/DTO/Converter/DefaultConverter.php
+++ b/src/sammo/DTO/Converter/DefaultConverter.php
@@ -78,11 +78,18 @@ class DefaultConverter implements Converter
return $raw;
}
- if($type === 'bool' && is_bool($raw)){
- $success = true;
- return $raw;
+ if($type === 'bool'){
+ if(is_bool($raw)){
+ $success = true;
+ return $raw;
+ }
+ if($raw === 0 || $raw === 1){
+ $success = true;
+ return $raw !== 0;
+ }
}
+
return null;
}
@@ -100,7 +107,7 @@ class DefaultConverter implements Converter
}
}
- throw new \Exception('DefaultConverter can not convert');
+ throw new \Exception('DefaultConverter can not convert '.gettype($raw));
}
public function convertTo(mixed $data): string|array|int|float|bool|null
diff --git a/src/sammo/DTO/DTO.php b/src/sammo/DTO/DTO.php
index 45cae1ca..3a9542f9 100644
--- a/src/sammo/DTO/DTO.php
+++ b/src/sammo/DTO/DTO.php
@@ -2,6 +2,7 @@
namespace sammo\DTO;
+use Ds\Set;
use sammo\DTO\Attr\Convert;
use sammo\DTO\Converter\DefaultConverter;
@@ -110,10 +111,13 @@ abstract class DTO
return $object;
}
- public function toArray(): array
+ public function toArray(string ...$exceptKeys): array
{
$reflection = new \ReflectionClass($this::class);
$result = [];
+
+ $exceptKeySet = new Set($exceptKeys);
+
foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$value = $property->getValue($this);
$attrs = Util\DTOUtil::getAttrs($property);
@@ -123,6 +127,10 @@ abstract class DTO
continue;
}
+ if($exceptKeySet->contains($name)){
+ continue;
+ }
+
if (key_exists(Attr\RawName::class, $attrs)) {
$rawAttr = $attrs[Attr\RawName::class];
$attr = new Attr\RawName(...$rawAttr->getArguments());
@@ -153,18 +161,4 @@ abstract class DTO
}
return $result;
}
-
- public function toArrayExcept(string ...$keys): array{
- $reflection = new \ReflectionClass($this::class);
- $values = $this->toArray();
- foreach($keys as $key){
- if(!$reflection->hasProperty($key)){
- throw new \Exception("Key {$key} does not exist");
- }
- if(key_exists($key, $values)){
- unset($values[$key]);
- }
- }
- return $values;
- }
}