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',