getName(); } /** @var \sammo\General */ protected $generalObj = null; protected $city = null; protected $nation = null; protected $arg = null; protected $env = null; /** @var \sammo\General */ protected $destGeneralObj = null; protected ?array $destCity = null; protected ?array $destNation = null; protected $cachedPermissionToReserve = false; protected $cachedMinConditionMet = false; protected $cachedFullConditionMet = false; protected $isArgValid=false; protected $reasonNotFullConditionMet = null; protected $reasonNotMinConditionMet = null; protected $reasonNoPermissionToReserve = null; protected $fullConditionConstraints = null; protected $minConditionConstraints = null; protected $permissionConstraints = null; protected $reasonConstraint = null; protected $logger; protected $alternative = null; static protected $isInitStatic = true; protected static function initStatic(){ } public function __construct(General $generalObj, array $env, $arg = null){ if(!static::$isInitStatic){ static::initStatic(); static::$isInitStatic = true; } $this->generalObj = $generalObj; $this->logger = $generalObj->getLogger(); $this->env = $env; $this->arg = $arg; $this->init(); if ($this->argTest()) { $this->isArgValid = true; if(static::$reqArg){ $this->initWithArg(); } } else{ $this->isArgValid = false; } } protected function resetTestCache():void{ $this->cachedFullConditionMet = false; $this->cachedMinConditionMet = false; $this->cachedPermissionToReserve = false; $this->reasonNotFullConditionMet = null; $this->reasonNotMinConditionMet = null; $this->reasonNoPermissionToReserve = null; } protected function setCity(){ $this->resetTestCache(); $db = DB::db(); $this->city = $this->generalObj->getRawCity(); if($this->city){ return; } $this->city = $db->queryFirstRow('SELECT * FROM city WHERE city=%i', $this->generalObj->getVar('city')); $this->generalObj->setRawCity($this->city); return; } protected function setNation(?array $args = null){ $this->resetTestCache(); if($args === null){ if(!$this->nation){ $this->nation = $this->generalObj->getStaticNation(); } return; } $nationID = $this->generalObj->getNationID(); if($nationID == 0){ $this->nation = $this->generalObj->getStaticNation(); $this->nation['aux'] = []; return; } $defaultArgs = ['nation', 'name', 'color', 'type', 'level', 'capital', 'gennum']; $args = array_unique(array_merge($defaultArgs, $args)); if($args == $defaultArgs){ $this->nation = $this->generalObj->getStaticNation(); return; } $defaultValues = [ 'nation'=>0, 'name'=>'재야', 'color'=>'#000000', 'type'=>GameConst::$neutralNationType, 'level'=>0, 'capital'=>0, 'gold'=>0, 'rice'=>2000, 'tech'=>0, 'gennum'=>1, 'aux'=>'{}', ]; if($this->nation && $this->nation['nation'] === $nationID){ $allArgExists = true; foreach($args as $arg){ if(!key_exists($arg, $this->nation)){ $allArgExists = false; break; } } if($allArgExists){ return; } } $db = DB::db(); $nation = $db->queryFirstRow('SELECT %l FROM nation WHERE nation=%i', Util::formatListOfBackticks($args), $nationID); if($nation === null){ $nation = []; foreach($args as $arg){ $nation[$arg] = $defaultValues[$arg]; } } if($this->nation){ //NOTE: 이 순서 맞다! https://www.php.net/manual/en/language.operators.array.php $this->nation = $nation + $this->nation; } else{ $this->nation = $nation; } if(is_string($this->nation['aux'] ?? null)){ $this->nation['aux'] = Json::decode($this->nation['aux']); } } protected function setDestGeneral(General $destGeneralObj){ $this->resetTestCache(); $this->destGeneralObj = $destGeneralObj; } protected function setDestCity(int $cityNo, bool $onlyName=false){ $this->resetTestCache(); $db = DB::db(); if($onlyName){ $cityObj = \sammo\CityConst::byID($cityNo); $this->destCity = [ 'city'=>$cityNo, 'name'=>$cityObj->name, 'region'=>$cityObj->region, ]; return; } $this->destCity = $db->queryFirstRow('SELECT * FROM city WHERE city=%i', $cityNo); } protected function setDestNation(int $nationID, ?array $args = null){ $this->resetTestCache(); if($args === null || $args === []){ $this->destNation = getNationStaticInfo($nationID); return; } $defaultArgs = ['nation', 'name', 'color', 'type', 'level', 'capital']; $args = array_unique(array_merge($defaultArgs, $args)); $defaultValues = [ 'nation'=>0, 'name'=>'재야', 'color'=>'#000000', 'type'=>GameConst::$neutralNationType, 'level'=>0, 'capital'=>0, 'gold'=>0, 'rice'=>2000, 'tech'=>0, 'gennum'=>1 ]; $db = DB::db(); $destNation = $db->queryFirstRow('SELECT %l FROM nation WHERE nation=%i', Util::formatListOfBackticks($args), $nationID); if($destNation === null){ $destNation = $defaultValues; } $this->destNation = $destNation; } abstract protected function init(); protected function initWithArg(){ if(static::$reqArg){ throw new NotInheritedMethodException(); } } abstract protected function argTest():bool; public function getArg():?array{ return $this->arg; } public function getGeneral():General{ return $this->generalObj; } public function getNationID(){ return $this->generalObj->getNationID(); } public function getOfficerLevel(){ return $this->generalObj->getVar('officer_level'); } public function getBrief():string{ return static::getName(); } public function getRawClassName(bool $shortName=true):string{ if($shortName){ return Util::getClassNameFromObj($this); } return static::class; } public function getCompensationStyle():?int{ //1 : Positive //0 : Neutral //-1 : Negative //null : can't calculate return 0; } static public function getName():string { return static::$actionName; } public function getLogger():?ActionLogger{ return $this->logger; } abstract public function getNextExecuteKey():string; abstract public function getNextAvailableTurn():?int; abstract public function setNextAvailable(?int $yearMonth=null); protected function testPostReqTurn():?array{ if(!$this->getPostReqTurn()){ return null; } $nextAvailableTurn = $this->getNextAvailableTurn(); if($nextAvailableTurn === null){ return null; } $yearMonth = Util::joinYearMonth($this->env['year'], $this->env['month']); $remainTurn = $nextAvailableTurn - $yearMonth; if($remainTurn <= 0){ return null; } return ['testPostReqTurn', "{$remainTurn}턴 더 기다려야 합니다"]; } public function testPermissionToReserve():?string{ if($this->cachedPermissionToReserve){ return $this->reasonNoPermissionToReserve; } if(!$this->permissionConstraints){ return null; } if($this->reasonNoPermissionToReserve){ return $this->reasonNoPermissionToReserve; } $this->generalObj->unpackAux(); $constraintInput = [ 'general'=>$this->generalObj, 'city'=>$this->city, 'nation'=>$this->nation, 'cmd_arg'=>$this->arg, 'destGeneral'=>$this->destGeneralObj??null, 'destCity'=>$this->destCity, 'destNation'=>$this->destNation, ]; [$this->reasonConstraint, $this->reasonNoPermissionToReserve] = Constraint::testAll($this->permissionConstraints??[], $constraintInput, $this->env); $this->cachedPermissionToReserve = true; return $this->reasonNoPermissionToReserve; } public function canDisplay():bool{ return true; } public function testMinConditionMet():?string{ if(!static::$reqArg && !$this->minConditionConstraints){ return $this->testFullConditionMet(); } if($this->minConditionConstraints === null){ throw new \InvalidArgumentException('minConditionConstraints가 제대로 설정되지 않았습니다'); } if($this->cachedMinConditionMet){ return $this->reasonNotMinConditionMet; } $this->generalObj->unpackAux(); $constraintInput = [ 'general'=>$this->generalObj, 'city'=>$this->city, 'nation'=>$this->nation, 'cmd_arg'=>$this->arg, 'destGeneral'=>$this->destGeneralObj??null, 'destCity'=>$this->destCity, 'destNation'=>$this->destNation, ]; [$this->reasonConstraint, $this->reasonNotMinConditionMet] = Constraint::testAll($this->minConditionConstraints??[], $constraintInput, $this->env); if($this->reasonNotMinConditionMet === null && !self::$isLazyCalcReqTurn){ [$this->reasonConstraint, $this->reasonNotMinConditionMet] = $this->testPostReqTurn(); } $this->cachedMinConditionMet = true; return $this->reasonNotMinConditionMet; } public function testFullConditionMet():?string{ if(!$this->isArgValid()){ $this->reasonNotFullConditionMet = '인자가 올바르지 않습니다.'; $this->cachedFullConditionMet = true; return $this->reasonNotFullConditionMet; } if($this->fullConditionConstraints === null){ throw new \InvalidArgumentException('fullConditionConstraints가 제대로 설정되지 않았습니다'); } if($this->cachedFullConditionMet){ return $this->reasonNotFullConditionMet; } $this->generalObj->unpackAux(); $constraintInput = [ 'general'=>$this->generalObj, 'city'=>$this->city, 'nation'=>$this->nation, 'cmd_arg'=>$this->arg, 'destGeneral'=>$this->destGeneralObj??null, 'destCity'=>$this->destCity, 'destNation'=>$this->destNation, ]; [$this->reasonConstraint, $this->reasonNotFullConditionMet] = Constraint::testAll($this->fullConditionConstraints??[], $constraintInput, $this->env); if($this->reasonNotFullConditionMet === null){ [$this->reasonConstraint, $this->reasonNotFullConditionMet] = $this->testPostReqTurn(); } $this->cachedFullConditionMet = true; return $this->reasonNotFullConditionMet; } public function getTermString():string{ $commandName = $this->getName(); $term = $this->getResultTurn()->getTerm(); $termMax = $this->getPreReqTurn() + 1; return "{$commandName} 수행중... ({$term}/{$termMax})"; } public function addTermStack():bool{ if($this->getPreReqTurn() == 0){ return true; } $lastTurn = $this->getLastTurn(); $commandName = $this->getName(); if($lastTurn->getCommand() != $commandName || $lastTurn->getArg() !== $this->arg){ $this->setResultTurn(new LastTurn( $commandName, $this->arg, 1 )); return false; } if($lastTurn->getTerm() < $this->getPreReqTurn()){ $this->setResultTurn(new LastTurn( $commandName, $this->arg, $lastTurn->getTerm() + 1 )); return false; } return true; } public function hasPermissionToReserve():bool{ return $this->testPermissionToReserve() === null; } public function isArgValid():bool{ return $this->isArgValid; } public function hasMinConditionMet():bool { return $this->testMinConditionMet() === null; } public function hasFullConditionMet():bool { return $this->testFullConditionMet() === null; } public function getFailString():string{ $commandName = $this->getName(); $failReason = $this->testFullConditionMet(); if($failReason === null){ throw new \RuntimeException('실행 가능한 커맨드에 대해 실패 이유를 수집'); } return "{$failReason} {$commandName} 실패."; } public function getAlternativeCommand():?self{ return $this->alternative; } abstract public function getCost():array; abstract public function getPreReqTurn():int; abstract public function getPostReqTurn():int; abstract public function run(RandUtil $rng):bool; public function exportJSVars():array { return []; } public function getLastTurn():LastTurn{ return $this->generalObj->getLastTurn(); } public function setResultTurn(LastTurn $lastTurn){ $this->generalObj->_setResultTurn($lastTurn); } public function getResultTurn():LastTurn{ return $this->generalObj->getResultTurn(); } }