generalObj = $general; } public function __destruct() { $this->applyDB(); } public function applyDB(){ $db = DB::db(); $this->generalObj->applyDB($db); } public function getGeneral():General{ return $this->generalObj; } public function preprocessCommand(){ $general = $this->getGeneral(); $caller = $general->getPreTurnExecuteTriggerList($general); $caller->merge(new GeneralTriggerCaller([ new GeneralTrigger\che_부상경감($general), new GeneralTrigger\che_병력군량소모($general), ])); $caller->fire(); } public function processBlocked():bool{ $general = $this->getGeneral(); $blocked = $general->getVar('block'); if($blocked < 2){ return false; } $date = substr($general->getVar('turntime'),11,5); $logger = $general->getLogger(); if($blocked == 2){ $general->increaseVarWithLimit('killturn', -1, 0); $logger->pushGeneralActionLog("현재 멀티, 또는 비매너로 인한블럭 대상자입니다. <1>$date"); } else if($blocked == 3){ $general->increaseVarWithLimit('killturn', -1, 0); $logger->pushGeneralActionLog("현재 악성유저로 분류되어 블럭 대상자입니다. <1>$date"); } else{ //Hmm? return false; } return true; } public function processNationCommand(string $commandClassName, array $commandArg, LastTurn $commandLast):LastTurn{ if(!$this->nationTurn){ throw new \InvalidArgumentException('nationTurn이 없음'); } $general = $this->getGeneral(); $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $commandObj = buildNationCommandClass($commandClassName, $general, $gameStor->getAll(true), $commandLast, $commandArg); while(true){ $failReason = $commandObj->testRunnable(); if($failReason){ $date = substr($general->getVar('turntime'),11,5); $failString = $commandObj->getFailString(); $text = "{$failString} <1>{$date}"; $general->getLogger()->pushGeneralActionLog($text); break; } $result = $commandObj->run(); if($result){ break; } $alt = $commandObj->getAlternativeCommand(); if($alt === null){ break; } $commandObj = $alt; $commandClassName = $alt->getName(); } return $commandObj->getResultTurn(); } public function processCommand(string $commandClassName, array $commandArg){ $general = $this->getGeneral(); $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $commandObj = buildGeneralCommandClass($commandClassName, $general, $gameStor->getAll(true), $commandArg); while(true){ $failReason = $commandObj->testRunnable(); if($failReason){ $date = substr($general->getVar('turntime'),11,5); $failString = $commandObj->getFailString(); $text = "{$failString} <1>{$date}"; $general->getLogger()->pushGeneralActionLog($text); break; } $result = $commandObj->run(); if($result){ break; } $alt = $commandObj->getAlternativeCommand(); if($alt === null){ break; } $commandObj = $alt; $commandClassName = $alt->getName(); } $general->clearActivatedSkill(); $killTurn = $gameStor->killturn; if($general->getVar('npc') >= 2){ $general->increaseVarWithLimit('killturn', -1); } else if($general->getVar('killturn') > $killTurn){ $general->increaseVarWithLimit('killturn', -1); } else if($commandClassName == '휴식'){ $general->increaseVarWithLimit('killturn', -1); } else{ $general->setVar('killturn', $killTurn); } return $general->getResultTurn(); } function updateTurnTime(){ $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); $general = $this->getGeneral(); $generalID = $general->getRaw('no'); $logger = $general->getLogger(); $generalName = $general->getName(); // 삭턴장수 삭제처리 if($general->getVar('killturn') <= 0){ // npc유저 삭턴시 npc로 전환 if($general->getVar('npc') == 1 && $general->getVar('deadyear') > $gameStor->year){ $ownerName = $general->getVar('name2'); $josaYi = JosaUtil::pick($ownerName, '이'); $logger->pushGlobalActionLog("{$ownerName}{$josaYi} {$generalName}의 육체에서 유체이탈합니다!"); $general->setVar('killturn', ($general->getVar('deadyear') - $gameStor->year) * 12); $general->setVar('npc', $general->getVar('npc_org')); $general->setVar('owner', 1); $general->setVar('mode', 2); $general->setVar('name2', null); } else{ $general->applyDB($db); storeOldGeneral($generalID, $gameStor->year, $gameStor->month); $general->kill($db); return; } } //은퇴 if($general->getVar('age') >= 80 && $general->getVar('npc') == 0) { if($gameStor->isunited == 0) { $general->applyDB($db); CheckHall($generalID); } $general->rebirth(); } $turntime = addTurn($general->getRaw('turntime'), $gameStor->turnterm); $general->setVar('turntime', $turntime); } static public function executeGeneralCommandUntil(string $date, \DateTimeInterface $limitActionTime, int $year, int $month){ $db = DB::db(); $generalsTodo = $db->queryFirstRow( 'SELECT no,name,turntime,killturn,block,npc,deadyear, general_turn.`action` AS `action`, general_turn.arg AS arg FROM general LEFT JOIN general_turn ON general.`no` = general_turn.general_id AND turn_idx = 0 WHERE turntime < %s ORDER BY turntime ASC, `no` ASC', $date ); $currentTurn = null; foreach($generalsTodo as $rawGeneral){ $generalCommand = $rawGeneral['action']; $generalArg = Json::decode($rawGeneral['arg'])??[]; unset($rawGeneral['action']); unset($rawGeneral['arg']); $currActionTime = new \DateTimeImmutable(); if($currActionTime > $limitActionTime){ return [true, $currentTurn]; } $general = General::createGeneralObjFromDB($rawGeneral['no']); $turnObj = new static($general); $hasNationTurn = false; if($general->getVar('nation') != 0 && $general->getVar('level') >= 5){ $nationStor = KVStorage::getStorage($db, 'nation_env'); $lastNationTurnKey = "turn_last_{$general->getVar('nation')}_{$general->getVar('level')}"; $lastNationTurn = $nationStor->getDBValue($lastNationTurnKey)??[]; //수뇌 몇 없는데 매번 left join 하는건 낭비인것 같다. $rawNationTurn = Json::decode($db->queryFirstRow( 'SELECT action, arg FROM nation_turn WHERE nation_id = %i AND level = %i AND turn_idx =0', $general->getVar('nation'), $general->getVar('level') ))??[]; $hasNationTurn = true; $nationCommand = $rawNationTurn['action']; $nationArg = Json::decode($rawNationTurn['arg']); } if($general->getVar('npc') >= 2){ $ai = new GeneralAI($turnObj->getGeneral()); if($hasNationTurn){ [$nationCommand, $nationArg] = $ai->chooseNationTurn($nationCommand, $nationArg); } [$generalCommand, $generalArg] = $ai->chooseGeneralTurn($generalCommand, $generalArg); // npc AI 처리 } if(!$turnObj->processBlocked()){ $turnObj->preprocessCommand(); if($hasNationTurn){ $resultNationTurn = $turnObj->processNationCommand( $nationCommand, $nationArg, $lastNationTurn ); $nationStor->setValue($lastNationTurnKey, $resultNationTurn->toJson()); } $turnObj->processCommand($generalCommand, $generalArg); } pullNationCommand($general->getVar('nation'), $general->getVar('level')); pullGeneralCommand($general->getID()); $currentTurn = $general->getVar('turntime'); $turnObj->updateTurnTime(); $turnObj->applyDB(); } return [false, $currentTurn]; } static public function executeAllCommand(){ if(!timeover()) { return; } $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); if(!tryLock()){ return; } if ($gameStor->isunited == 2) { //천통시에는 동결 return; } $gameStor->cacheAll(); // 1턴이상 갱신 없었으면 서버 지연 checkDelay(); // 접속자수, 접속국가, 국가별 접속장수 갱신 updateOnline(); //접속자 수 따라서 갱신제한 변경 CheckOverhead(); $date = TimeUtil::now(true); // 최종 처리 월턴의 다음 월턴시간 구함 $prevTurn = cutTurn($gameStor->turntime, $gameStor->turnterm); $nextTurn = addTurn($prevTurn, $gameStor->turnterm); $maxActionTime = ini_get('max_execution_time'); $maxActionTime = max($maxActionTime * 2 / 3, $maxActionTime - 10); $limitActionTime = (new \DateTimeImmutable())->add(TimeUtil::secondsToDateInterval($maxActionTime)); // 현재 턴 이전 월턴까지 모두처리. //최종 처리 이후 다음 월턴이 현재 시간보다 전이라면 while ($nextTurn <= $date) { [$executionOver, $currentTurn] = static::executeGeneralCommandUntil( $nextTurn, $limitActionTime, $gameStor->year, $gameStor->month ); // 트래픽 업데이트 updateTraffic(); if($executionOver){ if($currentTurn !== null){ $gameStor->turntime = $currentTurn; } return; } // 1달마다 처리하는 것들, 벌점 감소 및 건국,전턴,합병 -1, 군량 소모 if(!preUpdateMonthly()){ $gameStor->resetCache(true); unlock(); throw new \RuntimeException('preUpdateMonthly() 처리 에러'); } turnDate($nextTurn); // 이벤트 핸들러 동작 foreach (DB::db()->query('SELECT * from event') as $rawEvent) { $eventID = $rawEvent['id']; $cond = Json::decode($rawEvent['condition']); $action = Json::decode($rawEvent['action']); $event = new Event\EventHandler($cond, $action); $event->tryRunEvent(['currentEventID'=>$eventID] + $gameStor->getAll(true)); } $logger = new ActionLogger(0, 0, $gameStor->year, $gameStor->month, false); // 분기계산. 장수들 턴보다 먼저 있다면 먼저처리 if($gameStor->month == 1) { processGoldIncome(); processSpring(); updateYearly(); updateQuaterly(); disaster(); tradeRate(); addAge(); // 새해 알림 $logger->pushGlobalActionLog("{$gameStor->year}년이 되었습니다."); $logger->flush(); //TODO: globalAction류는 전역에서 관리하는것이 좋을 듯. } elseif($gameStor->month == 4) { updateQuaterly(); disaster(); } elseif($gameStor->month == 7) { processRiceIncome(); processFall(); updateQuaterly(); disaster(); tradeRate(); } elseif($gameStor->month == 10) { updateQuaterly(); disaster(); } postUpdateMonthly(); // 다음달로 넘김 $prevTurn = $nextTurn; $nextTurn = addTurn($prevTurn, $gameStor->turnterm); } // 이시각까지는 업데이트 완료했음 $gameStor->turntime = $prevTurn; // 그 시각 년도,월 저장 turnDate($prevTurn); // 현재시간의 월턴시간 이후 분단위 장수 처리 [$executionOver, $currentTurn] = static::executeGeneralCommandUntil( $nextTurn, $limitActionTime, $gameStor->year, $gameStor->month ); if($currentTurn !== null){ $gameStor->turntime = $currentTurn; } // 부상 과도 제한 //TODO: 없애고, 부상 자체가 80 이상 넘지 않도록 처리 $db->update('general', [ 'injury'=>80 ], 'injury>%i', 80); //토너먼트 처리 processTournament(); //거래 처리 processAuction(); // 잡금 해제 $gameStor->resetCache(true); unlock(); } }