diff --git a/hwe/func.php b/hwe/func.php
index 20a387ff..05e4e8dc 100644
--- a/hwe/func.php
+++ b/hwe/func.php
@@ -7,7 +7,6 @@ use Ds\Set;
use sammo\Enums\AuctionType;
use sammo\Enums\InheritanceKey;
use sammo\Enums\RankColumn;
-use sammo\Event\Action;
require_once 'process_war.php';
require_once 'func_gamerule.php';
@@ -1224,76 +1223,6 @@ function updateOnline()
$gameStor->online_nation = join(', ', $onlineNation);
}
-function addAge(RandUtil $rng)
-{
- $db = DB::db();
- $gameStor = KVStorage::getStorage($db, 'game_env');
-
-
- //나이와 호봉 증가
- $db->update('general', [
- 'age' => $db->sqleval('age+1'),
- ], true);
-
- $db->update('general', [
- 'belong' => $db->sqleval('belong+1')
- ], 'nation != 0');
-
- [$startYear, $year, $month] = $gameStor->getValuesAsArray(['startyear', 'year', 'month']);
-
- if ($year >= $startYear + 3) {
- foreach ($db->query('SELECT no,name,nation,leadership,strength,intel,aux from general where specage<=age and special=%s', GameConst::$defaultSpecialDomestic) as $general) {
- $generalID = $general['no'];
- $special = SpecialityHelper::pickSpecialDomestic(
- $rng,
- $general,
- (Json::decode($general['aux'])['prev_types_special']) ?? []
- );
- $specialClass = buildGeneralSpecialDomesticClass($special);
- $specialText = $specialClass->getName();
- $db->update('general', [
- 'special' => $special
- ], 'no=%i', $generalID);
-
- $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
-
- $josaUl = JosaUtil::pick($specialText, '을');
- $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
- $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
- }
-
- foreach ($db->query('SELECT no,name,nation,leadership,strength,intel,npc,dex1,dex2,dex3,dex4,dex5,aux from general where specage2<=age and special2=%s', GameConst::$defaultSpecialWar) as $general) {
- $generalID = $general['no'];
- $generalAux = Json::decode($general['aux']);
-
- $updateVars = [];
- if (key_exists('inheritSpecificSpecialWar', $generalAux)) {
- $special2 = $generalAux['inheritSpecificSpecialWar'];
- unset($generalAux['inheritSpecificSpecialWar']);
- $updateVars['aux'] = Json::encode($generalAux);
- } else {
- $special2 = SpecialityHelper::pickSpecialWar(
- $rng,
- $general,
- ($generalAux['prev_types_special2']) ?? []
- );
- }
-
- $specialClass = buildGeneralSpecialWarClass($special2);
- $specialText = $specialClass->getName();
-
- $updateVars['special2'] = $special2;
- $db->update('general', $updateVars, 'no=%i', $general['no']);
-
- $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
-
- $josaUl = JosaUtil::pick($specialText, '을');
- $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
- $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
- }
- }
-}
-
function turnDate($curtime)
{
$db = DB::db();
diff --git a/hwe/func_gamerule.php b/hwe/func_gamerule.php
index ec20b72c..728fb9dc 100644
--- a/hwe/func_gamerule.php
+++ b/hwe/func_gamerule.php
@@ -4,6 +4,7 @@ namespace sammo;
use sammo\DTO\AuctionInfo;
use sammo\Enums\AuctionType;
+use sammo\Enums\EventTarget;
use sammo\Enums\InheritanceKey;
use sammo\Enums\RankColumn;
@@ -184,27 +185,6 @@ function updateGeneralNumber()
refreshNationStaticInfo();
}
-function updateYearly()
-{
- //통계
- checkStatistic();
-}
-
-//관직 변경 해제
-function updateQuaterly()
-{
- $db = DB::db();
-
- //천도 제한 해제, 관직 변경 제한 해제
- $db->update('nation', [
- 'chief_set' => 0,
- ], true);
- //관직 변경 제한 해제
- $db->update('city', [
- 'officer_set' => 0,
- ], true);
-}
-
// 벌점 감소와 건국제한-1 전턴제한-1 외교제한-1, 1달마다 실행, 병사 있는 장수의 군량 감소, 수입비율 조정
function preUpdateMonthly()
{
@@ -220,58 +200,6 @@ function preUpdateMonthly()
$admin = $gameStor->getValues(['startyear', 'year', 'month']);
- $logger = new ActionLogger(0, 0, $admin['year'], $admin['month']);
-
- //보급선 체크
- checkSupply();
- //미보급도시 10% 감소
- $db->update('city', [
- 'pop' => $db->sqleval('pop * 0.9'),
- 'trust' => $db->sqleval('trust * 0.9'),
- 'agri' => $db->sqleval('agri * 0.9'),
- 'comm' => $db->sqleval('comm * 0.9'),
- 'secu' => $db->sqleval('secu * 0.9'),
- 'def' => $db->sqleval('def * 0.9'),
- 'wall' => $db->sqleval('wall * 0.9'),
- ], 'supply = 0');
- //미보급도시 장수 병 훈 사 5%감소
- //NOTE: update inner join도 가능하지만, meekrodb 기준으로 깔끔하게.
- $unsuppliedCities = $db->query('SELECT city, nation, trust, name FROM city WHERE supply = 0');
- foreach (Util::arrayGroupBy($unsuppliedCities, 'nation') as $nationID => $cityList) {
- $cityIDList = Util::squeezeFromArray($cityList, 'city');
- $db->update('general', [
- 'crew' => $db->sqleval('crew*0.95'),
- 'atmos' => $db->sqleval('atmos*0.95'),
- 'train' => $db->sqleval('train*0.95'),
- ], 'city IN %li AND nation = %i', $cityIDList, $nationID);
- }
-
- //민심30이하 공백지 처리
- $lostCities = [];
- foreach ($unsuppliedCities as $unsuppliedCity) {
- if ($unsuppliedCity['trust'] >= 30) {
- continue;
- }
- $lostCities[$unsuppliedCity['city']] = $unsuppliedCity;
- }
-
- if ($lostCities) {
- foreach ($lostCities as $lostCity) {
- $josaYi = JosaUtil::pick($lostCity['name'], '이');
- $logger->pushGlobalHistoryLog("【고립】>{$lostCity['name']}>{$josaYi} 보급이 끊겨 미지배> 도시가 되었습니다.");
- }
- $db->update('general', [
- 'officer_level' => 1,
- 'officer_city' => 0
- ], 'officer_city IN %li', array_keys($lostCities));
- $db->update('city', [
- 'nation' => 0,
- 'officer_set' => 0,
- 'conflict' => '{}',
- 'term' => 0,
- 'front' => 0
- ], 'city IN %li', array_keys($lostCities));
- }
//접률감소, 건국제한-1
$db->update('general', [
@@ -289,9 +217,6 @@ function preUpdateMonthly()
$develcost = ($admin['year'] - $admin['startyear'] + 10) * 2;
$gameStor->develcost = $develcost;
- //매달 사망자 수입 결산
- processWarIncome();
-
//계략, 전쟁표시 해제
$db->update('city', [
'state' => $db->sqleval(<<getValues(['killturn', 'year', 'month', 'fiction', 'startyear', 'show_img_level', 'turnterm', 'turntime']);
$year = $admin['year'];
$month = $admin['month'];
$startYear = $admin['startyear'];
- $assemblerCnts = [];
$nationCityCounts = [];
foreach($db->queryAllLists('SELECT nation, count(*) FROM city WHERE LEVEL>=4 GROUP BY nation') as [$nationID, $cityCnt]){
$nationCityCounts[$nationID] = $cityCnt;
}
- foreach ($db->queryAllLists('SELECT nation,count(no) FROM general WHERE npc = 5 GROUP BY nation') as [$nationID, $assemblerCnt]) {
- $assemblerCnts[$nationID] = $assemblerCnt;
- };
$nationLevelByCityCnt = [
0, //방랑군
@@ -747,62 +667,7 @@ function updateNationState()
}
}
}
-
- $assemblerCnt = $assemblerCnts[$nation['nation']] ?? 0;
- $maxAssemblerCnt = [
- 1 => 0,
- 2 => 1,
- 3 => 3,
- 4 => 4,
- 5 => 6,
- 6 => 7,
- 7 => 9
- ][$nationlevel] ?? 0;
-
- if ($assemblerCnt < $maxAssemblerCnt) {
- $lastAssemblerID = $gameStor->assembler_id ?? 0;
-
- $troopLeaderRng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
- UniqueConst::$hiddenSeed,
- 'troopLeader',
- $year,
- $month,
- $nationID
- )));
-
- while ($assemblerCnt < $maxAssemblerCnt) {
- $lastAssemblerID += 1;
- $npcObj = new Scenario\GeneralBuilder(
- $troopLeaderRng,
- sprintf('부대장%4d', $lastAssemblerID),
- false,
- null,
- $nation['nation']
- );
- $npcObj->setAffinity(999)->setStat(10, 10, 10)
- ->setSpecialSingle('척사')->setEgo('che_은둔')
- ->setKillturn(70)->setGoldRice(0, 0)
- ->setNPCType(5)->fillRemainSpecAsZero($admin);
- $npcObj->build($admin);
- $npcID = $npcObj->getGeneralID();
-
- $db->insert('troop', [
- 'troop_leader' => $npcID,
- 'name' => $npcObj->getGeneralName(),
- 'nation' => $nation['nation'],
- ]);
- $db->update('general', [
- 'troop' => $npcID
- ], 'no=%i', $npcID);
-
- $cmd = buildGeneralCommandClass('che_집합', General::createGeneralObjFromDB($npcID), $admin);
- _setGeneralCommand($cmd, iterator_to_array(Util::range(GameConst::$maxTurn)));
- $assemblerCnt += 1;
- $gameStor->assembler_id = $lastAssemblerID;
- }
- }
}
- pushGlobalHistoryLog($history, $admin['year'], $admin['month']);
}
function checkStatistic()
@@ -1090,7 +955,9 @@ function checkEmperior()
};
}
}
- processSumInheritPointRank();
+
+ TurnExecutionHelper::runEventHandler($db, $gameStor, EventTarget::United);
+
foreach ($allUserGenerals as $genObj) {
$inheritPointManager->mergeTotalInheritancePoint($genObj);
$inheritPointManager->applyInheritanceUser($genObj->getVar('owner'));
diff --git a/hwe/func_time_event.php b/hwe/func_time_event.php
index f0d6912a..aebe8e8e 100644
--- a/hwe/func_time_event.php
+++ b/hwe/func_time_event.php
@@ -4,173 +4,32 @@ namespace sammo;
use sammo\Enums\InheritanceKey;
use sammo\Enums\RankColumn;
use Ds\Map;
+use sammo\Enums\ResourceType;
+use sammo\Event\Action;
+
/**
* 시간 단위로 일어나는 이벤트들에 대한 함수 모음
*/
-
+/** @deprecated */
function processSumInheritPointRank(){
$db = DB::db();
-
- $generals = General::createGeneralObjListFromDB(null, null, 2);
-
- $points = new Map();
- $points->allocate(count($generals));
- foreach($generals as $general){
- $generalID = $general->getID();
- $points[$generalID] = 0;
- }
-
- foreach(InheritanceKey::cases() as $key){
- if($key === InheritanceKey::previous){
- continue;
- }
- $subPoints = InheritancePointManager::getInstance()->getInheritancePointFromAll($generals, $key);
- foreach($generals as $general){
- $generalID = $general->getID();
- $points[$generalID] += $subPoints[$generalID] ?? 0;
- }
- }
-
- $pointsPairs = [];
- foreach($points as $generalID => $point){
- $pointsPairs[] = [
- 'nation_id' => $generals[$generalID]->getNationID(),
- 'general_id' => $generalID,
- 'type' => RankColumn::inherit_point_earned_by_merge->value,
- 'value' => $point,
- ];
- }
- //XXX: multiple batch update가 제공되지 않으므로..
- $db->delete('rank_data', '`type` = %s', RankColumn::inherit_point_earned_by_merge->value);
- $db->insert('rank_data', $pointsPairs);
-
- $db->query(
- 'UPDATE `rank_data` D SET `value` = (SELECT SUM(`value`) FROM `rank_data` S WHERE S.general_id = D.general_id AND S.`type` IN %ls) WHERE D.`type` = %s',
- [RankColumn::inherit_point_earned_by_action->value, RankColumn::inherit_point_earned_by_merge->value],
- RankColumn::inherit_point_earned->value
- );
-
- $db->query(
- 'UPDATE `rank_data` D SET `value` = (SELECT `value` FROM `rank_data` S WHERE S.general_id = D.general_id AND S.`type` = %s) WHERE D.`type` = %s',
- RankColumn::inherit_point_spent_dynamic->value,
- RankColumn::inherit_point_spent->value
- );
-}
-
-//1월마다 실행
-function processSpring() {
- $db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
- // 1월엔 무조건 내정 1% 감소
- $db->update('city',[
- 'dead'=>0,
- 'agri'=>$db->sqleval('agri * 0.99'),
- 'comm'=>$db->sqleval('comm * 0.99'),
- 'secu'=>$db->sqleval('secu * 0.99'),
- 'def'=>$db->sqleval('def * 0.99'),
- 'wall'=>$db->sqleval('wall * 0.99'),
- ],true);
-
- //인구 증가
- popIncrease();
-
- // > 10000 유지비 3%, > 1000 유지비 1%
- // 유지비 1%
- $db->update('general', [
- 'gold'=>$db->sqleval('IF(gold > 10000, gold * 0.97, gold * 0.99)')
- ], 'gold > 1000');
-
- // > 100000 유지비 5%, > 100000 유지비 3%, > 1000 유지비 1%
- $db->update('nation', [
- 'gold'=>$db->sqleval('IF(gold > 100000, gold * 0.95, IF(gold > 10000, gold * 0.97, gold * 0.99))')
- ], 'gold > 1000');
-
- $admin = $gameStor->getValues(['year', 'month']);
-
- pushGlobalHistoryLog(["★>{$admin['year']}년 {$admin['month']}월: 모두들 즐거운 게임 하고 계신가요? ^^ 매너 있는 플레이> 부탁드리고, 지나친 훼접>은 삼가주세요~>"], $admin['year'], $admin['month']);
+ $e_env = $gameStor->getValues(['startyear', 'year', 'month']);
+ $e_env['currentEventID'] = 0;
+ Action::build(["ProcessIncome", ResourceType::rice->value])->run($e_env);
}
+
+/** @deprecated */
function processGoldIncome() {
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
- [$year, $month] = $gameStor->getValuesAsArray(['year', 'month']);
- $adminLog = [];
-
-
- $nationList = $db->query('SELECT name,nation,capital,gold,level,rate_tmp,bill,type from nation');
- $cityListByNation = Util::arrayGroupBy($db->query('SELECT * FROM city'), 'nation');
- $generalRawListByNation = Util::arrayGroupBy($db->query('SELECT no,name,nation,gold,officer_level,dedication,city FROM general WHERE npc != 5'), 'nation');
-
- //국가별 처리
- foreach($nationList as $nation) {
- $nationID = $nation['nation'];
-
- $generalRawList = $generalRawListByNation[$nationID];
- $income = getGoldIncome($nationID, $nation['level'], $nation['rate_tmp'], $nation['capital'], $nation['type'], $cityListByNation[$nationID]??[]);
- $originoutcome = getOutcome(100, $generalRawList);
- $outcome= Util::round($nation['bill'] / 100 * $originoutcome);
-
- // 실제 지급량 계산
- $nation['gold'] += $income;
- // 기본량도 안될경우
- if($nation['gold'] < GameConst::$basegold) {
- $realoutcome = 0;
- // 실지급률
- $ratio = 0;
- //기본량은 넘지만 요구량이 안될경우
- } elseif($nation['gold'] - GameConst::$basegold < $outcome) {
- $realoutcome = $nation['gold'] - GameConst::$basegold;
- $nation['gold'] = GameConst::$basegold;
- // 실지급률
- $ratio = $realoutcome / $originoutcome;
- } else {
- $realoutcome = $outcome;
- $nation['gold'] -= $realoutcome;
- // 실지급률
- $ratio = $realoutcome / $originoutcome;
- }
- $nation['gold'] = Util::valueFit($nation['gold'], GameConst::$basegold);
- $adminLog[] = StringUtil::padStringAlignRight((string)$nation['name'],12," ")
- ." // 세금 : ".StringUtil::padStringAlignRight((string)$income,6," ")
- ." // 세출 : ".StringUtil::padStringAlignRight((string)$originoutcome,6," ")
- ." // 실제 : ".tab2((string)$realoutcome,6," ")
- ." // 지급률 : ".tab2((string)round($ratio*100,2),5," ")
- ." % // 결과금 : ".tab2((string)$nation['gold'],6," ");
-
- $incomeText = number_format($income);
- $incomeLog = "이번 수입은 금 $incomeText>입니다.";
- $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
- $nationStor->prev_income_gold = $income;
-
- $db->update('nation', [
- 'gold'=>$nation['gold']
- ], 'nation=%i', $nationID);
-
- // 각 장수들에게 지급
- foreach ($generalRawList as $rawGeneral) {
- $generalObj = new General($rawGeneral, null, null, null, $year, $month, false);
- $gold = Util::round(getBill($generalObj->getVar('dedication'))*$ratio);
- $generalObj->increaseVar('gold', $gold);
-
- $logger = $generalObj->getLogger();
- if($generalObj->getVar('officer_level') > 4){
- $logger->pushGeneralActionLog($incomeLog, $logger::PLAIN);
- }
-
- $goldText = number_format($gold);
- $logger->pushGeneralActionLog("봉급으로 금 $goldText>을 받았습니다.", $logger::PLAIN);
- $generalObj->applyDB($db);
- }
- }
-
- $logger = new ActionLogger(0, 0, $year, $month);
- $logger->pushGlobalHistoryLog('【지급】>봄이 되어 봉록에 따라 자금이 지급됩니다.');
- $logger->flush();
-
- pushAdminLog($adminLog);
+ $e_env = $gameStor->getValues(['startyear', 'year', 'month']);
+ $e_env['currentEventID'] = 0;
+ Action::build(["ProcessIncome", ResourceType::gold->value])->run($e_env);
}
function popIncrease() {
@@ -304,29 +163,6 @@ function getGoldIncome(int $nationID, int $nationLevel, float $taxRate, int $cap
return $cityIncome;
}
-function processWarIncome() {
- $db = DB::db();
-
- $cityListByNation = Util::arrayGroupBy($db->query('SELECT * FROM city'), 'nation');
-
- foreach(getAllNationStaticInfo() as $nation){
- if($nation['level'] <= 0){
- continue;
- }
- $nationID = $nation['nation'];
- $income = getWarGoldIncome($nation['type'], $cityListByNation[$nationID]??[]);
- $db->update('nation', [
- 'gold'=>$db->sqleval('gold + %i', $income)
- ], 'nation=%i', $nationID);
- }
-
- // 10%수입, 20%부상병
- $db->update('city', [
- 'pop'=>$db->sqleval('pop + dead * %d', 0.2),
- 'dead'=>0
- ], true);
-}
-
function getWarGoldIncome(string $nationType, array $cityList){
$nationTypeObj = buildNationTypeClass($nationType);
@@ -338,115 +174,14 @@ function getWarGoldIncome(string $nationType, array $cityList){
return $cityIncome;
}
-
-
-//7월마다 실행
-function processFall() {
- $db = DB::db();
-
- // 7월엔 무조건 내정 1% 감소
- $db->update('city',[
- 'dead'=>0,
- 'agri'=>$db->sqleval('agri * 0.99'),
- 'comm'=>$db->sqleval('comm * 0.99'),
- 'secu'=>$db->sqleval('secu * 0.99'),
- 'def'=>$db->sqleval('def * 0.99'),
- 'wall'=>$db->sqleval('wall * 0.99'),
- ],true);
-
- //인구 증가
- popIncrease();
-
- // > 10000 유지비 3%, > 1000 유지비 1%
- // 유지비 1%
- $db->update('general', [
- 'rice'=>$db->sqleval('IF(rice > 10000, rice * 0.97, rice * 0.99)')
- ], 'rice > 1000');
-
- // > 100000 유지비 5%, > 100000 유지비 3%, > 1000 유지비 1%
- $db->update('nation', [
- 'rice'=>$db->sqleval('IF(rice > 100000, rice * 0.95, IF(rice > 10000, rice * 0.97, rice * 0.99))')
- ], 'rice > 1000');
-}
-
+/** @deprecated */
function processRiceIncome() {
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
- [$year, $month] = $gameStor->getValuesAsArray(['year', 'month']);
- $adminLog = [];
-
- $nationList = $db->query('SELECT name,level,nation,capital,rice,rate_tmp,bill,type from nation');
- $cityListByNation = Util::arrayGroupBy($db->query('SELECT * FROM city'), 'nation');
- $generalRawListByNation = Util::arrayGroupBy($db->query('SELECT no,name,nation,rice,officer_level,dedication,city FROM general WHERE npc != 5'), 'nation');
-
- //국가별 처리
- foreach($nationList as $nation) {
- $nationID = $nation['nation'];
-
- $generalRawList = $generalRawListByNation[$nationID];
- $income = getRiceIncome($nation['nation'], $nation['level'], $nation['rate_tmp'], $nation['capital'], $nation['type'], $cityListByNation[$nationID]??[]);
- $income += getWallIncome($nation['nation'], $nation['level'], $nation['rate_tmp'], $nation['capital'], $nation['type'], $cityListByNation[$nationID]??[]);
- $originoutcome = getOutcome(100, $generalRawList);
- $outcome= Util::round($nation['bill'] / 100 * $originoutcome);
-
- // 실제 지급량 계산
- $nation['rice'] += $income;
- // 기본량도 안될경우
- if($nation['rice'] < GameConst::$baserice) {
- $realoutcome = 0;
- // 실지급률
- $ratio = 0;
- //기본량은 넘지만 요구량이 안될경우
- } elseif($nation['rice'] - GameConst::$baserice < $outcome) {
- $realoutcome = $nation['rice'] - GameConst::$baserice;
- $nation['rice'] = GameConst::$baserice;
- // 실지급률
- $ratio = $realoutcome / $originoutcome;
- } else {
- $realoutcome = $outcome;
- $nation['rice'] -= $realoutcome;
- // 실지급률
- $ratio = $realoutcome / $originoutcome;
- }
- $nation['rice'] = Util::valueFit($nation['rice'], GameConst::$baserice);
- $adminLog[] = StringUtil::padStringAlignRight($nation['name'],12," ")
- ." // 세곡 : ".StringUtil::padStringAlignRight((string)$income,6," ")
- ." // 세출 : ".StringUtil::padStringAlignRight((string)$originoutcome,6," ")
- ." // 실제 : ".tab2((string)$realoutcome,6," ")
- ." // 지급률 : ".tab2((string)round($ratio*100,2),5," ")
- ." % // 결과곡 : ".tab2((string)$nation['rice'],6," ");
-
- $incomeText = number_format($income);
- $incomeLog = "이번 수입은 쌀 $incomeText>입니다.";
- $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
- $nationStor->prev_income_rice = $income;
-
- $db->update('nation', [
- 'rice'=>$nation['rice']
- ], 'nation=%i', $nationID);
-
- // 각 장수들에게 지급
- foreach ($generalRawList as $rawGeneral) {
- $generalObj = new General($rawGeneral, null, null, null, $year, $month, false);
- $rice = Util::round(getBill($generalObj->getVar('dedication'))*$ratio);
- $generalObj->increaseVar('rice', $rice);
-
- $logger = $generalObj->getLogger();
- if($generalObj->getVar('officer_level') > 4){
- $logger->pushGeneralActionLog($incomeLog, $logger::PLAIN);
- }
- $riceText = number_format($rice);
- $logger->pushGeneralActionLog("봉급으로 쌀 $riceText>을 받았습니다.", $logger::PLAIN);
- $generalObj->applyDB($db);
- }
- }
-
- $logger = new ActionLogger(0, 0, $year, $month);
- $logger->pushGlobalHistoryLog('【지급】>가을이 되어 봉록에 따라 군량이 지급됩니다.');
- $logger->flush();
-
- pushAdminLog($adminLog);
+ $e_env = $gameStor->getValues(['startyear', 'year', 'month']);
+ $e_env['currentEventID'] = 0;
+ Action::build(["ProcessIncome", ResourceType::rice->value])->run($e_env);
}
function getRiceIncome(int $nationID, int $nationLevel, float $taxRate, int $capitalID, string $nationType, ?array $cityList) {
diff --git a/hwe/process_war.php b/hwe/process_war.php
index c7d0ea9f..51139d44 100644
--- a/hwe/process_war.php
+++ b/hwe/process_war.php
@@ -2,6 +2,7 @@
namespace sammo;
+use sammo\Enums\EventTarget;
function processWar(string $warSeed, General $attackerGeneral, array $rawAttackerNation, array $rawDefenderCity)
{
@@ -549,23 +550,10 @@ function ConquerCity(array $admin, General $general, array $city)
$gameStor = KVStorage::getStorage($db, 'game_env');
// 이벤트 핸들러 동작
- $e_env = null;
- foreach (DB::db()->query('SELECT * FROM event WHERE target = "OCCUPY_CITY" ORDER BY `priority` DESC, `id` ASC') as $rawEvent) {
- if ($e_env === null) {
- $e_env = $gameStor->getAll(false);
- }
- $eventID = $rawEvent['id'];
- $cond = Json::decode($rawEvent['condition']);
- $action = Json::decode($rawEvent['action']);
- $event = new Event\EventHandler($cond, $action);
- $e_env['currentEventID'] = $eventID;
-
- $event->tryRunEvent($e_env);
+ if(TurnExecutionHelper::runEventHandler($db, $gameStor, EventTarget::OccupyCity)){
+ $gameStor->cacheAll();
}
- if ($e_env !== null) {
- $gameStor->resetCache();
- }
// 국가 멸망시
if ($defenderNationID && $db->queryFirstField('SELECT count(city) FROM city WHERE nation = %i', $defenderNationID) === 1) {
@@ -661,27 +649,11 @@ function ConquerCity(array $admin, General $general, array $city)
}
// 이벤트 핸들러 동작
- $e_env = null;
- foreach (DB::db()->query('SELECT * FROM event WHERE target = "DESTROY_NATION" ORDER BY `priority` DESC, `id` ASC') as $rawEvent) {
- if ($e_env === null) {
- $e_env = $gameStor->getAll(false);
- }
- $eventID = $rawEvent['id'];
- $cond = Json::decode($rawEvent['condition']);
- $action = Json::decode($rawEvent['action']);
- $event = new Event\EventHandler($cond, $action);
- $e_env['currentEventID'] = $eventID;
-
- $event->tryRunEvent($e_env);
- }
-
- if ($e_env !== null) {
- $gameStor->resetCache();
- }
+ TurnExecutionHelper::runEventHandler($db, $gameStor, EventTarget::DestroyNation);
// 멸망이 아니면
} else {
- // 태수,군사,종사은 일반으로...
+ // 태수,군사,종사는 일반으로...
$db->update('general', [
'officer_level' => 1,
'officer_city' => 0,
diff --git a/hwe/sammo/Command/General/che_해산.php b/hwe/sammo/Command/General/che_해산.php
index a1f84c2c..e3e53535 100644
--- a/hwe/sammo/Command/General/che_해산.php
+++ b/hwe/sammo/Command/General/che_해산.php
@@ -9,13 +9,15 @@ use \sammo\{
LastTurn,
Command,
Json,
- KVStorage
+ KVStorage,
+ TurnExecutionHelper
};
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\CityConst;
+use sammo\Enums\EventTarget;
use sammo\Event\EventHandler;
use function sammo\refreshNationStaticInfo;
@@ -110,24 +112,7 @@ class che_해산 extends Command\GeneralCommand{
$general->applyDB($db);
// 이벤트 핸들러 동작
- $gameStor = KVStorage::getStorage($db, 'game_env');
- $e_env = null;
- foreach (DB::db()->query('SELECT * FROM event WHERE target = "DESTROY_NATION" ORDER BY `priority` DESC, `id` ASC') as $rawEvent) {
- if ($e_env === null) {
- $e_env = $gameStor->getAll(false);
- }
- $eventID = $rawEvent['id'];
- $cond = Json::decode($rawEvent['condition']);
- $action = Json::decode($rawEvent['action']);
- $event = new EventHandler($cond, $action);
- $e_env['currentEventID'] = $eventID;
-
- $event->tryRunEvent($e_env);
- }
-
- if ($e_env !== null) {
- $gameStor->resetCache();
- }
+ TurnExecutionHelper::runEventHandler($db, null, EventTarget::OccupyCity);
return true;
}
diff --git a/hwe/sammo/Enums/EventTarget.php b/hwe/sammo/Enums/EventTarget.php
new file mode 100644
index 00000000..f97e762f
--- /dev/null
+++ b/hwe/sammo/Enums/EventTarget.php
@@ -0,0 +1,12 @@
+ */
$ref = new \ReflectionClass($className);
return $ref->newInstanceArgs($args);
}
diff --git a/hwe/sammo/Event/Action/AssignGeneralSpeciality.php b/hwe/sammo/Event/Action/AssignGeneralSpeciality.php
new file mode 100644
index 00000000..137c4974
--- /dev/null
+++ b/hwe/sammo/Event/Action/AssignGeneralSpeciality.php
@@ -0,0 +1,90 @@
+query('SELECT no,name,nation,leadership,strength,intel,aux from general where specage<=age and special=%s', GameConst::$defaultSpecialDomestic) as $general) {
+ $generalID = $general['no'];
+ $special = SpecialityHelper::pickSpecialDomestic(
+ $rng,
+ $general,
+ (Json::decode($general['aux'])['prev_types_special']) ?? []
+ );
+ $specialClass = buildGeneralSpecialDomesticClass($special);
+ $specialText = $specialClass->getName();
+ $db->update('general', [
+ 'special' => $special
+ ], 'no=%i', $generalID);
+
+ $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
+
+ $josaUl = JosaUtil::pick($specialText, '을');
+ $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
+ $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
+ }
+
+ foreach ($db->query('SELECT no,name,nation,leadership,strength,intel,npc,dex1,dex2,dex3,dex4,dex5,aux from general where specage2<=age and special2=%s', GameConst::$defaultSpecialWar) as $general) {
+ $generalID = $general['no'];
+ $generalAux = Json::decode($general['aux']);
+
+ $updateVars = [];
+ if (key_exists('inheritSpecificSpecialWar', $generalAux)) {
+ $special2 = $generalAux['inheritSpecificSpecialWar'];
+ unset($generalAux['inheritSpecificSpecialWar']);
+ $updateVars['aux'] = Json::encode($generalAux);
+ } else {
+ $special2 = SpecialityHelper::pickSpecialWar(
+ $rng,
+ $general,
+ ($generalAux['prev_types_special2']) ?? []
+ );
+ }
+
+ $specialClass = buildGeneralSpecialWarClass($special2);
+ $specialText = $specialClass->getName();
+
+ $updateVars['special2'] = $special2;
+ $db->update('general', $updateVars, 'no=%i', $general['no']);
+
+ $logger = new ActionLogger($generalID, $general['nation'], $year, $month);
+
+ $josaUl = JosaUtil::pick($specialText, '을');
+ $logger->pushGeneralActionLog("특기 【{$specialText}>】{$josaUl} 익혔습니다!", ActionLogger::PLAIN);
+ $logger->pushGeneralHistoryLog("특기 【{$specialText}>】{$josaUl} 습득");
+ }
+ }
+}
diff --git a/hwe/sammo/Event/Action/MergeInheritPointRank.php b/hwe/sammo/Event/Action/MergeInheritPointRank.php
new file mode 100644
index 00000000..a93d1e55
--- /dev/null
+++ b/hwe/sammo/Event/Action/MergeInheritPointRank.php
@@ -0,0 +1,66 @@
+allocate(count($generals));
+ foreach($generals as $general){
+ $generalID = $general->getID();
+ $points[$generalID] = 0;
+ }
+
+ foreach(InheritanceKey::cases() as $key){
+ if($key === InheritanceKey::previous){
+ continue;
+ }
+ $subPoints = InheritancePointManager::getInstance()->getInheritancePointFromAll($generals, $key);
+ foreach($generals as $general){
+ $generalID = $general->getID();
+ $points[$generalID] += $subPoints[$generalID] ?? 0;
+ }
+ }
+
+ $pointsPairs = [];
+ foreach($points as $generalID => $point){
+ $pointsPairs[] = [
+ 'nation_id' => $generals[$generalID]->getNationID(),
+ 'general_id' => $generalID,
+ 'type' => RankColumn::inherit_point_earned_by_merge->value,
+ 'value' => $point,
+ ];
+ }
+ //XXX: multiple batch update가 제공되지 않으므로..
+ $db->delete('rank_data', '`type` = %s', RankColumn::inherit_point_earned_by_merge->value);
+ $db->insert('rank_data', $pointsPairs);
+
+ $db->query(
+ 'UPDATE `rank_data` D SET `value` = (SELECT SUM(`value`) FROM `rank_data` S WHERE S.general_id = D.general_id AND S.`type` IN %ls) WHERE D.`type` = %s',
+ [RankColumn::inherit_point_earned_by_action->value, RankColumn::inherit_point_earned_by_merge->value],
+ RankColumn::inherit_point_earned->value
+ );
+
+ $db->query(
+ 'UPDATE `rank_data` D SET `value` = (SELECT `value` FROM `rank_data` S WHERE S.general_id = D.general_id AND S.`type` = %s) WHERE D.`type` = %s',
+ RankColumn::inherit_point_spent_dynamic->value,
+ RankColumn::inherit_point_spent->value
+ );
+ }
+}
diff --git a/hwe/sammo/Event/Action/NewYear.php b/hwe/sammo/Event/Action/NewYear.php
new file mode 100644
index 00000000..2a6827dc
--- /dev/null
+++ b/hwe/sammo/Event/Action/NewYear.php
@@ -0,0 +1,31 @@
+pushGlobalActionLog("{$year}>년이 되었습니다.");
+ $logger->pushGeneralHistoryLog("모두들 즐거운 게임 하고 계신가요? ^^ 매너 있는 플레이> 부탁드리고, 게임보단 건강이 먼저>란점, 잊지 마세요!>", $logger::NOTICE_YEAR_MONTH);
+ $logger->flush(); //TODO: globalAction류는 전역에서 관리하는것이 좋을 듯.
+
+ $db = DB::db();
+
+ //나이와 호봉 증가
+ $db->update('general', [
+ 'age' => $db->sqleval('age+1'),
+ ], true);
+
+ $db->update('general', [
+ 'belong' => $db->sqleval('belong+1')
+ ], 'nation != 0');
+ }
+}
diff --git a/hwe/sammo/Event/Action/ProcessIncome.php b/hwe/sammo/Event/Action/ProcessIncome.php
new file mode 100644
index 00000000..f0ad251d
--- /dev/null
+++ b/hwe/sammo/Event/Action/ProcessIncome.php
@@ -0,0 +1,201 @@
+query('SELECT name,nation,capital,gold,level,rate_tmp,bill,type from nation');
+ $cityListByNation = Util::arrayGroupBy($db->query('SELECT * FROM city'), 'nation');
+ $generalRawListByNation = Util::arrayGroupBy($db->query('SELECT no,name,nation,gold,officer_level,dedication,city FROM general WHERE npc != 5'), 'nation');
+
+ //국가별 처리
+ foreach ($nationList as $nation) {
+ $nationID = $nation['nation'];
+
+ $generalRawList = $generalRawListByNation[$nationID];
+ $income = getGoldIncome($nationID, $nation['level'], $nation['rate_tmp'], $nation['capital'], $nation['type'], $cityListByNation[$nationID] ?? []);
+ $originoutcome = getOutcome(100, $generalRawList);
+ $outcome = Util::round($nation['bill'] / 100 * $originoutcome);
+
+ // 실제 지급량 계산
+ $nation['gold'] += $income;
+ // 기본량도 안될경우
+ if ($nation['gold'] < GameConst::$basegold) {
+ $realoutcome = 0;
+ // 실지급률
+ $ratio = 0;
+ //기본량은 넘지만 요구량이 안될경우
+ } elseif ($nation['gold'] - GameConst::$basegold < $outcome) {
+ $realoutcome = $nation['gold'] - GameConst::$basegold;
+ $nation['gold'] = GameConst::$basegold;
+ // 실지급률
+ $ratio = $realoutcome / $originoutcome;
+ } else {
+ $realoutcome = $outcome;
+ $nation['gold'] -= $realoutcome;
+ // 실지급률
+ $ratio = $realoutcome / $originoutcome;
+ }
+ $nation['gold'] = Util::valueFit($nation['gold'], GameConst::$basegold);
+ $adminLog[] = StringUtil::padStringAlignRight((string)$nation['name'], 12, " ")
+ . " // 세금 : " . StringUtil::padStringAlignRight((string)$income, 6, " ")
+ . " // 세출 : " . StringUtil::padStringAlignRight((string)$originoutcome, 6, " ")
+ . " // 실제 : " . tab2((string)$realoutcome, 6, " ")
+ . " // 지급률 : " . tab2((string)round($ratio * 100, 2), 5, " ")
+ . " % // 결과금 : " . tab2((string)$nation['gold'], 6, " ");
+
+ $incomeText = number_format($income);
+ $incomeLog = "이번 수입은 금 $incomeText>입니다.";
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+ $nationStor->prev_income_gold = $income;
+
+ $db->update('nation', [
+ 'gold' => $nation['gold']
+ ], 'nation=%i', $nationID);
+
+ // 각 장수들에게 지급
+ foreach ($generalRawList as $rawGeneral) {
+ $generalObj = new General($rawGeneral, null, null, null, $year, $month, false);
+ $gold = Util::round(getBill($generalObj->getVar('dedication')) * $ratio);
+ $generalObj->increaseVar('gold', $gold);
+
+ $logger = $generalObj->getLogger();
+ if ($generalObj->getVar('officer_level') > 4) {
+ $logger->pushGeneralActionLog($incomeLog, $logger::PLAIN);
+ }
+
+ $goldText = number_format($gold);
+ $logger->pushGeneralActionLog("봉급으로 금 $goldText>을 받았습니다.", $logger::PLAIN);
+ $generalObj->applyDB($db);
+ }
+ }
+
+ $logger = new ActionLogger(0, 0, $year, $month);
+ $logger->pushGlobalHistoryLog('【지급】>봄이 되어 봉록에 따라 자금이 지급됩니다.');
+ $logger->flush();
+
+ pushAdminLog($adminLog);
+ }
+
+ private function processRiceIncome(array $env)
+ {
+ $db = DB::db();
+
+ [$year, $month] = [$env['year'], $env['month']];
+ $adminLog = [];
+
+ $nationList = $db->query('SELECT name,level,nation,capital,rice,rate_tmp,bill,type from nation');
+ $cityListByNation = Util::arrayGroupBy($db->query('SELECT * FROM city'), 'nation');
+ $generalRawListByNation = Util::arrayGroupBy($db->query('SELECT no,name,nation,rice,officer_level,dedication,city FROM general WHERE npc != 5'), 'nation');
+
+ //국가별 처리
+ foreach ($nationList as $nation) {
+ $nationID = $nation['nation'];
+
+ $generalRawList = $generalRawListByNation[$nationID];
+ $income = getRiceIncome($nation['nation'], $nation['level'], $nation['rate_tmp'], $nation['capital'], $nation['type'], $cityListByNation[$nationID] ?? []);
+ $income += getWallIncome($nation['nation'], $nation['level'], $nation['rate_tmp'], $nation['capital'], $nation['type'], $cityListByNation[$nationID] ?? []);
+ $originoutcome = getOutcome(100, $generalRawList);
+ $outcome = Util::round($nation['bill'] / 100 * $originoutcome);
+
+ // 실제 지급량 계산
+ $nation['rice'] += $income;
+ // 기본량도 안될경우
+ if ($nation['rice'] < GameConst::$baserice) {
+ $realoutcome = 0;
+ // 실지급률
+ $ratio = 0;
+ //기본량은 넘지만 요구량이 안될경우
+ } elseif ($nation['rice'] - GameConst::$baserice < $outcome) {
+ $realoutcome = $nation['rice'] - GameConst::$baserice;
+ $nation['rice'] = GameConst::$baserice;
+ // 실지급률
+ $ratio = $realoutcome / $originoutcome;
+ } else {
+ $realoutcome = $outcome;
+ $nation['rice'] -= $realoutcome;
+ // 실지급률
+ $ratio = $realoutcome / $originoutcome;
+ }
+ $nation['rice'] = Util::valueFit($nation['rice'], GameConst::$baserice);
+ $adminLog[] = StringUtil::padStringAlignRight($nation['name'], 12, " ")
+ . " // 세곡 : " . StringUtil::padStringAlignRight((string)$income, 6, " ")
+ . " // 세출 : " . StringUtil::padStringAlignRight((string)$originoutcome, 6, " ")
+ . " // 실제 : " . tab2((string)$realoutcome, 6, " ")
+ . " // 지급률 : " . tab2((string)round($ratio * 100, 2), 5, " ")
+ . " % // 결과곡 : " . tab2((string)$nation['rice'], 6, " ");
+
+ $incomeText = number_format($income);
+ $incomeLog = "이번 수입은 쌀 $incomeText>입니다.";
+ $nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
+ $nationStor->prev_income_rice = $income;
+
+ $db->update('nation', [
+ 'rice' => $nation['rice']
+ ], 'nation=%i', $nationID);
+
+ // 각 장수들에게 지급
+ foreach ($generalRawList as $rawGeneral) {
+ $generalObj = new General($rawGeneral, null, null, null, $year, $month, false);
+ $rice = Util::round(getBill($generalObj->getVar('dedication')) * $ratio);
+ $generalObj->increaseVar('rice', $rice);
+
+ $logger = $generalObj->getLogger();
+ if ($generalObj->getVar('officer_level') > 4) {
+ $logger->pushGeneralActionLog($incomeLog, $logger::PLAIN);
+ }
+ $riceText = number_format($rice);
+ $logger->pushGeneralActionLog("봉급으로 쌀 $riceText>을 받았습니다.", $logger::PLAIN);
+ $generalObj->applyDB($db);
+ }
+ }
+
+ $logger = new ActionLogger(0, 0, $year, $month);
+ $logger->pushGlobalHistoryLog('【지급】>가을이 되어 봉록에 따라 군량이 지급됩니다.');
+ $logger->flush();
+
+ pushAdminLog($adminLog);
+ }
+
+ public function run(array $env)
+ {
+ if ($this->resource === ResourceType::gold->value) {
+ $this->processGoldIncome($env);
+ } else {
+ $this->processRiceIncome($env);
+ }
+ }
+}
diff --git a/hwe/sammo/Event/Action/ProcessSemiAnnual.php b/hwe/sammo/Event/Action/ProcessSemiAnnual.php
new file mode 100644
index 00000000..b016a867
--- /dev/null
+++ b/hwe/sammo/Event/Action/ProcessSemiAnnual.php
@@ -0,0 +1,98 @@
+queryAllLists('SELECT nation,rate_tmp,type FROM nation');
+
+ // 인구 및 민심
+
+ $db->update('city', [
+ 'trust' => 50,
+ 'agri' => $db->sqleval('agri * 0.99'),
+ 'comm' => $db->sqleval('comm * 0.99'),
+ 'secu' => $db->sqleval('secu * 0.99'),
+ 'def' => $db->sqleval('def * 0.99'),
+ 'wall' => $db->sqleval('wall * 0.99'),
+ ], 'nation=0');
+
+ foreach ($nationList as [$nationID, $taxRate, $nationType]) {
+ $nationTypeObj = buildNationTypeClass($nationType);
+
+
+ $popRatio = (30 - $taxRate) / 200; // 20일때 5% 5일때 12.5% 50일때 -10%
+ $popRatio = $nationTypeObj->onCalcNationalIncome('pop', $popRatio);
+
+ $updateVar = [];
+ if ($popRatio >= 0) {
+ $updateVar['pop'] = $db->sqleval('least(pop_max, %i + pop * (1 + %d * (1 + secu / secu_max / 10)))', GameConst::$basePopIncreaseAmount, $popRatio);
+ } else {
+ $updateVar['pop'] = $db->sqleval('least(pop_max, %i + pop * (1 + %d * (1 - secu / secu_max / 10)))', GameConst::$basePopIncreaseAmount, $popRatio);
+ }
+
+ $genericRatio = (20 - $taxRate) / 200; // 20일때 0% 0일때 10% 100일때 -40%
+ foreach (['agri', 'comm', 'secu', 'def', 'wall'] as $key) {
+ $updateVar[$key] = $db->sqleval('least(%b, %b * (1 + %d))', $key . '_max', $key, $genericRatio);
+ }
+
+ $trustDiff = 20 - $taxRate;
+ $updateVar['trust'] = $db->sqleval('greatest(0, least(100, trust + %i))', $trustDiff);
+
+ $db->update('city', $updateVar, 'nation = %i AND supply = 1', $nationID);
+ }
+ }
+
+ public function run(array $env)
+ {
+ $db = DB::db();
+
+
+ $resource = $this->resource;
+
+ // 내정 1% 감소
+ $db->update('city', [
+ 'dead' => 0,
+ 'agri' => $db->sqleval('agri * 0.99'),
+ 'comm' => $db->sqleval('comm * 0.99'),
+ 'secu' => $db->sqleval('secu * 0.99'),
+ 'def' => $db->sqleval('def * 0.99'),
+ 'wall' => $db->sqleval('wall * 0.99'),
+ ], true);
+
+ //인구 증가
+ popIncrease();
+
+ // > 10000 유지비 3%, > 1000 유지비 1%
+ // 유지비 1%
+ $db->update('general', [
+ $resource => $db->sqleval('IF(%b > 10000, %b * 0.97, %b * 0.99)', $resource, $resource, $resource)
+ ], '%b > 1000', $resource);
+
+ // > 100000 유지비 5%, > 100000 유지비 3%, > 1000 유지비 1%
+ $db->update('nation', [
+ $resource => $db->sqleval('IF(%b > 100000, %b * 0.95, IF(%b > 10000, %b * 0.97, %b * 0.99))', $resource, $resource, $resource, $resource, $resource)
+ ], '%b > 1000', $resource);
+ }
+}
diff --git a/hwe/sammo/Event/Action/ProcessWarIncome.php b/hwe/sammo/Event/Action/ProcessWarIncome.php
new file mode 100644
index 00000000..08652501
--- /dev/null
+++ b/hwe/sammo/Event/Action/ProcessWarIncome.php
@@ -0,0 +1,36 @@
+query('SELECT * FROM city'), 'nation');
+
+ foreach(getAllNationStaticInfo() as $nation){
+ if($nation['level'] <= 0){
+ continue;
+ }
+ $nationID = $nation['nation'];
+ $income = getWarGoldIncome($nation['type'], $cityListByNation[$nationID]??[]);
+ $db->update('nation', [
+ 'gold'=>$db->sqleval('gold + %i', $income)
+ ], 'nation=%i', $nationID);
+ }
+
+ // 10%수입, 20%부상병
+ $db->update('city', [
+ 'pop'=>$db->sqleval('pop + dead * %d', 0.2),
+ 'dead'=>0
+ ], true);
+ }
+}
diff --git a/hwe/sammo/Event/Action/ProvideNPCTroopLeader.php b/hwe/sammo/Event/Action/ProvideNPCTroopLeader.php
new file mode 100644
index 00000000..42773c8e
--- /dev/null
+++ b/hwe/sammo/Event/Action/ProvideNPCTroopLeader.php
@@ -0,0 +1,93 @@
+ 0,
+ 2 => 1,
+ 3 => 3,
+ 4 => 4,
+ 5 => 6,
+ 6 => 7,
+ 7 => 9
+ ];
+
+ public function run(array $env)
+ {
+ $db = DB::db();
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+
+ $NPCTroopLeaderCntByNation = [];
+ foreach ($db->queryAllLists('SELECT nation,count(no) FROM general WHERE npc = 5 GROUP BY nation') as [$nationID, $NPCTroopLeaderCnt]) {
+ $NPCTroopLeaderCntByNation[$nationID] = $NPCTroopLeaderCnt;
+ };
+
+ $year = $env['year'];
+ $month = $env['month'];
+
+ foreach ($db->query('SELECT nation,name,level,tech,aux FROM nation') as $nation) {
+ $nationID = $nation['nation'];
+ $maxNPCTroopLeaderCnt = self::MaxNPCTroopLeaderCnt[$nation['level']];
+ $NPCTroopLeaderCnt = $NPCTroopLeaderCntByNation[$nationID] ?? 0;
+
+ if ($NPCTroopLeaderCnt >= $maxNPCTroopLeaderCnt) {
+ continue;
+ }
+
+ $lastNPCTroopLeaderID = $gameStor->lastNPCTroopLeaderID ?? 0;
+
+ $troopLeaderRng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
+ UniqueConst::$hiddenSeed,
+ 'troopLeader',
+ $year,
+ $month,
+ $nationID
+ )));
+
+ while ($NPCTroopLeaderCnt < $maxNPCTroopLeaderCnt) {
+ $lastNPCTroopLeaderID += 1;
+ $npcObj = new \sammo\Scenario\GeneralBuilder(
+ $troopLeaderRng,
+ sprintf('부대장%4d', $lastNPCTroopLeaderID),
+ false,
+ null,
+ $nation['nation']
+ );
+ $npcObj->setAffinity(999)->setStat(10, 10, 10)
+ ->setSpecialSingle('척사')->setEgo('che_은둔')
+ ->setKillturn(70)->setGoldRice(0, 0)
+ ->setNPCType(5)->fillRemainSpecAsZero($env);
+ $npcObj->build($env);
+ $npcID = $npcObj->getGeneralID();
+
+ $db->insert('troop', [
+ 'troop_leader' => $npcID,
+ 'name' => $npcObj->getGeneralName(),
+ 'nation' => $nation['nation'],
+ ]);
+ $db->update('general', [
+ 'troop' => $npcID
+ ], 'no=%i', $npcID);
+
+ $cmd = buildGeneralCommandClass('che_집합', General::createGeneralObjFromDB($npcID), $env);
+ _setGeneralCommand($cmd, iterator_to_array(Util::range(GameConst::$maxTurn)));
+ $NPCTroopLeaderCnt += 1;
+ $gameStor->lastNPCTroopLeaderID = $lastNPCTroopLeaderID;
+ }
+ }
+ }
+}
diff --git a/hwe/sammo/Event/Action/RaiseDisaster.php b/hwe/sammo/Event/Action/RaiseDisaster.php
new file mode 100644
index 00000000..28cc3b9a
--- /dev/null
+++ b/hwe/sammo/Event/Action/RaiseDisaster.php
@@ -0,0 +1,159 @@
+update('city', [
+ 'state' => 0,
+ ], 'state <= 10');
+
+ // 초반 3년은 스킵
+ if ($startYear + 3 > $year) return;
+
+ $boomingRate = [
+ 1 => 0,
+ 4 => 0.25,
+ 7 => 0.25,
+ 10 => 0
+ ];
+
+ $isGood = $rng->nextBool($boomingRate[$month]);
+
+
+ $targetCityList = [];
+
+ foreach ($db->query('SELECT city,name,secu,secu_max FROM city') as $city) {
+ //호황 발생 도시 선택 ( 기본 2% )
+ //재해 발생 도시 선택 ( 기본 6% )
+ if ($isGood) {
+ $raiseProp = 0.02 + ($city['secu'] / $city['secu_max']) * 0.05; // 2 ~ 7%
+ } else {
+ $raiseProp = 0.06 - ($city['secu'] / $city['secu_max']) * 0.05; // 1 ~ 6%
+ }
+
+ if ($rng->nextBool($raiseProp)) {
+ $targetCityList[] = $city;
+ }
+ }
+
+ if (!$targetCityList) {
+ return;
+ }
+
+ $targetCityNames = "" . join(' ', Util::squeezeFromArray($targetCityList, 'name')) . ">";
+ $disasterTextList = [
+ 1 => [
+ ['【재난】>', 4, '역병이 발생하여 도시가 황폐해지고 있습니다.'],
+ ['【재난】>', 5, '지진으로 피해가 속출하고 있습니다.'],
+ ['【재난】>', 3, '추위가 풀리지 않아 얼어죽는 백성들이 늘어나고 있습니다.'],
+ ['【재난】>', 9, '황건적이 출현해 도시를 습격하고 있습니다.'],
+ ],
+ 4 => [
+ ['【재난】>', 7, '홍수로 인해 피해가 급증하고 있습니다.'],
+ ['【재난】>', 5, '지진으로 피해가 속출하고 있습니다.'],
+ ['【재난】>', 6, '태풍으로 인해 피해가 속출하고 있습니다.'],
+ ],
+ 7 => [
+ ['【재난】>', 8, '메뚜기 떼가 발생하여 도시가 황폐해지고 있습니다.'],
+ ['【재난】>', 5, '지진으로 피해가 속출하고 있습니다.'],
+ ['【재난】>', 8, '흉년이 들어 굶어죽는 백성들이 늘어나고 있습니다.'],
+ ],
+ 10 => [
+ ['【재난】>', 3, '혹한으로 도시가 황폐해지고 있습니다.'],
+ ['【재난】>', 5, '지진으로 피해가 속출하고 있습니다.'],
+ ['【재난】>', 3, '눈이 많이 쌓여 도시가 황폐해지고 있습니다.'],
+ ['【재난】>', 9, '황건적이 출현해 도시를 습격하고 있습니다.'],
+ ]
+ ];
+
+ $boomingTextList = [
+ 1 => null,
+ 4 => [
+ ['【호황】>', 2, '호황으로 도시가 번창하고 있습니다.'],
+ ],
+ 7 => [
+ ['【풍작】>', 1, '풍작으로 도시가 번창하고 있습니다.'],
+ ],
+ 10 => null
+ ];
+
+ [$logTitle, $stateCode, $logBody] = $rng->choice(($isGood ? $boomingTextList : $disasterTextList)[$month]);
+
+ $logger = new ActionLogger(0, 0, $year, $month, false);
+
+ $logger->pushGlobalHistoryLog("{$logTitle}{$targetCityNames}에 {$logBody}");
+ $logger->flush();
+
+ if (!$isGood) {
+ $generalListByCity = Util::arrayGroupBy($db->query('SELECT no, name, nation, city, officer_level, injury, leadership, strength, intel, horse, weapon, book, item, crew, crewtype, atmos, train, special, special2 FROM general WHERE city IN %li', Util::squeezeFromArray($targetCityList, 'city')), 'city');
+ //NOTE: 쿼리 1번이지만 복잡하기 vs 쿼리 여러번이지만 조금 더 깔끔하기
+ foreach ($targetCityList as $city) {
+ $affectRatio = Util::valueFit($city['secu'] / $city['secu_max'] / 0.8, 0, 1);
+ $affectRatio = 0.8 + $affectRatio * 0.15;
+
+ $db->update('city', [
+ 'state' => $stateCode,
+ 'pop' => $db->sqleval('pop * %d', $affectRatio),
+ 'trust' => $db->sqleval('trust * %d', $affectRatio),
+ 'agri' => $db->sqleval('agri * %d', $affectRatio),
+ 'comm' => $db->sqleval('comm * %d', $affectRatio),
+ 'secu' => $db->sqleval('secu * %d', $affectRatio),
+ 'def' => $db->sqleval('def * %d', $affectRatio),
+ 'wall' => $db->sqleval('wall * %d', $affectRatio),
+ ], 'city = %i', $city['city']);
+
+ $generalList = array_map(
+ function ($rawGeneral) use ($city, $year, $month) {
+ return new General($rawGeneral, null, $city, null, $year, $month, false);
+ },
+ $generalListByCity[$city['city']] ?? []
+ );
+
+ SabotageInjury($rng, $generalList, '재난');
+ }
+ } else {
+ foreach ($targetCityList as $city) {
+ $affectRatio = Util::valueFit($city['secu'] / $city['secu_max'] / 0.8, 0, 1);
+ $affectRatio = 1.01 + $affectRatio * 0.04;
+
+ $db->update('city', [
+ 'state' => $stateCode,
+ 'pop' => $db->sqleval('least(pop * %d, pop_max)', $affectRatio),
+ 'trust' => $db->sqleval('least(trust * %d, 100)', $affectRatio),
+ 'agri' => $db->sqleval('least(agri * %d, agri_max)', $affectRatio),
+ 'comm' => $db->sqleval('least(comm * %d, comm_max)', $affectRatio),
+ 'secu' => $db->sqleval('least(secu * %d, secu_max)', $affectRatio),
+ 'def' => $db->sqleval('least(def * %d, def_max)', $affectRatio),
+ 'wall' => $db->sqleval('least(wall * %d, wall_max)', $affectRatio),
+ ], 'city = %i', $city['city']);
+ }
+ }
+ }
+}
diff --git a/hwe/sammo/Event/Action/RandomizeCityTradeRate.php b/hwe/sammo/Event/Action/RandomizeCityTradeRate.php
new file mode 100644
index 00000000..99e12a3d
--- /dev/null
+++ b/hwe/sammo/Event/Action/RandomizeCityTradeRate.php
@@ -0,0 +1,48 @@
+query('SELECT city,level FROM city') as $city) {
+ //시세
+ $prob = [
+ 1 => 0,
+ 2 => 0,
+ 3 => 0,
+ 4 => 0.2,
+ 5 => 0.4,
+ 6 => 0.6,
+ 7 => 0.8,
+ 8 => 1
+ ][$city['level']];
+ if ($prob > 0 && $rng->nextBool($prob)) {
+ $trade = $rng->nextRangeInt(95, 105);
+ } else {
+ $trade = null;
+ }
+ $db->update('city', [
+ 'trade' => $trade
+ ], 'city=%i', $city['city']);
+ }
+ }
+}
diff --git a/hwe/sammo/Event/Action/ResetOfficerLock.php b/hwe/sammo/Event/Action/ResetOfficerLock.php
new file mode 100644
index 00000000..d4ba4e6d
--- /dev/null
+++ b/hwe/sammo/Event/Action/ResetOfficerLock.php
@@ -0,0 +1,22 @@
+update('nation', [
+ 'chief_set' => 0,
+ ], true);
+ //관직 변경 제한 해제
+ $db->update('city', [
+ 'officer_set' => 0,
+ ], true);
+ }
+}
diff --git a/hwe/sammo/Event/Action/UpdateCitySupply.php b/hwe/sammo/Event/Action/UpdateCitySupply.php
new file mode 100644
index 00000000..cfff5317
--- /dev/null
+++ b/hwe/sammo/Event/Action/UpdateCitySupply.php
@@ -0,0 +1,133 @@
+query('SELECT city, nation FROM city WHERE nation != 0') as $city) {
+ $newCity = new \stdClass();
+ $newCity->id = Util::toInt($city['city']);
+ $newCity->nation = Util::toInt($city['nation']);
+ $newCity->supply = false;
+
+ $cities[$newCity->id] = $newCity;
+ }
+
+ $queue = new \SplQueue();
+ foreach ($db->queryAllLists('SELECT capital, nation FROM nation WHERE `level` > 0') as list($capitalID, $nationID)) {
+ if (!key_exists($capitalID, $cities)) {
+ continue;
+ }
+ $city = $cities[$capitalID];
+ if ($nationID != $city->nation) {
+ continue;
+ }
+ $city->supply = true;
+ $queue->enqueue($city);
+ }
+
+ while (!$queue->isEmpty()) {
+ $cityLink = $queue->dequeue();
+ $city = CityConst::byID($cityLink->id);
+
+ foreach (array_keys($city->path) as $connCityID) {
+ if (!key_exists($connCityID, $cities)) {
+ continue;
+ }
+ $connCity = $cities[$connCityID];
+ if ($connCity->nation != $cityLink->nation) {
+ continue;
+ }
+ if ($connCity->supply) {
+ continue;
+ }
+ $connCity->supply = true;
+ $queue->enqueue($connCity);
+ }
+ }
+
+ $db->update('city', [
+ 'supply' => 1
+ ], 'nation=0');
+
+ $db->update('city', [
+ 'supply' => 0
+ ], 'nation!=0');
+
+ $supply = [];
+
+ foreach ($cities as $city) {
+ if ($city->supply) {
+ $supply[] = $city->id;
+ }
+ }
+
+ if ($supply) {
+ $db->update('city', [
+ 'supply' => 1
+ ], 'city IN %li', $supply);
+ }
+
+ //미보급도시 10% 감소
+ $db->update('city', [
+ 'pop' => $db->sqleval('pop * 0.9'),
+ 'trust' => $db->sqleval('trust * 0.9'),
+ 'agri' => $db->sqleval('agri * 0.9'),
+ 'comm' => $db->sqleval('comm * 0.9'),
+ 'secu' => $db->sqleval('secu * 0.9'),
+ 'def' => $db->sqleval('def * 0.9'),
+ 'wall' => $db->sqleval('wall * 0.9'),
+ ], 'supply = 0');
+ //미보급도시 장수 병 훈 사 5%감소
+ //NOTE: update inner join도 가능하지만, meekrodb 기준으로 깔끔하게.
+ $unsuppliedCities = $db->query('SELECT city, nation, trust, name FROM city WHERE supply = 0');
+ foreach (Util::arrayGroupBy($unsuppliedCities, 'nation') as $nationID => $cityList) {
+ $cityIDList = Util::squeezeFromArray($cityList, 'city');
+ $db->update('general', [
+ 'crew' => $db->sqleval('crew*0.95'),
+ 'atmos' => $db->sqleval('atmos*0.95'),
+ 'train' => $db->sqleval('train*0.95'),
+ ], 'city IN %li AND nation = %i', $cityIDList, $nationID);
+ }
+
+ //민심30이하 공백지 처리
+ $lostCities = [];
+ foreach ($unsuppliedCities as $unsuppliedCity) {
+ if ($unsuppliedCity['trust'] >= 30) {
+ continue;
+ }
+ $lostCities[$unsuppliedCity['city']] = $unsuppliedCity;
+ }
+
+ $logger = new ActionLogger(0, 0, $env['year'], $env['month']);
+
+ if ($lostCities) {
+ foreach ($lostCities as $lostCity) {
+ $josaYi = JosaUtil::pick($lostCity['name'], '이');
+ $logger->pushGlobalHistoryLog("【고립】>{$lostCity['name']}>{$josaYi} 보급이 끊겨 미지배> 도시가 되었습니다.");
+ }
+ $db->update('general', [
+ 'officer_level' => 1,
+ 'officer_city' => 0
+ ], 'officer_city IN %li', array_keys($lostCities));
+ $db->update('city', [
+ 'nation' => 0,
+ 'officer_set' => 0,
+ 'conflict' => '{}',
+ 'term' => 0,
+ 'front' => 0
+ ], 'city IN %li', array_keys($lostCities));
+ }
+ }
+}
diff --git a/hwe/sammo/GameConstBase.php b/hwe/sammo/GameConstBase.php
index dfae9317..594157b5 100644
--- a/hwe/sammo/GameConstBase.php
+++ b/hwe/sammo/GameConstBase.php
@@ -414,6 +414,46 @@ class GameConstBase
]
];
public static $defaultEvents = [
+ [
+ "pre_month", 9000,
+ true,
+ ["UpdateCitySupply"],
+ ["ProcessWarIncome"]
+ ],
+ [
+ "month", 9000,
+ ["Date", "==", null, 1],
+ ["MergeInheritPointRank"],
+ ["ProcessSemiAnnual", "gold"],
+ ["ProcessIncome", "gold"],
+ ["ResetOfficerLock"],
+ ["RaiseDisaster"],
+ ["RandomizeCityTradeRate"],
+ ["NewYear"],
+ ["AssignGeneralSpeciality"],
+ ],
+ [
+ "month", 9000,
+ ["Date", "==", null, 4],
+ ["ResetOfficerLock"],
+ ["RaiseDisaster"],
+ ],
+ [
+ "month", 9000,
+ ["Date", "==", null, 7],
+ ["MergeInheritPointRank"],
+ ["ProcessSemiAnnual", "rice"],
+ ["ProcessIncome", "rice"],
+ ["ResetOfficerLock"],
+ ["RaiseDisaster"],
+ ["RandomizeCityTradeRate"],
+ ],
+ [
+ "month", 9000,
+ ["Date", "==", null, 10],
+ ["ResetOfficerLock"],
+ ["RaiseDisaster"],
+ ],
[
"month", 2000,
["DateRelative", "==", 1, 1],
@@ -446,5 +486,15 @@ class GameConstBase
["AddGlobalBetray", 1, 1],
["DeleteEvent"]
],
+ [
+ "month", 1000,
+ true,
+ ["ProvideNPCTroopLeader"]
+ ],
+ [
+ "united", 5000,
+ true,
+ ["MergeInheritPointRank"],
+ ]
];
}
diff --git a/hwe/sammo/Scenario.php b/hwe/sammo/Scenario.php
index 9123b6a6..364e3ee2 100644
--- a/hwe/sammo/Scenario.php
+++ b/hwe/sammo/Scenario.php
@@ -41,6 +41,8 @@ class Scenario{
private $initialEvents;
private $events;
+ private $ignoreDefaultEvents;
+
private $initBasic = false;
private $initOK = false;
@@ -208,11 +210,22 @@ class Scenario{
return $this->generateGeneral($rawGeneral, true, 6);
}, Util::array_get($data['general_neutral'], []));
+
+ if($this->ignoreDefaultEvents){
+ $rawInitialEvents = Util::array_get($data['initialEvents'], []);
+ $rawEvents = Util::array_get($data['events'], []);
+ }
+ else{
+ $rawInitialEvents = array_merge(GameConst::$defaultInitialEvents, Util::array_get($data['initialEvents'], []));
+ $rawEvents = array_merge(GameConst::$defaultEvents, Util::array_get($data['events'], []));
+ }
+
$this->initialEvents = array_map(function($rawEvent){
$cond = $rawEvent[0];
$action = array_slice($rawEvent, 1);
return new \sammo\Event\EventHandler($cond, $action);
- }, array_merge(GameConst::$defaultInitialEvents, Util::array_get($data['initialEvents'], [])));
+ }, $rawInitialEvents);
+
$this->events = array_map(function($rawEvent){
//event는 여기서 풀지 않는다. 평가만 한다.
@@ -235,7 +248,7 @@ class Scenario{
'cond' => $cond,
'action' => $action
];
- }, array_merge(GameConst::$defaultEvents, Util::array_get($data['events'], [])));
+ }, $rawEvents);
}
public function getGameConf(){
@@ -277,13 +290,14 @@ class Scenario{
$data = Json::decode(file_get_contents($scenarioPath));
$this->data = $data;
+ $this->ignoreDefaultEvents = $data['ignoreDefaultEvents'] ?? false;
$this->getGameConf();
- $this->year = Util::array_get($data['startYear']);
- $this->title = Util::array_get($data['title'] , '');
+ $this->year = $data['startYear'] ?? null;
+ $this->title = $data['title'] ?? '';
- $this->history = Util::array_get($data['history'], []);
+ $this->history = $data['history'] ?? [];
if(!$lazyInit){
$this->initFull();
diff --git a/hwe/sammo/TurnExecutionHelper.php b/hwe/sammo/TurnExecutionHelper.php
index 1c0e700c..c20a1a76 100644
--- a/hwe/sammo/TurnExecutionHelper.php
+++ b/hwe/sammo/TurnExecutionHelper.php
@@ -2,6 +2,7 @@
namespace sammo;
+use sammo\Enums\EventTarget;
use sammo\Enums\InheritanceKey;
use \Symfony\Component\Lock;
@@ -352,6 +353,29 @@ class TurnExecutionHelper
return [false, $currentTurn];
}
+ static public function runEventHandler(\MeekroDB $db, ?KVStorage $gameStor, EventTarget $eventTarget): bool{
+ if($gameStor === null){
+ $gameStor = KVStorage::getStorage($db, 'game_env');
+ }
+ $e_env = null;
+ foreach ($db->query('SELECT * FROM event WHERE target = %s ORDER BY `priority` DESC, `id` ASC', $eventTarget->value) as $rawEvent) {
+ if ($e_env === null) {
+ $e_env = $gameStor->getAll(false);
+ }
+ $eventID = $rawEvent['id'];
+ $cond = Json::decode($rawEvent['condition']);
+ $action = Json::decode($rawEvent['action']);
+ $event = new Event\EventHandler($cond, $action);
+ $e_env['currentEventID'] = $eventID;
+
+ $event->tryRunEvent($e_env);
+ }
+ if ($e_env === null) {
+ return false;
+ }
+ return true;
+ }
+
static public function executeAllCommand(&$executed = false, &$locked = false): string
{
$db = DB::db();
@@ -431,63 +455,18 @@ class TurnExecutionHelper
)));
// 1달마다 처리하는 것들, 벌점 감소 및 건국,전턴,합병 -1, 군량 소모
+ static::runEventHandler($db, $gameStor, EventTarget::PreMonth);
if (!preUpdateMonthly()) {
- $gameStor->resetCache();
unlock();
throw new \RuntimeException('preUpdateMonthly() 처리 에러');
}
-
turnDate($nextTurn);
- $logger = new ActionLogger(0, 0, $gameStor->year, $gameStor->month, false);
-
// 분기계산. 장수들 턴보다 먼저 있다면 먼저처리
if ($gameStor->month == 1) {
- processSumInheritPointRank();
- processSpring();
- processGoldIncome();
- updateYearly();
- updateQuaterly();
- disaster($monthlyRng);
- tradeRate($monthlyRng);
- addAge($monthlyRng);
- // 새해 알림
- $logger->pushGlobalActionLog("{$gameStor->year}>년이 되었습니다.");
- $logger->flush(); //TODO: globalAction류는 전역에서 관리하는것이 좋을 듯.
- } elseif ($gameStor->month == 4) {
- updateQuaterly();
- disaster($monthlyRng);
- } elseif ($gameStor->month == 7) {
- processSumInheritPointRank();
- processFall();
- processRiceIncome();
- updateQuaterly();
- disaster($monthlyRng);
- tradeRate($monthlyRng);
- } elseif ($gameStor->month == 10) {
- updateQuaterly();
- disaster($monthlyRng);
+ checkStatistic();
}
-
- // 이벤트 핸들러 동작
- $e_env = null;
- foreach (DB::db()->query('SELECT * FROM event WHERE target = "MONTH" ORDER BY `priority` DESC, `id` ASC') as $rawEvent) {
- if ($e_env === null) {
- $e_env = $gameStor->getAll(false);
- }
- $eventID = $rawEvent['id'];
- $cond = Json::decode($rawEvent['condition']);
- $action = Json::decode($rawEvent['action']);
- $event = new Event\EventHandler($cond, $action);
- $e_env['currentEventID'] = $eventID;
-
- $event->tryRunEvent($e_env);
- }
-
- if ($e_env !== null) {
- $gameStor->resetCache();
- }
-
+ static::runEventHandler($db, $gameStor, EventTarget::Month);
postUpdateMonthly($monthlyRng);
// 다음달로 넘김
diff --git a/hwe/sql/schema.sql b/hwe/sql/schema.sql
index 5d300bbb..397a805c 100644
--- a/hwe/sql/schema.sql
+++ b/hwe/sql/schema.sql
@@ -484,7 +484,7 @@ CREATE TABLE IF NOT EXISTS `ng_history` (
##############################
CREATE TABLE `event` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
- `target` ENUM('MONTH', 'OCCUPY_CITY', 'DESTROY_NATION') NOT NULL DEFAULT 'MONTH',
+ `target` ENUM('MONTH', 'OCCUPY_CITY', 'DESTROY_NATION','PRE_MONTH', 'UNITED') NOT NULL DEFAULT 'MONTH',
`priority` INT(11) NOT NULL DEFAULT '1000',
`condition` MEDIUMTEXT NOT NULL COLLATE 'utf8mb4_bin',
`action` MEDIUMTEXT NOT NULL COLLATE 'utf8mb4_bin',