fix: cap S100 NPC dex growth at forty percent
This commit was merged in pull request #265.
This commit is contained in:
@@ -37,6 +37,7 @@ final class CentennialAllStarGrowthService
|
||||
'naturalSpecialDomestic' => null,
|
||||
'eventSpecialDomestic' => null,
|
||||
'userInitialStats' => $userInitialStats,
|
||||
'dexTargetRatio' => 1.0,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -220,6 +221,24 @@ final class CentennialAllStarGrowthService
|
||||
);
|
||||
}
|
||||
|
||||
public static function calculateDexTargetFloor(
|
||||
int $target,
|
||||
array $env,
|
||||
float $targetRatio = 1.0
|
||||
): int {
|
||||
if ($targetRatio < 0 || $targetRatio > 1) {
|
||||
throw new \InvalidArgumentException('dex target ratio must be between 0 and 1');
|
||||
}
|
||||
$target = min(GameConst::$dexLimit, max(0, $target));
|
||||
$scaledTarget = (int) floor($target * $targetRatio);
|
||||
$progress = self::calculateProgress(
|
||||
(int) $env['startyear'],
|
||||
(int) $env['year'],
|
||||
(int) $env['month']
|
||||
);
|
||||
return CentennialAllStarGrowth::dexFloor($scaledTarget, $progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutates the General object but leaves persistence to the caller.
|
||||
*
|
||||
@@ -229,7 +248,8 @@ final class CentennialAllStarGrowthService
|
||||
General $general,
|
||||
array $targetInfo,
|
||||
array $env,
|
||||
float $progressMultiplier = 1.0
|
||||
float $progressMultiplier = 1.0,
|
||||
float $dexTargetRatio = 1.0
|
||||
): array
|
||||
{
|
||||
$startYear = (int) $env['startyear'];
|
||||
@@ -259,10 +279,14 @@ final class CentennialAllStarGrowthService
|
||||
$nextUserInitialStats = $targetChanged && $isUserTarget
|
||||
? self::calculateUserInitialStats($targetInfo)
|
||||
: ($aux['userInitialStats'] ?? null);
|
||||
$previousDexTargetRatio = is_numeric($aux['dexTargetRatio'] ?? null)
|
||||
? (float) $aux['dexTargetRatio']
|
||||
: 1.0;
|
||||
$dexTargetRatioChanged = $previousDexTargetRatio !== $dexTargetRatio;
|
||||
$userCurrentTargetStats = $isUserTarget
|
||||
? self::calculateUserCurrentTargetStats($targetInfo, $env)
|
||||
: null;
|
||||
$changed = false;
|
||||
$changed = $dexTargetRatioChanged;
|
||||
|
||||
foreach (self::STAT_KEYS as $key) {
|
||||
if (!array_key_exists($key, $targetInfo)) {
|
||||
@@ -303,10 +327,13 @@ final class CentennialAllStarGrowthService
|
||||
if (!array_key_exists($idx, $targetDex)) {
|
||||
continue;
|
||||
}
|
||||
$target = min(GameConst::$dexLimit, max(0, (int) $targetDex[$idx]));
|
||||
$floor = CentennialAllStarGrowth::dexFloor($target, $progress);
|
||||
$floor = self::calculateDexTargetFloor(
|
||||
(int) $targetDex[$idx],
|
||||
$env,
|
||||
$dexTargetRatio
|
||||
);
|
||||
$current = (int) $general->getVar($key);
|
||||
if ($targetChanged) {
|
||||
if ($targetChanged || $dexTargetRatioChanged) {
|
||||
$result = CentennialAllStarGrowth::replaceTarget(
|
||||
$current,
|
||||
(int) ($granted[$key] ?? 0),
|
||||
@@ -357,6 +384,7 @@ final class CentennialAllStarGrowthService
|
||||
$aux['progressMonth'] = max((int) ($aux['progressMonth'] ?? -1), $progressMonth);
|
||||
$aux['milestone'] = max($previousMilestone, $milestone);
|
||||
$aux['userInitialStats'] = $nextUserInitialStats;
|
||||
$aux['dexTargetRatio'] = $dexTargetRatio;
|
||||
$general->setAuxVar(self::AUX_KEY, $aux);
|
||||
|
||||
return [
|
||||
@@ -380,6 +408,13 @@ final class CentennialAllStarGrowthService
|
||||
: 1.0;
|
||||
}
|
||||
|
||||
public static function dexTargetRatioForNPCType(int $npcType): float
|
||||
{
|
||||
return in_array($npcType, [3, 4], true)
|
||||
? GameConst::$centennialNpcDexTargetRatio
|
||||
: 1.0;
|
||||
}
|
||||
|
||||
public static function applyCurrentTargetToBuiltNPC(
|
||||
\MeekroDB $db,
|
||||
GeneralBuilder $builder,
|
||||
@@ -398,7 +433,8 @@ final class CentennialAllStarGrowthService
|
||||
$general,
|
||||
$targetInfo,
|
||||
$env,
|
||||
self::NPC_PROGRESS_MULTIPLIER
|
||||
self::NPC_PROGRESS_MULTIPLIER,
|
||||
GameConst::$centennialNpcDexTargetRatio
|
||||
);
|
||||
$general->applyDB($db);
|
||||
return $result;
|
||||
|
||||
@@ -29,7 +29,10 @@ class AdvanceCentennialAllStar extends \sammo\Event\Action
|
||||
$general,
|
||||
$targetInfo,
|
||||
$env,
|
||||
CentennialAllStarGrowthService::progressMultiplierFor($general)
|
||||
CentennialAllStarGrowthService::progressMultiplierFor($general),
|
||||
CentennialAllStarGrowthService::dexTargetRatioForNPCType(
|
||||
$general->getNPCType()
|
||||
)
|
||||
);
|
||||
|
||||
if ($result['milestone'] > $result['previousMilestone']) {
|
||||
|
||||
@@ -420,6 +420,8 @@ class GameConstBase
|
||||
public static $retirementYear = 80;
|
||||
|
||||
public static $targetGeneralPool = 'RandomNameGeneral';
|
||||
/** @var float 100기 올스타 NPC의 원본 목표 대비 최종 숙련 비율 */
|
||||
public static $centennialNpcDexTargetRatio = 0.4;
|
||||
public static $generalPoolAllowOption = ['stat', 'ego', 'picture'];
|
||||
|
||||
public static $randGenFirstName = [
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"map": {
|
||||
"mapName": "miniche",
|
||||
"targetGeneralPool": "SPoolUnderU100",
|
||||
"generalPoolAllowOption": ["stat", "ego", "picture"]
|
||||
"generalPoolAllowOption": ["stat", "ego", "picture"],
|
||||
"centennialNpcDexTargetRatio": 0.4
|
||||
},
|
||||
"history": [
|
||||
"<C>●</>180년 1월:<L><b>【100기 이벤트】</b></> 역대 장수들이 평범한 능력으로 다시 모여, 지난 전성기의 힘과 서서히 동조하기 시작했다!"
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use sammo\CentennialAllStarGrowth;
|
||||
use sammo\CentennialAllStarGrowthService;
|
||||
use sammo\General;
|
||||
use sammo\GameConst;
|
||||
|
||||
$loader = require __DIR__ . '/../vendor/autoload.php';
|
||||
$loader->addPsr4('sammo\\', __DIR__ . '/../hwe/sammo', true);
|
||||
|
||||
require_once __DIR__ . '/../hwe/sammo/ActionLogger.php';
|
||||
require_once __DIR__ . '/../hwe/sammo/GameConstBase.php';
|
||||
@@ -46,6 +51,22 @@ final class CentennialAllStarGrowthTest extends TestCase
|
||||
CentennialAllStarGrowthService::calculateProgress(180, 210, 1, 0.9),
|
||||
0.000001
|
||||
);
|
||||
self::assertSame(
|
||||
91,
|
||||
CentennialAllStarGrowth::statFloor(100, 15, 0.9)
|
||||
);
|
||||
self::assertSame(
|
||||
0.4,
|
||||
CentennialAllStarGrowthService::dexTargetRatioForNPCType(3)
|
||||
);
|
||||
self::assertSame(
|
||||
0.4,
|
||||
CentennialAllStarGrowthService::dexTargetRatioForNPCType(4)
|
||||
);
|
||||
self::assertSame(
|
||||
1.0,
|
||||
CentennialAllStarGrowthService::dexTargetRatioForNPCType(2)
|
||||
);
|
||||
}
|
||||
|
||||
public function testProgressMultiplierMustStayWithinUnitInterval(): void
|
||||
@@ -148,6 +169,7 @@ final class CentennialAllStarGrowthTest extends TestCase
|
||||
|
||||
self::assertSame('A1000001', $aux['targetId']);
|
||||
self::assertSame($initial, $aux['userInitialStats']);
|
||||
self::assertSame(1.0, $aux['dexTargetRatio']);
|
||||
self::assertSame(50, $aux['granted']['leadership']);
|
||||
self::assertSame(43, $aux['granted']['strength']);
|
||||
self::assertSame(27, $aux['granted']['intel']);
|
||||
@@ -203,6 +225,161 @@ final class CentennialAllStarGrowthTest extends TestCase
|
||||
self::assertSame(900000, CentennialAllStarGrowth::dexFloor(900000, 1));
|
||||
}
|
||||
|
||||
public function testNpcDexStopsAtFortyPercentOfHistoricalTarget(): void
|
||||
{
|
||||
$target = 900000;
|
||||
$ratio = GameConst::$centennialNpcDexTargetRatio;
|
||||
|
||||
self::assertSame(0.4, $ratio);
|
||||
self::assertSame(
|
||||
57600,
|
||||
CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||
$target,
|
||||
['startyear' => 180, 'year' => 186, 'month' => 1],
|
||||
$ratio
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
360000,
|
||||
CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||
$target,
|
||||
['startyear' => 180, 'year' => 195, 'month' => 1],
|
||||
$ratio
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
360000,
|
||||
CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||
$target,
|
||||
['startyear' => 180, 'year' => 210, 'month' => 1],
|
||||
$ratio
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testScenarioConfigKeepsNpcDexTargetRatioAtFortyPercent(): void
|
||||
{
|
||||
$scenario = json_decode(
|
||||
file_get_contents(__DIR__ . '/../hwe/scenario/scenario_915.json'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
self::assertSame(
|
||||
GameConst::$centennialNpcDexTargetRatio,
|
||||
$scenario['map']['centennialNpcDexTargetRatio']
|
||||
);
|
||||
}
|
||||
|
||||
public function testNpcDexRatioChangeRemovesOnlyOldEventGrant(): void
|
||||
{
|
||||
self::assertSame(
|
||||
['value' => 360000, 'granted' => 360000, 'organic' => 0],
|
||||
CentennialAllStarGrowth::replaceTarget(810000, 810000, 360000)
|
||||
);
|
||||
self::assertSame(
|
||||
['value' => 360000, 'granted' => 310000, 'organic' => 50000],
|
||||
CentennialAllStarGrowth::replaceTarget(860000, 810000, 360000)
|
||||
);
|
||||
self::assertSame(
|
||||
['value' => 400000, 'granted' => 0, 'organic' => 400000],
|
||||
CentennialAllStarGrowth::replaceTarget(1210000, 810000, 360000)
|
||||
);
|
||||
}
|
||||
|
||||
public function testExistingNpcDexGrantIsRebasedWithoutChangingFinalStats(): void
|
||||
{
|
||||
$vars = [
|
||||
'leadership' => 91,
|
||||
'strength' => 91,
|
||||
'intel' => 91,
|
||||
'dex1' => 810000,
|
||||
'dex2' => 810000,
|
||||
'dex3' => 810000,
|
||||
'dex4' => 810000,
|
||||
'dex5' => 810000,
|
||||
'special' => 'None',
|
||||
];
|
||||
$aux = [
|
||||
'targetId' => 'A1000001',
|
||||
'granted' => [
|
||||
'leadership' => 76,
|
||||
'strength' => 76,
|
||||
'intel' => 76,
|
||||
'dex1' => 810000,
|
||||
'dex2' => 810000,
|
||||
'dex3' => 810000,
|
||||
'dex4' => 810000,
|
||||
'dex5' => 810000,
|
||||
],
|
||||
'progressMonth' => 162,
|
||||
'milestone' => 4,
|
||||
'naturalSpecialDomestic' => null,
|
||||
'eventSpecialDomestic' => null,
|
||||
'userInitialStats' => null,
|
||||
];
|
||||
$general = $this->getMockBuilder(General::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getAuxVar', 'setAuxVar', 'getVar', 'updateVar'])
|
||||
->getMock();
|
||||
$general->method('getAuxVar')->willReturnCallback(
|
||||
static fn(string $key) => $key === CentennialAllStarGrowthService::AUX_KEY
|
||||
? $aux
|
||||
: null
|
||||
);
|
||||
$general->method('getVar')->willReturnCallback(
|
||||
static fn(string $key) => $vars[$key] ?? null
|
||||
);
|
||||
$general->method('updateVar')->willReturnCallback(
|
||||
static function (string $key, $value) use (&$vars): void {
|
||||
$vars[$key] = $value;
|
||||
}
|
||||
);
|
||||
$general->method('setAuxVar')->willReturnCallback(
|
||||
static function (string $key, $value) use (&$aux): void {
|
||||
if ($key === CentennialAllStarGrowthService::AUX_KEY) {
|
||||
$aux = $value;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$result = CentennialAllStarGrowthService::applyTarget(
|
||||
$general,
|
||||
[
|
||||
'uniqueName' => 'A1000001',
|
||||
'leadership' => 100,
|
||||
'strength' => 100,
|
||||
'intel' => 100,
|
||||
'dex' => [900000, 900000, 900000, 900000, 900000],
|
||||
],
|
||||
['startyear' => 180, 'year' => 195, 'month' => 1],
|
||||
CentennialAllStarGrowthService::NPC_PROGRESS_MULTIPLIER,
|
||||
GameConst::$centennialNpcDexTargetRatio
|
||||
);
|
||||
|
||||
self::assertTrue($result['changed']);
|
||||
self::assertSame(91, $vars['leadership']);
|
||||
self::assertSame(91, $vars['strength']);
|
||||
self::assertSame(91, $vars['intel']);
|
||||
foreach (['dex1', 'dex2', 'dex3', 'dex4', 'dex5'] as $key) {
|
||||
self::assertSame(360000, $vars[$key]);
|
||||
self::assertSame(360000, $aux['granted'][$key]);
|
||||
}
|
||||
self::assertSame(0.4, $aux['dexTargetRatio']);
|
||||
}
|
||||
|
||||
public function testUserDexStillReachesFullHistoricalTarget(): void
|
||||
{
|
||||
self::assertSame(
|
||||
900000,
|
||||
CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||
900000,
|
||||
['startyear' => 180, 'year' => 195, 'month' => 1]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testUntrackedGeneralKeepsFullHallValue(): void
|
||||
{
|
||||
self::assertSame(
|
||||
|
||||
Reference in New Issue
Block a user