diff --git a/hwe/func_converter.php b/hwe/func_converter.php index b8f42c95..d45593d2 100644 --- a/hwe/func_converter.php +++ b/hwe/func_converter.php @@ -258,6 +258,43 @@ function getPersonalityClass(?string $type){ throw new \InvalidArgumentException("{$type}은 올바른 성격 클래스가 아님"); } +function getItemClass(int $itemCode){ + //XXX: 임시 구현임 + + //iAction이 필요한 것만 반환 + + static $basePath = __NAMESPACE__.'\\ActionItem\\'; + + $itemPath = [ + 1=>'che_환약_치료', + 5=>'che_이추_계략', + 6=>'che_향낭_계략', + + 7=>'che_오석산_치료', + 8=>'che_무후행군_치료', + 9=>'che_도소연명_치료', + 10=>'che_칠엽청점_치료', + 11=>'che_정력견혈_치료', + 21=>'che_육도_계략', + 22=>'che_삼략_계략', + 23=>'che_청낭서_의술', + 24=>'che_태평청령_의술', + ]; + + $itemClass = $itemPath[$itemCode]??null; + if($itemClass === null){ + return null; + } + + $classPath = ($basePath.$itemClass); + + if(class_exists($classPath)){ + return $classPath; + } + + throw new \InvalidArgumentException("{$itemCode}, {$itemClass}는 올바른 성격 클래스가 아님"); +} + function getGeneralSpecialDomesticClass(?string $type){ if($type === null){ $type = GameConst::$defaultSpecialDomestic; @@ -783,6 +820,14 @@ function getHorseEff($horse) : ?int { return $horse; } +function isConsumable($item) : bool{ + //XXX: 제거할 것. 정식 아이템 구현으로 이동 + if(1 <= $item && $item <= 6){ + return true; + } + return false; +} + function getItemName($item) : ?string { switch($item) { case 0: $itemname = '-'; break; diff --git a/hwe/func_process.php b/hwe/func_process.php index 0944a729..1c61207a 100644 --- a/hwe/func_process.php +++ b/hwe/func_process.php @@ -227,178 +227,6 @@ function process_domestic(array $rawGeneral, int $type){ $cmdObj->run(); } -function process_11(&$general, $type) { - if($type == 1){ - $type = '징병'; - } - else{ - $type = '모병'; - } - - $db = DB::db(); - $gameStor = KVStorage::getStorage($db, 'game_env'); - $date = substr($general['turntime'],11,5); - - if($type === '징병') { - $defaultatmos = GameConst::$defaultAtmosLow; - $defaulttrain = GameConst::$defaultTrainLow; - } - else { - $defaultatmos = GameConst::$defaultAtmosHigh; - $defaulttrain = GameConst::$defaultTrainHigh; - } - - [$startyear, $year, $month] = $gameStor->getValuesAsArray(['startyear', 'year', 'month']); - - $actLog = new ActionLogger($general['no'], $general['nation'], $year, $month); - - $command = DecodeCommand($general['turn0']); - $crewType = $command[2]; - $rawCrew = $command[1]; - - - if($crewType != $general['crewtype']) { - $general['crew'] = 0; - $general['train'] = $defaulttrain; - $general['atmos'] = $defaultatmos; - } - - if($general['crew'] != 0) { - $dtype = "추가".$type; - } - else { - $dtype = $type; - } - - if(!$general['nation']){ - $actLog->pushGeneralActionLog("재야입니다. $dtype 실패. <1>$date"); - return; - } - - $city = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $general['city']); - - if($city['nation'] != $general['nation']){ - $actLog->pushGeneralActionLog("아국이 아닙니다. $dtype 실패. <1>$date"); - return; - } - - $crewTypeObj = GameUnitConst::byID($crewType); - if($crewTypeObj === null){ - $actLog->pushGeneralActionLog("병종 코드 에러. $type 실패. <1>$date"); - return; - } - - [$nationLevel, $tech] = $db->queryFirstList('SELECT `level`,tech FROM nation WHERE nation=%i', $general['nation']); - - $lbonus = setLeadershipBonus($general, $nationLevel); - - //NOTE: 입력 변수는 100명 단위임 - $crew = $rawCrew * 100; - if($crew + $general['crew'] > getGeneralLeadership($general, true, true, true)*100) { - $crew = max(0, getGeneralLeadership($general, true, true, true) * 100 - $general['crew']); - } - - if($crew <= 0) { - $actLog->pushGeneralActionLog("더이상 $dtype 할 수 없습니다. $dtype 실패. <1>$date"); - return; - } - - $cost = $crewTypeObj->costWithTech($tech, $crew); - if($type === '모병') { - $cost *= 2; - } - //성격 보정 - $cost = Util::round(CharCost($cost, $general['personal'])); - - //특기 보정 : 징병, 보병, 궁병, 기병, 귀병, 공성 - if($general['special2'] == 72) { $cost *= 0.5; } - else if($general['special2'] == 50 && $crewTypeObj->armType == GameUnitConstBase::T_FOOTMAN) { $cost *= 0.9; } - else if($general['special2'] == 51 && $crewTypeObj->armType == GameUnitConstBase::T_ARCHER) { $cost *= 0.9; } - else if($general['special2'] == 52 && $crewTypeObj->armType == GameUnitConstBase::T_CAVALRY) { $cost *= 0.9; } - else if($general['special2'] == 40 && $crewTypeObj->armType == GameUnitConstBase::T_WIZARD) { $cost *= 0.9; } - else if($general['special2'] == 53 && $crewTypeObj->armType == GameUnitConstBase::T_SIEGE) { $cost *= 0.9; } - - if($general['gold'] < $cost){ - $actLog->pushGeneralActionLog("자금이 모자랍니다. $dtype 실패. <1>$date"); - return; - } - - if($general['rice'] < $crew / 100) { - $actLog->pushGeneralActionLog("군량이 모자랍니다. $dtype 실패. <1>$date"); - return; - } - - $ownCities = []; - $ownRegions = []; - foreach($db->queryFirstColumn('SELECT city FROM city WHERE nation = %i', $general['nation']) as $ownCity){ - $ownCities[$ownCity] = 1; - $ownRegions[CityConst::byId($ownCity)->region] = 1; - } - $valid = $crewTypeObj->isValid($ownCities, $ownRegions, $year - $startyear, $tech); - - if(!$valid) { - $actLog->pushGeneralActionLog("현재 $dtype 할 수 없는 병종입니다. $dtype 실패. <1>$date"); - return; - } - - if($city['pop']-30000 < $crew) { // 주민 30000명 이상만 가능 - $actLog->pushGeneralActionLog("주민이 모자랍니다. $dtype 실패. <1>$date"); - return; - } - if($city['trust'] < 20) { - $actLog->pushGeneralActionLog("민심이 낮아 주민들이 도망갑니다. $dtype 실패. <1>$date"); - return; - } - - $josaUl = JosaUtil::pick($crewTypeObj->name, '을'); - $actLog->pushGeneralActionLog($crewTypeObj->name."{$josaUl} {$crew}명 {$dtype}했습니다. <1>$date"); - $exp = Util::round($crew / 100); - $ded = Util::round($crew / 100); - // 숙련도 증가 - addGenDex($general['no'], GameConst::$maxAtmosByCommand, GameConst::$maxTrainByCommand, $crewType, $crew/100); - - // 성격 보정 - $exp = CharExperience($exp, $general['personal']); - $ded = CharDedication($ded, $general['personal']); - - $atmos = Util::round(($general['atmos'] * $general['crew'] + $defaultatmos * $crew) / ($general['crew'] + $crew)); - $train = Util::round(($general['train'] * $general['crew'] + $defaulttrain * $crew) / ($general['crew'] + $crew)); - $general['crew'] += $crew; - $general['gold'] -= $cost; - // 주민수 감소 // 민심 감소 - if($type === '징병') { - $city['trust'] -= ($crew / $city['pop'])*100; - } - else { - $city['trust'] -= ($crew / 2 / $city['pop'])*100; - } - if($city['trust'] < 0) { $city['trust'] = 0; } - - $db->update('city', [ - 'pop'=>$db->sqleval('pop-%i', $crew), - 'trust'=>$city['trust'] - ], 'city = %i', $general['city']); - - // 통솔경험, 병종 변경, 병사수 변경, 훈련치 변경, 사기치 변경, 자금 군량 하락, 공헌도, 명성 상승 - $general['leader2']++; - $db->update('general', [ - 'resturn'=>'SUCCESS', - 'leader2'=>$general['leader2'], - 'crewtype'=>$crewTypeObj->id, - 'crew'=>$general['crew'], - 'train'=>$train, - 'atmos'=>$atmos, - 'gold'=>$general['gold'], - 'rice'=>$db->sqleval('rice - %i', Util::round($crew/100)), - 'dedication'=>$db->sqleval('dedication + %i', $ded), - 'experience'=>$db->sqleval('experience + %i', $exp) - ], 'no=%i', $general['no']); - - checkAbilityEx($general['no'], $actLog); - uniqueItemEx($general['no'], $actLog); - $actLog->pushGeneralActionLog($log, ActionLogger::RAWTEXT); -} - function process_48(&$general) { $db = DB::db(); $gameStor = KVStorage::getStorage($db, 'game_env'); @@ -637,32 +465,3 @@ function process_49(&$general) { pushGenLog($general, $log); } - -function process_99(&$general) { - $db = DB::db(); - $gameStor = KVStorage::getStorage($db, 'game_env'); - $connect=$db->get(); - - $log = []; - $alllog = []; - $history = []; - $date = substr($general['turntime'],11,5); - - $admin = $gameStor->getValues(['year', 'month']); - - $log[] = "●{$admin['month']}월:아직 구현되지 않았습니다. <1>$date"; - - $exp = 100; - $ded = 0; - - // 성격 보정 - $exp = CharExperience($exp, $general['personal']); - $ded = CharDedication($ded, $general['personal']); - - // 명성 상승 - $query = "update general set experience=experience+'$exp' where no='{$general['no']}'"; - MYDB_query($query, $connect) or Error(__LINE__.MYDB_error($connect),""); - - pushGenLog($general, $log); -} - diff --git a/hwe/j_set_general_command.php b/hwe/j_set_general_command.php index 865da70d..978b9fba 100644 --- a/hwe/j_set_general_command.php +++ b/hwe/j_set_general_command.php @@ -24,10 +24,10 @@ $defaultCheck = [ ], 'integer'=>[ 'crewType', 'destGeneralID', 'destCityID', 'destNationID', - 'amount', 'colorType', 'nationType' + 'amount', 'colorType', 'nationType', 'itemCode' ], 'boolean'=>[ - 'isGold', 'buyRice' + 'isGold', 'buyRice', ], 'between'=>[ ['month', [1, 12]] diff --git a/hwe/sammo/ActionItem/che_Dummy.php b/hwe/sammo/ActionItem/che_Dummy.php new file mode 100644 index 00000000..99a8e83f --- /dev/null +++ b/hwe/sammo/ActionItem/che_Dummy.php @@ -0,0 +1,26 @@ +id = $itemCode = true; + [$this->name, $this->info] = getItemInfo($itemCode); + $this->cost = getItemCost2($itemCode); + $this->consumable = isConsumable($itemCode); + } +} \ No newline at end of file diff --git a/hwe/sammo/ActionItem/che_계략_삼략.php b/hwe/sammo/ActionItem/che_계략_삼략.php new file mode 100644 index 00000000..2c8111f0 --- /dev/null +++ b/hwe/sammo/ActionItem/che_계략_삼략.php @@ -0,0 +1,28 @@ +[전투] 페이즈마다 20% 확률로 치료 발동(아군 피해 1/3 감소)'; + public $cost = 200; + public $consumable = false; + + public function getPreTurnExecuteTriggerList(General $general):?GeneralTriggerCaller{ + return new GeneralTriggerCaller([ + new GeneralTrigger\che_도시치료($general) + ]); + } +} \ No newline at end of file diff --git a/hwe/sammo/ActionItem/che_의술_태평청령.php b/hwe/sammo/ActionItem/che_의술_태평청령.php new file mode 100644 index 00000000..4b9458af --- /dev/null +++ b/hwe/sammo/ActionItem/che_의술_태평청령.php @@ -0,0 +1,19 @@ +[전투] 페이즈마다 20% 확률로 치료 발동(아군 피해 1/3 감소)'; + public $cost = 200; + public $consumable = false; + + public function getPreTurnExecuteTriggerList(General $general):?GeneralTriggerCaller{ + return new GeneralTriggerCaller([ + new GeneralTrigger\che_도시치료($general) + ]); + } +} \ No newline at end of file diff --git a/hwe/sammo/ActionItem/che_치료_도소연명.php b/hwe/sammo/ActionItem/che_치료_도소연명.php new file mode 100644 index 00000000..b22bc49d --- /dev/null +++ b/hwe/sammo/ActionItem/che_치료_도소연명.php @@ -0,0 +1,27 @@ +'명마', + 'weap'=>'무기', + 'book'=>'서적', + 'item'=>'도구', + ]; + + protected function init(){ + + $general = $this->generalObj; + + $this->setCity(); + $this->setNation(); + + $itemType = $this->arg['itemType']; + $itemTypeName = static::$itemMap[$itemType]; + $itemCode = $this->arg['itemCode']; + + [$reqGold, $reqRice] = $this->getCost(); + + $this->runnableConstraints=[ + ['ReqCityTrader', $general->getVar('npc')], + ['ReqCityCapacity', 'secu', '치안 수치', $itemCode * 1000], + ['ReqGeneralGold', $reqGold], + ['ReqGeneralRice', $reqRice], + ]; + + if($itemCode == 0){ + $this->runnableConstraints[] = ['ReqGeneralValue', $itemType, $itemTypeName, 1]; + } + else if($itemCode == $general->getVar($itemType)){ + $this->runnableConstraints = [['AlwaysFail', '이미 가지고 있습니다.']]; + } + else if($itemType != 'item' && $general->getVar($itemType) > 6){ + $this->runnableConstraints[] = [['AlwaysFail', '이미 진귀한 것을 가지고 있습니다.']]; + } + + } + + protected function argTest():bool{ + $itemType = $this->arg['itemType']??null; + if(!in_array($itemType, array_keys(static::$itemMap))){ + return false; + } + $itemCode = $this->arg['itemCode']??null; + if(!is_int($itemCode)){ + return false; + } + if($itemCode < 0 || 6 < $itemCode){ + return false; + } + $this->arg = [ + 'itemType'=>$itemType, + 'itemCode'=>$itemCode + ]; + return true; + } + + public function getCost():array{ + $itemType = $this->arg['itemType']; + $itemCode = $this->arg['itemCode']; + if($itemType == 'item'){ + $reqGold = getItemCost2($itemCode); + } + else{ + $reqGold = getItemCost($itemCode); + } + return [$reqGold, 0]; + } + + public function getPreReqTurn():int{ + return 0; + } + + public function getPostReqTurn():int{ + return 0; + } + + public function run():bool{ + if(!$this->isRunnable()){ + throw new \RuntimeException('불가능한 커맨드를 강제로 실행 시도'); + } + + $db = DB::db(); + + $general = $this->generalObj; + $date = substr($general->getVar('turntime'),11,5); + + $itemType = $this->arg['itemType']; + $itemCode = $this->arg['itemCode']; + + if($itemCode == 0){ + $buying = false; + $itemCode = $general->getVar($itemType); + if($itemType == 'item'){ + $cost = getItemCost2($itemCode); + } + else{ + $cost = getItemCost($itemCode); + } + } + else{ + $buying = true; + $cost = $this->getCost()[0]; + } + + if($itemType == 'horse'){ + $itemName = getHorseName($itemCode); + } + else if($itemType == 'weap'){ + $itemName = getWeapName($itemCode); + } + else if($itemType == 'book'){ + $itemName = getBookName($itemCode); + } + else{ + $itemName = getItemName($itemCode); + } + $josaUl = JosaUtil::pick($itemName, '을'); + + $logger = $general->getLogger(); + + if($buying){ + $logger->pushGeneralActionLog("{$itemName}{$josaUl} 구입했습니다. <1>$date"); + $general->increaseVarWithLimit('gold', -$cost, 0); + $general->setVar($itemType, $itemCode); + } + else{ + $logger->pushGeneralActionLog("{$itemName}{$josaUl} 판매했습니다. <1>$date"); + $general->increaseVarWithLimit('gold', $cost / 2); + $general->setVar($itemType, 0); + } + + $exp = 10; + $exp = $general->onPreGeneralStatUpdate($general, 'experience', $exp); + + $general->increaseVar('experience', $exp); + $general->setResultTurn(new LastTurn(static::getName(), $this->arg)); + $general->checkStatChange(); + $general->applyDB($db); + + return true; + } + + +} \ No newline at end of file diff --git a/hwe/sammo/Constraint/AdhocCallback.php b/hwe/sammo/Constraint/AdhocCallback.php new file mode 100644 index 00000000..1bc2f0d1 --- /dev/null +++ b/hwe/sammo/Constraint/AdhocCallback.php @@ -0,0 +1,35 @@ +arg)){ + if(!$throwExeception){return false; } + throw new \InvalidArgumentException("require callback"); + } + + return true; + } + + public function test():bool{ + $this->checkInputValues(); + $this->tested = true; + + $reason = ($this->arg)(); + if($reason === null){ + return true; + } + + $this->reason = $reason; + return false; + } +} \ No newline at end of file diff --git a/hwe/sammo/Constraint/AlwaysFail.php b/hwe/sammo/Constraint/AlwaysFail.php new file mode 100644 index 00000000..ed2ad7d2 --- /dev/null +++ b/hwe/sammo/Constraint/AlwaysFail.php @@ -0,0 +1,25 @@ +checkInputValues(); + $this->tested = true; + + $this->reason = $this->arg; + return false; + } +} \ No newline at end of file diff --git a/hwe/sammo/Constraint/OccupiedCity.php b/hwe/sammo/Constraint/OccupiedCity.php index e48eb156..2891ffef 100644 --- a/hwe/sammo/Constraint/OccupiedCity.php +++ b/hwe/sammo/Constraint/OccupiedCity.php @@ -27,6 +27,11 @@ class OccupiedCity extends Constraint{ $this->checkInputValues(); $this->tested = true; + //특수 예외로 ARG가 지정된 경우에는 재야여도 'OccupiedCity'로 허용함 + if($this->arg && $this->general['nation'] == 0){ + return true; + } + if($this->city['nation'] == $this->general['nation']){ return true; } diff --git a/hwe/sammo/Constraint/ReqCityCapacity.php b/hwe/sammo/Constraint/ReqCityCapacity.php index a5bf5868..82fa23b0 100644 --- a/hwe/sammo/Constraint/ReqCityCapacity.php +++ b/hwe/sammo/Constraint/ReqCityCapacity.php @@ -64,7 +64,7 @@ class ReqCityCapacity extends Constraint{ } $josaYi = JosaUtil::pick($keyNick, '이'); - $this->reason = "{$keyNick}{$josaUn} 모자랍니다."; + $this->reason = "{$keyNick}{$josaUn} 부족합니다."; return false; } } \ No newline at end of file diff --git a/hwe/sammo/Constraint/ReqCityTrader.php b/hwe/sammo/Constraint/ReqCityTrader.php index 83ab0793..eb1387af 100644 --- a/hwe/sammo/Constraint/ReqCityTrader.php +++ b/hwe/sammo/Constraint/ReqCityTrader.php @@ -3,7 +3,7 @@ namespace sammo\Constraint; class ReqCityTrader extends Constraint{ - const REQ_VALUES = Constraint::REQ_CITY; + const REQ_VALUES = Constraint::REQ_CITY|Constraint::REQ_INT_ARG; public function checkInputValues(bool $throwExeception=true){ if(!parent::checkInputValues($throwExeception) && !$throwException){ @@ -22,7 +22,7 @@ class ReqCityTrader extends Constraint{ $this->checkInputValues(); $this->tested = true; - if($this->city['trade'] !== null){ + if($this->city['trade'] !== null || $this->arg >= 2){ return true; } diff --git a/hwe/sammo/Constraint/ReqGeneralValue.php b/hwe/sammo/Constraint/ReqGeneralValue.php new file mode 100644 index 00000000..5fe91d0c --- /dev/null +++ b/hwe/sammo/Constraint/ReqGeneralValue.php @@ -0,0 +1,77 @@ +key, $this->keyNick, $this->reqVal] = $this->arg; + + $this->maxKey = $this->key.'2'; + + if(!key_exists($this->key, $this->city)){ + if(!$throwExeception){return false; } + throw new \InvalidArgumentException("require {$this->key} in city"); + } + + if(is_numeric($this->reqVal)){ + $this->isPercent = false; + } + else if(is_str($this->reqVal)){ + $this->reqVal = Util::convPercentStrToFloat($this->reqVal); + if($this->reqVal === null){ + if(!$throwExeception){return false; } + throw new \InvalidArgumentException("require valid reqVal(percentStr|numeric) format"); + } + + if(!key_exists($this->maxKey, $this->city)){ + if(!$throwExeception){return false; } + throw new \InvalidArgumentException("require {$this->maxKey} in general"); + } + $this->isPercent = true; + } + + return true; + } + + public function test():bool{ + $this->checkInputValues(); + $this->tested = true; + + if($this->isPercent){ + if($this->general[$this->key] >= $this->general[$this->maxKey] * $this->reqVal){ + return true; + } + + } + else{ + if($this->general[$this->key] >= $this->reqVal){ + return true; + } + } + + if($this->reqVal === 1){ + $josaYi = JosaUtil::pick($keyNick, '이'); + $this->reason = "{$keyNick}{$josaUn} 없습니다."; + } + else{ + $josaYi = JosaUtil::pick($keyNick, '이'); + $this->reason = "{$keyNick}{$josaUn} 부족합니다."; + } + + return false; + } +} \ No newline at end of file diff --git a/hwe/sammo/DefaultActionTrigger.php b/hwe/sammo/DefaultActionTrigger.php index bb7dc22d..3d81786e 100644 --- a/hwe/sammo/DefaultActionTrigger.php +++ b/hwe/sammo/DefaultActionTrigger.php @@ -13,6 +13,10 @@ trait DefaultAction{ return $value; } + public function onCalcStat(General $general, string $stat, $value){ + return $value; + } + public function onCalcStrategic(string $turnType, string $varType, $value){ return $value; } diff --git a/hwe/sammo/General.php b/hwe/sammo/General.php index 1ee27d57..75fcebdc 100644 --- a/hwe/sammo/General.php +++ b/hwe/sammo/General.php @@ -79,10 +79,26 @@ class General implements iAction{ $personalityClass = getPersonalityClass($raw['personal']); $this->personalityObj = new $personalityClass; - + + $itemClass = getItemClass($raw['item']); + if($itemClass !== null){ + $this->itemObjs['item'] = new $itemClass; + } + else{ + $this->itemObjs['item'] = new ActionItem\che_Dummy($raw['item']); + } //TODO: $specialItemClass 설정 } + function deleteItem(){ + $this->setVar('item', 0); + $this->itemObjs['item'] = new ActionItem\che_Dummy(0); + } + + function getItem():BaseItem{ + return $this->itemObjs['item']; + } + function getLastTurn():LastTurn{ return $this->lastTurn; } diff --git a/hwe/sammo/GeneralTrigger/che_아이템치료.php b/hwe/sammo/GeneralTrigger/che_아이템치료.php new file mode 100644 index 00000000..69fd393c --- /dev/null +++ b/hwe/sammo/GeneralTrigger/che_아이템치료.php @@ -0,0 +1,33 @@ +object; + + if($general->getVar('injury') > 0){ + $general->updateVar('injury', 0); + $general->activateSkill('pre.부상경감', 'pre.치료'); + $itemObj = $general->getItem(); + $itemName = $itemObj->$name; + $josaUl = JosaUtil::pick($itemName, '을'); + $logger->pushGeneralActionLog("{$itemName}{$josaUl} 사용하여 치료합니다!", ActionLogger::PLAIN); + + if($itemObj::$consumable && $itemObj->isValidTurnItem('GeneralTrigger', 'che_아이템치료')){ + $general->deleteItem(); + } + } + + return $env; + } +} \ No newline at end of file diff --git a/hwe/sammo/iAction.php b/hwe/sammo/iAction.php index 0fd7d323..d9018d0c 100644 --- a/hwe/sammo/iAction.php +++ b/hwe/sammo/iAction.php @@ -2,12 +2,12 @@ namespace sammo; interface iAction{ - //TODO: 능력치는? public function getPreTurnExecuteTriggerList(General $general):?GeneralTriggerCaller; public function onCalcDomestic(string $turnType, string $varType, float $value, $aux=null):float; public function onPreGeneralStatUpdate(General $general, string $statName, $value); + public function onCalcStat(General $general, string $stat, $value); public function onCalcStrategic(string $turnType, string $varType, $value); public function onCalcNationalIncome(string $type, int $amount):int;