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 { if ($startAmount < GameConst::$inheritItemUniqueMinPoint) { return '최소 경매 금액은 ' . GameConst::$inheritItemUniqueMinPoint . '입니다.'; } if ($general->getInheritancePoint(InheritanceKey::previous) < $startAmount) { return '경매를 시작할 포인트가 부족합니다.'; } if ($item->isBuyable()) { return '구매할 수 있는 아이템입니다.'; } $now = new DateTimeImmutable(); $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $turnTerm = $gameStor->getValue('turnterm'); $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, $item->getRawClassName(), $general->getID(), ResourceType::inheritancePoint, $now, $closeDate, new AuctionInfoDetail( "{$item->getName()} 경매", static::genObfuscatedName($general->getID()), $startAmount, null, 1, $availableLatestBidCloseDate, ) ); $auctionID = static::openAuction($info, $general); if (!is_int($auctionID)) { return $auctionID; } $auction = new static($auctionID, $general); try { $auction->bid($startAmount); } catch (\Exception $e) { //실패해선 안된다. $msg = $e->getMessage(); $auction->closeAuction(); return "경매를 시작했지만, 첫 입찰에 실패했습니다: {$msg}"; } return $auction; } private function giveUniqueItem(AuctionBidItem $highestBid, General $bidder): ?string { $itemKey = $this->info->target; $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) { 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} 습득했습니다!"); $general->applyDB($db); return null; } public function checkCloseDate(): ?bool { $now = new DateTimeImmutable(); if ($now < $this->info->closeDate) { return null; } //경매를 닫아야한다. $highestBid = $this->getHighestBid(); if ($highestBid === null) { $this->closeAuction(); return true; } if ($highestBid->data->tryExtendCloseDate) { //연장 요청이 있었다. $extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $this->info->turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60 )); if ($this->extendCloseDate($extendedCloseDate) === null) { return false; } } $bidder = General::createGeneralObjFromDB($highestBid->generalID); $failReason = $this->giveUniqueItem($highestBid, $bidder); if ($failReason === null) { $this->closeAuction(); return true; } $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); //아이템 제한에 걸렸다면 자동 연장 $extendedCloseDate = $this->info->closeDate->add(TimeUtil::secondsToDateInterval( max(static::MIN_EXTENSION_MINUTES_BY_EXTENSION_QUERY, $this->info->turnTerm * static::COEFF_EXTENSION_MINUTES_BY_EXTENSION_QUERY) * 60 )); $this->extendCloseDate($extendedCloseDate, true); return false; } public function bid(int $amount, bool $tryExtendCloseDate = false): ?string { //TODO: 입찰 가능 제한확인(다른 경매 진행 등) return parent::bid($amount, $tryExtendCloseDate); } }