fix: prevent S100 dex conversion refills
This commit was merged in pull request #266.
This commit is contained in:
@@ -32,6 +32,8 @@ final class CentennialAllStarGrowthService
|
||||
return [
|
||||
'targetId' => (string) ($targetInfo['uniqueName'] ?? ''),
|
||||
'granted' => $granted,
|
||||
'dexConsumed' => array_fill_keys(self::DEX_KEYS, 0),
|
||||
'dexFloor' => array_fill_keys(self::DEX_KEYS, 0),
|
||||
'progressMonth' => -1,
|
||||
'milestone' => 0,
|
||||
'naturalSpecialDomestic' => null,
|
||||
@@ -275,6 +277,15 @@ final class CentennialAllStarGrowthService
|
||||
? $aux['granted']
|
||||
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
|
||||
$targetChanged = ($aux['targetId'] ?? '') !== $targetId;
|
||||
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
|
||||
? $aux['dexConsumed']
|
||||
: array_fill_keys(self::DEX_KEYS, 0);
|
||||
if ($targetChanged) {
|
||||
$dexConsumed = array_fill_keys(self::DEX_KEYS, 0);
|
||||
}
|
||||
$dexFloor = is_array($aux['dexFloor'] ?? null)
|
||||
? $aux['dexFloor']
|
||||
: array_fill_keys(self::DEX_KEYS, 0);
|
||||
$isUserTarget = is_array($aux['userInitialStats'] ?? null);
|
||||
$nextUserInitialStats = $targetChanged && $isUserTarget
|
||||
? self::calculateUserInitialStats($targetInfo)
|
||||
@@ -327,11 +338,15 @@ final class CentennialAllStarGrowthService
|
||||
if (!array_key_exists($idx, $targetDex)) {
|
||||
continue;
|
||||
}
|
||||
$floor = self::calculateDexTargetFloor(
|
||||
(int) $targetDex[$idx],
|
||||
$env,
|
||||
$dexTargetRatio
|
||||
$floor = max(
|
||||
0,
|
||||
self::calculateDexTargetFloor(
|
||||
(int) $targetDex[$idx],
|
||||
$env,
|
||||
$dexTargetRatio
|
||||
) - max(0, (int) ($dexConsumed[$key] ?? 0))
|
||||
);
|
||||
$dexFloor[$key] = $floor;
|
||||
$current = (int) $general->getVar($key);
|
||||
if ($targetChanged || $dexTargetRatioChanged) {
|
||||
$result = CentennialAllStarGrowth::replaceTarget(
|
||||
@@ -381,6 +396,8 @@ final class CentennialAllStarGrowthService
|
||||
$milestone = min(5, (int) floor($progress * 5 + 0.0000001));
|
||||
$aux['targetId'] = $targetId;
|
||||
$aux['granted'] = $granted;
|
||||
$aux['dexConsumed'] = $dexConsumed;
|
||||
$aux['dexFloor'] = $dexFloor;
|
||||
$aux['progressMonth'] = max((int) ($aux['progressMonth'] ?? -1), $progressMonth);
|
||||
$aux['milestone'] = max($previousMilestone, $milestone);
|
||||
$aux['userInitialStats'] = $nextUserInitialStats;
|
||||
@@ -440,6 +457,93 @@ final class CentennialAllStarGrowthService
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the event-backed part of a dex conversion with the converted
|
||||
* value and consumes any guaranteed floor crossed by the source value.
|
||||
* This keeps the monthly floor from refilling points already converted.
|
||||
*/
|
||||
public static function reconcileDexConversion(
|
||||
General $general,
|
||||
string $sourceKey,
|
||||
string $destinationKey,
|
||||
int $sourceBefore,
|
||||
int $sourceAfter,
|
||||
int $destinationBefore,
|
||||
int $destinationAfter,
|
||||
float $convertCoeff
|
||||
): void {
|
||||
if (!in_array($sourceKey, self::DEX_KEYS, true)
|
||||
|| !in_array($destinationKey, self::DEX_KEYS, true)
|
||||
|| $sourceKey === $destinationKey
|
||||
) {
|
||||
throw new \InvalidArgumentException('invalid dex conversion keys');
|
||||
}
|
||||
if ($convertCoeff < 0 || $convertCoeff > 1) {
|
||||
throw new \InvalidArgumentException('dex conversion coefficient must be between 0 and 1');
|
||||
}
|
||||
|
||||
$sourceDecrease = max(0, $sourceBefore - $sourceAfter);
|
||||
$destinationIncrease = max(0, $destinationAfter - $destinationBefore);
|
||||
if ($sourceDecrease === 0 && $destinationIncrease === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aux = $general->getAuxVar(self::AUX_KEY);
|
||||
if (!is_array($aux)) {
|
||||
return;
|
||||
}
|
||||
$granted = is_array($aux['granted'] ?? null)
|
||||
? $aux['granted']
|
||||
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
|
||||
$dexConsumed = is_array($aux['dexConsumed'] ?? null)
|
||||
? $aux['dexConsumed']
|
||||
: array_fill_keys(self::DEX_KEYS, 0);
|
||||
$dexFloor = is_array($aux['dexFloor'] ?? null)
|
||||
? $aux['dexFloor']
|
||||
: [];
|
||||
|
||||
$sourceGrantedBefore = min(
|
||||
max(0, $sourceBefore),
|
||||
max(0, (int) ($granted[$sourceKey] ?? 0))
|
||||
);
|
||||
$sourceOrganicBefore = max(0, $sourceBefore - $sourceGrantedBefore);
|
||||
$sourceGrantedAfter = max(
|
||||
0,
|
||||
$sourceAfter - min($sourceAfter, $sourceOrganicBefore)
|
||||
);
|
||||
$eventGrantRemoved = max(0, $sourceGrantedBefore - $sourceGrantedAfter);
|
||||
|
||||
$destinationGrantedBefore = min(
|
||||
max(0, $destinationBefore),
|
||||
max(0, (int) ($granted[$destinationKey] ?? 0))
|
||||
);
|
||||
$eventGrantTransferred = min(
|
||||
$destinationIncrease,
|
||||
(int) floor($eventGrantRemoved * $convertCoeff)
|
||||
);
|
||||
$granted[$sourceKey] = $sourceGrantedAfter;
|
||||
$granted[$destinationKey] = min(
|
||||
max(0, $destinationAfter),
|
||||
$destinationGrantedBefore + $eventGrantTransferred
|
||||
);
|
||||
|
||||
$sourceFloor = max(
|
||||
0,
|
||||
(int) ($dexFloor[$sourceKey] ?? $sourceBefore)
|
||||
);
|
||||
$gapBefore = max(0, $sourceFloor - $sourceBefore);
|
||||
$gapAfter = max(0, $sourceFloor - $sourceAfter);
|
||||
$dexConsumed[$sourceKey] = max(
|
||||
0,
|
||||
(int) ($dexConsumed[$sourceKey] ?? 0)
|
||||
+ max(0, $gapAfter - $gapBefore)
|
||||
);
|
||||
|
||||
$aux['granted'] = $granted;
|
||||
$aux['dexConsumed'] = $dexConsumed;
|
||||
$general->setAuxVar(self::AUX_KEY, $aux);
|
||||
}
|
||||
|
||||
public static function recordableValue(General $general, string $key): int
|
||||
{
|
||||
$aux = $general->getAuxVar(self::AUX_KEY);
|
||||
|
||||
@@ -14,6 +14,7 @@ use function \sammo\tryUniqueItemLottery;
|
||||
|
||||
use \sammo\Constraint\ConstraintHelper;
|
||||
use sammo\StaticEventHandler;
|
||||
use sammo\CentennialAllStarGrowthService;
|
||||
|
||||
class che_숙련전환 extends Command\GeneralCommand
|
||||
{
|
||||
@@ -157,6 +158,7 @@ class che_숙련전환 extends Command\GeneralCommand
|
||||
$logger = $general->getLogger();
|
||||
|
||||
$srcDex = $general->getVar('dex' . $this->srcArmType);
|
||||
$destDex = $general->getVar('dex' . $this->destArmType);
|
||||
$cutDex = Util::toInt($srcDex * static::$decreaseCoeff);
|
||||
$cutDexText = number_format($cutDex);
|
||||
$addDex = Util::toInt($cutDex * static::$convertCoeff);
|
||||
@@ -164,6 +166,19 @@ class che_숙련전환 extends Command\GeneralCommand
|
||||
|
||||
$general->increaseVar('dex' . $this->srcArmType, -$cutDex);
|
||||
$general->increaseVar('dex' . $this->destArmType, $addDex);
|
||||
// 100기 이벤트 지급분을 목적 숙련으로 옮기고 소비한 성장 하한은 다시 채우지 않는다.
|
||||
if (CentennialAllStarGrowthService::isActive()) {
|
||||
CentennialAllStarGrowthService::reconcileDexConversion(
|
||||
$general,
|
||||
'dex' . $this->srcArmType,
|
||||
'dex' . $this->destArmType,
|
||||
$srcDex,
|
||||
$general->getVar('dex' . $this->srcArmType),
|
||||
$destDex,
|
||||
$general->getVar('dex' . $this->destArmType),
|
||||
static::$convertCoeff
|
||||
);
|
||||
}
|
||||
|
||||
$josaUl = JosaUtil::pick($cutDex, '을');
|
||||
$josaRo = JosaUtil::pick($addDex, '로');
|
||||
|
||||
@@ -369,6 +369,221 @@ final class CentennialAllStarGrowthTest extends TestCase
|
||||
self::assertSame(0.4, $aux['dexTargetRatio']);
|
||||
}
|
||||
|
||||
public function testDexConversionConsumesEventFloorWithoutMonthlyRefill(): void
|
||||
{
|
||||
$vars = [
|
||||
'leadership' => 100,
|
||||
'strength' => 100,
|
||||
'intel' => 100,
|
||||
'dex1' => 216000,
|
||||
'dex2' => 489600,
|
||||
'dex3' => 360000,
|
||||
'dex4' => 360000,
|
||||
'dex5' => 360000,
|
||||
'special' => 'None',
|
||||
];
|
||||
$aux = [
|
||||
'targetId' => 'A1000001',
|
||||
'granted' => [
|
||||
'leadership' => 85,
|
||||
'strength' => 85,
|
||||
'intel' => 85,
|
||||
'dex1' => 360000,
|
||||
'dex2' => 360000,
|
||||
'dex3' => 360000,
|
||||
'dex4' => 360000,
|
||||
'dex5' => 360000,
|
||||
],
|
||||
'dexConsumed' => [
|
||||
'dex1' => 0,
|
||||
'dex2' => 0,
|
||||
'dex3' => 0,
|
||||
'dex4' => 0,
|
||||
'dex5' => 0,
|
||||
],
|
||||
'progressMonth' => 180,
|
||||
'milestone' => 5,
|
||||
'naturalSpecialDomestic' => null,
|
||||
'eventSpecialDomestic' => null,
|
||||
'userInitialStats' => [],
|
||||
'dexTargetRatio' => 1.0,
|
||||
];
|
||||
$general = $this->createStateGeneralMock($vars, $aux);
|
||||
|
||||
CentennialAllStarGrowthService::reconcileDexConversion(
|
||||
$general,
|
||||
'dex1',
|
||||
'dex2',
|
||||
360000,
|
||||
216000,
|
||||
360000,
|
||||
489600,
|
||||
0.9
|
||||
);
|
||||
|
||||
self::assertSame(216000, $aux['granted']['dex1']);
|
||||
self::assertSame(489600, $aux['granted']['dex2']);
|
||||
self::assertSame(144000, $aux['dexConsumed']['dex1']);
|
||||
|
||||
CentennialAllStarGrowthService::applyTarget(
|
||||
$general,
|
||||
[
|
||||
'uniqueName' => 'A1000001',
|
||||
'leadership' => 100,
|
||||
'strength' => 100,
|
||||
'intel' => 100,
|
||||
'dex' => [360000, 360000, 360000, 360000, 360000],
|
||||
],
|
||||
['startyear' => 180, 'year' => 195, 'month' => 1]
|
||||
);
|
||||
|
||||
self::assertSame(216000, $vars['dex1']);
|
||||
self::assertSame(489600, $vars['dex2']);
|
||||
self::assertSame(216000, $aux['dexFloor']['dex1']);
|
||||
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
|
||||
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex2'));
|
||||
|
||||
$vars['dex1'] = 129600;
|
||||
$vars['dex2'] = 567360;
|
||||
CentennialAllStarGrowthService::reconcileDexConversion(
|
||||
$general,
|
||||
'dex1',
|
||||
'dex2',
|
||||
216000,
|
||||
129600,
|
||||
489600,
|
||||
567360,
|
||||
0.9
|
||||
);
|
||||
CentennialAllStarGrowthService::applyTarget(
|
||||
$general,
|
||||
[
|
||||
'uniqueName' => 'A1000001',
|
||||
'leadership' => 100,
|
||||
'strength' => 100,
|
||||
'intel' => 100,
|
||||
'dex' => [360000, 360000, 360000, 360000, 360000],
|
||||
],
|
||||
['startyear' => 180, 'year' => 195, 'month' => 1]
|
||||
);
|
||||
|
||||
self::assertSame(129600, $vars['dex1']);
|
||||
self::assertSame(567360, $vars['dex2']);
|
||||
self::assertSame(230400, $aux['dexConsumed']['dex1']);
|
||||
self::assertSame(129600, $aux['dexFloor']['dex1']);
|
||||
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
|
||||
self::assertSame(0, CentennialAllStarGrowthService::recordableValue($general, 'dex2'));
|
||||
}
|
||||
|
||||
public function testNaturalDexConversionDoesNotBecomeEventGrant(): void
|
||||
{
|
||||
$vars = [
|
||||
'dex1' => 216000,
|
||||
'dex2' => 129600,
|
||||
];
|
||||
$aux = [
|
||||
'targetId' => 'A1000001',
|
||||
'granted' => [
|
||||
'dex1' => 0,
|
||||
'dex2' => 0,
|
||||
],
|
||||
'dexConsumed' => [
|
||||
'dex1' => 0,
|
||||
'dex2' => 0,
|
||||
],
|
||||
'dexFloor' => [
|
||||
'dex1' => 360000,
|
||||
'dex2' => 0,
|
||||
],
|
||||
];
|
||||
$general = $this->createStateGeneralMock($vars, $aux);
|
||||
|
||||
CentennialAllStarGrowthService::reconcileDexConversion(
|
||||
$general,
|
||||
'dex1',
|
||||
'dex2',
|
||||
360000,
|
||||
216000,
|
||||
0,
|
||||
129600,
|
||||
0.9
|
||||
);
|
||||
|
||||
self::assertSame(0, $aux['granted']['dex1']);
|
||||
self::assertSame(0, $aux['granted']['dex2']);
|
||||
self::assertSame(144000, $aux['dexConsumed']['dex1']);
|
||||
self::assertSame(216000, CentennialAllStarGrowthService::recordableValue($general, 'dex1'));
|
||||
self::assertSame(129600, CentennialAllStarGrowthService::recordableValue($general, 'dex2'));
|
||||
}
|
||||
|
||||
public function testReselectionResetsConsumedDexFloorAndTransferredGrant(): void
|
||||
{
|
||||
$vars = [
|
||||
'leadership' => 100,
|
||||
'strength' => 100,
|
||||
'intel' => 100,
|
||||
'dex1' => 216000,
|
||||
'dex2' => 489600,
|
||||
'dex3' => 360000,
|
||||
'dex4' => 360000,
|
||||
'dex5' => 360000,
|
||||
'special' => 'None',
|
||||
];
|
||||
$aux = [
|
||||
'targetId' => 'A1000001',
|
||||
'granted' => [
|
||||
'leadership' => 85,
|
||||
'strength' => 85,
|
||||
'intel' => 85,
|
||||
'dex1' => 216000,
|
||||
'dex2' => 489600,
|
||||
'dex3' => 360000,
|
||||
'dex4' => 360000,
|
||||
'dex5' => 360000,
|
||||
],
|
||||
'dexConsumed' => [
|
||||
'dex1' => 144000,
|
||||
'dex2' => 0,
|
||||
'dex3' => 0,
|
||||
'dex4' => 0,
|
||||
'dex5' => 0,
|
||||
],
|
||||
'dexFloor' => [
|
||||
'dex1' => 216000,
|
||||
'dex2' => 360000,
|
||||
'dex3' => 360000,
|
||||
'dex4' => 360000,
|
||||
'dex5' => 360000,
|
||||
],
|
||||
'progressMonth' => 180,
|
||||
'milestone' => 5,
|
||||
'naturalSpecialDomestic' => null,
|
||||
'eventSpecialDomestic' => null,
|
||||
'userInitialStats' => [],
|
||||
'dexTargetRatio' => 1.0,
|
||||
];
|
||||
$general = $this->createStateGeneralMock($vars, $aux);
|
||||
|
||||
CentennialAllStarGrowthService::applyTarget(
|
||||
$general,
|
||||
[
|
||||
'uniqueName' => 'A1000002',
|
||||
'leadership' => 100,
|
||||
'strength' => 100,
|
||||
'intel' => 100,
|
||||
'dex' => [400000, 400000, 400000, 400000, 400000],
|
||||
],
|
||||
['startyear' => 180, 'year' => 195, 'month' => 1]
|
||||
);
|
||||
|
||||
self::assertSame(400000, $vars['dex1']);
|
||||
self::assertSame(400000, $vars['dex2']);
|
||||
self::assertSame(0, $aux['dexConsumed']['dex1']);
|
||||
self::assertSame(400000, $aux['dexFloor']['dex1']);
|
||||
self::assertSame(400000, $aux['granted']['dex1']);
|
||||
self::assertSame(400000, $aux['granted']['dex2']);
|
||||
}
|
||||
|
||||
public function testUserDexStillReachesFullHistoricalTarget(): void
|
||||
{
|
||||
self::assertSame(
|
||||
@@ -390,4 +605,37 @@ final class CentennialAllStarGrowthTest extends TestCase
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function createStateGeneralMock(array &$vars, array &$aux): General
|
||||
{
|
||||
$general = $this->getMockBuilder(General::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getAuxVar', 'setAuxVar', 'getVar', 'updateVar'])
|
||||
->getMock();
|
||||
$general->method('getAuxVar')->willReturnCallback(
|
||||
static function (string $key) use (&$aux) {
|
||||
return $key === CentennialAllStarGrowthService::AUX_KEY
|
||||
? $aux
|
||||
: null;
|
||||
}
|
||||
);
|
||||
$general->method('getVar')->willReturnCallback(
|
||||
static function (string $key) use (&$vars) {
|
||||
return $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;
|
||||
}
|
||||
}
|
||||
);
|
||||
return $general;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user