feat,wip: 유니크 입찰 기본 구현
This commit is contained in:
@@ -4,10 +4,12 @@ namespace sammo\API\Auction;
|
|||||||
|
|
||||||
use sammo\Session;
|
use sammo\Session;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
use sammo\AuctionUniqueItem;
|
||||||
use sammo\DB;
|
use sammo\DB;
|
||||||
use sammo\DTO\AuctionBidItem;
|
use sammo\DTO\AuctionBidItem;
|
||||||
use sammo\DTO\AuctionInfo;
|
use sammo\DTO\AuctionInfo;
|
||||||
use sammo\Enums\AuctionType;
|
use sammo\Enums\AuctionType;
|
||||||
|
use sammo\TimeUtil;
|
||||||
use sammo\Validator;
|
use sammo\Validator;
|
||||||
|
|
||||||
class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
|
class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
|
||||||
@@ -16,14 +18,14 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
|
|||||||
{
|
{
|
||||||
$v = new Validator($this->args);
|
$v = new Validator($this->args);
|
||||||
$v->rule('required', [
|
$v->rule('required', [
|
||||||
'auction_id',
|
'auctionID',
|
||||||
])
|
])
|
||||||
->rule('integer', 'auction_id');
|
->rule('integer', 'auctionID');
|
||||||
|
|
||||||
if (!$v->validate()) {
|
if (!$v->validate()) {
|
||||||
return $v->errorStr();
|
return $v->errorStr();
|
||||||
}
|
}
|
||||||
$this->args['auction_id'] = (int)$this->args['auction_id'];
|
$this->args['auctionID'] = (int)$this->args['auctionID'];
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,11 +39,11 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
|
|||||||
$db = DB::db();
|
$db = DB::db();
|
||||||
|
|
||||||
$generalID = $session->generalID;
|
$generalID = $session->generalID;
|
||||||
$auctionID = $this->args['auction_id'];
|
$auctionID = $this->args['auctionID'];
|
||||||
|
|
||||||
$rawAuction = $db->queryFirstRow(
|
$rawAuction = $db->queryFirstRow(
|
||||||
'SELECT * FROM `ng_auction` WHERE `type` = %s AND `auction_id` = %s',
|
'SELECT * FROM `ng_auction` WHERE `type` = %s AND `id` = %i',
|
||||||
AuctionType::UniqueItem,
|
AuctionType::UniqueItem->value,
|
||||||
$auctionID
|
$auctionID
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -63,10 +65,12 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
|
|||||||
'generalName' => $bid->aux->generalName,
|
'generalName' => $bid->aux->generalName,
|
||||||
'amount' => $bid->amount,
|
'amount' => $bid->amount,
|
||||||
'isCallerHighestBidder' => $bid->generalID === $generalID,
|
'isCallerHighestBidder' => $bid->generalID === $generalID,
|
||||||
'date' => $bid->date,
|
'date' => TimeUtil::format($bid->date, false),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$obfuscatedName = AuctionUniqueItem::genObfuscatedName($generalID);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'result' => true,
|
'result' => true,
|
||||||
'auction' => [
|
'auction' => [
|
||||||
@@ -80,6 +84,7 @@ class GetUniqueItemAuctionDetail extends \sammo\BaseAPI
|
|||||||
'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseDate,
|
'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseDate,
|
||||||
],
|
],
|
||||||
'bidList' => $responseBid,
|
'bidList' => $responseBid,
|
||||||
|
'obfuscatedName' => $obfuscatedName,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ namespace sammo\API\Auction;
|
|||||||
|
|
||||||
use sammo\Session;
|
use sammo\Session;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
use sammo\AuctionUniqueItem;
|
||||||
use sammo\DB;
|
use sammo\DB;
|
||||||
use sammo\DTO\AuctionBidItem;
|
use sammo\DTO\AuctionBidItem;
|
||||||
use sammo\DTO\AuctionInfo;
|
use sammo\DTO\AuctionInfo;
|
||||||
use sammo\Enums\AuctionType;
|
use sammo\Enums\AuctionType;
|
||||||
|
use sammo\TimeUtil;
|
||||||
use sammo\Util;
|
use sammo\Util;
|
||||||
|
|
||||||
class GetUniqueItemAuctionList extends \sammo\BaseAPI
|
class GetUniqueItemAuctionList extends \sammo\BaseAPI
|
||||||
@@ -56,6 +58,8 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
|
|||||||
$rawHighestBids
|
$rawHighestBids
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$obfuscatedName = AuctionUniqueItem::genObfuscatedName($generalID);
|
||||||
|
|
||||||
$response = [];
|
$response = [];
|
||||||
foreach ($auctions as $auction) {
|
foreach ($auctions as $auction) {
|
||||||
$auctionID = $auction->id;
|
$auctionID = $auction->id;
|
||||||
@@ -71,8 +75,9 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
|
|||||||
'target' => $auction->target,
|
'target' => $auction->target,
|
||||||
'isCallerHost' => $auction->hostGeneralID === $generalID,
|
'isCallerHost' => $auction->hostGeneralID === $generalID,
|
||||||
'hostName' => $auction->detail->hostName,
|
'hostName' => $auction->detail->hostName,
|
||||||
|
'closeDate' => TimeUtil::format($auction->closeDate, false),
|
||||||
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
|
'remainCloseDateExtensionCnt' => $auction->detail->remainCloseDateExtensionCnt,
|
||||||
'availableLatestBidCloseDate' => $auction->detail->availableLatestBidCloseDate,
|
'availableLatestBidCloseDate' => TimeUtil::format($auction->detail->availableLatestBidCloseDate, false),
|
||||||
'highestBid' => [
|
'highestBid' => [
|
||||||
'generalName' => $highestBid->aux->generalName,
|
'generalName' => $highestBid->aux->generalName,
|
||||||
'amount' => $highestBid->amount,
|
'amount' => $highestBid->amount,
|
||||||
@@ -85,6 +90,7 @@ class GetUniqueItemAuctionList extends \sammo\BaseAPI
|
|||||||
return [
|
return [
|
||||||
'result' => true,
|
'result' => true,
|
||||||
'list' => $response,
|
'list' => $response,
|
||||||
|
'obfuscatedName' => $obfuscatedName,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-2
@@ -31,6 +31,37 @@ abstract class Auction
|
|||||||
|
|
||||||
protected AuctionBidItem|null|false $_highestBid = false;
|
protected AuctionBidItem|null|false $_highestBid = false;
|
||||||
|
|
||||||
|
static public function genObfuscatedName(int $id): string
|
||||||
|
{
|
||||||
|
$db = DB::db();
|
||||||
|
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||||
|
|
||||||
|
$namePool = $gameStor->getValue('obfuscatedNamePool');
|
||||||
|
if ($namePool === null) {
|
||||||
|
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
||||||
|
UniqueConst::$hiddenSeed,
|
||||||
|
)));
|
||||||
|
$namePool = [];
|
||||||
|
foreach (GameConst::$randGenFirstName as $ch0) {
|
||||||
|
foreach (GameConst::$randGenMiddleName as $ch1) {
|
||||||
|
foreach (GameConst::$randGenLastName as $ch2) {
|
||||||
|
$namePool[] = "{$ch0}{$ch1}{$ch2}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$namePool = $rng->shuffle($namePool);
|
||||||
|
$gameStor->setValue('obfuscatedNamePool', $namePool);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$dupIdx = intdiv($id, count($namePool));
|
||||||
|
$subIdx = $id % count($namePool);
|
||||||
|
if ($dupIdx == 0) {
|
||||||
|
return $namePool[$subIdx];
|
||||||
|
}
|
||||||
|
return "{$namePool[$subIdx]}{$dupIdx}";
|
||||||
|
}
|
||||||
|
|
||||||
static protected function openAuction(AuctionInfo $info, General $general): int|string
|
static protected function openAuction(AuctionInfo $info, General $general): int|string
|
||||||
{
|
{
|
||||||
$db = DB::db();
|
$db = DB::db();
|
||||||
@@ -238,6 +269,7 @@ abstract class Auction
|
|||||||
return '유산포인트가 부족합니다.';
|
return '유산포인트가 부족합니다.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$obfuscatedName = static::genObfuscatedName($general->getID());
|
||||||
//여기서부터 입찰 성공
|
//여기서부터 입찰 성공
|
||||||
|
|
||||||
$newBid = new AuctionBidItem(
|
$newBid = new AuctionBidItem(
|
||||||
@@ -249,7 +281,7 @@ abstract class Auction
|
|||||||
$now,
|
$now,
|
||||||
new AuctionBidItemData(
|
new AuctionBidItemData(
|
||||||
$general->getVar('owner_name'),
|
$general->getVar('owner_name'),
|
||||||
$general->getName(),
|
$obfuscatedName,
|
||||||
$tryExtendCloseDate,
|
$tryExtendCloseDate,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -275,7 +307,7 @@ abstract class Auction
|
|||||||
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $morePoint);
|
$general->increaseRankVar(RankColumn::inherit_point_spent_dynamic, $morePoint);
|
||||||
|
|
||||||
if ($highestBid !== null && $myPrevBid === null) {
|
if ($highestBid !== null && $myPrevBid === null) {
|
||||||
$this->refundBid($highestBid, "{$auctionInfo->id}번 $auctionInfo->detail->title}에 상회입찰자가 나타났습니다.");
|
$this->refundBid($highestBid, "{$auctionInfo->id}번 {$auctionInfo->detail->title}에 상회입찰자가 나타났습니다.");
|
||||||
}
|
}
|
||||||
$general->applyDB($db);
|
$general->applyDB($db);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -17,37 +17,6 @@ class AuctionUniqueItem extends Auction
|
|||||||
|
|
||||||
static AuctionType $auctionType = AuctionType::UniqueItem;
|
static AuctionType $auctionType = AuctionType::UniqueItem;
|
||||||
|
|
||||||
static public function genObfuscatedName(int $id): string
|
|
||||||
{
|
|
||||||
$db = DB::db();
|
|
||||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
|
||||||
|
|
||||||
$namePool = $gameStor->getValue('obfuscatedNamePool');
|
|
||||||
if ($namePool === null) {
|
|
||||||
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
|
||||||
UniqueConst::$hiddenSeed,
|
|
||||||
)));
|
|
||||||
$namePool = [];
|
|
||||||
foreach (GameConst::$randGenFirstName as $ch0) {
|
|
||||||
foreach (GameConst::$randGenMiddleName as $ch1) {
|
|
||||||
foreach (GameConst::$randGenLastName as $ch2) {
|
|
||||||
$namePool[] = "{$ch0}{$ch1}{$ch2}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$namePool = $rng->shuffle($namePool);
|
|
||||||
$gameStor->setValue('obfuscatedNamePool', $namePool);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$dupIdx = intdiv($id, count($namePool));
|
|
||||||
$subIdx = $id % count($namePool);
|
|
||||||
if ($dupIdx == 0) {
|
|
||||||
return $namePool[$subIdx];
|
|
||||||
}
|
|
||||||
return "{$namePool[$subIdx]}{$dupIdx}";
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function openItemAuction(BaseItem $item, General $general, int $startAmount): self|string
|
static public function openItemAuction(BaseItem $item, General $general, int $startAmount): self|string
|
||||||
{
|
{
|
||||||
if ($startAmount < GameConst::$inheritItemUniqueMinPoint) {
|
if ($startAmount < GameConst::$inheritItemUniqueMinPoint) {
|
||||||
|
|||||||
@@ -1,7 +1,150 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>유니크 경매장</div>
|
<div class="bg0">
|
||||||
|
<div class="bg2">유니크 경매장</div>
|
||||||
|
<div>내 가명: {{ obfuscatedName }}</div>
|
||||||
|
<template v-if="currentAuction !== undefined">
|
||||||
|
<div class="bg1">상세</div>
|
||||||
|
<div class="row gx-0">
|
||||||
|
<div class="col-4 col-md-2 bg2">경매 번호</div>
|
||||||
|
<div class="col-4 col-md-2">{{ currentAuction.auction.id }}</div>
|
||||||
|
|
||||||
|
<div class="col-4 col-md-2 bg2">경매명</div>
|
||||||
|
<div class="col-4 col-md-2">{{ currentAuction.auction.title }}</div>
|
||||||
|
|
||||||
|
<div class="col-4 col-md-2 bg2">주최자(익명)</div>
|
||||||
|
<div class="col-4 col-md-2">{{ currentAuction.auction.hostName }}</div>
|
||||||
|
|
||||||
|
<div class="col-4 col-md-2 bg2">종료시점</div>
|
||||||
|
<div class="col-4 col-md-2">{{ currentAuction.auction.closeDate }}</div>
|
||||||
|
|
||||||
|
<div class="col-4 col-md-2 bg2">연장 허용</div>
|
||||||
|
<div class="col-4 col-md-2">{{ currentAuction.auction.remainCloseDateExtensionCnt }}회</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg1">입찰자 목록</div>
|
||||||
|
<div class="row gx-0 bg2">
|
||||||
|
<div class="col-4">입찰자</div>
|
||||||
|
<div class="col-4">입찰포인트</div>
|
||||||
|
<div class="col-4">시각</div>
|
||||||
|
</div>
|
||||||
|
<div v-for="bidder of currentAuction.bidList" :key="bidder.amount" class="row gx-0">
|
||||||
|
<div class="col-4">{{ bidder.generalName }}</div>
|
||||||
|
<div class="col-4">{{ bidder.amount }}</div>
|
||||||
|
<div class="col-4">{{ bidder.date }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg1">입찰하기</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<NumberInputWithInfo
|
||||||
|
v-model="bidAmount"
|
||||||
|
:int="true"
|
||||||
|
:min="currentAuction.bidList[0].amount"
|
||||||
|
title="유산포인트"
|
||||||
|
:step="1"
|
||||||
|
></NumberInputWithInfo>
|
||||||
|
</div>
|
||||||
|
<div><BButton @click="bidAuction">입찰</BButton></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="bg1">목록</div>
|
||||||
|
<div v-for="[auctionID, auction] of auctionList" :key="auctionID" class="row" @click="currentAuctionID = auctionID">
|
||||||
|
<div class="col">{{ auction.id }}</div>
|
||||||
|
<div class="col">{{ auction.finished ? "종료됨" : "진행중" }}</div>
|
||||||
|
<div class="col">{{ auction.title }}</div>
|
||||||
|
<div class="col">{{ auction.hostName }}</div>
|
||||||
|
<div class="col">{{ auction.closeDate }}</div>
|
||||||
|
<div class="col">{{ auction.remainCloseDateExtensionCnt > 0 ? "남음" : "소진" }}</div>
|
||||||
|
<div class="col">{{ auction.highestBid.generalName }}</div>
|
||||||
|
<div class="col">{{ auction.highestBid.amount }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { UniqueItemAuctionDetail, UniqueItemAuctionList } from "@/defs/API/Auction";
|
||||||
|
import { SammoAPI } from "@/SammoAPI";
|
||||||
|
import { unwrap } from "@/util/unwrap";
|
||||||
|
import { useToast, BButton } from "bootstrap-vue-3";
|
||||||
|
import { isString } from "lodash";
|
||||||
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
|
||||||
|
|
||||||
|
type AuctionItemInfo = UniqueItemAuctionList["list"][0];
|
||||||
|
|
||||||
|
const currentAuctionID = ref<number>();
|
||||||
|
const currentAuction = ref<UniqueItemAuctionDetail | undefined>(undefined);
|
||||||
|
const bidAmount = ref<number>(5000);
|
||||||
|
watch(currentAuctionID, async (auctionID) => {
|
||||||
|
if (auctionID === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
currentAuction.value = await SammoAPI.Auction.GetUniqueItemAuctionDetail({ auctionID });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (isString(e)) {
|
||||||
|
unwrap(useToast()).danger({
|
||||||
|
title: "에러",
|
||||||
|
body: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const auctionList = ref(new Map<number, AuctionItemInfo>());
|
||||||
|
const obfuscatedName = ref("");
|
||||||
|
|
||||||
|
const toasts = unwrap(useToast());
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
try {
|
||||||
|
const result = await SammoAPI.Auction.GetUniqueItemAuctionList();
|
||||||
|
obfuscatedName.value = result.obfuscatedName;
|
||||||
|
auctionList.value = new Map(result.list.map((auction) => [auction.id, auction]));
|
||||||
|
if (currentAuctionID.value === undefined && result.list.length > 0) {
|
||||||
|
currentAuctionID.value = result.list[0].id;
|
||||||
|
bidAmount.value = result.list[0].highestBid.amount;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (isString(e)) {
|
||||||
|
toasts.danger({
|
||||||
|
title: "에러",
|
||||||
|
body: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bidAuction(){
|
||||||
|
if(currentAuction.value === undefined){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const amount = bidAmount.value;
|
||||||
|
const auctionInfo = currentAuction.value.auction;
|
||||||
|
|
||||||
|
if(confirm(`${auctionInfo.title}에 ${amount}유산포인트를 입찰하시겠습니까?`)){
|
||||||
|
try{
|
||||||
|
await SammoAPI.Auction.BidUniqueAuction({ auctionID: auctionInfo.id, amount });
|
||||||
|
toasts.success({
|
||||||
|
title: "성공",
|
||||||
|
body: "입찰이 완료되었습니다.",
|
||||||
|
});
|
||||||
|
await refresh();
|
||||||
|
}catch(e){
|
||||||
|
console.error(e);
|
||||||
|
if(isString(e)){
|
||||||
|
toasts.danger({
|
||||||
|
title: "에러",
|
||||||
|
body: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void refresh();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -34,6 +34,7 @@ export type UniqueItemAuctionInfo = {
|
|||||||
target: string;
|
target: string;
|
||||||
isCallerHost: boolean;
|
isCallerHost: boolean;
|
||||||
hostName: string;
|
hostName: string;
|
||||||
|
closeDate: string;
|
||||||
remainCloseDateExtensionCnt: number;
|
remainCloseDateExtensionCnt: number;
|
||||||
availableLatestBidCloseDate: string;
|
availableLatestBidCloseDate: string;
|
||||||
};
|
};
|
||||||
@@ -46,19 +47,18 @@ export type ActiveResourceAuctionList = ValidResponse & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UniqueItemAuctionList = ValidResponse & {
|
export type UniqueItemAuctionList = ValidResponse & {
|
||||||
list: (
|
list: (UniqueItemAuctionInfo & {
|
||||||
| UniqueItemAuctionInfo
|
highestBid: UniqueItemAuctionBidder;
|
||||||
| {
|
})[];
|
||||||
highestBid: UniqueItemAuctionBidder;
|
obfuscatedName: string;
|
||||||
}
|
|
||||||
)[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UniqueItemAuctionDetail = ValidResponse & {
|
export type UniqueItemAuctionDetail = ValidResponse & {
|
||||||
auction: UniqueItemAuctionInfo;
|
auction: UniqueItemAuctionInfo;
|
||||||
bidList: UniqueItemAuctionBidder[];
|
bidList: UniqueItemAuctionBidder[];
|
||||||
}
|
obfuscatedName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type OpenAuctionResponse = ValidResponse & {
|
export type OpenAuctionResponse = ValidResponse & {
|
||||||
auctionID: number;
|
auctionID: number;
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user