Compare commits

...
13 Commits
Author SHA1 Message Date
Hide_D 3762d9050c fix: GetCityInfo에서 출력됨 2023-03-26 02:55:42 +09:00
Hide_D 75d88d48b6 feat,wip: GetCityInfo 2023-03-26 02:43:33 +09:00
Hide_D 633defcb21 feat: wip, 도시정보 2023-03-26 01:04:09 +09:00
Hide_D 9e997a7ee2 feat: 도시 장수 정의에 nationID를 추가 2023-03-26 00:58:53 +09:00
Hide_D 9a8f70d87b fix: 감찰부 안보이는 버그 수정
- query로 옮겨서 생기는 문제
2023-03-26 00:58:32 +09:00
Hide_D 7132aeab0c fix: installVue3Components() 호출부 통일 2023-03-26 00:48:05 +09:00
Hide_D 994c73adb2 feat: 설문조사 정보도 DB정보를 이용하여 한번만 처리 2023-03-25 03:07:38 +09:00
Hide_D 9206b74500 feat: 이미 읽은 메시지를 DB에서 처리 2023-03-25 02:09:23 +09:00
Hide_D 5294aefbbd misc: 동향 변경 안내 시간 단축 2023-03-24 03:03:50 +09:00
Hide_D 8f0f586af9 fix: 버그 수정 2023-03-24 02:44:14 +09:00
Hide_D 9121533b93 fix: 국가 승급을 Event로 떼내어야 함
- 부대장 지급보다 앞으로
2023-03-24 02:43:07 +09:00
Hide_D 90bff7b210 misc: 연도 업데이트 2023-03-22 23:22:46 +09:00
Hide_D 55f7b87ced misc: GetConst의 cache key를 getVersion으로 2023-03-22 23:19:28 +09:00
28 changed files with 791 additions and 252 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2022 Hide_D, 62che
Copyright (c) 2023 Hide_D, 62che
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
-205
View File
@@ -423,8 +423,6 @@ function postUpdateMonthly(RandUtil $rng)
if ($admin['year'] >= $admin['startyear'] + 2) {
checkWander($rng);
}
// 작위 업데이트
updateNationState();
updateGeneralNumber();
refreshNationStaticInfo();
// 천통여부 검사
@@ -467,209 +465,6 @@ function checkWander(RandUtil $rng)
}
}
function updateNationState()
{
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$admin = $gameStor->getValues(['killturn', 'year', 'month', 'fiction', 'startyear', 'show_img_level', 'turnterm', 'turntime']);
$year = $admin['year'];
$month = $admin['month'];
$startYear = $admin['startyear'];
$nationCityCounts = [];
foreach($db->queryAllLists('SELECT nation, count(*) FROM city WHERE LEVEL>=4 GROUP BY nation') as [$nationID, $cityCnt]){
$nationCityCounts[$nationID] = $cityCnt;
}
$nationLevelByCityCnt = [
0, //방랑군
1, //호족
2, //군벌
5, //주자사
8, //주목
11, //공
16, //왕
21, //황제
];
foreach ($db->query('SELECT nation,name,level,tech,aux FROM nation') as $nation) {
//TODO: level이 진관수이소중대특 체계를 벗어날 수 있음
$nationID = $nation['nation'];
$cityCnt = $nationCityCounts[$nationID] ?? 0;
/** @var int */
$nationlevel = 0;
foreach($nationLevelByCityCnt as $cmpNationLevel => $cmpCityCnt){
if($cityCnt < $cmpCityCnt){
break;
}
$nationlevel = $cmpNationLevel;
}
if ($nationlevel > $nation['level']) {
$levelDiff = $nationlevel - $nation['level'];
$oldLevel = $nation['level'];
$nation['level'] = $nationlevel;
$updateVals = [
'level' => $nationlevel,
'gold' => $db->sqleval('gold + %i', $nationlevel * 1000),
'rice' => $db->sqleval('rice + %i', $nationlevel * 1000),
];
$nationName = $nation['name'];
$lordName = $db->queryFirstField('SELECT name FROM general WHERE nation = %i AND officer_level = 12', $nationID);
$oldNationLevelText = getNationLevel($oldLevel);
$nationLevelText = getNationLevel($nationlevel);
$logger = new ActionLogger(0, $nationID, $year, $month, false);
$josaYi = JosaUtil::pick($lordName, '이');
switch ($nationlevel) {
case 7: //황제
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><D><b>{$nationName}</b></> {$oldNationLevelText} <Y>{$lordName}</>{$josaYi} <C>{$nationLevelText}</>{$josaRo} 옹립되었습니다.");
$logger->pushNationalHistoryLog("<D><b>{$nationName}</b></> {$oldNationLevelText} <Y>{$lordName}</>{$josaYi} <C>{$nationLevelText}</>{$josaRo} 옹립");
$auxVal = Json::decode($nation['aux']);
$auxVal['can_국기변경'] = 1;
$auxVal['can_국호변경'] = 1;
$updateVals['aux'] = Json::encode($auxVal);
break;
case 6: //왕
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 책봉되었습니다.");
$logger->pushNationalHistoryLog("<D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 책봉");
break;
case 5: //공
case 4: //주목
case 3: //주자사
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 임명되었습니다.");
$logger->pushNationalHistoryLog("<D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 임명됨");
break;
case 2: //군벌
$josaRa = JosaUtil::pick($nationName, '라');
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><Y>{$lordName}</>{$josaYi} 독립하여 <D><b>{$nationName}</b></>{$josaRa}는 <C>$nationLevelText</>{$josaRo} 나섰습니다.");
$logger->pushNationalHistoryLog("<Y>{$lordName}</>{$josaYi} 독립하여 <D><b>{$nationName}</b></>{$josaRa}는 <C>$nationLevelText</>{$josaRo} 나서다");
break;
}
$db->update('nation', $updateVals, 'nation=%i', $nation['nation']);
$logger->flush();
$turnRows = [];
foreach (Util::range(getNationChiefLevel($nation['level']), 12) as $chiefLevel) {
foreach (Util::range(GameConst::$maxChiefTurn) as $turnIdx) {
$turnRows[] = [
'nation_id' => $nation['nation'],
'officer_level' => $chiefLevel,
'turn_idx' => $turnIdx,
'action' => '휴식',
'arg' => null,
'brief' => '휴식'
];
}
}
$db->insertIgnore('nation_turn', $turnRows);
if ($levelDiff) {
//유니크 아이템 하나 돌리자
$targetKillTurn = $admin['killturn'];
$targetKillTurn -= 24 * 60 / $admin['turnterm'];
$nationGenIDList = $db->queryFirstColumn(
'SELECT no FROM general WHERE nation = %i AND killturn >= %i AND npc < 2',
$nation['nation'],
$targetKillTurn
);
$nationGenList = General::createGeneralObjListFromDB($nationGenIDList, ['belong', 'npc', 'aux'], 2);
$chiefID = null;
$uniqueLotteryWeightList = [];
$relYear = $year - $startYear;
$maxTrialCountByYear = 1;
foreach (GameConst::$maxUniqueItemLimit as $tmpVals) {
[$targetYear, $targetTrialCnt] = $tmpVals;
if ($relYear < $targetYear) {
break;
}
$maxTrialCountByYear = $targetTrialCnt;
}
foreach ($nationGenList as $nationGen) {
if ($nationGen->getVar('officer_level') == 12) {
$chiefID = $nationGen->getID();
}
$trialCnt = min($maxTrialCountByYear, count(GameConst::$allItems));
foreach ($nationGen->getItems() as $item) {
if (!$item->isBuyable()) {
$trialCnt -= 1;
}
}
if ($trialCnt <= 0) {
continue;
}
$score = $nationGen->getVar('belong') + 10;
if ($nationGen->getVar('officer_level') == 12) {
$score += 60;
} else if ($nationGen->getVar('officer_level') == 11) {
$score += 30;
} else if ($nationGen->getVar('officer_level') > 4) {
$score += 15;
}
$score *= 2 ** $trialCnt;
$uniqueLotteryWeightList[$nationGen->getID()] = [$nationGen, $score];
}
$nationLevelUpRNG = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'nationLevelUp',
$year,
$month,
$nationID
)));
foreach (Util::range($levelDiff) as $idx) {
if (!$uniqueLotteryWeightList) {
break;
}
/** @var General */
$winnerObj = $nationLevelUpRNG->choiceUsingWeightPair($uniqueLotteryWeightList);
unset($uniqueLotteryWeightList[$winnerObj->getID()]);
$givenUniqueRNG = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'givenUnique',
$year,
$month,
$nationID,
$winnerObj->getID(),
)));
giveRandomUniqueItem($givenUniqueRNG, $winnerObj, '작위보상');
$winnerObj->applyDB($db);
}
if ($chiefID) {
$chiefObj = General::createGeneralObjFromDB($chiefID, ['belong', 'npc', 'aux'], 2);
$chiefObj->increaseInheritancePoint(InheritanceKey::unifier, 250 * $levelDiff);
$chiefObj->applyDB($db);
}
}
}
}
}
function checkStatistic()
{
$db = DB::db();
+332
View File
@@ -0,0 +1,332 @@
<?php
namespace sammo\API\City;
use ArrayObject;
use Ds\Set;
use sammo\CityConst;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\General;
use sammo\Json;
use sammo\Session;
use sammo\Util;
use sammo\Validator;
use function sammo\calcLeadershipBonus;
use function sammo\checkLimit;
use function sammo\checkSecretPermission;
use function sammo\getAllNationStaticInfo;
use function sammo\getBillByLevel;
use function sammo\getDedLevelText;
use function sammo\getHonor;
use function sammo\getNationStaticInfo;
use function sammo\getOfficerLevelText;
use function sammo\increaseRefresh;
class GetCityInfo extends \sammo\BaseAPI
{
const FAR_CITY = 0;
const NEAR_CITY = 1;
const ON_CITY = 2;
const OUR_NATION = 3;
static $viewColumns = [
'no' => self::NEAR_CITY,
'name' => self::NEAR_CITY,
'nation' => self::NEAR_CITY,
'npc' => self::NEAR_CITY,
'injury' => self::NEAR_CITY,
'leadership' => self::NEAR_CITY,
'strength' => self::NEAR_CITY,
'intel' => self::NEAR_CITY,
'picture' => self::NEAR_CITY,
'imgsvr' => self::NEAR_CITY,
'city' => self::NEAR_CITY,
'officer_level' => self::NEAR_CITY,
'officer_city' => self::NEAR_CITY,
'defence_train' => self::OUR_NATION,
'crewtype' => self::OUR_NATION,
'crew' => self::ON_CITY,
'train' => self::OUR_NATION,
'atmos' => self::OUR_NATION,
];
static $columnRemap = [
'nation' => 'nationID',
];
static $customViewColumns = [
'officerLevel' => self::NEAR_CITY,
'officerLevelText' => self::NEAR_CITY,
'lbonus' => self::NEAR_CITY,
'reservedCommand' => self::OUR_NATION,
'isOurNation' => self::NEAR_CITY,
];
public function validateArgs(): ?string
{
$v = new Validator($this->args);
$v->rule('int', 'troopID');
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
private function getOfficerLevel($rawGeneral)
{
$level = $rawGeneral['officer_level'];
return $level;
}
private function filterCity(array $rawCity, int $showLevel)
{
$cityID = $rawCity['city'];
$db = DB::db();
$officerList = $db->query(
'SELECT no, npc, name, nation, picture, imgsvr FROM general WHERE city = %i AND 2 <= officer_level AND officer_level <= 4',
$cityID
);
$filteredCity = [
'city' => $cityID,
'name' => $rawCity['name'],
'level' => $rawCity['level'],
'nation' => $rawCity['nation'],
'supply' => $rawCity['supply'],
'pop_max' => $rawCity['pop_max'],
'agri_max' => $rawCity['agri_max'],
'comm_max' => $rawCity['comm_max'],
'secu_max' => $rawCity['secu_max'],
'trade' => $rawCity['trade'],
'def_max' => $rawCity['def_max'],
'wall_max' => $rawCity['wall_max'],
'region' => $rawCity['region'],
'officerList' => $officerList,
];
if ($showLevel >= self::ON_CITY) {
$filteredCity = array_merge($filteredCity, [
'pop' => $rawCity['pop'],
'agri' => $rawCity['agri'],
'comm' => $rawCity['comm'],
'secu' => $rawCity['secu'],
'def' => $rawCity['def'],
'wall' => $rawCity['wall'],
'officer_set' => $rawCity['officer_set'],
]);
}
return $filteredCity;
}
private function getGeneralList(int $cityID, int $nationID, int $showLevel)
{
if ($showLevel == self::FAR_CITY) {
return [
'column' => [],
'list' => []
];
}
$db = DB::db();
$nationStaticList = getAllNationStaticInfo();
$rawGeneralList = Util::convertArrayToDict($db->query('SELECT %l from general WHERE city = %i ORDER BY turntime ASC', Util::formatListOfBackticks(array_keys(static::$viewColumns)), $cityID), 'no');
$ourNationGeneralIDList = new Set();
foreach ($rawGeneralList as $rawGeneral) {
if ($rawGeneral['nation'] == $nationID) {
$ourNationGeneralIDList->add($rawGeneral['no']);
}
}
$reservedCommand = [];
$reservedCommandTargetGeneralIDList = [];
foreach ($rawGeneralList as $rawGeneral) {
if ($rawGeneral['nation'] != $nationID) {
continue;
}
if ($rawGeneral['npc'] < 2) {
$reservedCommandTargetGeneralIDList[$rawGeneral['no']] = $rawGeneral['no'];
}
}
if ($reservedCommandTargetGeneralIDList) {
$rawTurnList = $db->query(
'SELECT general_id, turn_idx, action, arg, brief FROM general_turn WHERE general_id IN %li AND turn_idx < 5 ORDER BY general_id asc, turn_idx asc',
array_values($reservedCommandTargetGeneralIDList)
);
foreach ($rawTurnList as $rawTurn) {
[
'general_id' => $generalID,
'action' => $action,
'arg' => $arg,
'brief' => $brief,
] = $rawTurn;
if (!key_exists($generalID, $reservedCommand)) {
$reservedCommand[$generalID] = [];
}
$reservedCommand[$generalID][] = [
'action' => $action,
'arg' => $arg,
'brief' => $brief
];
}
}
$specialViewFilter = [
'officerLevel' => fn ($rawGeneral) => $this->getOfficerLevel($rawGeneral),
'officerLevelText' => fn ($rawGeneral) => getOfficerLevelText($this->getOfficerLevel($rawGeneral), $nationStaticList[$rawGeneral['nation']]['level']),
'lbonus' => fn ($rawGeneral) => calcLeadershipBonus($rawGeneral['officer_level'], $nationStaticList[$rawGeneral['nation']]['level']),
'reservedCommand' => fn ($rawGeneral) => $reservedCommand[$rawGeneral['no']] ?? null,
'isOurNation' => fn ($rawGeneral) => $rawGeneral['nation'] == $nationID,
];
$showFullColumn = $showLevel == self::OUR_NATION || $ourNationGeneralIDList->count() > 0;
$resultColumns = [];
foreach (static::$viewColumns as $column => $reqShowLevel) {
if (!$showFullColumn && $reqShowLevel > $showLevel) {
continue;
}
if (key_exists($column, static::$columnRemap)) {
$newColumn = static::$columnRemap[$column];
if ($newColumn !== null) {
$resultColumns[$newColumn] = $column;
}
} else {
$resultColumns[$column] = $column;
}
}
foreach (static::$customViewColumns as $column => $reqShowLevel) {
if (!$showFullColumn && $reqShowLevel > $showLevel) {
continue;
}
$resultColumns[$column] = $column;
}
$generalList = [];
foreach ($rawGeneralList as $rawGeneral) {
$item = [];
foreach ($resultColumns as $column) {
$reqShowLevel = static::$viewColumns[$column];
if ($rawGeneral['nation'] != $nationID && $reqShowLevel == self::OUR_NATION) {
$item[] = null;
continue;
}
if (key_exists($column, $specialViewFilter)) {
$value = $specialViewFilter[$column]($rawGeneral);
} else {
$value = $rawGeneral[$column];
}
$item[] = $value;
}
$generalList[] = $item;
}
return [
'column' => array_keys($resultColumns),
'list' => $generalList,
];
}
private function calcShowLevel(int $cityID, int $currentCityID, int $nationID, Set $cityList, bool $isSpyPresent): int
{
$db = DB::db();
if ($cityList->contains($currentCityID)) {
return self::OUR_NATION;
}
if ($cityID == $currentCityID) {
return self::ON_CITY;
}
if ($isSpyPresent) {
return self::ON_CITY;
}
$existsGeneralID = $db->queryFirstField(
'SELECT no FROM general WHERE city = %i AND nation = %i LIMIT 1',
$cityID,
$nationID
);
if ($existsGeneralID) {
return self::ON_CITY;
}
$cityConstObj = CityConst::byID($cityID);
$nearCityIDList = array_keys($cityConstObj->path);
foreach ($nearCityIDList as $nearCityID) {
if ($cityList->contains($nearCityID)) {
return self::NEAR_CITY;
}
}
return self::FAR_CITY;
}
public function launch(Session $session, ?\DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{
increaseRefresh("도시정보", 1);
$db = DB::db();
$gameStor = \sammo\KVStorage::getStorage($db, 'game_env');
$lastExecuted = $gameStor->getValue('turntime');
$me = $db->queryFirstRow('SELECT con, turntime, belong, city, nation, officer_level, permission, penalty FROM general WHERE owner=%i', $session->getUserID());
if (key_exists('cityID', $this->args)) {
$cityID = (int)$this->args['cityID'];
} else {
$cityID = $me['city'];
}
$con = checkLimit($me['con']);
if ($con >= 2) {
return '접속 제한중입니다. 1턴 이내에 너무 많은 갱신을 하셨습니다.';
}
$nationID = $me['nation'];
$currentCityID = $me['city'];
//TODO: 조건 조사
$cityList = new Set($db->queryFirstColumn('SELECT city FROM city WHERE nation=%i', $nationID));
$spyList = JSON::decode($db->queryFirstField('SELECT spy FROM nation WHERE nation=%i', $nationID));
$showLevel = $this->calcShowLevel($cityID, $currentCityID, $nationID, $cityList, key_exists($cityID, $spyList));
$rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city=%i', $cityID);
$filteredCity = $this->filterCity($rawCity, $showLevel);
$result = [
'result' => true,
'lastExcuted' => $lastExecuted,
'cityInfo' => $filteredCity,
'spyList' => $spyList,
'nationCityList' => $cityList->toArray(),
'myGeneralID' => $session->generalID,
'currentCityID' => $currentCityID,
'showLevel' => $showLevel,
'cityGeneralList' => $this->getGeneralList($cityID, $nationID, $showLevel)
];
return $result;
}
}
+9
View File
@@ -569,6 +569,14 @@ class GetFrontInfo extends \sammo\BaseAPI
//TODO: 마지막 투표, 토너먼트, 베팅을 했는지 정보를 별도로 가져와야 함. aux?
$auxInfo = [];
if ($globalInfo['lastVote']) {
$myLastVoteID = $db->queryFirstField('SELECT vote_id FROM vote WHERE general_id = %i ORDER BY vote_id DESC LIMIT 1', $generalID);
if($myLastVoteID) {
$auxInfo['myLastVote'] = $myLastVoteID;
}
}
return [
'result' => true,
@@ -577,6 +585,7 @@ class GetFrontInfo extends \sammo\BaseAPI
'nation' => $nationInfo,
'general' => $generalInfo,
'city' => $cityInfo,
'aux' => $auxInfo,
];
}
}
+2 -2
View File
@@ -86,10 +86,10 @@ class GetConst extends \sammo\BaseAPI
if ($lastModified !== null) {
$versionHash = "lt{$lastModified}";
} else {
$versionHash = VersionGit::getHash();
$versionHash = VersionGit::getVersion();
}
} else {
$versionHash = VersionGit::getHash();
$versionHash = VersionGit::getVersion();
}
$apiVersion = static::CONST_API_VERSION;
@@ -6,8 +6,10 @@ use sammo\Session;
use DateTimeInterface;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralStorKey;
use sammo\Enums\MessageType;
use sammo\Json;
use sammo\KVStorage;
use sammo\Message;
use sammo\TimeUtil;
use sammo\Util;
@@ -140,9 +142,19 @@ class GetRecentMessage extends \sammo\BaseAPI
array_pop($result[$lastType]);
}
$generalStor = KVStorage::getStorage($db, "general_{$generalID}");
[$latestReadDiplomacyMsg, $latestReadPrivateMsg] = $generalStor->getValuesAsArray([
GeneralStorKey::latestReadDiplomacyMsg,
GeneralStorKey::latestReadPrivateMsg
]);
$result['sequence'] = $nextSequence;
$result['nationID'] = $nationID;
$result['generalName'] = $generalName;
$result['latestRead'] = [
'diplomacy' => $latestReadDiplomacyMsg ?? 0,
'private' => $latestReadPrivateMsg ?? 0,
];
return $result;
}
@@ -0,0 +1,52 @@
<?php
namespace sammo\API\Message;
use sammo\DB;
use sammo\Enums\APIRecoveryType;
use sammo\Enums\GeneralStorKey;
use sammo\Enums\MessageType;
use sammo\KVStorage;
use sammo\Session;
use sammo\Validator;
class ReadLatestMessage extends \sammo\BaseAPI
{
public function validateArgs(): ?string
{
//type이 개인 메시지 혹은 외교 메시지여야함
$v = new Validator($this->args);
$v->rule('required', ['type', 'msgID'])
->rule('in', 'type', [MessageType::private->value, MessageType::diplomacy->value])
->rule('int', 'msgID');
if (!$v->validate()) {
return $v->errorStr();
}
return null;
}
public function getRequiredSessionMode(): int
{
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
}
public function launch(Session $session, ?\DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
{
$db = DB::db();
$generalID = $session->generalID;
$generalStor = KVStorage::getStorage($db, "general_{$generalID}");
$type = MessageType::from($this->args['type']);
$msgID = $this->args['msgID'];
$storKey = GeneralStorKey::latestReadPrivateMsg;
if($type == MessageType::diplomacy){
$storKey = GeneralStorKey::latestReadDiplomacyMsg;
}
$oldValue = $generalStor->getValue($storKey);
if($oldValue === null || $oldValue < $msgID){
$generalStor->setValue($storKey, $msgID);
}
return null;
}
}
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace sammo\Enums;
enum GeneralStorKey: string{
case latestReadPrivateMsg = 'latestReadPrivateMsg';
case latestReadDiplomacyMsg = 'latestReadDiplomacyMsg';
}
@@ -0,0 +1,225 @@
<?php
namespace sammo\Event\Action;
use sammo\ActionLogger;
use sammo\CityConst;
use sammo\DB;
use sammo\Enums\InheritanceKey;
use sammo\GameConst;
use sammo\General;
use sammo\JosaUtil;
use sammo\Json;
use sammo\KVStorage;
use sammo\LiteHashDRBG;
use sammo\RandUtil;
use sammo\UniqueConst;
use sammo\Util;
use function sammo\getNationChiefLevel;
use function sammo\getNationLevel;
use function sammo\giveRandomUniqueItem;
class UpdateNationLevel extends \sammo\Event\Action
{
public function run(array $env)
{
$db = DB::db();
$year = $env['year'];
$month = $env['month'];
$startYear = $env['startyear'];
$nationCityCounts = [];
foreach ($db->queryAllLists('SELECT nation, count(*) FROM city WHERE LEVEL>=4 GROUP BY nation') as [$nationID, $cityCnt]) {
$nationCityCounts[$nationID] = $cityCnt;
}
$nationLevelByCityCnt = [
0, //방랑군
1, //호족
2, //군벌
5, //주자사
8, //주목
11, //공
16, //왕
21, //황제
];
foreach ($db->query('SELECT nation,name,level,tech,aux FROM nation') as $nation) {
//TODO: level이 진관수이소중대특 체계를 벗어날 수 있음
$nationID = $nation['nation'];
$cityCnt = $nationCityCounts[$nationID] ?? 0;
/** @var int */
$nationlevel = 0;
foreach ($nationLevelByCityCnt as $cmpNationLevel => $cmpCityCnt) {
if ($cityCnt < $cmpCityCnt) {
break;
}
$nationlevel = $cmpNationLevel;
}
if ($nationlevel > $nation['level']) {
$levelDiff = $nationlevel - $nation['level'];
$oldLevel = $nation['level'];
$nation['level'] = $nationlevel;
$updateVals = [
'level' => $nationlevel,
'gold' => $db->sqleval('gold + %i', $nationlevel * 1000),
'rice' => $db->sqleval('rice + %i', $nationlevel * 1000),
];
$nationName = $nation['name'];
$lordName = $db->queryFirstField('SELECT name FROM general WHERE nation = %i AND officer_level = 12', $nationID);
$oldNationLevelText = getNationLevel($oldLevel);
$nationLevelText = getNationLevel($nationlevel);
$logger = new ActionLogger(0, $nationID, $year, $month, false);
$josaYi = JosaUtil::pick($lordName, '이');
switch ($nationlevel) {
case 7: //황제
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><D><b>{$nationName}</b></> {$oldNationLevelText} <Y>{$lordName}</>{$josaYi} <C>{$nationLevelText}</>{$josaRo} 옹립되었습니다.");
$logger->pushNationalHistoryLog("<D><b>{$nationName}</b></> {$oldNationLevelText} <Y>{$lordName}</>{$josaYi} <C>{$nationLevelText}</>{$josaRo} 옹립");
$auxVal = Json::decode($nation['aux']);
$auxVal['can_국기변경'] = 1;
$auxVal['can_국호변경'] = 1;
$updateVals['aux'] = Json::encode($auxVal);
break;
case 6: //왕
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 책봉되었습니다.");
$logger->pushNationalHistoryLog("<D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 책봉");
break;
case 5: //공
case 4: //주목
case 3: //주자사
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 임명되었습니다.");
$logger->pushNationalHistoryLog("<D><b>{$nationName}</b></>의 <Y>{$lordName}</>{$josaYi} <C>$nationLevelText</>{$josaRo} 임명됨");
break;
case 2: //군벌
$josaRa = JosaUtil::pick($nationName, '라');
$josaRo = JosaUtil::pick($nationLevelText, '로');
$logger->pushGlobalHistoryLog("<Y><b>【작위】</b></><Y>{$lordName}</>{$josaYi} 독립하여 <D><b>{$nationName}</b></>{$josaRa}는 <C>$nationLevelText</>{$josaRo} 나섰습니다.");
$logger->pushNationalHistoryLog("<Y>{$lordName}</>{$josaYi} 독립하여 <D><b>{$nationName}</b></>{$josaRa}는 <C>$nationLevelText</>{$josaRo} 나서다");
break;
}
$db->update('nation', $updateVals, 'nation=%i', $nation['nation']);
$logger->flush();
$turnRows = [];
foreach (Util::range(getNationChiefLevel($nation['level']), 12) as $chiefLevel) {
foreach (Util::range(GameConst::$maxChiefTurn) as $turnIdx) {
$turnRows[] = [
'nation_id' => $nation['nation'],
'officer_level' => $chiefLevel,
'turn_idx' => $turnIdx,
'action' => '휴식',
'arg' => null,
'brief' => '휴식'
];
}
}
$db->insertIgnore('nation_turn', $turnRows);
if ($levelDiff) {
//유니크 아이템 하나 돌리자
$targetKillTurn = $env['killturn'];
$targetKillTurn -= 24 * 60 / $env['turnterm'];
$nationGenIDList = $db->queryFirstColumn(
'SELECT no FROM general WHERE nation = %i AND killturn >= %i AND npc < 2',
$nation['nation'],
$targetKillTurn
);
$nationGenList = General::createGeneralObjListFromDB($nationGenIDList, ['belong', 'npc', 'aux'], 2);
$chiefID = null;
$uniqueLotteryWeightList = [];
$relYear = $year - $startYear;
$maxTrialCountByYear = 1;
foreach (GameConst::$maxUniqueItemLimit as $tmpVals) {
[$targetYear, $targetTrialCnt] = $tmpVals;
if ($relYear < $targetYear) {
break;
}
$maxTrialCountByYear = $targetTrialCnt;
}
foreach ($nationGenList as $nationGen) {
if ($nationGen->getVar('officer_level') == 12) {
$chiefID = $nationGen->getID();
}
$trialCnt = min($maxTrialCountByYear, count(GameConst::$allItems));
foreach ($nationGen->getItems() as $item) {
if (!$item->isBuyable()) {
$trialCnt -= 1;
}
}
if ($trialCnt <= 0) {
continue;
}
$score = $nationGen->getVar('belong') + 10;
if ($nationGen->getVar('officer_level') == 12) {
$score += 60;
} else if ($nationGen->getVar('officer_level') == 11) {
$score += 30;
} else if ($nationGen->getVar('officer_level') > 4) {
$score += 15;
}
$score *= 2 ** $trialCnt;
$uniqueLotteryWeightList[$nationGen->getID()] = [$nationGen, $score];
}
$nationLevelUpRNG = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'nationLevelUp',
$year,
$month,
$nationID
)));
foreach (Util::range($levelDiff) as $idx) {
if (!$uniqueLotteryWeightList) {
break;
}
/** @var General */
$winnerObj = $nationLevelUpRNG->choiceUsingWeightPair($uniqueLotteryWeightList);
unset($uniqueLotteryWeightList[$winnerObj->getID()]);
$givenUniqueRNG = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
UniqueConst::$hiddenSeed,
'givenUnique',
$year,
$month,
$nationID,
$winnerObj->getID(),
)));
giveRandomUniqueItem($givenUniqueRNG, $winnerObj, '작위보상');
$winnerObj->applyDB($db);
}
if ($chiefID) {
$chiefObj = General::createGeneralObjFromDB($chiefID, ['belong', 'npc', 'aux'], 2);
$chiefObj->increaseInheritancePoint(InheritanceKey::unifier, 250 * $levelDiff);
$chiefObj->applyDB($db);
}
}
}
}
}
}
+1
View File
@@ -486,6 +486,7 @@ class GameConstBase
[
"month", 1000,
true,
["UpdateNationLevel"],
["ProvideNPCTroopLeader"]
],
[
+2 -2
View File
@@ -17,7 +17,7 @@
@include media-1000px{
#container {
#app {
width: 1000px;
margin: 0 auto;
}
@@ -28,7 +28,7 @@
overflow-x: hidden;
}
#container {
#app {
width: 500px;
margin: 0 auto;
}
+9 -7
View File
@@ -72,14 +72,9 @@
</BContainer>
</template>
<script lang="ts">
declare const queryValues: {
generalID: number | null;
};
</script>
<script lang="ts" setup>
import { BContainer, useToast, BFormSelect, BFormSelectOption, BButton } from "bootstrap-vue-next";
import { onMounted, provide, ref, watch } from "vue";
import { onMounted, provide, ref, toRef, watch } from "vue";
import { getGameConstStore, type GameConstStore } from "./GameConstStore";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
@@ -98,6 +93,10 @@ import { parseTime } from "./util/parseTime";
const toasts = unwrap(useToast());
const props = defineProps<{
generalID?: number;
}>();
const generalList = ref(new Map<number, GeneralListItemP1>());
const textMap = {
recent_war: [
@@ -118,7 +117,10 @@ const orderedInvGeneralKeyIndex = ref(new Map<number, number>());
const orderBy = ref<keyof typeof textMap>("turntime");
const targetGeneral = ref<GeneralListItemP1>();
const targetGeneralID = ref(queryValues.generalID ?? undefined);
const targetGeneralID = ref(props.generalID);
watch(()=>props.generalID, (generalID)=>{
targetGeneralID.value = generalID;
})
type GeneralLogs = {
[key in GeneralLogType]: Map<number, string>;
+17
View File
@@ -0,0 +1,17 @@
<template>
<div>
<div>도시 정보</div>
<div>{{cityID}}</div>
</div>
</template>
<script lang="ts">
</script>
<script lang="ts" setup>
import { toRef } from 'vue';
const props = defineProps<{
cityID?: number
}>()
const cityID = toRef(props, 'cityID');
</script>
+3 -3
View File
@@ -447,19 +447,19 @@ watch(refreshCounter, async () => {
title: "갱신 완료",
body: `동향 변경이 있습니다.`,
},
{ delay: 1000 * 3 }
{ delay: 100 * 15 }
);
} else {
toasts.success(
{
title: "갱신 완료",
},
{ delay: 1000 * 3 }
{ delay: 100 * 5 }
);
}
const lastVoteID = response.global.lastVoteID;
if (lastVoteID > lastVoteState.value) {
if (lastVoteID > lastVoteState.value && lastVoteID > (response.aux.myLastVote ?? 0)) {
lastVoteState.value = lastVoteID;
toasts.warning(
{
+10 -6
View File
@@ -29,7 +29,7 @@ import type {
GetFrontInfoResponse,
GetHistoryResponse,
GetMenuResponse,
GetRecentRecordResponse,
GetRecentRecordResponse,
} from "./defs/API/Global";
import type { CachedMapResult, CommandTableResponse, GeneralListResponse, ItemTypeKey, MapResult } from "./defs";
import type { VoteDetailResult, VoteListResult } from "./defs/API/Vote";
@@ -71,12 +71,12 @@ const apiRealPath = {
amount: number;
}>,
GetUniqueItemAuctionDetail: GET as APICallT<{
auctionID: number;
auctionID: number;
}, UniqueItemAuctionDetail>,
GetUniqueItemAuctionList: GET as APICallT<undefined, UniqueItemAuctionList>,
OpenUniqueAuction: POST as APICallT<{
itemID: string,
amount: number,
itemID: string,
amount: number,
}, OpenAuctionResponse>,
},
Betting: {
@@ -199,7 +199,11 @@ const apiRealPath = {
SendMessage: POST as APICallT<{
mailbox: number;
text: string;
}, ValidResponse & {msgID: number, msgType: MsgType}>
}, ValidResponse & { msgID: number, msgType: MsgType }>,
ReadLatestMessage: PATCH as APICallT<{
type: 'diplomacy' | 'private';
msgID: number;
}>
},
Misc: {
UploadImage: POST as APICallT<
@@ -273,7 +277,7 @@ const apiRealPath = {
troopID: number;
troopName: string;
}>,
GetNationInfo: GET as APICallT<{full?: boolean}, NationInfoResponse>,
GetNationInfo: GET as APICallT<{ full?: boolean }, NationInfoResponse>,
},
Troop: {
NewTroop: POST as APICallT<{
+1
View File
@@ -27,6 +27,7 @@
"v_board": "v_board.ts",
"v_cachedMap": "v_cachedMap",
"v_chiefCenter": "v_chiefCenter.ts",
"v_cityInfo": "v_cityInfo.ts",
"v_NPCControl": "v_NPCControl.ts",
"v_join": "v_join.ts",
"v_main": "v_main.ts",
+30 -15
View File
@@ -255,21 +255,8 @@ const messageIndexedList: Record<MsgType, Ref<MsgItem[]>> = {
diplomacy: messageDiplomacy,
};
function generateLatestMsgState(msgType: MsgType) {
const storageKey = `state.${serverID}.latestReadMsg.${msgType}`;
const latestReadMsgID = parseInt(localStorage.getItem(storageKey) ?? "0");
const obj = ref<[string | undefined, number, number]>([undefined, latestReadMsgID, latestReadMsgID]);
watch(obj, ([, newMsgID], [, oldMsgID]) => {
if (newMsgID == oldMsgID) {
return;
}
localStorage.setItem(storageKey, newMsgID.toString());
});
return obj;
}
const latestPrivateMsgToastInfo = generateLatestMsgState("private");
const latestDiplomacyMsgToastInfo = generateLatestMsgState("diplomacy");
const latestPrivateMsgToastInfo = ref<[string | undefined, number, number]>([undefined, 0, 0]);
const latestDiplomacyMsgToastInfo = ref<[string | undefined, number, number]>([undefined, 0, 0]);
function readLatestMsg(msgType: MsgType) {
const targetMap = {
@@ -288,6 +275,13 @@ function readLatestMsg(msgType: MsgType) {
if (toastID) {
toasts.remove(toastID);
}
if(msgType == "private" || msgType == "diplomacy"){
void SammoAPI.Message.ReadLatestMessage({
type: msgType,
msgID: lastestReceivedID,
});
}
target.value = [undefined, lastestReceivedID, lastestReceivedID];
}
@@ -403,6 +397,27 @@ function updateMsgResponse(response: MsgResponse) {
return;
}
if(response.latestRead){
const newDiplomacyIdx = response.latestRead.diplomacy;
const [diplomacyToastID, diplomacyLatestMsgID] = latestDiplomacyMsgToastInfo.value;
if(diplomacyLatestMsgID < newDiplomacyIdx){
if(diplomacyToastID){
toasts.remove(diplomacyToastID);
}
latestDiplomacyMsgToastInfo.value = [undefined, newDiplomacyIdx, newDiplomacyIdx];
}
const newPrivateIdx = response.latestRead.private;
const [privateToastID, privateLatestMsgID] = latestPrivateMsgToastInfo.value;
if(privateLatestMsgID < newPrivateIdx){
if(privateToastID){
toasts.remove(privateToastID);
}
latestPrivateMsgToastInfo.value = [undefined, newPrivateIdx, newPrivateIdx];
}
}
lastSequence.value = Math.max(lastSequence.value, response.sequence);
for (const msgType of Object.keys(messageIndexedList) as (keyof typeof messageIndexedList)[]) {
+3
View File
@@ -221,6 +221,9 @@ export type GetFrontInfoResponse = {
} | null
>;
};
aux: {
myLastVote?: number;
};
};
+4
View File
@@ -58,6 +58,10 @@ export type MsgResponse = {
nationID: number;
generalName: string;
sequence: number;
latestRead?: {
private: number;
diplomacy: number;
}
};
export type MailboxItem = {
+1
View File
@@ -40,6 +40,7 @@ export type GeneralListItemP0 = {
autorun_limit: number;
nationID?: number;
city: number;
troop: number;
};
+4 -2
View File
@@ -10,11 +10,13 @@ import { insertCustomCSS } from "./util/customCSS";
import { installVue3Components } from "./util/installVue3Components";
declare const query:{
generalID?: number;
};
auto500px();
htmlReady(() => {
insertCustomCSS();
});
installVue3Components(createApp(PageBattleCenter)).mount('#app');
installVue3Components(createApp(PageBattleCenter, query)).mount('#app');
-1
View File
@@ -1,7 +1,6 @@
import "@scss/board.scss";
import { createApp } from 'vue'
import PageBoard from '@/PageBoard.vue';
import BootstrapVueNext from 'bootstrap-vue-next'
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
import { auto500px } from "./util/auto500px";
import { insertCustomCSS } from "./util/customCSS";
+16
View File
@@ -0,0 +1,16 @@
import { createApp } from 'vue'
import { auto500px } from './util/auto500px';
import { htmlReady } from './util/htmlReady';
import { insertCustomCSS } from './util/customCSS';
import PageCityInfo from './PageCityInfo.vue';
import { installVue3Components } from './util/installVue3Components';
auto500px();
declare const query: {
cityID?: number
};
htmlReady(() => {
insertCustomCSS();
});
installVue3Components(createApp(PageCityInfo, query)).mount('#app');
+3 -4
View File
@@ -1,8 +1,6 @@
import '@scss/processing.scss';
import { unwrap } from "@util/unwrap";
import BootstrapVueNext from 'bootstrap-vue-next'
import Multiselect from 'vue-multiselect';
import { commandMap as GeneralActions } from "@/processing/General";
import { commandMap as NationActions } from '@/processing/Nation';
import { type App, createApp } from 'vue';
@@ -14,6 +12,7 @@ import { StoredActionsHelper } from './util/StoredActionsHelper';
import type { ReserveCommandResponse } from './defs/API/Command';
import { htmlReady } from './util/htmlReady';
import { insertCustomCSS } from './util/customCSS';
import { installVue3Components } from './util/installVue3Components';
declare const staticValues: {
serverNick: string,
@@ -100,13 +99,13 @@ if (app === undefined) {
console.error(`모듈이 지정되지 않음`, entryInfo);
}
else {
const div = unwrap(document.querySelector('#container'));
const div = unwrap(document.querySelector('#app'));
div.addEventListener('customSubmit', (e: Event) => {
const { detail } = e as unknown as CustomEvent<Args>;
void submitCommand(entryInfo[0] == 'Nation', turnList, entryInfo[1], detail);
}, true);
app.use(BootstrapVueNext).component('v-multiselect', Multiselect).mount('#container');
installVue3Components(app).mount('#app');
}
+1 -1
View File
@@ -27,7 +27,7 @@ $gen = Util::getReq('gen', 'int');
'mapName' => GameConst::$mapName,
'unitSet' => GameConst::$unitSet,
],
'queryValues' => [
'query' => [
'generalID' => $gen,
]
], false) ?>
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace sammo;
include "lib.php";
include "func.php";
$session = Session::requireGameLogin()->setReadOnly();
$cityID = Util::getReq('cityID', 'int');
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="dark">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=500" />
<title><?= UniqueConst::$serverName ?>: 도시 정보</title>
<?= WebUtil::printStaticValues([
'staticValues' => [
'serverName' => UniqueConst::$serverName,
'serverNick' => DB::prefix(),
'serverID' => UniqueConst::$serverID,
'mapName' => GameConst::$mapName,
'unitSet' => GameConst::$unitSet,
],
'query' => [
'cityID' => $cityID,
]
], false) ?>
<?= WebUtil::printJS('../d_shared/common_path.js') ?>
<?= WebUtil::printCSS('../d_shared/common.css') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" />
<?= WebUtil::printDist('vue', 'v_cityInfo', true) ?>
</head>
<body>
<div id="app"></div>
</body>
</html>
+1 -1
View File
@@ -101,7 +101,7 @@ if (!$commandObj->hasPermissionToReserve()) {
</head>
<body class="img_back">
<div id="container"></div>
<div id="app"></div>
</body>
</html>
+2 -2
View File
@@ -137,8 +137,8 @@ foreach (ServConfig::getServerList() as $setting) {
</div>
<div id="bottom_box">
<div class="container"><a href="terms.2.html">개인정보처리방침</a> &amp; <a href="terms.1.html">이용약관</a><br>© 2022 • HideD
<br>크롬 파이어폭스에 최적화되어있습니다.
<div class="container"><a href="terms.2.html">개인정보처리방침</a> &amp; <a href="terms.1.html">이용약관</a><br>© 2023 • HideD
<br>크롬, 엣지, 파이어폭스에 최적화되어있습니다.
</div>
</div>