diff --git a/hwe/sammo/AutorunGeneralPolicy.php b/hwe/sammo/AutorunGeneralPolicy.php index 76c8ffd2..8cee7d19 100644 --- a/hwe/sammo/AutorunGeneralPolicy.php +++ b/hwe/sammo/AutorunGeneralPolicy.php @@ -20,7 +20,7 @@ class AutorunGeneralPolicy{ static $출병 = '출병'; - static $NPC증여 = 'NPC증여'; + //static $NPC증여 = 'NPC증여'; static $NPC헌납 = 'NPC헌납'; static $후방워프 = '후방워프'; @@ -31,7 +31,7 @@ class AutorunGeneralPolicy{ //static $전투이동 = '전투이동'; //static $내정이동 = '내정이동'; - static $임관 = '임관'; + static $국가선택 = '국가선택'; static $집합 = '집합'; static $건국 = '건국'; static $선양 = '선양'; @@ -44,7 +44,7 @@ class AutorunGeneralPolicy{ '긴급내정', '전투준비', '전방워프', - 'NPC증여', + //'NPC증여', 'NPC헌납', '징병', '후방워프', @@ -71,7 +71,7 @@ class AutorunGeneralPolicy{ public $can출병 = true; - public $canNPC증여 = true; + //public $canNPC증여 = true; public $canNPC헌납 = true; public $can후방워프 = true; @@ -80,7 +80,7 @@ class AutorunGeneralPolicy{ public $can귀환 = true; - public $can임관 = true; + public $can국가선택 = true; public $can집합 = false; public $can건국 = true; public $can선양 = false; @@ -92,12 +92,12 @@ class AutorunGeneralPolicy{ if($npc==5){ $this->can선양 = true; $this->can집합 = true; - $this->can임관 = false; + $this->can국가선택 = false; return; } if($nationID != 0){ - $this->can임관 = false; + $this->can국가선택 = false; $this->can건국 = false; } @@ -127,14 +127,14 @@ class AutorunGeneralPolicy{ $this->can출병 = false; - $this->canNPC증여 = false; + //$this->canNPC증여 = false; $this->canNPC헌납 = false; $this->can후방워프 = false; $this->can전방워프 = false; $this->can내정워프 = false; - $this->can임관 = false; + $this->can국가선택 = false; $this->can집합 = false; $this->can건국 = false; $this->can선양 = false; diff --git a/hwe/sammo/AutorunNationPolicy.php b/hwe/sammo/AutorunNationPolicy.php index 5ede6dc7..cdf5ddc9 100644 --- a/hwe/sammo/AutorunNationPolicy.php +++ b/hwe/sammo/AutorunNationPolicy.php @@ -117,6 +117,7 @@ class AutorunNationPolicy { public $allowNpcAttackCity = true; public $minNPCRecruitCityPopulation = 50000; public $safeRecruitCityPopulation = 50000; + public $properWarTrainAtmos = 90; function __construct(General $general, array $nationPolicy, array $serverPolicy) diff --git a/hwe/sammo/GeneralAI.php b/hwe/sammo/GeneralAI.php index af7d8b1e..c5d14ceb 100644 --- a/hwe/sammo/GeneralAI.php +++ b/hwe/sammo/GeneralAI.php @@ -15,8 +15,11 @@ class GeneralAI * @var General $general */ protected $general; + /** @var array */ protected $city; + /** @var array */ protected $nation; + /** @var int */ protected $genType; /** @var AutorunGeneralPolicy */ @@ -24,6 +27,7 @@ class GeneralAI /** @var AutorunNationPolicy */ protected $nationPolicy; + /** @var array */ protected $env; protected $baseDevelCost; @@ -31,6 +35,10 @@ class GeneralAI protected $strength; protected $intel; + protected $fullLeadership; + protected $fullStrength; + protected $fullIntel; + protected $dipState; protected $warTargetNation; /** @var bool */ @@ -99,6 +107,10 @@ class GeneralAI $this->strength = $general->getStrength(); $this->intel = $general->getIntel(); + $this->fullLeadership = $general->getLeadership(false); + $this->fullStrength = $general->getStrength(false); + $this->fullIntel = $general->getIntel(false); + $this->genType = $this::calcGenType($general); $this->calcDiplomacyState(); @@ -134,7 +146,7 @@ class GeneralAI } //통솔 - if ($leadership >= 40) { + if ($leadership >= $this->nationPolicy->minNPCWarLeadership) { $genType |= self::t통솔장; } return $genType; @@ -882,22 +894,311 @@ class GeneralAI //일반장 행동 protected function do일반내정(GeneralCommand $reservedCommand): ?GeneralCommand { - return null; + $leadership = $this->leadership; + $strength = $this->strength; + $intel = $this->intel; + + $general = $this->general; + $env = $this->env; + $genType = $this->genType; + + $city = $this->city; + $nation = $this->nation; + + $develRate = [ + 'trust' => Util::valueFit($city['trust'], 0.001), + 'pop' => Util::valueFit($city['pop'] / $city['pop_max'], 0.001), + 'agri' => Util::valueFit($city['agri'] / $city['agri_max'], 0.001), + 'comm' => Util::valueFit($city['comm'] / $city['comm_max'], 0.001), + 'secu' => Util::valueFit($city['secu'] / $city['secu_max'], 0.001), + 'def' => Util::valueFit($city['def'] / $city['def_max'], 0.001), + 'wall' => Util::valueFit($city['wall'] / $city['wall_max'], 0.001), + ]; + + $cmdList = []; + + if ($genType & self::t통솔장) { + if ($develRate['trust'] < 0.95) { + $cmd = buildGeneralCommandClass('che_주민선정', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $leadership / $develRate['trust'] * 2]; + } + } + if ($develRate['pop'] < 0.8) { + $cmd = buildGeneralCommandClass('che_정착장려', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $leadership / $develRate['pop']]; + } + } + } + + if($genType & self::t무장){ + if($develRate['def'] < 1){ + $cmd = buildGeneralCommandClass('che_수비강화', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $strength / $develRate['def']]; + } + } + if($develRate['wall'] < 1){ + $cmd = buildGeneralCommandClass('che_성벽보수', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $strength / $develRate['wall']]; + } + } + if($develRate['comm'] < 0.9){ + $cmd = buildGeneralCommandClass('che_치안강화', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $strength / Util::valueFit($develRate['comm'] / 0.8, null, 1)]; + } + } + } + + if($genType & self::t지장){ + if (!TechLimit($env['startyear'], $env['year'], $nation['tech'])) { + $cmd = buildGeneralCommandClass('che_기술연구', $general, $env); + if ($cmd->isRunnable()) { + if (!TechLimit($env['startyear'], $env['year'], $nation['tech'] + 1000)) { + //한등급 이상 뒤쳐져 있다면, 조금 더 열심히 하자. + $cmdList[] = [$cmd, $intel * 2]; + } else { + $cmdList[] = [$cmd, $intel]; + } + } + } + if ($develRate['agri'] < 1) { + $cmd = buildGeneralCommandClass('che_농지개간', $general, $env); + if ($cmd->isRunnable()) { + $cmdList[] = [$cmd, $intel]; + } + } + if ($develRate['comm'] < 1) { + $cmd = buildGeneralCommandClass('che_상업투자', $general, $env); + if ($cmd->isRunnable()) { + $cmdList[] = [$cmd, $intel]; + } + } + } + + return Util::choiceRandomUsingWeightPair($cmdList); } protected function do긴급내정(GeneralCommand $reservedCommand): ?GeneralCommand { - return null; + if($this->dipState === self::d평화){ + return null; + } + + $leadership = $this->leadership; + $strength = $this->strength; + $intel = $this->intel; + + $general = $this->general; + $env = $this->env; + $genType = $this->genType; + + $city = $this->city; + + if($city['trust'] < 0.3 && Util::randBool($leadership / GameConst::$chiefStatMin)){ + $cmd = buildGeneralCommandClass('che_주민선정', $general, $env); + if($cmd->isRunnable()){ + return $cmd; + } + } + + if($city['pop'] < $this->nationPolicy->safeRecruitCityPopulation && Util::randBool($leadership / GameConst::$chiefStatMin / 2)){ + $cmd = buildGeneralCommandClass('che_정착장려', $general, $env); + if($cmd->isRunnable()){ + return $cmd; + } + } } protected function do전쟁내정(GeneralCommand $reservedCommand): ?GeneralCommand { - return null; + + if($this->dipState === self::d평화){ + return null; + } + + $leadership = $this->leadership; + $strength = $this->strength; + $intel = $this->intel; + + $general = $this->general; + $env = $this->env; + $genType = $this->genType; + + $city = $this->city; + $nation = $this->nation; + + $develRate = [ + 'trust' => Util::valueFit($city['trust'], 0.001), + 'pop' => Util::valueFit($city['pop'] / $city['pop_max'], 0.001), + 'agri' => Util::valueFit($city['agri'] / $city['agri_max'], 0.001), + 'comm' => Util::valueFit($city['comm'] / $city['comm_max'], 0.001), + 'secu' => Util::valueFit($city['secu'] / $city['secu_max'], 0.001), + 'def' => Util::valueFit($city['def'] / $city['def_max'], 0.001), + 'wall' => Util::valueFit($city['wall'] / $city['wall_max'], 0.001), + ]; + + $cmdList = []; + + if ($genType & self::t통솔장) { + if ($develRate['trust'] < 0.9) { + $cmd = buildGeneralCommandClass('che_주민선정', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $leadership / $develRate['trust'] / 2]; + } + } + if ($develRate['pop'] < 0.8) { + $cmd = buildGeneralCommandClass('che_정착장려', $general, $env); + if($cmd->isRunnable()){ + if (in_array($city['front'], [1, 3])) { + $cmdList[] = [$cmd, $leadership / $develRate['pop']]; + } + else{ + $cmdList[] = [$cmd, $leadership / $develRate['pop'] / 2]; + } + } + } + } + + if($genType & self::t무장){ + if($develRate['def'] < 0.5){ + $cmd = buildGeneralCommandClass('che_수비강화', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $strength / $develRate['def'] / 2]; + } + } + if($develRate['wall'] < 0.5){ + $cmd = buildGeneralCommandClass('che_성벽보수', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $strength / $develRate['wall'] / 2]; + } + } + if($develRate['comm'] < 0.5){ + $cmd = buildGeneralCommandClass('che_치안강화', $general, $env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, $strength / Util::valueFit($develRate['comm'] / 0.8, null, 1) / 4]; + } + } + } + + if($genType & self::t지장){ + if (!TechLimit($env['startyear'], $env['year'], $nation['tech'])) { + $cmd = buildGeneralCommandClass('che_기술연구', $general, $env); + if ($cmd->isRunnable()) { + if (!TechLimit($env['startyear'], $env['year'], $nation['tech'] + 1000)) { + //한등급 이상 뒤쳐져 있다면, 조금 더 열심히 하자. + $cmdList[] = [$cmd, $intel * 2]; + } else { + $cmdList[] = [$cmd, $intel]; + } + } + } + if ($develRate['agri'] < 0.5) { + $cmd = buildGeneralCommandClass('che_농지개간', $general, $env); + if ($cmd->isRunnable()) { + if (in_array($city['front'], [1, 3])) { + $cmdList[] = [$cmd, $intel / 4]; + } + else{ + $cmdList[] = [$cmd, $intel / 2]; + } + } + } + if ($develRate['comm'] < 0.5) { + $cmd = buildGeneralCommandClass('che_상업투자', $general, $env); + if ($cmd->isRunnable()) { + if (in_array($city['front'], [1, 3])) { + $cmdList[] = [$cmd, $intel / 4]; + } + else{ + $cmdList[] = [$cmd, $intel / 2]; + } + } + } + } + + return Util::choiceRandomUsingWeightPair($cmdList); } protected function do금쌀구매(GeneralCommand $reservedCommand): ?GeneralCommand { + $general = $this->general; + $avgAmount = ($general->getVar('gold') + $general->getVar('rice'))/2; + + if($this->city['trade'] === null && !$this->generalPolicy->can상인무시){ + return null; + } + + if($avgAmount < $this->baseDevelCost){ + return null; + } + + if($this->dipState !== self::d평화 && ($this->genType & self::t통솔장)){ + $crewType = $general->getCrewTypeObj(); + if($this->generalPolicy->can모병){ + $costCmd = buildGeneralCommandClass('che_모병', $general, $this->env, [ + 'crewType'=>$crewType->id, + 'amount'=>$this->fullLeadership*100 + ]); + } + else{ + $costCmd = buildGeneralCommandClass('che_징병', $general, $this->env, [ + 'crewType'=>$crewType->id, + 'amount'=>$this->fullLeadership*100 + ]); + } + + $goldCost = $costCmd->getCost()[0]; + $riceCost = $crewType->riceWithTech( + $this->nation['tech'], + $this->fullLeadership*100 * + $general->getRankVar('killcrew')/max($general->getRankVar('deathcrew'),1) + ); + + if($avgAmount * 2 > $goldCost + $riceCost){ + if ($general->getVar('rice') < $riceCost * 2 && $general->getVar('gold') >= $goldCost * 4) { + //1:1 + return buildGeneralCommandClass('che_군량매매', $general, $this->env, + [ + 'buyRice' => true, + 'amount' => Util::valueFit(Util::toInt($general->getVar('gold') - $avgAmount), 100, GameConst::$maxResourceActionAmount) + ] + ); + } + if ($general->getVar('gold') < $goldCost && $general->getVar('rice') >= $riceCost * 2) { + $avgAmount = ($general->getVar('gold') + $general->getVar('rice'))/2; + return buildGeneralCommandClass('che_군량매매', $general, $this->env, + [ + 'buyRice' => false, + 'amount' => Util::valueFit(Util::toInt($general->getVar('rice') - $avgAmount), 100, GameConst::$maxResourceActionAmount) + ] + ); + } + } + } + + if ($general->getVar('rice') < $this->baseDevelCost && $general->getVar('gold') >= $this->baseDevelCost * 3) { + return buildGeneralCommandClass('che_군량매매', $general, $this->env, + [ + 'buyRice' => true, + 'amount' => Util::valueFit(Util::toInt($general->getVar('gold') - $avgAmount), 100, GameConst::$maxResourceActionAmount) + ] + ); + } + if ($general->getVar('gold') < $this->baseDevelCost && $general->getVar('rice') >= $this->baseDevelCost * 3) { + $avgAmount = ($general->getVar('gold') + $general->getVar('rice'))/2; + return buildGeneralCommandClass('che_군량매매', $general, $this->env, + [ + 'buyRice' => false, + 'amount' => Util::valueFit(Util::toInt($general->getVar('rice') - $avgAmount), 100, GameConst::$maxResourceActionAmount) + ] + ); + } + return null; } @@ -1001,12 +1302,58 @@ class GeneralAI protected function do전투준비(GeneralCommand $reservedCommand): ?GeneralCommand { - return null; + if (!$this->attackable){ + return null; + } + if (in_array($this->dipState, [self::d평화, self::d선포])) { + return null; + } + $cmdList = []; + $general = $this->general; + $cmd사기진작 = buildGeneralCommandClass('che_사기진작', $general, $this->env); + + $train = $general->getVar('train'); + $atmos = $general->getVar('atmos'); + + if($train < $this->nationPolicy->properWarTrainAtmos){ + $cmd = buildGeneralCommandClass('che_훈련', $general, $this->env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, GameConst::$maxTrainByCommand / Util::valueFit($train, 1)]; + } + } + + if($train < $this->nationPolicy->properWarTrainAtmos){ + $cmd = buildGeneralCommandClass('che_사기진작', $general, $this->env); + if($cmd->isRunnable()){ + $cmdList[] = [$cmd, GameConst::$maxAtmosByCommand / Util::valueFit($atmos, 1)]; + } + } + + if(!$cmd){ + return null; + } + return Util::choiceRandomUsingWeightPair($cmdList); } public function do소집해제(GeneralCommand $reservedCommand): ?GeneralCommand { - return null; + if ($this->attackable){ + return null; + } + if ($this->dipState !== self::d평화) { + return null; + } + if ($this->general->getVar('crew') == 0){ + return null; + } + if (Util::randBool(0.75)) { + return null; + } + $cmd = buildGeneralCommandClass('che_소집해제', $this->general, $this->env); + if(!$cmd->isRunnable()){ + return null; + } + return $cmd; } @@ -1044,14 +1391,16 @@ class GeneralAI throw new \RuntimeException('출병 불가' . $cityID . var_export($attackableNations, true) . var_export($nearCities, true)); } - return buildGeneralCommandClass('che_출병', $general, $this->env, ['destCityID' => Util::choiceRandom($attackableCities)]) + return buildGeneralCommandClass('che_출병', $general, $this->env, ['destCityID' => Util::choiceRandom($attackableCities)]); } + /* protected function doNPC증여(GeneralCommand $reservedCommand): ?GeneralCommand { return null; } + */ protected function doNPC헌납(GeneralCommand $reservedCommand): ?GeneralCommand { @@ -1080,12 +1429,6 @@ class GeneralAI return null; } - - protected function do임관(GeneralCommand $reservedCommand): ?GeneralCommand - { - return null; - } - protected function do집합(GeneralCommand $reservedCommand): ?GeneralCommand { return null; @@ -1111,6 +1454,110 @@ class GeneralAI return null; } + protected function do국가선택(GeneralCommand $reservedCommand): ?GeneralCommand + { + $general = $this->getGeneralObj(); + $city = $this->city; + $env = $this->env; + + $db = DB::db(); + + $arg = null; + + // 오랑캐는 바로 임관 + if ($general->getVar('npc') == 9) { + $rulerNation = $db->queryFirstField( + 'SELECT nation FROM general WHERE `level`=12 AND npc=9 and nation not in %li ORDER BY RAND() limit 1', + $general->getAuxVar('joinedNations') ?? [0] + ); + + if ($rulerNation) { + $cmd = buildGeneralCommandClass('che_임관', $general, $env, ['destNationID' => $rulerNation]); + if(!$cmd->isRunnable()){ + return null; + } + return $cmd; + } + } + + switch (Util::choiceRandomUsingWeight([ + '임관' => 11.4, + '거병_견문' => 40, + '이동' => 20, + '기타' => 28.6 + ])) { + //임관 + case '임관': + + $available = true; + + if ($env['startyear'] + 3 > $env['year']) { + //초기 임관 기간에서는 국가가 적을수록 임관 시도가 적음 + $nationCnt = $db->queryFirstField('SELECT count(nation) FROM nation'); + $notFullNationCnt = $db->queryFirstField('SELECT count(nation) FROM nation WHERE gennum < %i', GameConst::$initialNationGenLimit); + if ($nationCnt == 0 || $notFullNationCnt == 0) { + $available = false; + } else if (Util::randBool(pow(1 / $nationCnt / pow($notFullNationCnt, 3), 1 / 4))) { + //국가가 1개일 경우에는 '임관하지 않음' + $available = false; + } + } + + if ($general->getVar('affinity') == 999 || !$available) { + return null; + } + + //랜임 커맨드 입력. + $cmd = buildGeneralCommandClass('che_랜덤임관', $general, $env, ['destNationID' => $rulerNation]); + if(!$cmd->isRunnable()){ + return null; + } + return $cmd; + + break; + case '거병_견문': //거병이나 견문 + // 초반이면서 능력이 좋은놈 위주로 1.4%확률로 거병 + + if($general->getVar('makelimit')){ + return null; + } + + if(!$this->generalPolicy->can건국){ + return null; + } + + $prop = Util::randF() * (GameConst::$defaultStatNPCMax + GameConst::$chiefStatMin) / 2; + $ratio = ($general->getVar('leadership') + $general->getVar('strength') + $general->getVar('intel')) / 3; + + + if($prop >= $ratio){ + return null; + } + + if(Util::randBool(1 - 0.014)){ + return null; + } + + $cmd = buildGeneralCommandClass('che_거병', $general, $env, null); + if(!$cmd->isRunnable()){ + return null; + } + return $cmd; + break; + case '이동': //이동 + + $paths = array_keys(CityConst::byID($city['city'])->path); + + $cmd = buildGeneralCommandClass('che_이동', $general, $env, ['destCityID' => Util::choiceRandom($paths)]); + if(!$cmd->isRunnable()){ + return null; + } + return $cmd; + break; + } + return null; + } + protected function do중립(GeneralCommand $reservedCommand): GeneralCommand { $general = $this->general; @@ -1327,8 +1774,13 @@ class GeneralAI } } - if($nationID != 0){ - + if($nationID === 0 && $this->generalPolicy->can국가선택){ + $result = $this->do국가선택($reservedCommand); + if($result !== null){ + return $result; + } + } + else { if($npcType >= 2 && $general->getVar('level') == 12 && !$this->nation['capital']){ //방랑군 건국 $result = $this->do건국($reservedCommand); @@ -1352,12 +1804,6 @@ class GeneralAI } } } - else if(2 <= $npcType && $npcType <= 4){ - $result = $this->do임관($reservedCommand); - if($result !== null){ - return $result; - } - } return $this->do중립($reservedCommand); } @@ -1593,7 +2039,7 @@ class GeneralAI if ($nationGeneral->npc < 2 && $nationGeneral->killturn >= 5) { $userGenerals[] = $nationGeneral; - } else if ($nationGeneral->getLeadership() >= 40 && $nationGeneral->killturn >= 5) { + } else if ($nationGeneral->getLeadership() >= $this->nationPolicy->minNPCWarLeadership && $nationGeneral->killturn >= 5) { $npcWarGenerals[] = $nationGeneral; } else { //삭턴이 몇 안남은 장수는 '내정장 npc'로 처리 @@ -2380,7 +2826,7 @@ class GeneralAI return null; } - $sumLeadershipInCity = $db->queryFirstField('SELECT sum(leadership) FROM general WHERE nation = %i AND city = %i AND leadership > 40', $nationID, $cityID); + $sumLeadershipInCity = $db->queryFirstField('SELECT sum(leadership) FROM general WHERE nation = %i AND city = %i AND leadership > %i', $nationID, $cityID, $this->nationPolicy->minNPCWarLeadership); if ( $sumLeadershipInCity > 0 && $city['pop'] > 30000 + $general->getLeadership(false) * 100 * 1.3 &&