Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32d2f7b0d1 |
@@ -10,6 +10,8 @@ final class CentennialAllStarGrowthService
|
|||||||
public const AUX_KEY = 'event100_allstar';
|
public const AUX_KEY = 'event100_allstar';
|
||||||
public const TRAIT_UNLOCK_PROGRESS = 0.4;
|
public const TRAIT_UNLOCK_PROGRESS = 0.4;
|
||||||
public const NPC_PROGRESS_MULTIPLIER = 0.9;
|
public const NPC_PROGRESS_MULTIPLIER = 0.9;
|
||||||
|
public const DEX_EXTENSION_START_YEAR_OFFSET = 20;
|
||||||
|
public const DEX_EXTENSION_YEARS = 5;
|
||||||
|
|
||||||
private const STAT_KEYS = ['leadership', 'strength', 'intel'];
|
private const STAT_KEYS = ['leadership', 'strength', 'intel'];
|
||||||
private const DEX_KEYS = ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'];
|
private const DEX_KEYS = ['dex1', 'dex2', 'dex3', 'dex4', 'dex5'];
|
||||||
@@ -41,6 +43,7 @@ final class CentennialAllStarGrowthService
|
|||||||
'dexFloor' => array_fill_keys(self::DEX_KEYS, 0),
|
'dexFloor' => array_fill_keys(self::DEX_KEYS, 0),
|
||||||
'progressMonth' => -1,
|
'progressMonth' => -1,
|
||||||
'milestone' => 0,
|
'milestone' => 0,
|
||||||
|
'dexExtensionMilestone' => 0,
|
||||||
'naturalSpecialDomestic' => null,
|
'naturalSpecialDomestic' => null,
|
||||||
'eventSpecialDomestic' => null,
|
'eventSpecialDomestic' => null,
|
||||||
'userInitialStats' => $userInitialStats,
|
'userInitialStats' => $userInitialStats,
|
||||||
@@ -236,7 +239,7 @@ final class CentennialAllStarGrowthService
|
|||||||
if ($targetRatio < 0 || $targetRatio > 1) {
|
if ($targetRatio < 0 || $targetRatio > 1) {
|
||||||
throw new \InvalidArgumentException('dex target ratio must be between 0 and 1');
|
throw new \InvalidArgumentException('dex target ratio must be between 0 and 1');
|
||||||
}
|
}
|
||||||
$target = min(GameConst::$dexLimit, max(0, $target));
|
$target = self::calculateDexExtendedTarget($target, $env);
|
||||||
$scaledTarget = (int) floor($target * $targetRatio);
|
$scaledTarget = (int) floor($target * $targetRatio);
|
||||||
$progress = self::calculateProgress(
|
$progress = self::calculateProgress(
|
||||||
(int) $env['startyear'],
|
(int) $env['startyear'],
|
||||||
@@ -246,10 +249,34 @@ final class CentennialAllStarGrowthService
|
|||||||
return CentennialAllStarGrowth::dexFloor($scaledTarget, $progress);
|
return CentennialAllStarGrowth::dexFloor($scaledTarget, $progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function calculateDexExtensionProgress(array $env): float
|
||||||
|
{
|
||||||
|
return CentennialAllStarGrowth::progress(
|
||||||
|
(int) $env['startyear'] + self::DEX_EXTENSION_START_YEAR_OFFSET,
|
||||||
|
(int) $env['year'],
|
||||||
|
(int) $env['month'],
|
||||||
|
self::DEX_EXTENSION_YEARS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function calculateDexExtendedTarget(int $target, array $env): int
|
||||||
|
{
|
||||||
|
$target = max(0, $target);
|
||||||
|
$baseTarget = min(GameConst::$dexLimit, $target);
|
||||||
|
$extensionTarget = max(0, $target - $baseTarget);
|
||||||
|
if ($extensionTarget === 0) {
|
||||||
|
return $baseTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $baseTarget + (int) floor(
|
||||||
|
$extensionTarget * self::calculateDexExtensionProgress($env)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mutates the General object but leaves persistence to the caller.
|
* Mutates the General object but leaves persistence to the caller.
|
||||||
*
|
*
|
||||||
* @return array{progress:float,milestone:int,previousMilestone:int,targetChanged:bool,changed:bool}
|
* @return array{progress:float,milestone:int,previousMilestone:int,dexExtensionMilestone:int,previousDexExtensionMilestone:int,hasDexExtensionTarget:bool,targetChanged:bool,changed:bool}
|
||||||
*/
|
*/
|
||||||
public static function applyTarget(
|
public static function applyTarget(
|
||||||
General $general,
|
General $general,
|
||||||
@@ -339,10 +366,14 @@ final class CentennialAllStarGrowthService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$targetDex = $targetInfo['dex'] ?? [];
|
$targetDex = $targetInfo['dex'] ?? [];
|
||||||
|
$hasDexExtensionTarget = false;
|
||||||
foreach (self::DEX_KEYS as $idx => $key) {
|
foreach (self::DEX_KEYS as $idx => $key) {
|
||||||
if (!array_key_exists($idx, $targetDex)) {
|
if (!array_key_exists($idx, $targetDex)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ((int) $targetDex[$idx] > GameConst::$dexLimit) {
|
||||||
|
$hasDexExtensionTarget = true;
|
||||||
|
}
|
||||||
$floor = max(
|
$floor = max(
|
||||||
0,
|
0,
|
||||||
self::calculateDexTargetFloor(
|
self::calculateDexTargetFloor(
|
||||||
@@ -399,12 +430,21 @@ final class CentennialAllStarGrowthService
|
|||||||
|
|
||||||
$previousMilestone = (int) ($aux['milestone'] ?? 0);
|
$previousMilestone = (int) ($aux['milestone'] ?? 0);
|
||||||
$milestone = min(5, (int) floor($progress * 5 + 0.0000001));
|
$milestone = min(5, (int) floor($progress * 5 + 0.0000001));
|
||||||
|
$previousDexExtensionMilestone = (int) ($aux['dexExtensionMilestone'] ?? 0);
|
||||||
|
$dexExtensionProgress = self::calculateDexExtensionProgress($env);
|
||||||
|
$dexExtensionMilestone = $hasDexExtensionTarget
|
||||||
|
? min(5, (int) floor($dexExtensionProgress * 5 + 0.0000001))
|
||||||
|
: 0;
|
||||||
$aux['targetId'] = $targetId;
|
$aux['targetId'] = $targetId;
|
||||||
$aux['granted'] = $granted;
|
$aux['granted'] = $granted;
|
||||||
$aux['dexConsumed'] = $dexConsumed;
|
$aux['dexConsumed'] = $dexConsumed;
|
||||||
$aux['dexFloor'] = $dexFloor;
|
$aux['dexFloor'] = $dexFloor;
|
||||||
$aux['progressMonth'] = max((int) ($aux['progressMonth'] ?? -1), $progressMonth);
|
$aux['progressMonth'] = max((int) ($aux['progressMonth'] ?? -1), $progressMonth);
|
||||||
$aux['milestone'] = max($previousMilestone, $milestone);
|
$aux['milestone'] = max($previousMilestone, $milestone);
|
||||||
|
$aux['dexExtensionMilestone'] = max(
|
||||||
|
$previousDexExtensionMilestone,
|
||||||
|
$dexExtensionMilestone
|
||||||
|
);
|
||||||
$aux['userInitialStats'] = $nextUserInitialStats;
|
$aux['userInitialStats'] = $nextUserInitialStats;
|
||||||
$aux['dexTargetRatio'] = $dexTargetRatio;
|
$aux['dexTargetRatio'] = $dexTargetRatio;
|
||||||
$general->setAuxVar(self::AUX_KEY, $aux);
|
$general->setAuxVar(self::AUX_KEY, $aux);
|
||||||
@@ -413,8 +453,14 @@ final class CentennialAllStarGrowthService
|
|||||||
'progress' => $progress,
|
'progress' => $progress,
|
||||||
'milestone' => $milestone,
|
'milestone' => $milestone,
|
||||||
'previousMilestone' => $previousMilestone,
|
'previousMilestone' => $previousMilestone,
|
||||||
|
'dexExtensionMilestone' => $dexExtensionMilestone,
|
||||||
|
'previousDexExtensionMilestone' => $previousDexExtensionMilestone,
|
||||||
|
'hasDexExtensionTarget' => $hasDexExtensionTarget,
|
||||||
'targetChanged' => $targetChanged,
|
'targetChanged' => $targetChanged,
|
||||||
'changed' => $changed || $targetChanged || $milestone > $previousMilestone,
|
'changed' => $changed
|
||||||
|
|| $targetChanged
|
||||||
|
|| $milestone > $previousMilestone
|
||||||
|
|| $dexExtensionMilestone > $previousDexExtensionMilestone,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,18 @@ class AdvanceCentennialAllStar extends \sammo\Event\Action
|
|||||||
"<L>올스타 동조율 {$percent}% 달성</>"
|
"<L>올스타 동조율 {$percent}% 달성</>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if ($result['hasDexExtensionTarget']
|
||||||
|
&& $result['dexExtensionMilestone'] > $result['previousDexExtensionMilestone']
|
||||||
|
) {
|
||||||
|
$percent = $result['dexExtensionMilestone'] * 20;
|
||||||
|
$general->getLogger()->pushGeneralActionLog(
|
||||||
|
"<L>추가 숙련 동조율</>이 <C>{$percent}%</>에 도달했습니다!",
|
||||||
|
ActionLogger::PLAIN
|
||||||
|
);
|
||||||
|
$general->getLogger()->pushGeneralHistoryLog(
|
||||||
|
"<L>추가 숙련 동조율 {$percent}% 달성</>"
|
||||||
|
);
|
||||||
|
}
|
||||||
if ($general->applyDB($db)) {
|
if ($general->applyDB($db)) {
|
||||||
$updated++;
|
$updated++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ declare const validCustomOption: string[];
|
|||||||
const templateGeneralCard = '<div class="general_card">\
|
const templateGeneralCard = '<div class="general_card">\
|
||||||
<h4 class="bg1 with_border"><%generalName%></h4>\
|
<h4 class="bg1 with_border"><%generalName%></h4>\
|
||||||
<h4><img src="<%iconPath%>" height=64 width=64></h4><p>\
|
<h4><img src="<%iconPath%>" height=64 width=64></h4><p>\
|
||||||
<%if(event100Growth){%><b>195년 최종 동조 목표</b><br><%}%>\
|
<%if(event100Growth){%><b>역사 장수 최종 목표</b><br>능력치·기본 숙련 195년 / 추가 숙련 205년 동조<br><%}%>\
|
||||||
<%if(leadership){%>\
|
<%if(leadership){%>\
|
||||||
<%leadership%> / <%strength%> / <%intel%><br>\
|
<%leadership%> / <%strength%> / <%intel%><br>\
|
||||||
<%}%>\
|
<%}%>\
|
||||||
|
|||||||
@@ -371,6 +371,209 @@ final class CentennialAllStarGrowthTest extends TestCase
|
|||||||
self::assertSame(900000, CentennialAllStarGrowth::dexFloor(900000, 1));
|
self::assertSame(900000, CentennialAllStarGrowth::dexFloor(900000, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDexExtensionRunsFromYear200ThroughYear205(): void
|
||||||
|
{
|
||||||
|
$env = static fn(int $year, int $month): array => [
|
||||||
|
'startyear' => 180,
|
||||||
|
'year' => $year,
|
||||||
|
'month' => $month,
|
||||||
|
];
|
||||||
|
|
||||||
|
self::assertSame(0.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(199, 12)));
|
||||||
|
self::assertSame(0.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(200, 1)));
|
||||||
|
self::assertEqualsWithDelta(
|
||||||
|
0.2,
|
||||||
|
CentennialAllStarGrowthService::calculateDexExtensionProgress($env(201, 1)),
|
||||||
|
0.000001
|
||||||
|
);
|
||||||
|
self::assertEqualsWithDelta(
|
||||||
|
0.5,
|
||||||
|
CentennialAllStarGrowthService::calculateDexExtensionProgress($env(202, 7)),
|
||||||
|
0.000001
|
||||||
|
);
|
||||||
|
self::assertSame(1.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(205, 1)));
|
||||||
|
self::assertSame(1.0, CentennialAllStarGrowthService::calculateDexExtensionProgress($env(210, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDexExtensionOnlyAddsHistoricalTargetAboveOneMillion(): void
|
||||||
|
{
|
||||||
|
$env = static fn(int $year, int $month): array => [
|
||||||
|
'startyear' => 180,
|
||||||
|
'year' => $year,
|
||||||
|
'month' => $month,
|
||||||
|
];
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
900000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(900000, $env(205, 1))
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
1000000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(199, 12))
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
1000000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(200, 1))
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
1200000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(201, 1))
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
1500000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(202, 7))
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
2000000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(2000000, $env(205, 1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEveryCandidateDexTargetExtendsMonotonicallyToItsHistoricalValue(): void
|
||||||
|
{
|
||||||
|
$pool = json_decode(
|
||||||
|
file_get_contents(__DIR__ . '/../hwe/sammo/GeneralPool/Pool/UnderS100.json'),
|
||||||
|
true,
|
||||||
|
512,
|
||||||
|
JSON_THROW_ON_ERROR
|
||||||
|
);
|
||||||
|
$columns = array_flip($pool['columns']);
|
||||||
|
$dates = [
|
||||||
|
['startyear' => 180, 'year' => 199, 'month' => 7],
|
||||||
|
['startyear' => 180, 'year' => 200, 'month' => 1],
|
||||||
|
['startyear' => 180, 'year' => 201, 'month' => 1],
|
||||||
|
['startyear' => 180, 'year' => 202, 'month' => 7],
|
||||||
|
['startyear' => 180, 'year' => 205, 'month' => 1],
|
||||||
|
];
|
||||||
|
$extendedCandidateCount = 0;
|
||||||
|
$maximumTarget = 0;
|
||||||
|
|
||||||
|
foreach ($pool['data'] as $row) {
|
||||||
|
$hasExtendedTarget = false;
|
||||||
|
foreach ($row[$columns['dex']] as $target) {
|
||||||
|
$target = (int) $target;
|
||||||
|
$maximumTarget = max($maximumTarget, $target);
|
||||||
|
$hasExtendedTarget = $hasExtendedTarget || $target > GameConst::$dexLimit;
|
||||||
|
$floors = array_map(
|
||||||
|
static fn(array $env): int => CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||||
|
$target,
|
||||||
|
$env
|
||||||
|
),
|
||||||
|
$dates
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
min(GameConst::$dexLimit, $target),
|
||||||
|
$floors[0],
|
||||||
|
$row[$columns['generalName']]
|
||||||
|
);
|
||||||
|
self::assertSame($floors[0], $floors[1], $row[$columns['generalName']]);
|
||||||
|
$sortedFloors = $floors;
|
||||||
|
sort($sortedFloors, SORT_NUMERIC);
|
||||||
|
self::assertSame($sortedFloors, $floors, $row[$columns['generalName']]);
|
||||||
|
self::assertSame($target, $floors[4], $row[$columns['generalName']]);
|
||||||
|
}
|
||||||
|
if ($hasExtendedTarget) {
|
||||||
|
$extendedCandidateCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::assertSame(208, $extendedCandidateCount);
|
||||||
|
self::assertSame(2990401, $maximumTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNpcReceivesFortyPercentOfExtendedHistoricalDexTarget(): void
|
||||||
|
{
|
||||||
|
$ratio = GameConst::$centennialNpcDexTargetRatio;
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
400000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||||
|
2000000,
|
||||||
|
['startyear' => 180, 'year' => 200, 'month' => 1],
|
||||||
|
$ratio
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
480000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||||
|
2000000,
|
||||||
|
['startyear' => 180, 'year' => 201, 'month' => 1],
|
||||||
|
$ratio
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
800000,
|
||||||
|
CentennialAllStarGrowthService::calculateDexTargetFloor(
|
||||||
|
2000000,
|
||||||
|
['startyear' => 180, 'year' => 205, 'month' => 1],
|
||||||
|
$ratio
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDexExtensionNeverLowersNaturalGrowthAndKeepsItRecordable(): void
|
||||||
|
{
|
||||||
|
$target = $this->singleDexTarget('extended', 0, 2000000);
|
||||||
|
$vars = $this->emptyGeneralVars();
|
||||||
|
$aux = CentennialAllStarGrowthService::initialAux($target, []);
|
||||||
|
$general = $this->createStateGeneralMock($vars, $aux);
|
||||||
|
|
||||||
|
CentennialAllStarGrowthService::applyTarget(
|
||||||
|
$general,
|
||||||
|
$target,
|
||||||
|
['startyear' => 180, 'year' => 199, 'month' => 7]
|
||||||
|
);
|
||||||
|
self::assertSame(1000000, $vars['dex1']);
|
||||||
|
self::assertSame(1000000, $aux['granted']['dex1']);
|
||||||
|
|
||||||
|
$vars['dex1'] += 300000;
|
||||||
|
$result = CentennialAllStarGrowthService::applyTarget(
|
||||||
|
$general,
|
||||||
|
$target,
|
||||||
|
['startyear' => 180, 'year' => 201, 'month' => 1]
|
||||||
|
);
|
||||||
|
self::assertSame(1300000, $vars['dex1']);
|
||||||
|
self::assertSame(1000000, $aux['granted']['dex1']);
|
||||||
|
self::assertSame(300000, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
|
||||||
|
self::assertSame(1, $result['dexExtensionMilestone']);
|
||||||
|
self::assertSame(0, $result['previousDexExtensionMilestone']);
|
||||||
|
|
||||||
|
$result = CentennialAllStarGrowthService::applyTarget(
|
||||||
|
$general,
|
||||||
|
$target,
|
||||||
|
['startyear' => 180, 'year' => 205, 'month' => 1]
|
||||||
|
);
|
||||||
|
self::assertSame(2000000, $vars['dex1']);
|
||||||
|
self::assertSame(1700000, $aux['granted']['dex1']);
|
||||||
|
self::assertSame(300000, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
|
||||||
|
self::assertSame(5, $result['dexExtensionMilestone']);
|
||||||
|
self::assertSame(1, $result['previousDexExtensionMilestone']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDexExtensionDoesNotRefillConsumedConversionBudget(): void
|
||||||
|
{
|
||||||
|
$target = $this->singleDexTarget('extended-consumed', 0, 2000000);
|
||||||
|
$vars = $this->emptyGeneralVars();
|
||||||
|
$vars['dex1'] = 800000;
|
||||||
|
$aux = CentennialAllStarGrowthService::initialAux($target, []);
|
||||||
|
$aux['granted']['dex1'] = 800000;
|
||||||
|
$aux['dexConsumed']['dex1'] = 200000;
|
||||||
|
$aux['dexFloor']['dex1'] = 800000;
|
||||||
|
$general = $this->createStateGeneralMock($vars, $aux);
|
||||||
|
|
||||||
|
CentennialAllStarGrowthService::applyTarget(
|
||||||
|
$general,
|
||||||
|
$target,
|
||||||
|
['startyear' => 180, 'year' => 205, 'month' => 1]
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(1800000, $vars['dex1']);
|
||||||
|
self::assertSame(1800000, $aux['granted']['dex1']);
|
||||||
|
self::assertSame(200000, $aux['dexConsumed']['dex1']);
|
||||||
|
self::assertSame(1800000, $aux['dexFloor']['dex1']);
|
||||||
|
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testNpcDexStopsAtFortyPercentOfHistoricalTarget(): void
|
public function testNpcDexStopsAtFortyPercentOfHistoricalTarget(): void
|
||||||
{
|
{
|
||||||
$target = 900000;
|
$target = 900000;
|
||||||
@@ -945,10 +1148,10 @@ final class CentennialAllStarGrowthTest extends TestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function singleDexTarget(string $id, int $dexIndex): array
|
private function singleDexTarget(string $id, int $dexIndex, int $amount = 900000): array
|
||||||
{
|
{
|
||||||
$dex = [0, 0, 0, 0, 0];
|
$dex = [0, 0, 0, 0, 0];
|
||||||
$dex[$dexIndex] = 900000;
|
$dex[$dexIndex] = $amount;
|
||||||
return [
|
return [
|
||||||
'uniqueName' => $id,
|
'uniqueName' => $id,
|
||||||
'leadership' => 100,
|
'leadership' => 100,
|
||||||
|
|||||||
Reference in New Issue
Block a user