Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update version to 1.2.19: Add unit & rank up #81

Merged
merged 11 commits into from
Dec 30, 2023
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "last-origin-unit-viewer",
"version": "1.2.18",
"version": "1.2.19",
"private": true,
"license": "UNLICENSED",
"author": "harry0000 <https://github.com/harry0000>",
Expand Down
Binary file added public/unit_icon/241.webp
Binary file not shown.
111 changes: 65 additions & 46 deletions src/component/skill/SkillEffectConditionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ import {
ActivationTargetState,
ArmoredBulgasari,
DefenderAndArmoredBulgasari,
DefenderAndCyclopsPrincess,
InSquadTaggedUnitState,
NumOfUnitsInSquadState,
NumOfCrossAdjacentCondition,
NumOfDefenderAndArmoredBulgasariCondition,
NumOfUnitsCompareToEnemiesCondition,
SelfSkillEffectActivationCondition,
SelfSkillEffectActivationState,
SkillEffectActivationCondition,
SkillEffectActivationState,
TargetSkillEffectActivationCondition,
UnitAliasAndRole,
isDefenderAndArmoredBulgasari,
isUnitsInSquadCondition
isDefenderAndCyclopsPrincess,
isUnitsInSquadCondition,
isNumOfCrossAdjacentCondition,
isNumOfDefenderAndArmoredBulgasariCondition,
isNumOfUnitsCompareToEnemiesCondition
} from '../../domain/skill/SkillEffectActivationCondition';
import { Effect } from '../../domain/Effect';
import { EffectActivationState } from '../../domain/EffectActivationState';
Expand All @@ -35,7 +42,7 @@ import { SkillEffect, isTargetSkillEffect } from '../../domain/skill/UnitSkills'
import { SkillEffectScaleFactor } from '../../domain/skill/SkillEffectScaleFactor';
import { SkillEffectTarget } from '../../domain/skill/SkillEffectTarget';
import { UnitAlias, isUnitAlias, unitNumbersForAlias } from '../../domain/UnitAlias';
import { UnitNumber, UnitRole, UnitType } from '../../domain/UnitBasicInfo';
import { UnitKind, UnitNumber, UnitRole, UnitType } from '../../domain/UnitBasicInfo';

import SkillEffectConditionViewModel from './SkillEffectConditionViewModel';

