zenno: add support card level and effect functonality
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed

This commit is contained in:
2026-08-13 17:39:23 -04:00
parent 1fe397ca81
commit bde080eadd
10 changed files with 2547 additions and 32 deletions
+411
View File
@@ -0,0 +1,411 @@
import * as support from './support';
import { describe, expect, test } from 'vitest';
import * as bakushinR from './testdata/support-10018-bakushin-R.json';
import * as bakushinSR from './testdata/support-20042-bakushin-SR.json';
import * as bakushinSSR from './testdata/support-30083-bakushin-SSR.json';
test.for([
{ rarity: support.Rarity.R, exp: 0, level: 1 },
{ rarity: support.Rarity.R, exp: 5, level: 2 },
{ rarity: support.Rarity.R, exp: 10, level: 2 },
{ rarity: support.Rarity.R, exp: 40510, level: 40 },
{ rarity: support.Rarity.SR, exp: 0, level: 1 },
{ rarity: support.Rarity.SR, exp: 10, level: 2 },
{ rarity: support.Rarity.SR, exp: 20, level: 2 },
{ rarity: support.Rarity.SR, exp: 74990, level: 45 },
{ rarity: support.Rarity.SSR, exp: 0, level: 1 },
{ rarity: support.Rarity.SSR, exp: 15, level: 2 },
{ rarity: support.Rarity.SSR, exp: 30, level: 2 },
{ rarity: support.Rarity.SSR, exp: 118185, level: 50 },
])('level($rarity, $exp) -> $level', ({ rarity, exp, level }) => expect(support.level(rarity, exp)).toBe(level));
describe('effects', () => {
describe('Bakushin R', () => {
test('level 1', () => {
const got = support.effects(bakushinR as support.SupportCard, 1);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 5 },
{ type: support.EffectType.MoodEffect, value: 0 },
{ type: support.EffectType.SpeedBonus, value: 0 },
{ type: support.EffectType.InitialSpeed, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 1 },
{ type: support.EffectType.SpecialtyPriority, value: 0 },
],
});
});
test('level 2', () => {
const got = support.effects(bakushinR as support.SupportCard, 2);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 5 },
{ type: support.EffectType.MoodEffect, value: 0 },
{ type: support.EffectType.SpeedBonus, value: 0 },
{ type: support.EffectType.InitialSpeed, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 1 },
{ type: support.EffectType.SpecialtyPriority, value: 0 },
],
});
});
test('level 5', () => {
const got = support.effects(bakushinR as support.SupportCard, 5);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 6 },
{ type: support.EffectType.MoodEffect, value: 10 },
{ type: support.EffectType.SpeedBonus, value: 0 },
{ type: support.EffectType.InitialSpeed, value: 10 },
{ type: support.EffectType.InitialFriendship, value: 10 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 2 },
{ type: support.EffectType.SpecialtyPriority, value: 5 },
],
});
});
test('level 6', () => {
const got = support.effects(bakushinR as support.SupportCard, 6);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 6 },
{ type: support.EffectType.MoodEffect, value: 10 },
{ type: support.EffectType.SpeedBonus, value: 0 },
{ type: support.EffectType.InitialSpeed, value: 10 },
{ type: support.EffectType.InitialFriendship, value: 10 },
{ type: support.EffectType.RaceBonus, value: 2 },
{ type: support.EffectType.FanBonus, value: 3 },
{ type: support.EffectType.SpecialtyPriority, value: 6 },
],
});
});
test('level 39', () => {
const got = support.effects(bakushinR as support.SupportCard, 39);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 15 },
{ type: support.EffectType.MoodEffect, value: 25 },
{ type: support.EffectType.SpeedBonus, value: 0 },
{ type: support.EffectType.InitialSpeed, value: 20 },
{ type: support.EffectType.InitialFriendship, value: 20 },
{ type: support.EffectType.RaceBonus, value: 5 },
{ type: support.EffectType.FanBonus, value: 14 },
{ type: support.EffectType.SpecialtyPriority, value: 35 },
],
});
});
test('level 40', () => {
const got = support.effects(bakushinR as support.SupportCard, 40);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 15 },
{ type: support.EffectType.MoodEffect, value: 25 },
{ type: support.EffectType.SpeedBonus, value: 1 },
{ type: support.EffectType.InitialSpeed, value: 20 },
{ type: support.EffectType.InitialFriendship, value: 20 },
{ type: support.EffectType.RaceBonus, value: 5 },
{ type: support.EffectType.FanBonus, value: 15 },
{ type: support.EffectType.SpecialtyPriority, value: 35 },
],
});
});
});
describe('Bakushin SR', () => {
test('level 1', () => {
const got = support.effects(bakushinSR as support.SupportCard, 1);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 10 },
{ type: support.EffectType.PowerBonus, value: 0 },
{ type: support.EffectType.TrainingEffectiveness, value: 0 },
{ type: support.EffectType.InitialPower, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 1 },
{ type: support.EffectType.SpecialtyPriority, value: 0 },
],
});
});
test('level 2', () => {
const got = support.effects(bakushinSR as support.SupportCard, 2);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 10 },
{ type: support.EffectType.PowerBonus, value: 0 },
{ type: support.EffectType.TrainingEffectiveness, value: 0 },
{ type: support.EffectType.InitialPower, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 1 },
{ type: support.EffectType.SpecialtyPriority, value: 0 },
],
});
});
test('level 5', () => {
const got = support.effects(bakushinSR as support.SupportCard, 5);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 11 },
{ type: support.EffectType.PowerBonus, value: 0 },
{ type: support.EffectType.TrainingEffectiveness, value: 0 },
{ type: support.EffectType.InitialPower, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 3 },
{ type: support.EffectType.SpecialtyPriority, value: 5 },
],
});
});
test('level 24', () => {
const got = support.effects(bakushinSR as support.SupportCard, 24);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 15 },
{ type: support.EffectType.PowerBonus, value: 0 },
{ type: support.EffectType.TrainingEffectiveness, value: 5 },
{ type: support.EffectType.InitialPower, value: 15 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 4 },
{ type: support.EffectType.FanBonus, value: 14 },
{ type: support.EffectType.SpecialtyPriority, value: 33 },
],
});
});
test('level 25', () => {
const got = support.effects(bakushinSR as support.SupportCard, 25);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 15 },
{ type: support.EffectType.PowerBonus, value: 0 },
{ type: support.EffectType.TrainingEffectiveness, value: 5 },
{ type: support.EffectType.InitialPower, value: 15 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 5 },
{ type: support.EffectType.FanBonus, value: 15 },
{ type: support.EffectType.SpecialtyPriority, value: 35 },
],
unique: {
name: 'The Perfect Morning Greeting!',
description: 'Friendship Bonus and Specialty Priority',
card_level: 25,
type: support.EffectType.FriendshipBonus,
args: [10],
type2: support.EffectType.SpecialtyPriority,
args2: [20],
idle_mode_sub_rate: 0,
} satisfies support.UniqueEffect,
});
});
test('level 44', () => {
const got = support.effects(bakushinSR as support.SupportCard, 44);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 20 },
{ type: support.EffectType.PowerBonus, value: 0 },
{ type: support.EffectType.TrainingEffectiveness, value: 5 },
{ type: support.EffectType.InitialPower, value: 20 },
{ type: support.EffectType.InitialFriendship, value: 20 },
{ type: support.EffectType.RaceBonus, value: 9 },
{ type: support.EffectType.FanBonus, value: 19 },
{ type: support.EffectType.SpecialtyPriority, value: 49 },
],
unique: {
name: 'The Perfect Morning Greeting!',
description: 'Friendship Bonus and Specialty Priority',
card_level: 25,
type: support.EffectType.FriendshipBonus,
args: [10],
type2: support.EffectType.SpecialtyPriority,
args2: [20],
idle_mode_sub_rate: 0,
} satisfies support.UniqueEffect,
});
});
test('level 45', () => {
const got = support.effects(bakushinSR as support.SupportCard, 45);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 20 },
{ type: support.EffectType.PowerBonus, value: 1 },
{ type: support.EffectType.TrainingEffectiveness, value: 5 },
{ type: support.EffectType.InitialPower, value: 20 },
{ type: support.EffectType.InitialFriendship, value: 20 },
{ type: support.EffectType.RaceBonus, value: 10 },
{ type: support.EffectType.FanBonus, value: 20 },
{ type: support.EffectType.SpecialtyPriority, value: 50 },
],
unique: {
name: 'The Perfect Morning Greeting!',
description: 'Friendship Bonus and Specialty Priority',
card_level: 25,
type: support.EffectType.FriendshipBonus,
args: [10],
type2: support.EffectType.SpecialtyPriority,
args2: [20],
idle_mode_sub_rate: 0,
} satisfies support.UniqueEffect,
});
});
})
describe('Bakushin SSR', () => {
test('level 1', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 1);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 10 },
{ type: support.EffectType.MoodEffect, value: 0 },
{ type: support.EffectType.GutsBonus, value: 0 },
{ type: support.EffectType.InitialGuts, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 1 },
{ type: support.EffectType.HintLevels, value: 1 },
{ type: support.EffectType.HintFrequency, value: 5 },
],
});
});
test('level 2', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 2);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 10 },
{ type: support.EffectType.MoodEffect, value: 0 },
{ type: support.EffectType.GutsBonus, value: 0 },
{ type: support.EffectType.InitialGuts, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 1 },
{ type: support.EffectType.HintLevels, value: 1 },
{ type: support.EffectType.HintFrequency, value: 6 },
],
});
});
test('level 5', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 5);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 12 },
{ type: support.EffectType.MoodEffect, value: 0 },
{ type: support.EffectType.GutsBonus, value: 0 },
{ type: support.EffectType.InitialGuts, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 0 },
{ type: support.EffectType.RaceBonus, value: 1 },
{ type: support.EffectType.FanBonus, value: 2 },
{ type: support.EffectType.HintLevels, value: 1 },
{ type: support.EffectType.HintFrequency, value: 9 },
],
});
});
test('level 10', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 10);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 14 },
{ type: support.EffectType.MoodEffect, value: 0 },
{ type: support.EffectType.GutsBonus, value: 0 },
{ type: support.EffectType.InitialGuts, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 10 },
{ type: support.EffectType.RaceBonus, value: 2 },
{ type: support.EffectType.FanBonus, value: 5 },
{ type: support.EffectType.HintLevels, value: 1 },
{ type: support.EffectType.HintFrequency, value: 15 },
],
});
});
test('level 11', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 11);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 15 },
{ type: support.EffectType.MoodEffect, value: 0 },
{ type: support.EffectType.GutsBonus, value: 0 },
{ type: support.EffectType.InitialGuts, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 10 },
{ type: support.EffectType.RaceBonus, value: 2 },
{ type: support.EffectType.FanBonus, value: 5 },
{ type: support.EffectType.HintLevels, value: 1 },
{ type: support.EffectType.HintFrequency, value: 17 },
],
});
});
test('level 30', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 30);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 25 },
{ type: support.EffectType.MoodEffect, value: 20 },
{ type: support.EffectType.GutsBonus, value: 0 },
{ type: support.EffectType.InitialGuts, value: 0 },
{ type: support.EffectType.InitialFriendship, value: 20 },
{ type: support.EffectType.RaceBonus, value: 5 },
{ type: support.EffectType.FanBonus, value: 15 },
{ type: support.EffectType.HintLevels, value: 2 },
{ type: support.EffectType.HintFrequency, value: 40 },
],
unique: {
name: 'Super! Sonic! Flower Power!',
description: 'Increases Training Effectiveness when not on preferred training. (Friendship 80+)',
card_level: 30,
type: support.EffectType.UniqueOffSpecialty,
args: [80, 20],
idle_mode_sub_rate: 30,
} satisfies support.UniqueEffect,
});
});
test('level 49', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 49);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 34 },
{ type: support.EffectType.MoodEffect, value: 30 },
{ type: support.EffectType.GutsBonus, value: 1 },
{ type: support.EffectType.InitialGuts, value: 27 },
{ type: support.EffectType.InitialFriendship, value: 25 },
{ type: support.EffectType.RaceBonus, value: 9 },
{ type: support.EffectType.FanBonus, value: 19 },
{ type: support.EffectType.HintLevels, value: 2 },
{ type: support.EffectType.HintFrequency, value: 49 },
],
unique: {
name: 'Super! Sonic! Flower Power!',
description: 'Increases Training Effectiveness when not on preferred training. (Friendship 80+)',
card_level: 30,
type: support.EffectType.UniqueOffSpecialty,
args: [80, 20],
idle_mode_sub_rate: 30,
} satisfies support.UniqueEffect,
});
});
test('level 50', () => {
const got = support.effects(bakushinSSR as support.SupportCard, 50);
expect(got).toStrictEqual({
effects: [
{ type: support.EffectType.FriendshipBonus, value: 35 },
{ type: support.EffectType.MoodEffect, value: 30 },
{ type: support.EffectType.GutsBonus, value: 1 },
{ type: support.EffectType.InitialGuts, value: 30 },
{ type: support.EffectType.InitialFriendship, value: 25 },
{ type: support.EffectType.RaceBonus, value: 10 },
{ type: support.EffectType.FanBonus, value: 20 },
{ type: support.EffectType.HintLevels, value: 3 },
{ type: support.EffectType.HintFrequency, value: 50 },
],
unique: {
name: 'Super! Sonic! Flower Power!',
description: 'Increases Training Effectiveness when not on preferred training. (Friendship 80+)',
card_level: 30,
type: support.EffectType.UniqueOffSpecialty,
args: [80, 20],
idle_mode_sub_rate: 30,
} satisfies support.UniqueEffect,
});
});
});
});
+142
View File
@@ -1,3 +1,5 @@
import * as support_card_level from './support_card_level.json';
/** /**
* Support card definitions. * Support card definitions.
*/ */
@@ -224,3 +226,143 @@ export const SPECIALTY_ICON = {
Pal: 5, Pal: 5,
Group: 6, Group: 6,
} as const; } as const;
/**
* Compute the current effects of a support card at a given level.
* @param card Support card to compute current effects for.
* @param level Experience level of the support card.
* @returns Current support card effects and its unique effect if active at the given level.
*/
export function effects(card: SupportCard, level: number) {
const j = Math.floor(level / 5);
const k = Math.ceil(level / 5);
const effects = card.effects[0].map((e) => {
const s = card.effects.map((l) => l.find((f) => f.type === e.type)!);
const left = s.findLastIndex((f, i) => i <= j && f.value >= 0);
const right = s.findIndex((f, i) => k <= i && f.value >= 0);
if (left < 0) {
// Effect isn't yet active.
return { type: e.type, value: 0 };
}
if (right < 0) {
// Effect is at its maximum value.
return { type: e.type, value: s[left]!.value };
}
if (left === right) {
// The current level is an interpolation boundary.
// Don't try to interpolate.
return { type: e.type, value: s[left]!.value };
}
const xl = Math.max(left * 5, 1);
const xr = Math.max(right * 5, 1);
const lo = s[left]!.value;
const hi = s[right]!.value;
const value = Math.floor((hi * (level - xl) + lo * (xr - level)) / (xr - xl));
return { type: e.type, value };
});
if (card.unique_effect != null && level >= card.unique_effect.card_level) {
return { effects, unique: card.unique_effect }
}
return { effects };
}
export function level(rarity: Rarity, exp: number) {
// We use the ! assertion because we want an error when exp is nonsense.
return support_card_level.levels.findLast((l) => l.rarity === rarity && l.exp <= exp)!.level;
}
function singleUniqueEffects(type: EffectType, args: number[], deck: SupportCard[]): Effect[] {
switch (type) {
case EffectType.FriendshipBonus:
case EffectType.MoodEffect:
case EffectType.SpeedBonus:
case EffectType.StaminaBonus:
case EffectType.PowerBonus:
case EffectType.GutsBonus:
case EffectType.WitBonus:
case EffectType.TrainingEffectiveness:
case EffectType.InitialSpeed:
case EffectType.InitialStamina:
case EffectType.InitialPower:
case EffectType.InitialGuts:
case EffectType.InitialWit:
case EffectType.InitialFriendship:
case EffectType.RaceBonus:
case EffectType.FanBonus:
case EffectType.HintLevels:
case EffectType.HintFrequency:
case EffectType.SpecialtyPriority:
case EffectType.MaxSpeed:
case EffectType.MaxStamina:
case EffectType.MaxPower:
case EffectType.MaxGuts:
case EffectType.MaxWit:
case EffectType.EventRecovery:
case EffectType.EventEffectiveness:
case EffectType.FailureProtection:
case EffectType.EnergyCostReduction:
case EffectType.MinigameEffectiveness:
case EffectType.SkillPointBonus:
case EffectType.WitFriendshipRecovery:
return [{ type, value: args[0] }];
case EffectType.UniqueFriendshipThreshold:
return [{ type: args[1], value: args[2] }, { type: args[3], value: args[4] }];
case EffectType.UniqueOffSpecialty:
return [{ type: EffectType.TrainingEffectiveness, value: args[1] }];
case EffectType.UniqueColorThresholdTrainingEffectiveness:
if (new Set(deck.map((card) => card.specialty)).size < args[0]) {
return [];
}
return [{ type: EffectType.TrainingEffectiveness, value: args[1] }];
case EffectType.UniqueFanScalingTrainingEffectiveness:
return [{ type: EffectType.TrainingEffectiveness, value: args[1] }]
case EffectType.UniqueColorInitialStats:
return deck.flatMap((card) => {
switch (card.specialty) {
case Specialty.Speed:
return [{ type: EffectType.InitialSpeed, value: args[0] }];
case Specialty.Power:
return [{ type: EffectType.InitialPower, value: args[0] }];
case Specialty.Guts:
return [{ type: EffectType.InitialGuts, value: args[0] }];
case Specialty.Stamina:
return [{ type: EffectType.InitialStamina, value: args[0] }];
case Specialty.Wit:
return [{ type: EffectType.InitialWit, value: args[0] }];
default:
return [
{ type: EffectType.InitialSpeed, value: args[1] },
{ type: EffectType.InitialStamina, value: args[1] },
{ type: EffectType.InitialPower, value: args[1] },
{ type: EffectType.InitialGuts, value: args[1] },
{ type: EffectType.InitialWit, value: args[1] },
];
}
})
case EffectType.UniqueRainbowStack:
return [{ type: args[1], value: args[0] * args[2] }];
case EffectType.UniqueLowEnergyScaling:
return [{ type: args[0], value: args[3] }];
case EffectType.UniqueMaxEnergyScaling:
return [{ type: args[0], value: args[4] }];
case EffectType.UniqueTotalFriendship:
return [{ type: args[0], value: 600 / args[1] }];
case EffectType.UniqueCardCount:
return [{ type: args[0], value: 5 * args[1] }];
case EffectType.UniqueFacilityLevel:
return [{ type: args[0], value: 5 * args[1] }];
case EffectType.UniqueDisableFailure:
return [];
case EffectType.UniqueFriendshipTrainingAdditionalEffect:
return [{ type: args[0], value: args[1] }];
}
}
export function flattenUnique(unique: UniqueEffect, deck: SupportCard[]): Effect[] {
const r = [...singleUniqueEffects(unique.type, unique.args, deck)];
if (unique.type2 != null) {
r.push(...singleUniqueEffects(unique.type2, unique.args2!, deck));
}
return r;
}
+679
View File
@@ -0,0 +1,679 @@
{
"levels": [
{
"rarity": 1,
"exp": 0,
"level": 1
},
{
"rarity": 1,
"exp": 5,
"level": 2
},
{
"rarity": 1,
"exp": 15,
"level": 3
},
{
"rarity": 1,
"exp": 30,
"level": 4
},
{
"rarity": 1,
"exp": 50,
"level": 5
},
{
"rarity": 1,
"exp": 80,
"level": 6
},
{
"rarity": 1,
"exp": 120,
"level": 7
},
{
"rarity": 1,
"exp": 170,
"level": 8
},
{
"rarity": 1,
"exp": 230,
"level": 9
},
{
"rarity": 1,
"exp": 310,
"level": 10
},
{
"rarity": 1,
"exp": 410,
"level": 11
},
{
"rarity": 1,
"exp": 530,
"level": 12
},
{
"rarity": 1,
"exp": 680,
"level": 13
},
{
"rarity": 1,
"exp": 860,
"level": 14
},
{
"rarity": 1,
"exp": 1080,
"level": 15
},
{
"rarity": 1,
"exp": 1340,
"level": 16
},
{
"rarity": 1,
"exp": 1650,
"level": 17
},
{
"rarity": 1,
"exp": 2010,
"level": 18
},
{
"rarity": 1,
"exp": 2430,
"level": 19
},
{
"rarity": 1,
"exp": 2910,
"level": 20
},
{
"rarity": 1,
"exp": 3460,
"level": 21
},
{
"rarity": 1,
"exp": 4090,
"level": 22
},
{
"rarity": 1,
"exp": 4810,
"level": 23
},
{
"rarity": 1,
"exp": 5630,
"level": 24
},
{
"rarity": 1,
"exp": 6560,
"level": 25
},
{
"rarity": 1,
"exp": 7610,
"level": 26
},
{
"rarity": 1,
"exp": 8790,
"level": 27
},
{
"rarity": 1,
"exp": 10110,
"level": 28
},
{
"rarity": 1,
"exp": 11580,
"level": 29
},
{
"rarity": 1,
"exp": 13210,
"level": 30
},
{
"rarity": 1,
"exp": 15010,
"level": 31
},
{
"rarity": 1,
"exp": 16990,
"level": 32
},
{
"rarity": 1,
"exp": 19160,
"level": 33
},
{
"rarity": 1,
"exp": 21530,
"level": 34
},
{
"rarity": 1,
"exp": 24110,
"level": 35
},
{
"rarity": 1,
"exp": 26910,
"level": 36
},
{
"rarity": 1,
"exp": 29940,
"level": 37
},
{
"rarity": 1,
"exp": 33210,
"level": 38
},
{
"rarity": 1,
"exp": 36730,
"level": 39
},
{
"rarity": 1,
"exp": 40510,
"level": 40
},
{
"rarity": 2,
"exp": 0,
"level": 1
},
{
"rarity": 2,
"exp": 10,
"level": 2
},
{
"rarity": 2,
"exp": 30,
"level": 3
},
{
"rarity": 2,
"exp": 60,
"level": 4
},
{
"rarity": 2,
"exp": 110,
"level": 5
},
{
"rarity": 2,
"exp": 180,
"level": 6
},
{
"rarity": 2,
"exp": 270,
"level": 7
},
{
"rarity": 2,
"exp": 390,
"level": 8
},
{
"rarity": 2,
"exp": 540,
"level": 9
},
{
"rarity": 2,
"exp": 720,
"level": 10
},
{
"rarity": 2,
"exp": 940,
"level": 11
},
{
"rarity": 2,
"exp": 1200,
"level": 12
},
{
"rarity": 2,
"exp": 1510,
"level": 13
},
{
"rarity": 2,
"exp": 1870,
"level": 14
},
{
"rarity": 2,
"exp": 2290,
"level": 15
},
{
"rarity": 2,
"exp": 2770,
"level": 16
},
{
"rarity": 2,
"exp": 3320,
"level": 17
},
{
"rarity": 2,
"exp": 3940,
"level": 18
},
{
"rarity": 2,
"exp": 4640,
"level": 19
},
{
"rarity": 2,
"exp": 5420,
"level": 20
},
{
"rarity": 2,
"exp": 6290,
"level": 21
},
{
"rarity": 2,
"exp": 7250,
"level": 22
},
{
"rarity": 2,
"exp": 8310,
"level": 23
},
{
"rarity": 2,
"exp": 9470,
"level": 24
},
{
"rarity": 2,
"exp": 10740,
"level": 25
},
{
"rarity": 2,
"exp": 12130,
"level": 26
},
{
"rarity": 2,
"exp": 13650,
"level": 27
},
{
"rarity": 2,
"exp": 15310,
"level": 28
},
{
"rarity": 2,
"exp": 17120,
"level": 29
},
{
"rarity": 2,
"exp": 19090,
"level": 30
},
{
"rarity": 2,
"exp": 21230,
"level": 31
},
{
"rarity": 2,
"exp": 23550,
"level": 32
},
{
"rarity": 2,
"exp": 26060,
"level": 33
},
{
"rarity": 2,
"exp": 28770,
"level": 34
},
{
"rarity": 2,
"exp": 31690,
"level": 35
},
{
"rarity": 2,
"exp": 34830,
"level": 36
},
{
"rarity": 2,
"exp": 38200,
"level": 37
},
{
"rarity": 2,
"exp": 41810,
"level": 38
},
{
"rarity": 2,
"exp": 45670,
"level": 39
},
{
"rarity": 2,
"exp": 49790,
"level": 40
},
{
"rarity": 2,
"exp": 54190,
"level": 41
},
{
"rarity": 2,
"exp": 58890,
"level": 42
},
{
"rarity": 2,
"exp": 63910,
"level": 43
},
{
"rarity": 2,
"exp": 69270,
"level": 44
},
{
"rarity": 2,
"exp": 74990,
"level": 45
},
{
"rarity": 3,
"exp": 0,
"level": 1
},
{
"rarity": 3,
"exp": 15,
"level": 2
},
{
"rarity": 3,
"exp": 45,
"level": 3
},
{
"rarity": 3,
"exp": 95,
"level": 4
},
{
"rarity": 3,
"exp": 165,
"level": 5
},
{
"rarity": 3,
"exp": 265,
"level": 6
},
{
"rarity": 3,
"exp": 395,
"level": 7
},
{
"rarity": 3,
"exp": 565,
"level": 8
},
{
"rarity": 3,
"exp": 775,
"level": 9
},
{
"rarity": 3,
"exp": 1035,
"level": 10
},
{
"rarity": 3,
"exp": 1345,
"level": 11
},
{
"rarity": 3,
"exp": 1715,
"level": 12
},
{
"rarity": 3,
"exp": 2145,
"level": 13
},
{
"rarity": 3,
"exp": 2645,
"level": 14
},
{
"rarity": 3,
"exp": 3215,
"level": 15
},
{
"rarity": 3,
"exp": 3865,
"level": 16
},
{
"rarity": 3,
"exp": 4595,
"level": 17
},
{
"rarity": 3,
"exp": 5415,
"level": 18
},
{
"rarity": 3,
"exp": 6325,
"level": 19
},
{
"rarity": 3,
"exp": 7335,
"level": 20
},
{
"rarity": 3,
"exp": 8445,
"level": 21
},
{
"rarity": 3,
"exp": 9665,
"level": 22
},
{
"rarity": 3,
"exp": 10995,
"level": 23
},
{
"rarity": 3,
"exp": 12445,
"level": 24
},
{
"rarity": 3,
"exp": 14015,
"level": 25
},
{
"rarity": 3,
"exp": 15715,
"level": 26
},
{
"rarity": 3,
"exp": 17545,
"level": 27
},
{
"rarity": 3,
"exp": 19515,
"level": 28
},
{
"rarity": 3,
"exp": 21625,
"level": 29
},
{
"rarity": 3,
"exp": 23885,
"level": 30
},
{
"rarity": 3,
"exp": 26305,
"level": 31
},
{
"rarity": 3,
"exp": 28895,
"level": 32
},
{
"rarity": 3,
"exp": 31665,
"level": 33
},
{
"rarity": 3,
"exp": 34625,
"level": 34
},
{
"rarity": 3,
"exp": 37785,
"level": 35
},
{
"rarity": 3,
"exp": 41155,
"level": 36
},
{
"rarity": 3,
"exp": 44745,
"level": 37
},
{
"rarity": 3,
"exp": 48565,
"level": 38
},
{
"rarity": 3,
"exp": 52625,
"level": 39
},
{
"rarity": 3,
"exp": 56935,
"level": 40
},
{
"rarity": 3,
"exp": 61515,
"level": 41
},
{
"rarity": 3,
"exp": 66385,
"level": 42
},
{
"rarity": 3,
"exp": 71565,
"level": 43
},
{
"rarity": 3,
"exp": 77075,
"level": 44
},
{
"rarity": 3,
"exp": 82935,
"level": 45
},
{
"rarity": 3,
"exp": 89165,
"level": 46
},
{
"rarity": 3,
"exp": 95785,
"level": 47
},
{
"rarity": 3,
"exp": 102815,
"level": 48
},
{
"rarity": 3,
"exp": 110275,
"level": 49
},
{
"rarity": 3,
"exp": 118185,
"level": 50
}
]
}
@@ -0,0 +1,415 @@
{
"id": 10018,
"name": "[Tracen Academy] Sakura Bakushin O",
"chara_id": 1041,
"chara_name": "Sakura Bakushin O",
"rarity": 1,
"specialty": 101,
"type": 1,
"effects": [
[
{
"type": 1,
"value": 5
},
{
"type": 2,
"value": -1
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": 1
},
{
"type": 16,
"value": 1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": 10
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": 10
},
{
"type": 14,
"value": 10
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": 5
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": 10
},
{
"type": 2,
"value": 15
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": 15
},
{
"type": 14,
"value": 15
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": 20
}
],
[
{
"type": 1,
"value": 10
},
{
"type": 2,
"value": 15
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": 15
},
{
"type": 14,
"value": 15
},
{
"type": 15,
"value": 5
},
{
"type": 16,
"value": 10
},
{
"type": 19,
"value": 20
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": 15
},
{
"type": 2,
"value": 25
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": 20
},
{
"type": 14,
"value": 20
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": 35
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 3,
"value": 1
},
{
"type": 9,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": 5
},
{
"type": 16,
"value": 15
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 3,
"value": -1
},
{
"type": 9,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
]
],
"hints": [
{
"skill_id": 200652
},
{
"skill_id": 200791
},
{
"skill_id": 200841
},
{
"skill_id": 200962
},
{
"skill_id": 200972
},
{
"skill_id": 200982
},
{
"skill_id": 200992
},
{
"skill_id": 201012
},
{
"speed": 6,
"power": 2
}
]
}
@@ -0,0 +1,429 @@
{
"id": 20042,
"name": "[The Perfect Morning Greeting!] Sakura Bakushin O",
"chara_id": 1041,
"chara_name": "Sakura Bakushin O",
"rarity": 2,
"specialty": 102,
"type": 1,
"effects": [
[
{
"type": 1,
"value": 10
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": -1
},
{
"type": 11,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": 1
},
{
"type": 16,
"value": 1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": -1
},
{
"type": 11,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": 5
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": 1
},
{
"type": 11,
"value": 10
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": -1
},
{
"type": 11,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": 15
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": 5
},
{
"type": 11,
"value": 15
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": 15
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": 5
},
{
"type": 11,
"value": 15
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": 5
},
{
"type": 16,
"value": 15
},
{
"type": 19,
"value": 35
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": -1
},
{
"type": 11,
"value": -1
},
{
"type": 14,
"value": 10
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": -1
},
{
"type": 11,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": 20
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": 5
},
{
"type": 11,
"value": 20
},
{
"type": 14,
"value": 20
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 5,
"value": 1
},
{
"type": 8,
"value": -1
},
{
"type": 11,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": 10
},
{
"type": 16,
"value": 20
},
{
"type": 19,
"value": 50
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 5,
"value": -1
},
{
"type": 8,
"value": -1
},
{
"type": 11,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 19,
"value": -1
}
]
],
"unique_effect": {
"name": "The Perfect Morning Greeting!",
"description": "Friendship Bonus and Specialty Priority",
"card_level": 25,
"type": 1,
"args": [
10
],
"type2": 19,
"args2": [
20
],
"idle_mode_sub_rate": 0
},
"hints": [
{
"skill_id": 200652
},
{
"skill_id": 200791
},
{
"skill_id": 200841
},
{
"skill_id": 200962
},
{
"skill_id": 200972
},
{
"skill_id": 200982
},
{
"skill_id": 200992
},
{
"skill_id": 201012
},
{
"stamina": 2,
"power": 6
}
]
}
@@ -0,0 +1,471 @@
{
"id": 30083,
"name": "[Super! Sonic! Flower Power!] Sakura Bakushin O",
"chara_id": 1041,
"chara_name": "Sakura Bakushin O",
"rarity": 3,
"specialty": 103,
"type": 1,
"effects": [
[
{
"type": 1,
"value": 10
},
{
"type": 2,
"value": -1
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": 1
},
{
"type": 16,
"value": 1
},
{
"type": 17,
"value": 1
},
{
"type": 18,
"value": 5
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": 10
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": 10
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": 20
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": 20
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": 25
},
{
"type": 2,
"value": 20
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": 20
},
{
"type": 15,
"value": 5
},
{
"type": 16,
"value": 15
},
{
"type": 17,
"value": 2
},
{
"type": 18,
"value": 40
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 6,
"value": 1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": -1
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": -1
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": -1
},
{
"type": 2,
"value": 30
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": 15
},
{
"type": 14,
"value": 25
},
{
"type": 15,
"value": -1
},
{
"type": 16,
"value": -1
},
{
"type": 17,
"value": -1
},
{
"type": 18,
"value": -1
}
],
[
{
"type": 1,
"value": 35
},
{
"type": 2,
"value": -1
},
{
"type": 6,
"value": -1
},
{
"type": 12,
"value": 30
},
{
"type": 14,
"value": -1
},
{
"type": 15,
"value": 10
},
{
"type": 16,
"value": 20
},
{
"type": 17,
"value": 3
},
{
"type": 18,
"value": 50
}
]
],
"unique_effect": {
"name": "Super! Sonic! Flower Power!",
"description": "Increases Training Effectiveness when not on preferred training. (Friendship 80+)",
"card_level": 30,
"type": 102,
"args": [
80,
20
],
"idle_mode_sub_rate": 30
},
"hints": [
{
"skill_id": 200652
},
{
"skill_id": 200791
},
{
"skill_id": 200841
},
{
"skill_id": 200962
},
{
"skill_id": 200972
},
{
"skill_id": 200982
},
{
"skill_id": 200992
},
{
"skill_id": 201012
},
{
"speed": 1,
"power": 1,
"guts": 6
}
]
}
@@ -1,8 +0,0 @@
<script>
import { greet } from './greet';
let { host = 'SvelteKit', guest = 'Vitest' } = $props();
</script>
<h1>{greet(host)}</h1>
<p>{greet(guest)}</p>
@@ -1,13 +0,0 @@
import { page } from 'vitest/browser';
import { describe, expect, it } from 'vitest';
import { render } from 'vitest-browser-svelte';
import Welcome from './Welcome.svelte';
describe('Welcome.svelte', () => {
it('renders greetings for host and guest', async () => {
render(Welcome, { host: 'SvelteKit', guest: 'Vitest' });
await expect.element(page.getByRole('heading', { level: 1 })).toHaveTextContent('Hello, SvelteKit!');
await expect.element(page.getByText('Hello, Vitest!')).toBeInTheDocument();
});
});
@@ -1,8 +0,0 @@
import { describe, it, expect } from 'vitest';
import { greet } from './greet';
describe('greet', () => {
it('returns a greeting', () => {
expect(greet('Svelte')).toBe('Hello, Svelte!');
});
});
-3
View File
@@ -1,3 +0,0 @@
export function greet(name: string): string {
return 'Hello, ' + name + '!';
}