From dd383443192fc5635d6eaa55787f343381aeb71c Mon Sep 17 00:00:00 2001 From: Hide_D Date: Thu, 9 Jun 2022 01:21:55 +0900 Subject: [PATCH] =?UTF-8?q?feat,refac:=20=EA=B2=BD=EB=A7=A4=EC=9E=A5=20?= =?UTF-8?q?=EC=9E=AC=EC=84=A4=EA=B3=84,=20=EC=9C=A0=EB=8B=88=ED=81=AC=20?= =?UTF-8?q?=EA=B2=BD=EB=A7=A4=EC=9E=A5=20=EA=B5=AC=ED=98=84=20(#221)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 경매장을 모든 입찰 기록이 남는 새로운 경매장 로직으로 변경 - ng_auction, ng_auction_bid - DTO 사용 - 상회입찰 시 개인메시지로 알림 - 기존의 '배경에서 조용히 이루어지는' 유니크 입찰을 공개된 유니크 경매장으로 변경 - 종료 기간 명시 - 종료 기간에 가까워질때 입찰하면 자동 연장 - 최대 연장기간 있음 - 유니크 제한인 경우 24턴 연장 - 중원정세에서 '보물수배'로 알림 Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/221 --- .phan/config.php | 2 - hwe/_119_b.php | 12 +- hwe/_admin2.php | 57 +- hwe/_admin2_submit.php | 89 --- hwe/b_auction.php | 306 ---------- hwe/c_auction.php | 247 --------- hwe/func.php | 237 +------- hwe/func_auction.php | 267 ++------- hwe/func_gamerule.php | 2 +- hwe/func_history.php | 4 +- hwe/index.php | 2 +- hwe/j_install.php | 2 - hwe/sammo/API/Auction/BidBuyRiceAuction.php | 51 ++ hwe/sammo/API/Auction/BidSellRiceAuction.php | 50 ++ hwe/sammo/API/Auction/BidUniqueAuction.php | 53 ++ .../Auction/GetActiveResourceAuctionList.php | 123 ++++ .../Auction/GetUniqueItemAuctionDetail.php | 91 +++ .../API/Auction/GetUniqueItemAuctionList.php | 105 ++++ hwe/sammo/API/Auction/OpenBuyRiceAuction.php | 97 ++++ hwe/sammo/API/Auction/OpenSellRiceAuction.php | 97 ++++ hwe/sammo/API/Auction/OpenUniqueAuction.php | 82 +++ hwe/sammo/API/General/DropItem.php | 5 +- .../API/InheritAction/BuySpecificUnique.php | 104 ---- hwe/sammo/Auction.php | 524 ++++++++++++++++++ hwe/sammo/AuctionBasicResource.php | 246 ++++++++ hwe/sammo/AuctionBuyRice.php | 14 + hwe/sammo/AuctionSellRice.php | 14 + hwe/sammo/AuctionUniqueItem.php | 293 ++++++++++ hwe/sammo/Command/General/che_NPC능동.php | 3 - hwe/sammo/Command/General/che_모반시도.php | 3 - hwe/sammo/Command/General/che_방랑.php | 2 - hwe/sammo/Command/General/che_선양.php | 3 - hwe/sammo/Command/General/che_소집해제.php | 3 - hwe/sammo/Command/General/che_요양.php | 3 - hwe/sammo/Command/General/che_장비매매.php | 5 +- hwe/sammo/Command/General/che_첩보.php | 2 - hwe/sammo/Command/General/che_하야.php | 3 - hwe/sammo/Command/General/che_해산.php | 2 - hwe/sammo/Command/General/che_화계.php | 3 - hwe/sammo/Command/General/휴식.php | 3 - hwe/sammo/DTO/AuctionBidItem.php | 31 ++ hwe/sammo/DTO/AuctionBidItemData.php | 17 + hwe/sammo/DTO/AuctionInfo.php | 37 ++ hwe/sammo/DTO/AuctionInfoDetail.php | 28 + hwe/sammo/DummyGeneral.php | 68 ++- hwe/sammo/Enums/AuctionType.php | 14 + hwe/sammo/Enums/ResourceType.php | 18 + hwe/sammo/General.php | 18 +- hwe/scss/auction.scss | 0 hwe/sql/reset.sql | 8 +- hwe/sql/schema.sql | 60 +- hwe/templates/commandButton.php | 2 +- hwe/ts/PageAuction.vue | 32 ++ hwe/ts/PageInheritPoint.vue | 30 +- hwe/ts/SammoAPI.ts | 404 ++++++++------ hwe/ts/build_exports.json | 1 + hwe/ts/components/AuctionResource.vue | 289 ++++++++++ hwe/ts/components/AuctionUniqueItem.vue | 155 ++++++ hwe/ts/defs/API/Auction.ts | 64 +++ hwe/ts/v_auction.ts | 14 + hwe/v_auction.php | 39 ++ src/sammo/APIHelper.php | 4 +- src/sammo/DTO/Converter/DefaultConverter.php | 15 +- src/sammo/DTO/DTO.php | 24 +- 64 files changed, 3053 insertions(+), 1530 deletions(-) delete mode 100644 hwe/b_auction.php delete mode 100644 hwe/c_auction.php create mode 100644 hwe/sammo/API/Auction/BidBuyRiceAuction.php create mode 100644 hwe/sammo/API/Auction/BidSellRiceAuction.php create mode 100644 hwe/sammo/API/Auction/BidUniqueAuction.php create mode 100644 hwe/sammo/API/Auction/GetActiveResourceAuctionList.php create mode 100644 hwe/sammo/API/Auction/GetUniqueItemAuctionDetail.php create mode 100644 hwe/sammo/API/Auction/GetUniqueItemAuctionList.php create mode 100644 hwe/sammo/API/Auction/OpenBuyRiceAuction.php create mode 100644 hwe/sammo/API/Auction/OpenSellRiceAuction.php create mode 100644 hwe/sammo/API/Auction/OpenUniqueAuction.php delete mode 100644 hwe/sammo/API/InheritAction/BuySpecificUnique.php create mode 100644 hwe/sammo/Auction.php create mode 100644 hwe/sammo/AuctionBasicResource.php create mode 100644 hwe/sammo/AuctionBuyRice.php create mode 100644 hwe/sammo/AuctionSellRice.php create mode 100644 hwe/sammo/AuctionUniqueItem.php create mode 100644 hwe/sammo/DTO/AuctionBidItem.php create mode 100644 hwe/sammo/DTO/AuctionBidItemData.php create mode 100644 hwe/sammo/DTO/AuctionInfo.php create mode 100644 hwe/sammo/DTO/AuctionInfoDetail.php create mode 100644 hwe/sammo/Enums/AuctionType.php create mode 100644 hwe/sammo/Enums/ResourceType.php create mode 100644 hwe/scss/auction.scss create mode 100644 hwe/ts/PageAuction.vue create mode 100644 hwe/ts/components/AuctionResource.vue create mode 100644 hwe/ts/components/AuctionUniqueItem.vue create mode 100644 hwe/ts/defs/API/Auction.ts create mode 100644 hwe/ts/v_auction.ts create mode 100644 hwe/v_auction.php 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 " - - - 아이템 지급 - - - - - - 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 ?>: 거래장 - - - - - - - - - - - - - - - - - -
거 래 장
- 거 래 장 -
- - - - - - - - - - - - - - - - - - - query('SELECT * from auction where type=0 order by expire') as $auction) { - $radio = ""; - $alert = ""; - $alert2 = ""; - if ($auction['no1'] == $me['no']) { - $radio = " disabled"; - } elseif ($auction['no2'] > 0 && $auction['amount'] * 2 <= $auction['value']) { - $radio = " disabled"; - $alert = ""; - $alert2 = ""; - } elseif ($auction['no2'] > 0 && $auction['topv'] <= $auction['value']) { - $radio = " disabled"; - $alert = ""; - $alert2 = ""; - } elseif ($chk == 0) { - $radio = " checked"; - $chk = 1; - } - $pv = round($auction['value'] * 100 / $auction['amount']) / 100 + 0.001; - $pv = substr((string)$pv, 0, 4); - - echo " - - - - - - - - - - - - - - "; - } - ?> - - - - - - - - - - - - - - - - -
- 팝 니 다 -
거래번호선택판매자물품수량시작판매가현재판매가즉시판매가단가구매 예정자거래종료
{$auction['no']}{$auction['name1']}{$auction['amount']}금 {$auction['cost']}{$alert}금 {$auction['value']}{$alert2}{$alert}금 {$auction['topv']}{$alert2}{$alert}{$pv}{$alert2}{$alert}{$auction['name2']}{$alert2}{$auction['expire']}
등록결과
입찰등록 -  지불할 금액: - name=btn value='구매시도' onclick='return confirm("정말 입찰하시겠습니까?");'> -
거래등록 -  종료: 턴 후 -  물품: 쌀 -  판매량: -  시작가: -  즉구가: - name=btn value='판매' onclick='return confirm("정말 판매하시겠습니까?");'> -
- ㆍHint) 거래자가 판매(물품 판매, 금 수령), 입찰자가 구매(물품 구입, 금 지불).
- ㆍHint) 단가가 1.00보다 높을수록 판매자 유리.
- ㆍHint) 단가가 1.00보다 낮을수록 입찰자 유리.
-
-
- - - - - - - - - - - - - - - - - - - query('SELECT * from auction where type=1 order by expire') as $auction) { - $radio = ""; - $alert = ""; - $alert2 = ""; - if ($auction['no1'] == $me['no']) { - $radio = " disabled"; - } elseif ($auction['no2'] > 0 && $auction['amount'] >= $auction['value'] * 2) { - $radio = " disabled"; - $alert = ""; - $alert2 = ""; - } elseif ($auction['no2'] > 0 && $auction['topv'] >= $auction['value']) { - $radio = " disabled"; - $alert = ""; - $alert2 = ""; - } elseif ($chk == 0) { - $radio = " checked"; - $chk = 1; - } - $pv = round($auction['value'] * 100 / $auction['amount']) / 100 + 0.001; - $pv = substr((string)$pv, 0, 4); - echo " - - - - - - - - - - - - - - "; - } - ?> - - - - - - - - - - - - - - - - -
- 삽 니 다 -
거래번호선택구매자물품수량시작구매가현재구매가즉시구매가단가판매 예정자거래종료
{$auction['no']}{$auction['name1']}{$auction['amount']}금 {$auction['cost']}{$alert}금 {$auction['value']}{$alert2}{$alert}금 {$auction['topv']}{$alert2}{$alert}{$pv}{$alert2}{$alert}{$auction['name2']}{$alert2}{$auction['expire']}
등록결과
입찰등록 -  수령할 금액: - name=btn value='판매시도' onclick='return confirm("정말 입찰하시겠습니까?");'> -
거래등록 -  종료: 턴 후 -  물품: 쌀 -  구입량: -  시작가: -  즉구가: - name=btn value='구매' onclick='return confirm("정말 구매하시겠습니까?");'> -
- ㆍHint) 거래자가 구매(물품 구매, 금 지불), 입찰자가 판매(물품 판매, 금 수령).
- ㆍHint) 단가가 1.00보다 낮을수록 구매자 유리.
- ㆍHint) 단가가 1.00보다 높을수록 입찰자 유리.
-
-
- - - - - - - - - - - - - - - - - - - -
- 최 근 기 록 -
- -
- 도 움 말 -
- - ㆍ판매거래는 거래자가 판매할 물품을 거래하면, 구입을 희망하는 사람이 현재가보다 높게 입찰하여 구입하는 방식입니다.
- ㆍ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) 금이 비싸므로 구매가 이득입니다.
- ㆍ즐거운 거래! -
-
- - - \ 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 @@ '>감 찰 부 유산 관리 내 정보&설정 -거 래 장 +거 래 장 베 팅 장 \ 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 @@ >
- 구입 + 구입
-
유니크 입찰
+
유니크 경매