统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
This commit is contained in:
+153
-49
@@ -3,11 +3,19 @@ import {
|
||||
detectCustomWorldThemeMode,
|
||||
resolveCustomWorldCompatibilityTemplateWorldType,
|
||||
} from '../services/customWorldTheme';
|
||||
import { CustomWorldItem, CustomWorldProfile, InventoryItem, WorldTemplateType, WorldType } from '../types';
|
||||
import {
|
||||
CustomWorldItem,
|
||||
CustomWorldProfile,
|
||||
InventoryItem,
|
||||
WorldTemplateType,
|
||||
WorldType,
|
||||
} from '../types';
|
||||
|
||||
let runtimeCustomWorldProfile: CustomWorldProfile | null = null;
|
||||
|
||||
export function setRuntimeCustomWorldProfile(profile: CustomWorldProfile | null) {
|
||||
export function setRuntimeCustomWorldProfile(
|
||||
profile: CustomWorldProfile | null,
|
||||
) {
|
||||
runtimeCustomWorldProfile = profile;
|
||||
}
|
||||
|
||||
@@ -17,7 +25,10 @@ export function getRuntimeCustomWorldProfile() {
|
||||
|
||||
export function resolveCompatibilityTemplateWorldType(
|
||||
worldType: WorldType | null | undefined,
|
||||
customWorldProfile: CustomWorldProfile | null | undefined = runtimeCustomWorldProfile,
|
||||
customWorldProfile:
|
||||
| CustomWorldProfile
|
||||
| null
|
||||
| undefined = runtimeCustomWorldProfile,
|
||||
): WorldTemplateType | null {
|
||||
if (!worldType) return null;
|
||||
if (worldType === WorldType.CUSTOM) {
|
||||
@@ -41,22 +52,32 @@ function hashText(value: string) {
|
||||
}
|
||||
|
||||
function compactStrings(values: Array<string | null | undefined | false>) {
|
||||
return [...new Set(
|
||||
values
|
||||
.map(value => typeof value === 'string' ? value.trim() : '')
|
||||
.filter(Boolean),
|
||||
)];
|
||||
return [
|
||||
...new Set(
|
||||
values
|
||||
.map((value) => (typeof value === 'string' ? value.trim() : ''))
|
||||
.filter(Boolean),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function pickCyclic<T>(items: readonly T[], index: number, fallback: T): T {
|
||||
return items[index % items.length] ?? fallback;
|
||||
}
|
||||
|
||||
function normalizeInventoryItemId(item: CustomWorldItem, quantity: number, seedKey: string) {
|
||||
function normalizeInventoryItemId(
|
||||
item: CustomWorldItem,
|
||||
quantity: number,
|
||||
seedKey: string,
|
||||
) {
|
||||
return `custom:${item.id}:${quantity}:${hashText(seedKey).toString(36)}`;
|
||||
}
|
||||
|
||||
function toInventoryItem(item: CustomWorldItem, quantity: number, seedKey: string): InventoryItem {
|
||||
function toInventoryItem(
|
||||
item: CustomWorldItem,
|
||||
quantity: number,
|
||||
seedKey: string,
|
||||
): InventoryItem {
|
||||
return {
|
||||
id: normalizeInventoryItemId(item, quantity, seedKey),
|
||||
category: item.category,
|
||||
@@ -89,8 +110,22 @@ export interface RuntimeCustomWorldItemQueryOptions {
|
||||
rarityFloor?: CustomWorldItem['rarity'];
|
||||
}
|
||||
|
||||
const RARITY_ORDER: CustomWorldItem['rarity'][] = ['common', 'uncommon', 'rare', 'epic', 'legendary'];
|
||||
const DEFAULT_RUNTIME_CATEGORIES = ['武器', '护甲', '饰品', '消耗品', '材料', '稀有品', '专属物'] as const;
|
||||
const RARITY_ORDER: CustomWorldItem['rarity'][] = [
|
||||
'common',
|
||||
'uncommon',
|
||||
'rare',
|
||||
'epic',
|
||||
'legendary',
|
||||
];
|
||||
const DEFAULT_RUNTIME_CATEGORIES = [
|
||||
'武器',
|
||||
'护甲',
|
||||
'饰品',
|
||||
'消耗品',
|
||||
'材料',
|
||||
'稀有品',
|
||||
'专属物',
|
||||
] as const;
|
||||
const CATEGORY_DEFAULT_TAGS: Record<string, string[]> = {
|
||||
武器: ['weapon', '战斗'],
|
||||
护甲: ['armor', '防护'],
|
||||
@@ -146,26 +181,49 @@ function buildRuntimeItemTags(
|
||||
seed: number,
|
||||
) {
|
||||
const baseTags = [...(CATEGORY_DEFAULT_TAGS[category] ?? ['world-item'])];
|
||||
const preferredTags = [...new Set((options.preferredTags ?? []).map(tag => tag.trim()).filter(Boolean))];
|
||||
const keywordTags = [...new Set((options.keywords ?? []).map(tag => tag.trim()).filter(Boolean))];
|
||||
const selectedPreferredTag = preferredTags.length > 0
|
||||
? preferredTags[seed % preferredTags.length]
|
||||
: undefined;
|
||||
const selectedKeywordTag = keywordTags.length > 0
|
||||
? keywordTags[(seed >>> 3) % keywordTags.length]
|
||||
: undefined;
|
||||
const preferredTags = [
|
||||
...new Set(
|
||||
(options.preferredTags ?? []).map((tag) => tag.trim()).filter(Boolean),
|
||||
),
|
||||
];
|
||||
const keywordTags = [
|
||||
...new Set(
|
||||
(options.keywords ?? []).map((tag) => tag.trim()).filter(Boolean),
|
||||
),
|
||||
];
|
||||
const selectedPreferredTag =
|
||||
preferredTags.length > 0
|
||||
? preferredTags[seed % preferredTags.length]
|
||||
: undefined;
|
||||
const selectedKeywordTag =
|
||||
keywordTags.length > 0
|
||||
? keywordTags[(seed >>> 3) % keywordTags.length]
|
||||
: undefined;
|
||||
|
||||
if (category === '消耗品' && preferredTags.some(tag => /mana|法力|灵气|内息/u.test(tag))) {
|
||||
if (
|
||||
category === '消耗品' &&
|
||||
preferredTags.some((tag) => /mana|法力|灵气|内息/u.test(tag))
|
||||
) {
|
||||
baseTags.push('mana');
|
||||
}
|
||||
if (category === '消耗品' && preferredTags.some(tag => /heal|疗|血|恢复/u.test(tag))) {
|
||||
if (
|
||||
category === '消耗品' &&
|
||||
preferredTags.some((tag) => /heal|疗|血|恢复/u.test(tag))
|
||||
) {
|
||||
baseTags.push('healing');
|
||||
}
|
||||
|
||||
return compactStrings([...baseTags, selectedPreferredTag, selectedKeywordTag]).slice(0, 5);
|
||||
return compactStrings([
|
||||
...baseTags,
|
||||
selectedPreferredTag,
|
||||
selectedKeywordTag,
|
||||
]).slice(0, 5);
|
||||
}
|
||||
|
||||
function inferRuntimeItemRarity(seed: number, rarityFloorValue: number): CustomWorldItem['rarity'] {
|
||||
function inferRuntimeItemRarity(
|
||||
seed: number,
|
||||
rarityFloorValue: number,
|
||||
): CustomWorldItem['rarity'] {
|
||||
const rolledRarity = [0, 1, 1, 2, 2, 2, 3, 3, 4][seed % 9] ?? 0;
|
||||
return RARITY_ORDER[Math.max(rarityFloorValue, rolledRarity)] ?? 'common';
|
||||
}
|
||||
@@ -194,7 +252,10 @@ function inferRuntimeItemMechanics(
|
||||
equipmentSlotId: 'armor' as const,
|
||||
statProfile: {
|
||||
maxHpBonus: 10 * rarityTier + (seed % 8),
|
||||
incomingDamageMultiplier: Math.max(0.72, Number((1 - rarityTier * 0.04).toFixed(2))),
|
||||
incomingDamageMultiplier: Math.max(
|
||||
0.72,
|
||||
Number((1 - rarityTier * 0.04).toFixed(2)),
|
||||
),
|
||||
},
|
||||
useProfile: null,
|
||||
value: 26 * rarityTier,
|
||||
@@ -218,7 +279,10 @@ function inferRuntimeItemMechanics(
|
||||
equipmentSlotId: null,
|
||||
statProfile: null,
|
||||
useProfile: tags.includes('mana')
|
||||
? { manaRestore: 12 * rarityTier, cooldownReduction: rarityTier >= 3 ? 1 : 0 }
|
||||
? {
|
||||
manaRestore: 12 * rarityTier,
|
||||
cooldownReduction: rarityTier >= 3 ? 1 : 0,
|
||||
}
|
||||
: { hpRestore: 16 * rarityTier },
|
||||
value: 18 * rarityTier,
|
||||
};
|
||||
@@ -241,7 +305,11 @@ function buildProceduralRuntimeItem(
|
||||
const themeMode = detectCustomWorldThemeMode(profile);
|
||||
const seed = hashText(`${profile.id}:${seedKey}:${index}`);
|
||||
const defaultCategory = DEFAULT_RUNTIME_CATEGORIES[0] ?? 'world-item';
|
||||
const categories = compactStrings(options.categories?.length ? options.categories : [...DEFAULT_RUNTIME_CATEGORIES]);
|
||||
const categories = compactStrings(
|
||||
options.categories?.length
|
||||
? options.categories
|
||||
: [...DEFAULT_RUNTIME_CATEGORIES],
|
||||
);
|
||||
const category = pickCyclic(categories, seed, defaultCategory);
|
||||
const rarityFloorValue = getRarityFloorValue(options.rarityFloor);
|
||||
const rarity = inferRuntimeItemRarity(seed, rarityFloorValue);
|
||||
@@ -251,7 +319,9 @@ function buildProceduralRuntimeItem(
|
||||
const fallbackNounPool = ['sigil', 'relic', 'token', 'seal', 'core', 'mark'];
|
||||
const resolvedNounPool = nounPool ?? fallbackNounPool;
|
||||
const worldSeed = getWorldSeedLabel(profile);
|
||||
const optionSeed = sanitizeNameFragment((options.preferredTags ?? [])[0] ?? '') || sanitizeNameFragment((options.keywords ?? [])[0] ?? '');
|
||||
const optionSeed =
|
||||
sanitizeNameFragment((options.preferredTags ?? [])[0] ?? '') ||
|
||||
sanitizeNameFragment((options.keywords ?? [])[0] ?? '');
|
||||
const prefix = pickCyclic(prefixPool, seed >>> 2, prefixPool[0] ?? 'world');
|
||||
const noun = pickCyclic(resolvedNounPool, seed >>> 5, fallbackNounPool[0]);
|
||||
const name = `${prefix}${optionSeed || worldSeed}${noun}${index + 1}`;
|
||||
@@ -277,10 +347,16 @@ function matchesRuntimeQuery(
|
||||
options: RuntimeCustomWorldItemQueryOptions,
|
||||
rarityFloorValue: number,
|
||||
) {
|
||||
if (options.categories?.length && !options.categories.includes(item.category)) {
|
||||
if (
|
||||
options.categories?.length &&
|
||||
!options.categories.includes(item.category)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (options.tags?.length && !options.tags.some(tag => item.tags.includes(tag))) {
|
||||
if (
|
||||
options.tags?.length &&
|
||||
!options.tags.some((tag) => item.tags.includes(tag))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (rarityFloorValue >= 0) {
|
||||
@@ -292,18 +368,26 @@ function matchesRuntimeQuery(
|
||||
return true;
|
||||
}
|
||||
|
||||
function scoreItemRelevance(item: CustomWorldItem, options: RuntimeCustomWorldItemQueryOptions) {
|
||||
const haystack = normalizeLookupText([
|
||||
item.name,
|
||||
item.category,
|
||||
item.description,
|
||||
...(item.tags ?? []),
|
||||
].join(' '));
|
||||
const itemTags = new Set((item.tags ?? []).map(tag => normalizeLookupText(tag)));
|
||||
function scoreItemRelevance(
|
||||
item: CustomWorldItem,
|
||||
options: RuntimeCustomWorldItemQueryOptions,
|
||||
) {
|
||||
const haystack = normalizeLookupText(
|
||||
[item.name, item.category, item.description, ...(item.tags ?? [])].join(
|
||||
' ',
|
||||
),
|
||||
);
|
||||
const itemTags = new Set(
|
||||
(item.tags ?? []).map((tag) => normalizeLookupText(tag)),
|
||||
);
|
||||
let score = 0;
|
||||
|
||||
const preferredTags = [...new Set((options.preferredTags ?? []).map(normalizeLookupText).filter(Boolean))];
|
||||
preferredTags.forEach(tag => {
|
||||
const preferredTags = [
|
||||
...new Set(
|
||||
(options.preferredTags ?? []).map(normalizeLookupText).filter(Boolean),
|
||||
),
|
||||
];
|
||||
preferredTags.forEach((tag) => {
|
||||
if (itemTags.has(tag)) {
|
||||
score += 10;
|
||||
return;
|
||||
@@ -313,8 +397,14 @@ function scoreItemRelevance(item: CustomWorldItem, options: RuntimeCustomWorldIt
|
||||
}
|
||||
});
|
||||
|
||||
const keywords = [...new Set((options.keywords ?? []).map(normalizeLookupText).filter(keyword => keyword.length >= 2))];
|
||||
keywords.forEach(keyword => {
|
||||
const keywords = [
|
||||
...new Set(
|
||||
(options.keywords ?? [])
|
||||
.map(normalizeLookupText)
|
||||
.filter((keyword) => keyword.length >= 2),
|
||||
),
|
||||
];
|
||||
keywords.forEach((keyword) => {
|
||||
if (!haystack.includes(keyword)) {
|
||||
return;
|
||||
}
|
||||
@@ -331,10 +421,15 @@ function scoreItemRelevance(item: CustomWorldItem, options: RuntimeCustomWorldIt
|
||||
return score;
|
||||
}
|
||||
|
||||
function rankItems(items: CustomWorldItem[], seedKey: string, options: RuntimeCustomWorldItemQueryOptions = {}) {
|
||||
function rankItems(
|
||||
items: CustomWorldItem[],
|
||||
seedKey: string,
|
||||
options: RuntimeCustomWorldItemQueryOptions = {},
|
||||
) {
|
||||
const seed = hashText(seedKey);
|
||||
return [...items].sort((left, right) => {
|
||||
const relevanceDelta = scoreItemRelevance(right, options) - scoreItemRelevance(left, options);
|
||||
const relevanceDelta =
|
||||
scoreItemRelevance(right, options) - scoreItemRelevance(left, options);
|
||||
if (relevanceDelta !== 0) {
|
||||
return relevanceDelta;
|
||||
}
|
||||
@@ -353,13 +448,20 @@ export function pickRuntimeCustomWorldItems(
|
||||
if (!profile) return [] as CustomWorldItem[];
|
||||
|
||||
const rarityFloorValue = getRarityFloorValue(options.rarityFloor);
|
||||
const sourceItems = Array.from({ length: Math.max(16, (options.count ?? 1) * 10) }, (_, index) =>
|
||||
buildProceduralRuntimeItem(profile, seedKey, options, index),
|
||||
const sourceItems = Array.from(
|
||||
{ length: Math.max(16, (options.count ?? 1) * 10) },
|
||||
(_, index) => buildProceduralRuntimeItem(profile, seedKey, options, index),
|
||||
);
|
||||
|
||||
const filtered = sourceItems.filter(item => matchesRuntimeQuery(item, options, rarityFloorValue));
|
||||
const filtered = sourceItems.filter((item) =>
|
||||
matchesRuntimeQuery(item, options, rarityFloorValue),
|
||||
);
|
||||
|
||||
return rankItems(filtered.length > 0 ? filtered : sourceItems, seedKey, options).slice(0, options.count ?? 1);
|
||||
return rankItems(
|
||||
filtered.length > 0 ? filtered : sourceItems,
|
||||
seedKey,
|
||||
options,
|
||||
).slice(0, options.count ?? 1);
|
||||
}
|
||||
|
||||
export function buildRuntimeCustomWorldInventoryItems(
|
||||
@@ -369,5 +471,7 @@ export function buildRuntimeCustomWorldInventoryItems(
|
||||
const count = options.count ?? 1;
|
||||
return pickRuntimeCustomWorldItems(seedKey, options)
|
||||
.slice(0, count)
|
||||
.map((item, index) => toInventoryItem(item, options.quantity ?? 1, `${seedKey}:${index}`));
|
||||
.map((item, index) =>
|
||||
toInventoryItem(item, options.quantity ?? 1, `${seedKey}:${index}`),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user