feat: 100기 NPC 동조 및 후보 추첨 기능 개선과 초기 능력치 계산 로직 추가

This commit is contained in:
2026-07-28 16:28:13 +00:00
parent ce807e2564
commit 883080e275
8 changed files with 499 additions and 113 deletions
+16
View File
@@ -51,6 +51,22 @@
display: none; display: none;
} }
.picture_choice {
margin: 8px auto;
}
.picture_choice label,
.event_picture label {
display: inline-flex;
align-items: center;
gap: 4px;
margin: 4px 8px;
}
.picture_choice img {
object-fit: cover;
}
.custom_picture { .custom_picture {
display: none; display: none;
} }
+7
View File
@@ -11,6 +11,13 @@ function sortTokens(&$tokens){
} }
function putInfoText(&$info){ function putInfoText(&$info){
if (($info['event100Growth'] ?? false) === true) {
$initialStats = CentennialAllStarGrowthService::calculateUserInitialStats($info);
$info['initialLeadership'] = $initialStats['leadership'];
$info['initialStrength'] = $initialStats['strength'];
$info['initialIntel'] = $initialStats['intel'];
}
if(key_exists('specialDomestic', $info)){ if(key_exists('specialDomestic', $info)){
$class = buildGeneralSpecialDomesticClass($info['specialDomestic']); $class = buildGeneralSpecialDomesticClass($info['specialDomestic']);
$info['specialDomesticName'] = $class->getName(); $info['specialDomesticName'] = $class->getName();
+36 -3
View File
@@ -21,6 +21,7 @@ $intel = Util::getPost(
); );
$personal = Util::getPost('personal', 'string', null); $personal = Util::getPost('personal', 'string', null);
$use_own_picture = Util::getPost('use_own_picture', 'bool', false); $use_own_picture = Util::getPost('use_own_picture', 'bool', false);
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){ if(!$pick){
@@ -69,13 +70,36 @@ if(!$selectInfo){
} }
$selectInfo = Json::decode($selectInfo); $selectInfo = Json::decode($selectInfo);
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID); $ownerInfo = RootDB::db()->queryFirstRow(
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){ if(!$ownerInfo){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
'reason'=>'멤버 정보를 가져오지 못했습니다.' 'reason'=>'멤버 정보를 가져오지 못했습니다.'
]); ]);
} }
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['selected', 'own'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $env['show_img_level'] >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2'); $gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
@@ -102,9 +126,12 @@ if ($isCentennialAllStar) {
} }
$builder = $pickedGeneral->getGeneralBuilder(); $builder = $pickedGeneral->getGeneralBuilder();
if ($isCentennialAllStar) {
CentennialAllStarGrowthService::prepareInitialUser($builder, $selectInfo);
}
foreach(GameConst::$generalPoolAllowOption as $allowOption){ foreach(GameConst::$generalPoolAllowOption as $allowOption){
if($allowOption == 'stat'){ if($allowOption == 'stat' && !$isCentennialAllStar){
$leadership = Util::valueFit($leadership, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $leadership = Util::valueFit($leadership, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$strength = Util::valueFit($strength, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $strength = Util::valueFit($strength, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
$intel = Util::valueFit($intel, GameConst::$defaultStatMin, GameConst::$defaultStatMax); $intel = Util::valueFit($intel, GameConst::$defaultStatMin, GameConst::$defaultStatMax);
@@ -117,7 +144,13 @@ foreach(GameConst::$generalPoolAllowOption as $allowOption){
} }
$builder->setStat($leadership, $strength, $intel); $builder->setStat($leadership, $strength, $intel);
} }
else if($allowOption == 'picture' && $use_own_picture){ else if(
$allowOption == 'picture'
&& (
(!$isCentennialAllStar && $use_own_picture)
|| ($isCentennialAllStar && $pictureSource === 'own')
)
){
$builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']); $builder->setPicture($ownerInfo['imgsvr'], $ownerInfo['picture']);
} }
else if($allowOption == 'ego'){ else if($allowOption == 'ego'){
+47 -5
View File
@@ -7,6 +7,7 @@ include "func.php";
WebUtil::requireAJAX(); WebUtil::requireAJAX();
$pick = Util::getPost('pick'); $pick = Util::getPost('pick');
$pictureSource = Util::getPost('picture_source', 'string', 'selected');
if(!$pick){ if(!$pick){
Json::die([ Json::die([
@@ -38,8 +39,17 @@ list(
$startYear, $startYear,
$maxgeneral, $maxgeneral,
$npcmode, $npcmode,
$turnterm $turnterm,
) = $gameStor->getValuesAsArray(['year', 'month', 'startyear', 'maxgeneral', 'npcmode', 'turnterm']); $showImgLevel
) = $gameStor->getValuesAsArray([
'year',
'month',
'startyear',
'maxgeneral',
'npcmode',
'turnterm',
'show_img_level',
]);
if($npcmode!=2){ if($npcmode!=2){
Json::die([ Json::die([
@@ -56,7 +66,10 @@ if(!$info){
]); ]);
} }
$ownerInfo = RootDB::db()->queryFirstRow('SELECT `name`,`picture`,`imgsvr` FROM member WHERE `NO`=%i',$userID); $ownerInfo = RootDB::db()->queryFirstRow(
'SELECT `name`,`picture`,`imgsvr`,`grade` FROM member WHERE `NO`=%i',
$userID
);
if(!$ownerInfo){ if(!$ownerInfo){
Json::die([ Json::die([
'result'=>false, 'result'=>false,
@@ -65,6 +78,27 @@ if(!$ownerInfo){
} }
$info = Json::decode($info); $info = Json::decode($info);
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
if ($isCentennialAllStar) {
if (!in_array($pictureSource, ['current', 'own', 'selected'], true)) {
Json::die([
'result' => false,
'reason' => '올바르지 않은 전콘 선택입니다.',
]);
}
if ($pictureSource === 'own') {
$canUseOwnPicture = in_array('picture', GameConst::$generalPoolAllowOption, true)
&& $showImgLevel >= 1
&& $ownerInfo['grade'] >= 1
&& $ownerInfo['picture'] !== '';
if (!$canUseOwnPicture) {
Json::die([
'result' => false,
'reason' => '사용할 수 있는 내 전콘이 없습니다.',
]);
}
}
}
$generalObj = General::createObjFromDB($generalID); $generalObj = General::createObjFromDB($generalID);
@@ -102,8 +136,8 @@ $db->update('select_pool',[
'reserved_until'=>null, 'reserved_until'=>null,
], '(owner=%i or reserved_until < %s) AND general_id is NULL', $userID, $now); ], '(owner=%i or reserved_until < %s) AND general_id is NULL', $userID, $now);
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
if ($isCentennialAllStar) { if ($isCentennialAllStar) {
CentennialAllStarGrowthService::prepareLegacyUserReselection($generalObj);
CentennialAllStarGrowthService::applyTarget($generalObj, $info, [ CentennialAllStarGrowthService::applyTarget($generalObj, $info, [
'startyear' => $startYear, 'startyear' => $startYear,
'year' => $year, 'year' => $year,
@@ -132,7 +166,15 @@ if ($isCentennialAllStar) {
$generalObj->updateVar('special2', $info['specialWar']); $generalObj->updateVar('special2', $info['specialWar']);
} }
} }
if(key_exists('picture', $info)){ if ($isCentennialAllStar) {
if ($pictureSource === 'own') {
$generalObj->updateVar('imgsvr', $ownerInfo['imgsvr']);
$generalObj->updateVar('picture', $ownerInfo['picture']);
} elseif ($pictureSource === 'selected' && key_exists('picture', $info)) {
$generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']);
}
} elseif(key_exists('picture', $info)){
$generalObj->updateVar('imgsvr', $info['imgsvr']); $generalObj->updateVar('imgsvr', $info['imgsvr']);
$generalObj->updateVar('picture', $info['picture']); $generalObj->updateVar('picture', $info['picture']);
} }
+143 -2
View File
@@ -19,15 +19,24 @@ final class CentennialAllStarGrowthService
return GameConst::$targetGeneralPool === self::POOL_CLASS; return GameConst::$targetGeneralPool === self::POOL_CLASS;
} }
public static function initialAux(array $targetInfo): array public static function initialAux(array $targetInfo, ?array $userInitialStats = null): array
{ {
$granted = array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
if ($userInitialStats !== null) {
foreach (self::STAT_KEYS as $key) {
$initial = (int) ($userInitialStats[$key] ?? GameConst::$defaultStatMin);
$granted[$key] = max(0, $initial - min($initial, GameConst::$defaultStatMin));
}
}
return [ return [
'targetId' => (string) ($targetInfo['uniqueName'] ?? ''), 'targetId' => (string) ($targetInfo['uniqueName'] ?? ''),
'granted' => array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0), 'granted' => $granted,
'progressMonth' => -1, 'progressMonth' => -1,
'milestone' => 0, 'milestone' => 0,
'naturalSpecialDomestic' => null, 'naturalSpecialDomestic' => null,
'eventSpecialDomestic' => null, 'eventSpecialDomestic' => null,
'userInitialStats' => $userInitialStats,
]; ];
} }
@@ -36,6 +45,130 @@ final class CentennialAllStarGrowthService
$builder->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo)); $builder->setAuxVar(self::AUX_KEY, self::initialAux($targetInfo));
} }
/**
* Builds an ordinary-user stat total while preserving the selected
* candidate's relative strengths as closely as integer stats allow.
*
* @return array{leadership:int,strength:int,intel:int}
*/
public static function calculateUserInitialStats(array $targetInfo): array
{
$targets = [];
$bases = [];
foreach (self::STAT_KEYS as $key) {
$target = min(
GameConst::$defaultStatMax,
max(0, (int) ($targetInfo[$key] ?? 0))
);
$targets[$key] = $target;
$bases[$key] = min($target, GameConst::$defaultStatMin);
}
$targetTotal = array_sum($targets);
$desiredTotal = min(GameConst::$defaultStatTotal, $targetTotal);
$baseTotal = array_sum($bases);
$capacityTotal = $targetTotal - $baseTotal;
if ($capacityTotal <= 0 || $desiredTotal <= $baseTotal) {
return $bases;
}
$ratio = ($desiredTotal - $baseTotal) / $capacityTotal;
$result = [];
$fractions = [];
foreach (self::STAT_KEYS as $idx => $key) {
$raw = $bases[$key] + ($targets[$key] - $bases[$key]) * $ratio;
$result[$key] = (int) floor($raw);
$fractions[] = [
'key' => $key,
'fraction' => $raw - $result[$key],
'order' => $idx,
];
}
usort($fractions, static function (array $lhs, array $rhs): int {
$fractionOrder = $rhs['fraction'] <=> $lhs['fraction'];
return $fractionOrder !== 0 ? $fractionOrder : $lhs['order'] <=> $rhs['order'];
});
$remainder = $desiredTotal - array_sum($result);
foreach ($fractions as $fraction) {
if ($remainder <= 0) {
break;
}
$key = $fraction['key'];
if ($result[$key] >= $targets[$key]) {
continue;
}
$result[$key]++;
$remainder--;
}
return $result;
}
public static function prepareInitialUser(
GeneralBuilder $builder,
array $targetInfo
): void {
$initialStats = self::calculateUserInitialStats($targetInfo);
$builder->setStat(
$initialStats['leadership'],
$initialStats['strength'],
$initialStats['intel']
);
$builder->setAuxVar(
self::AUX_KEY,
self::initialAux($targetInfo, $initialStats)
);
}
/**
* Old 100th-season characters did not distinguish their form-entered
* initial stats from organic growth. Before their first reselection, treat
* the ordinary creation range as the replaceable initial allocation.
*/
public static function prepareLegacyUserReselection(General $general): void
{
$aux = $general->getAuxVar(self::AUX_KEY);
if (!is_array($aux) || is_array($aux['userInitialStats'] ?? null)) {
return;
}
$granted = is_array($aux['granted'] ?? null)
? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$legacyInitialStats = [];
foreach (self::STAT_KEYS as $key) {
$current = (int) $general->getVar($key);
$granted[$key] = self::calculateLegacyUserGrant(
$current,
(int) ($granted[$key] ?? 0)
);
$beforeEventGrant = max(
0,
$current - max(0, (int) ($aux['granted'][$key] ?? 0))
);
$legacyInitialStats[$key] = min(
$beforeEventGrant,
GameConst::$defaultStatMax
);
}
$aux['granted'] = $granted;
$aux['userInitialStats'] = $legacyInitialStats;
$general->setAuxVar(self::AUX_KEY, $aux);
}
public static function calculateLegacyUserGrant(int $current, int $eventGrant): int
{
$eventGrant = max(0, $eventGrant);
$beforeEventGrant = max(0, $current - $eventGrant);
$replaceableInitialGrant = max(
0,
min($beforeEventGrant, GameConst::$defaultStatMax)
- min($beforeEventGrant, GameConst::$defaultStatMin)
);
return $eventGrant + $replaceableInitialGrant;
}
public static function calculateProgress( public static function calculateProgress(
int $startYear, int $startYear,
int $year, int $year,
@@ -87,6 +220,10 @@ final class CentennialAllStarGrowthService
? $aux['granted'] ? $aux['granted']
: array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0); : array_fill_keys(array_merge(self::STAT_KEYS, self::DEX_KEYS), 0);
$targetChanged = ($aux['targetId'] ?? '') !== $targetId; $targetChanged = ($aux['targetId'] ?? '') !== $targetId;
$isUserTarget = is_array($aux['userInitialStats'] ?? null);
$nextUserInitialStats = $targetChanged && $isUserTarget
? self::calculateUserInitialStats($targetInfo)
: ($aux['userInitialStats'] ?? null);
$changed = false; $changed = false;
foreach (self::STAT_KEYS as $key) { foreach (self::STAT_KEYS as $key) {
@@ -99,6 +236,9 @@ final class CentennialAllStarGrowthService
GameConst::$defaultStatMin, GameConst::$defaultStatMin,
$progress $progress
); );
if ($nextUserInitialStats !== null) {
$floor = max($floor, (int) ($nextUserInitialStats[$key] ?? 0));
}
$current = (int) $general->getVar($key); $current = (int) $general->getVar($key);
if ($targetChanged) { if ($targetChanged) {
$result = CentennialAllStarGrowth::replaceTarget( $result = CentennialAllStarGrowth::replaceTarget(
@@ -178,6 +318,7 @@ final class CentennialAllStarGrowthService
$aux['granted'] = $granted; $aux['granted'] = $granted;
$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['userInitialStats'] = $nextUserInitialStats;
$general->setAuxVar(self::AUX_KEY, $aux); $general->setAuxVar(self::AUX_KEY, $aux);
return [ return [
+59 -2
View File
@@ -19,8 +19,16 @@ if ($admin['npcmode'] != 2) {
$member = RootDB::db()->queryFirstRow("SELECT no,name,picture,imgsvr,grade from member where no= %i", $userID); $member = RootDB::db()->queryFirstRow("SELECT no,name,picture,imgsvr,grade from member where no= %i", $userID);
$generalID = $db->queryFirstField('SELECT no FROM general WHERE owner = %i', $userID); $currentGeneral = $db->queryFirstRow(
'SELECT no,picture,imgsvr FROM general WHERE owner = %i',
$userID
);
$generalID = $currentGeneral['no'] ?? null;
$gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2'); $gencount = $db->queryFirstField('SELECT count(`no`) FROM general WHERE npc<2');
$isCentennialAllStar = CentennialAllStarGrowthService::isActive();
$canUseOwnPicture = $admin['show_img_level'] >= 1
&& $member['grade'] >= 1
&& $member['picture'] != "";
$nationList = $db->query('SELECT nation,`name`,color,scout FROM nation'); $nationList = $db->query('SELECT nation,`name`,color,scout FROM nation');
shuffle($nationList); shuffle($nationList);
@@ -54,6 +62,7 @@ foreach (getCharacterList(false) as $id => [$name, $info]) {
<script> <script>
var hasGeneralID = <?= $generalID === null ? 'false' : 'true' ?>; var hasGeneralID = <?= $generalID === null ? 'false' : 'true' ?>;
var isCentennialAllStar = <?= $isCentennialAllStar ? 'true' : 'false' ?>;
var defaultStatTotal = <?= GameConst::$defaultStatTotal ?>; var defaultStatTotal = <?= GameConst::$defaultStatTotal ?>;
var defaultStatMin = <?= GameConst::$defaultStatMin ?>; var defaultStatMin = <?= GameConst::$defaultStatMin ?>;
var defaultStatMax = <?= GameConst::$defaultStatMax ?>; var defaultStatMax = <?= GameConst::$defaultStatMax ?>;
@@ -104,6 +113,27 @@ if ($gencount >= $admin['maxgeneral']) {
<small id="valid_until">(<span id="valid_until_text"></span>까지 유효)</small><small id="outdate_token">- 만료 -</small><br> <small id="valid_until">(<span id="valid_until_text"></span>까지 유효)</small><small id="outdate_token">- 만료 -</small><br>
<form class="card_holder"> <form class="card_holder">
</form> </form>
<?php if ($isCentennialAllStar && $generalID !== null) : ?>
<div id="reselect_picture_plate" class="picture_choice">
<strong>변경 후 전콘</strong>
<label>
<input type="radio" name="reselect_picture_source" value="selected" checked>
새로 선택할 장수 전콘
</label>
<label>
<input type="radio" name="reselect_picture_source" value="current">
<img width="32" height="32" src="<?= GetImageURL($currentGeneral['imgsvr']) ?>/<?= $currentGeneral['picture'] ?>" border="0">
현재 장수 전콘
</label>
<?php if ($canUseOwnPicture) : ?>
<label>
<input type="radio" name="reselect_picture_source" value="own">
<img width="32" height="32" src="<?= GetImageURL($member['imgsvr']) ?>/<?= $member['picture'] ?>" border="0">
내 원래 전콘
</label>
<?php endif; ?>
</div>
<?php endif; ?>
</div> </div>
</div> </div>
@@ -117,7 +147,28 @@ if ($gencount >= $admin['maxgeneral']) {
<form id='custom_form'> <form id='custom_form'>
<table class='tb_layout' style='width:100%;text-align:left;'> <table class='tb_layout' style='width:100%;text-align:left;'>
<?php <?php
if ($admin['show_img_level'] >= 1 && $member['grade'] >= 1 && $member['picture'] != "") { if ($isCentennialAllStar) {
echo "
<tr class='event_picture'>
<td align=right class='bg1'>전콘 선택</td>
<td colspan=2>
<label><input type=radio name=picture_source value=selected checked> 선택한 장수 전콘</label>
";
if ($canUseOwnPicture) {
$imageTemp = GetImageURL($member['imgsvr']);
echo "
<label>
<input type=radio name=picture_source value=own>
<img width='64' height='64' src='{$imageTemp}/{$member['picture']}' border='0'>
내 전콘
</label>
";
}
echo "
</td>
</tr>
";
} elseif ($canUseOwnPicture) {
$imageTemp = GetImageURL($member['imgsvr']); $imageTemp = GetImageURL($member['imgsvr']);
echo " echo "
<tr class='custom_picture'> <tr class='custom_picture'>
@@ -144,6 +195,7 @@ if ($gencount >= $admin['maxgeneral']) {
</select> <span id="charInfoText"></span> </select> <span id="charInfoText"></span>
</td> </td>
</tr> </tr>
<?php if (!$isCentennialAllStar) : ?>
<tr class='custom_stat'> <tr class='custom_stat'>
<td align=right class='bg1'>통솔</td> <td align=right class='bg1'>통솔</td>
<td colspan=2><input type="number" name="leadership" id="leadership" value="50"></td> <td colspan=2><input type="number" name="leadership" id="leadership" value="50"></td>
@@ -171,9 +223,14 @@ if ($gencount >= $admin['maxgeneral']) {
그 외의 능력치는 가입되지 않습니다.</font> 그 외의 능력치는 가입되지 않습니다.</font>
</td> </td>
</tr> </tr>
<?php endif; ?>
<tr> <tr>
<td align=center colspan=3> <td align=center colspan=3>
<?php if ($isCentennialAllStar) : ?>
선택한 장수의 최종 능력치 비율을 반영한 약화 능력치로 시작합니다.<br>
<?php else : ?>
<span class='custom_stat'>능력치의 총합은 <?= GameConst::$defaultStatTotal ?>입니다. 가입후 0~10의 능력치 보너스를 받게 됩니다.<br></span> <span class='custom_stat'>능력치의 총합은 <?= GameConst::$defaultStatTotal ?>입니다. 가입후 0~10의 능력치 보너스를 받게 됩니다.<br></span>
<?php endif; ?>
임의의 도시에서 재야로 시작하며 건국과 임관은 게임 내에서 실행합니다. 임의의 도시에서 재야로 시작하며 건국과 임관은 게임 내에서 실행합니다.
</td> </td>
</tr> </tr>
+29 -13
View File
@@ -32,6 +32,9 @@ type CardItem = {
leadership?: number, leadership?: number,
strength?: number, strength?: number,
intel?: number, intel?: number,
initialLeadership?: number,
initialStrength?: number,
initialIntel?: number,
dex?: number[], dex?: number[],
} }
@@ -43,6 +46,7 @@ type GeneralPoolResponse = {
declare const characterInfo: Record<string, { name: string, info: string }>; declare const characterInfo: Record<string, { name: string, info: string }>;
declare const hasGeneralID: number; declare const hasGeneralID: number;
declare const isCentennialAllStar: boolean;
declare let currentGeneralInfo: CardItem | undefined; declare let currentGeneralInfo: CardItem | undefined;
declare const cards: Record<string, CardItem>; declare const cards: Record<string, CardItem>;
declare const validCustomOption: string[]; declare const validCustomOption: string[];
@@ -54,6 +58,10 @@ const templateGeneralCard = '<div class="general_card">\
<%if(leadership){%>\ <%if(leadership){%>\
<%leadership%> / <%strength%> / <%intel%><br>\ <%leadership%> / <%strength%> / <%intel%><br>\
<%}%>\ <%}%>\
<%if(initialLeadership){%>\
<b>시작 능력치</b><br>\
<%initialLeadership%> / <%initialStrength%> / <%initialIntel%><br>\
<%}%>\
<%if(personalText){%><%personalText%><br><%}%>\ <%if(personalText){%><%personalText%><br><%}%>\
<%if(specialDomesticText||specialWarText){%>\ <%if(specialDomesticText||specialWarText){%>\
<%specialDomesticText%> / <%specialWarText%><br>\ <%specialDomesticText%> / <%specialWarText%><br>\
@@ -108,7 +116,10 @@ async function pickGeneral(this: HTMLElement, e: JQuery.Event) {
method: 'post', method: 'post',
responseType: 'json', responseType: 'json',
data: convertFormData({ data: convertFormData({
pick: unwrap_any<string>($btn.val()) pick: unwrap_any<string>($btn.val()),
picture_source: unwrap_any<string>(
$('input[name="reselect_picture_source"]:checked').val() ?? 'selected'
)
}) })
}); });
result = response.data; result = response.data;
@@ -138,22 +149,24 @@ async function buildGeneral(e: JQuery.Event) {
let result: InvalidResponse; let result: InvalidResponse;
try { try {
const formData: Record<string, string|number|boolean> = {
pick: unwrap(currentGeneralInfo).uniqueName,
use_own_picture: $('#use_own_picture').is(':checked'),
picture_source: unwrap_any<string>(
$('input[name="picture_source"]:checked').val() ?? 'selected'
),
personal: unwrap_any<string>($('#selChar').val())
};
if (!isCentennialAllStar) {
formData.leadership = parseInt(unwrap_any<string>($('#leadership').val()));
formData.strength = parseInt(unwrap_any<string>($('#leadership').val()));
formData.intel = parseInt(unwrap_any<string>($('#leadership').val()));
}
const response = await axios({ const response = await axios({
url: 'j_select_picked_general.php', url: 'j_select_picked_general.php',
method: 'post', method: 'post',
responseType: 'json', responseType: 'json',
data: convertFormData({ data: convertFormData(formData)
pick: unwrap(currentGeneralInfo).uniqueName,
use_own_picture: $('#use_own_picture').is(':checked'),
leadership: parseInt(unwrap_any<string>($('#leadership').val())),
strength: parseInt(unwrap_any<string>(
$(unwrap(currentGeneralInfo).event100Growth ? '#strength' : '#leadership').val()
)),
intel: parseInt(unwrap_any<string>(
$(unwrap(currentGeneralInfo).event100Growth ? '#intel' : '#leadership').val()
)),
personal: unwrap_any<string>($('#selChar').val())
})
}) })
result = response.data; result = response.data;
if (!result.result) { if (!result.result) {
@@ -202,6 +215,9 @@ function printGenerals(value: GeneralPoolResponse) {
'leadership': null, 'leadership': null,
'strength': null, 'strength': null,
'intel': null, 'intel': null,
'initialLeadership': null,
'initialStrength': null,
'initialIntel': null,
'personalText': null, 'personalText': null,
'specialDomesticText': null, 'specialDomesticText': null,
'specialWarText': null, 'specialWarText': null,
+74
View File
@@ -4,6 +4,9 @@ use PHPUnit\Framework\TestCase;
use sammo\CentennialAllStarGrowth; use sammo\CentennialAllStarGrowth;
use sammo\CentennialAllStarGrowthService; use sammo\CentennialAllStarGrowthService;
require_once __DIR__ . '/../hwe/sammo/ActionLogger.php';
require_once __DIR__ . '/../hwe/sammo/GameConstBase.php';
require_once __DIR__ . '/../hwe/d_setting/GameConst.php';
require_once __DIR__ . '/../hwe/sammo/CentennialAllStarGrowthService.php'; require_once __DIR__ . '/../hwe/sammo/CentennialAllStarGrowthService.php';
final class CentennialAllStarGrowthTest extends TestCase final class CentennialAllStarGrowthTest extends TestCase
@@ -65,6 +68,77 @@ final class CentennialAllStarGrowthTest extends TestCase
); );
} }
public function testUserInitialStatsPreserveCandidateShapeAtOrdinaryTotal(): void
{
$initial = CentennialAllStarGrowthService::calculateUserInitialStats([
'leadership' => 80,
'strength' => 70,
'intel' => 50,
]);
self::assertSame([
'leadership' => 65,
'strength' => 58,
'intel' => 42,
], $initial);
self::assertSame(165, array_sum($initial));
self::assertLessThanOrEqual(80, $initial['leadership']);
self::assertLessThanOrEqual(70, $initial['strength']);
self::assertLessThanOrEqual(50, $initial['intel']);
}
public function testUserInitialStatsDoNotRaiseCandidateBelowOrdinaryTotal(): void
{
self::assertSame([
'leadership' => 60,
'strength' => 45,
'intel' => 30,
], CentennialAllStarGrowthService::calculateUserInitialStats([
'leadership' => 60,
'strength' => 45,
'intel' => 30,
]));
}
public function testInitialUserGrantMakesInitialAllocationReplaceable(): void
{
$initial = CentennialAllStarGrowthService::calculateUserInitialStats([
'uniqueName' => 'A1000001',
'leadership' => 80,
'strength' => 70,
'intel' => 50,
]);
$aux = CentennialAllStarGrowthService::initialAux([
'uniqueName' => 'A1000001',
], $initial);
self::assertSame('A1000001', $aux['targetId']);
self::assertSame($initial, $aux['userInitialStats']);
self::assertSame(50, $aux['granted']['leadership']);
self::assertSame(43, $aux['granted']['strength']);
self::assertSame(27, $aux['granted']['intel']);
}
public function testLegacyInitialGrantKeepsOnlyGrowthBeyondCreationRange(): void
{
self::assertSame(
35,
CentennialAllStarGrowthService::calculateLegacyUserGrant(50, 0)
);
self::assertSame(
75,
CentennialAllStarGrowthService::calculateLegacyUserGrant(90, 40)
);
self::assertSame(
105,
CentennialAllStarGrowthService::calculateLegacyUserGrant(140, 40)
);
self::assertSame(
35,
140 - CentennialAllStarGrowthService::calculateLegacyUserGrant(140, 40)
);
}
public function testReselectDropsOldGrantAndKeepsOrganicGrowth(): void public function testReselectDropsOldGrantAndKeepsOrganicGrowth(): void
{ {
self::assertSame( self::assertSame(