Expand Down Expand Up @@ -167,32 +174,34 @@ function affectedByStateView(

function unitStateView(key: typeof EffectActivationState.InSquad, state: ValueOf<ActivationSquadState, typeof EffectActivationState.InSquad> | ReadonlyArray<UnitNumber>, selfUnitNumber: UnitNumber, t: TFunction): Exclude<ReactNode, undefined>
function unitStateView(key: typeof EffectActivationState.NotInSquad, state: ValueOf<ActivationSquadState, typeof EffectActivationState.NotInSquad> | 41, selfUnitNumber: UnitNumber, t: TFunction): Exclude<ReactNode, undefined>
function unitStateView(key: typeof EffectActivationState.Unit, state: typeof UnitAlias.SteelLine | typeof UnitType.Flying, selfUnitNumber: UnitNumber, t: TFunction): Exclude<ReactNode, undefined>
function unitStateView(key: typeof EffectActivationState.Unit, state: typeof UnitAlias.SteelLine | typeof UnitAlias.OrbitalWatcher | typeof UnitType.Flying, selfUnitNumber: UnitNumber, t: TFunction): Exclude<ReactNode, undefined>
function unitStateView(
key: typeof EffectActivationState['InSquad' | 'NotInSquad' | 'Unit' | 'NumOfUnitsLessThanEnemies'],
key: typeof EffectActivationState['InSquad' | 'NotInSquad' | 'Unit'],
state:
UnitNumber |
ReadonlyArray<UnitNumber> |
UnitAliasAndRole<typeof UnitAlias['SteelLine' | 'AACannonier'], typeof UnitRole.Supporter> |
UnitAliasAndRole<typeof UnitAlias['MongooseTeam'], typeof UnitRole.Defender> |
UnitAliasAndRole<typeof UnitAlias.Strikers, typeof UnitRole.Attacker> |
typeof UnitAlias[
'ElectricActive' |
'SteelLine' |
'SteelLineOfficerRanks' |
'SteelLineExcludingOfficerRanks' |
'Horizon' |
'Kunoichi' |
'OrbitalWatcher' |
'DEntertainment' |
'KouheiChurch' |
'EmpressHound' |
'Mermaid'
] |
typeof SkillAreaType.CrossAdjacent |
typeof UnitKind.AGS |
UnitType |
UnitRole |
ArmoredBulgasari |
DefenderAndArmoredBulgasari |
DefenderAndCyclopsPrincess |
'golden_factory' |
{ equipment: 'hot_pack', effect: typeof Effect.MinimumIceResistUp } |
{ [EffectActivationState.AffectedBy]: { unit: 83, effect: typeof Effect.TargetProtect } } |
Expand All @@ -212,6 +221,8 @@ function unitStateView(
let unit;
if (isDefenderAndArmoredBulgasari(state)) {
unit = `${t(`effect:unit.${state[0]}`)}${t('effect:unit_separator')}${t(`effect:unit.${state[1]}`)}`;
} else if (isDefenderAndCyclopsPrincess(state)) {
unit = `${t(`effect:unit.${state[0]}`)}${t('effect:unit_separator')}${unitName(state[1])}`;
} else {
const separator =
key === EffectActivationState.NotInSquad ?
Expand All @@ -235,11 +246,11 @@ function unitStateView(
</React.Fragment>
);
} else {
const isTypeOrRole = state !== 'cross_adjacent' && state !== 'golden_factory';
const isUnitBasicInfo = state !== 'cross_adjacent' && state !== 'armored_bulgasari' && state !== 'golden_factory';
return (
<React.Fragment>
{ifTruthy(
isSquadCond && isTypeOrRole,
isSquadCond && isUnitBasicInfo,
(<span>{t('effect:unit.self')}{t('effect:except_preposition')}</span>)
)}
<span>{t(`effect:condition.state.${key}`, { unit: t(`effect:unit.${state}`) })}</span>
Expand Down Expand Up @@ -317,38 +328,29 @@ const SelfAndTargetStateView: React.FC<{
);
};

type NumOfCrossAdjacent = Extract<NumOfUnitsInSquadState['num_of_units'], { unit: typeof SkillAreaType.CrossAdjacent }>

function isNumOfCrossAdjacent(
arg: NumOfUnitsInSquadState['num_of_units']
): arg is NumOfCrossAdjacent {
return arg.unit === SkillAreaType.CrossAdjacent;
}

type NumOfDefenderAndArmoredBulgasari = Extract<NumOfUnitsInSquadState['num_of_units'], { unit: DefenderAndArmoredBulgasari }>

function isNumOfDefenderAndArmoredBulgasari(
arg: NumOfUnitsInSquadState['num_of_units']
): arg is NumOfDefenderAndArmoredBulgasari {
return isDefenderAndArmoredBulgasari(arg.unit);
}

const SquadStateView: React.FC<{
state: ValueOf<SkillEffectActivationState, 'squad'>,
unitNumber: UnitNumber
}> = ({ state, unitNumber }) => {
const { t } = useTranslation();

const numOfCrossAdjacent = (state: NumOfCrossAdjacent): string => {
const numOfCrossAdjacent = (state: NumOfCrossAdjacentCondition): string => {
return 'equal' in state ?
t('effect:condition.state.cross_adjacent_eq', state) :
'less_or_equal' in state ?
t('effect:condition.state.cross_adjacent', state) :
t('effect:condition.state.cross_adjacent_ge', state);
};
const numOfDefenderAndArmoredBulgasari = (state: NumOfDefenderAndArmoredBulgasari): string => {
const numOfDefenderAndArmoredBulgasari = (state: NumOfDefenderAndArmoredBulgasariCondition): string => {
return t('effect:condition.state.num_of_defender_armored_bulgasari', state);
};
const numOfUnitsCompareEnemies = (state: NumOfUnitsCompareToEnemiesCondition): string => {
return 'less_than' in state ?
t('effect:condition.state.num_of_allies_lt_enemies', state) :
'greater_than' in state ?
t('effect:condition.state.num_of_units_gt_enemies', state) :
t('effect:condition.state.num_of_units_le_enemies', state);
};

if (isReadonlyArray(state)) {
return (
Expand All @@ -365,10 +367,14 @@ const SquadStateView: React.FC<{
}
</React.Fragment>
);
} else if (
EffectActivationState.NumOfUnits in state &&
isNumOfUnitsCompareToEnemiesCondition(state.num_of_units)
) {
return (<span>{numOfUnitsCompareEnemies(state.num_of_units)}</span>);
} else {
return EffectActivationState.NumOfUnitsLessThanEnemies in state ?
(<span>{t(`effect:condition.state.${EffectActivationState.NumOfUnitsLessThanEnemies}`)}</span>) :
(<React.Fragment>
return (
<React.Fragment>
{t('effect:condition.target.squad')}
{
typedEntries(state).map((entry, i, array) => {
Expand All @@ -393,22 +399,34 @@ const SquadStateView: React.FC<{
);
case EffectActivationState.NumOfUnits: {
const squadState = entry[1];
return (
<React.Fragment key={entry[0]}>
{
isNumOfCrossAdjacent(squadState) ?
numOfCrossAdjacent(squadState) :
isNumOfDefenderAndArmoredBulgasari(squadState) ?
numOfDefenderAndArmoredBulgasari(squadState) :
'equal' in squadState ?
t('effect:condition.state.num_of_units_eq', squadState) :
'greater_or_equal' in squadState ?
t('effect:condition.state.num_of_units_ge', squadState) :
t('effect:condition.state.num_of_units_le', squadState)
}
<Separator />
</React.Fragment>
);
if (isNumOfCrossAdjacentCondition(squadState)) {
return (
<React.Fragment key={entry[0]}>
{numOfCrossAdjacent(squadState)}
<Separator />
</React.Fragment>
);
} else if (isNumOfDefenderAndArmoredBulgasariCondition(squadState)) {
return (
<React.Fragment key={entry[0]}>
{numOfDefenderAndArmoredBulgasari(squadState)}
<Separator />
</React.Fragment>
);
} else {
return (
<React.Fragment key={entry[0]}>
{
'equal' in squadState ?
t('effect:condition.state.num_of_units_eq', squadState) :
'greater_or_equal' in squadState ?
t('effect:condition.state.num_of_units_ge', squadState) :
t('effect:condition.state.num_of_units_le', squadState)
}
<Separator />
</React.Fragment>
);
}
}
default: {
const _exhaustiveCheck: never = entry;
Expand All @@ -417,7 +435,8 @@ const SquadStateView: React.FC<{
}
})
}
</React.Fragment>);
</React.Fragment>
);
}
};

Expand Down
60 changes: 31 additions & 29 deletions src/component/skill/SkillEffectTargetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,54 @@ import { UnitNumber } from '../../domain/UnitBasicInfo';
import { isUnitAlias } from '../../domain/UnitAlias';

import { ifNonNullable } from '../../util/react';
import { isReadonlyArray } from '../../util/type';

const SkillEffectTargetView: React.FC<{
target: SkillEffectTarget,
selfUnitNumber: UnitNumber
}> = ({ target, selfUnitNumber }) => {
const { t } = useTranslation();
function unitName(unit: UnitNumber): string {
return t('effect:with_quotes', { value: t('unit:display', { number: unit }) });
}

const hasConditions = 'conditions' in target && target.conditions;
const except = 'except' in target ? target.except : undefined;
const exceptUnit =
target.kind === SkillEffectTargetKind.AllyExceptSelf ?
selfUnitNumber :
!isReadonlyArray(except) ?
except :
undefined;

return (
<React.Fragment>
{t(`effect:effect.target.kind.${target.kind}`)}
{'conditions' in target ? t('effect:of_preposition') : null}
{hasConditions || !!except ? t('effect:of_preposition') : null}
{ifNonNullable(
except,
v => (
<React.Fragment>
{isReadonlyArray(v) ?
`${unitName(v[0])}${t('effect:unit_separator')}${unitName(v[1])}` :
unitName(v)}
{t('effect:except_preposition')}
{hasConditions ? null : t('effect:unit.unit')}
</React.Fragment>
)
)}
{
'conditions' in target && target.conditions ?
hasConditions ?
target.conditions.map((cond, i, arr ) => {
const separator: string = ++i < arr.length ? t('effect:unit_separator') : '';

if (typeof cond === 'number') {
return t('effect:with_quotes', { value: t('unit:display', { number: cond }) }) + separator;
return unitName(cond) + separator;
} else if (typeof cond === 'string') {
return isUnitAlias(cond) ?
(
<React.Fragment key={cond}>
<UnitAliasView unitAlias={cond} exceptUnit={target.kind === SkillEffectTargetKind.AllyExceptSelf ? selfUnitNumber : undefined} />
<UnitAliasView unitAlias={cond} exceptUnit={exceptUnit} />
{separator}
</React.Fragment>
) :
Expand All @@ -45,24 +70,10 @@ const SkillEffectTargetView: React.FC<{
'role' in cond ?
t(`effect:unit.${cond.role}`) :
null;
const except = 'except' in cond ? cond.except : undefined;

return (
<React.Fragment key={JSON.stringify(cond)}>
{ifNonNullable(
except,
v => (
<React.Fragment>
{
v === selfUnitNumber ?
t('effect:unit.self') :
t('effect:with_quotes', { value: t('unit:display', { number: v }) })
}
{t('effect:except_preposition')}
</React.Fragment>
)
)}
<UnitAliasView unitAlias={cond.alias} exceptUnit={except} />
<UnitAliasView unitAlias={cond.alias} exceptUnit={exceptUnit} />
{unit ? t('effect:of_preposition') : null}
{unit}
{separator}
Expand All @@ -71,7 +82,7 @@ const SkillEffectTargetView: React.FC<{
} else if ('not_alias' in cond) {
return (
<React.Fragment key={JSON.stringify(cond)}>
<UnitAliasView unitAlias={cond.not_alias} />
<UnitAliasView unitAlias={cond.not_alias} exceptUnit={exceptUnit} />
{t('effect:negative_form')}
{'type' in cond ?
t(`effect:unit.${cond.type}`) :
Expand All @@ -81,15 +92,6 @@ const SkillEffectTargetView: React.FC<{
{separator}
</React.Fragment>
);
} else if ('except' in cond) {
return (
<React.Fragment key={JSON.stringify(cond)}>
{t('effect:with_quotes', { value: t('unit:display', { number: cond.except }) })}
{t('effect:except_preposition')}
{t('effect:unit.unit')}
{separator}
</React.Fragment>
);
} else {
return t(`effect:unit.${cond.type}`) + t(`effect:unit.${cond.role}`) + separator;
}
Expand Down
Loading