diff --git a/src/locales/de.ts b/src/locales/de.ts index 99f33a4..08cba2f 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -1085,6 +1085,15 @@ export default { }, gmView: { title: 'GM-Kontrollpanel', + presets: 'Vorlagen', + choosePreset: 'Vorlage wählen', + defaultPreset: 'Standardvorlage', + applyPreset: 'Vorlage anwenden', + savePreset: 'Vorlage speichern', + presetName: 'Vorlagenname', + presetNameRequired: 'Bitte geben Sie einen Namen ein', + presetSaved: 'Vorlage gespeichert', + presetApplied: 'Vorlage angewendet', adminOnly: 'Nur Admin', selectPlanet: 'Planet auswählen', choosePlanet: 'Einen Planeten auswählen', @@ -1185,6 +1194,13 @@ export default { } }, diplomacy: { + sort: { + label: 'Sortieren', + reputation: 'Ruf', + planets: 'Planeten', + difficulty: 'Schwierigkeit', + allies: 'Verbündete' + }, title: 'Diplomatie', description: 'Verwalte diplomatische Beziehungen mit NPCs', tabs: { diff --git a/src/locales/en.ts b/src/locales/en.ts index 26b6422..8b9786e 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -1101,6 +1101,15 @@ export default { }, gmView: { title: 'GM Control Panel', + presets: 'Presets', + choosePreset: 'Choose Preset', + defaultPreset: 'Default Preset', + applyPreset: 'Apply Preset', + savePreset: 'Save Preset', + presetName: 'Preset Name', + presetNameRequired: 'Please enter a preset name', + presetSaved: 'Preset saved', + presetApplied: 'Preset applied', adminOnly: 'Admin Only', selectPlanet: 'Select Planet', choosePlanet: 'Choose a planet', @@ -1291,6 +1300,13 @@ export default { npcEliminatedMessage: "You destroyed all of {npcName}'s planets! This faction has been completely wiped out." }, searchPlaceholder: 'Search NPC name...', + sort: { + label: 'Sort', + reputation: 'Reputation', + planets: 'Planets', + difficulty: 'Difficulty', + allies: 'Allies' + }, // Notification types notificationType: { tradeOffer: 'Trade Offer', diff --git a/src/locales/es-LA.ts b/src/locales/es-LA.ts index 8f3614f..a23e1f5 100644 --- a/src/locales/es-LA.ts +++ b/src/locales/es-LA.ts @@ -1093,6 +1093,15 @@ export default { }, gmView: { title: 'Panel de Control GM', + presets: 'Preajustes', + choosePreset: 'Elegir preajuste', + defaultPreset: 'Preajuste predeterminado', + applyPreset: 'Aplicar preajuste', + savePreset: 'Guardar preajuste', + presetName: 'Nombre del preajuste', + presetNameRequired: 'Ingrese el nombre del preajuste', + presetSaved: 'Preajuste guardado', + presetApplied: 'Preajuste aplicado', adminOnly: 'Solo Administrador', selectPlanet: 'Seleccionar Planeta', choosePlanet: 'Elige un planeta', @@ -1193,6 +1202,13 @@ export default { } }, diplomacy: { + sort: { + label: 'Ordenar', + reputation: 'Reputación', + planets: 'Planetas', + difficulty: 'Dificultad', + allies: 'Aliados' + }, title: 'Diplomacia', description: 'Gestionar relaciones diplomáticas con NPCs', tabs: { diff --git a/src/locales/ja.ts b/src/locales/ja.ts index d149f87..40d1886 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -1111,6 +1111,15 @@ export default { }, gmView: { title: 'GMコントロールパネル', + presets: 'プリセット', + choosePreset: 'プリセット選択', + defaultPreset: 'デフォルト', + applyPreset: '適用', + savePreset: '保存', + presetName: 'プリセット名', + presetNameRequired: 'プリセット名を入力してください', + presetSaved: '保存しました', + presetApplied: '適用しました', adminOnly: '管理者専用', selectPlanet: '惑星を選択', choosePlanet: '惑星を選択してください', @@ -1210,6 +1219,13 @@ export default { } }, diplomacy: { + sort: { + label: '並び替え', + reputation: '評判', + planets: '惑星', + difficulty: '難易度', + allies: '同盟' + }, title: '外交', description: 'NPCとの外交関係を管理', tabs: { diff --git a/src/locales/ko.ts b/src/locales/ko.ts index bd03c41..db0adb9 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -1061,6 +1061,15 @@ export default { }, gmView: { title: 'GM 제어판', + presets: '프리셋', + choosePreset: '프리셋 선택', + defaultPreset: '기본 프리셋', + applyPreset: '프리셋 적용', + savePreset: '프리셋 저장', + presetName: '프리셋 이름', + presetNameRequired: '프리셋 이름을 입력하세요', + presetSaved: '프리셋 저장됨', + presetApplied: '프리셋 적용됨', adminOnly: '관리자 전용', selectPlanet: '행성 선택', choosePlanet: '행성을 선택하세요', @@ -1160,6 +1169,13 @@ export default { } }, diplomacy: { + sort: { + label: '정렬', + reputation: '평판', + planets: '행성', + difficulty: '난이도', + allies: '동맹' + }, title: '외교', description: 'NPC와의 외교 관계 관리', tabs: { diff --git a/src/locales/ru.ts b/src/locales/ru.ts index aa3b1fb..d891a67 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -1087,6 +1087,15 @@ export default { }, gmView: { title: 'Панель управления GM', + presets: 'Предустановки', + choosePreset: 'Выбрать предустановку', + defaultPreset: 'Стандартная', + applyPreset: 'Применить', + savePreset: 'Сохранить', + presetName: 'Название', + presetNameRequired: 'Введите название', + presetSaved: 'Сохранено', + presetApplied: 'Применено', adminOnly: 'Только для администратора', selectPlanet: 'Выбрать планету', choosePlanet: 'Выберите планету', @@ -1186,6 +1195,13 @@ export default { } }, diplomacy: { + sort: { + label: 'Сортировка', + reputation: 'Репутация', + planets: 'Планеты', + difficulty: 'Сложность', + allies: 'Союзники' + }, title: 'Дипломатия', description: 'Управление дипломатическими отношениями с NPC', tabs: { diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index c05e298..c44c2e3 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1095,6 +1095,15 @@ export default { modifyOfficers: '修改军官', officersDesc: '快速设置军官到期时间', days: '天', + presets: '预设', + choosePreset: '选择预设', + defaultPreset: '默认预设', + applyPreset: '应用预设', + presetName: '预设名称', + savePreset: '保存预设', + presetNameRequired: '请输入预设名称', + presetSaved: '预设保存成功', + presetApplied: '预设应用成功', npcTesting: 'NPC 测试', npcTestingDesc: '测试NPC侦查和攻击行为', selectNPC: '选择NPC', @@ -1259,6 +1268,13 @@ export default { npcEliminatedMessage: '你消灭了{npcName}的所有星球!该势力已被彻底摧毁。' }, searchPlaceholder: '搜索NPC名称...', + sort: { + label: '排序', + reputation: '好感度', + planets: '星球数量', + difficulty: '难度', + allies: '盟友数量' + }, // 通知类型 notificationType: { tradeOffer: '贸易提议', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index f1d6fd1..4df3e95 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -1080,6 +1080,15 @@ export default { }, gmView: { title: 'GM 管理面板', + presets: '預設', + choosePreset: '選擇預設', + defaultPreset: '預設範本', + applyPreset: '套用預設', + savePreset: '儲存預設', + presetName: '預設名稱', + presetNameRequired: '請輸入預設名稱', + presetSaved: '預設已儲存', + presetApplied: '預設已套用', adminOnly: '僅管理員', selectPlanet: '選擇星球', choosePlanet: '選擇一個星球', @@ -1179,6 +1188,13 @@ export default { } }, diplomacy: { + sort: { + label: '排序', + reputation: '聲望', + planets: '星球', + difficulty: '難度', + allies: '盟友' + }, title: '外交', description: '管理與NPC的外交關係', tabs: { diff --git a/src/logic/battleLogic.ts b/src/logic/battleLogic.ts index 00159af..4244a1a 100644 --- a/src/logic/battleLogic.ts +++ b/src/logic/battleLogic.ts @@ -66,7 +66,7 @@ export const simulateBattle = async ( // 生成战斗报告 const battleResult: BattleResult = { - id: `battle_${Date.now()}`, + id: `battle_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, timestamp: Date.now(), attackerId: '', defenderId: '', diff --git a/src/logic/campaignLogic.ts b/src/logic/campaignLogic.ts index 1f817e5..0820654 100644 --- a/src/logic/campaignLogic.ts +++ b/src/logic/campaignLogic.ts @@ -515,7 +515,7 @@ export const createQuestNotification = ( ): QuestNotification => { const quest = getQuestById(questId) return { - id: `quest_notification_${Date.now()}`, + id: `quest_notification_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, timestamp: Date.now(), questId, questTitleKey: quest?.titleKey || '', diff --git a/src/logic/fleetLogic.ts b/src/logic/fleetLogic.ts index 9d67b3e..a377037 100644 --- a/src/logic/fleetLogic.ts +++ b/src/logic/fleetLogic.ts @@ -61,7 +61,7 @@ export const createFleetMission = ( ): FleetMission => { const now = Date.now() return { - id: `mission_${now}`, + id: `mission_${now}_${Math.random().toString(36).substring(2, 9)}`, playerId, originPlanetId, // 深拷贝targetPosition,避免多个任务共享同一个引用 @@ -414,7 +414,7 @@ export const processColonizeArrival = ( // 创建新殖民地 const newPlanet: Planet = { - id: `planet_${Date.now()}`, + id: `planet_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, name: `${colonyNameTemplate} ${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`, ownerId: player.id, position: mission.targetPosition, @@ -564,7 +564,7 @@ export const processSpyArrival = ( const wasDetected = Math.random() < detectionChance const spyReport: SpyReport = { - id: `spy_${Date.now()}`, + id: `spy_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, timestamp: Date.now(), spyId: attacker.id, targetPlanetId: targetPlanet.id, diff --git a/src/logic/moonLogic.ts b/src/logic/moonLogic.ts index 47105d2..ac8378a 100644 --- a/src/logic/moonLogic.ts +++ b/src/logic/moonLogic.ts @@ -79,7 +79,7 @@ export const tryGenerateMoon = ( // 生成月球 const moon: Planet = { - id: `moon_${Date.now()}`, + id: `moon_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, name: `Moon [${planetPosition.galaxy}:${planetPosition.system}:${planetPosition.position}]`, ownerId: playerId, position: planetPosition, diff --git a/src/logic/planetLogic.ts b/src/logic/planetLogic.ts index 08b1aec..a13a5b2 100644 --- a/src/logic/planetLogic.ts +++ b/src/logic/planetLogic.ts @@ -173,7 +173,7 @@ export const createMoon = ( moonSuffix: string = "'s Moon", diameter?: number ): Planet => { - const moonId = `moon_${Date.now()}` + const moonId = `moon_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` const moon: Planet = { id: moonId, name: `${parentPlanet.name}${moonSuffix}`, diff --git a/src/utils/migration.ts b/src/utils/migration.ts index b6053a0..a97446f 100644 --- a/src/utils/migration.ts +++ b/src/utils/migration.ts @@ -100,6 +100,39 @@ export const migrateGameData = (): void => { needsSave = true } + // 修复重复的星球ID + if (oldData.player?.planets && Array.isArray(oldData.player.planets)) { + const idCounts = new Map() + const idMap = new Map() // 映射:原始ID + 坐标 -> 新ID + + oldData.player.planets.forEach((planet: Planet) => { + const count = idCounts.get(planet.id) || 0 + if (count > 0) { + // 发现重复ID + const newId = `${planet.id}_${Math.random().toString(36).substring(2, 9)}` + const posKey = `${planet.position.galaxy}:${planet.position.system}:${planet.position.position}` + idMap.set(`${planet.id}_${posKey}`, newId) + planet.id = newId + needsSave = true + } + idCounts.set(planet.id, count + 1) + }) + + // 如果有ID被修改,需要更新月球的 parentPlanetId + if (idMap.size > 0) { + oldData.player.planets.forEach((planet: Planet) => { + if (planet.isMoon && planet.parentPlanetId) { + const posKey = `${planet.position.galaxy}:${planet.position.system}:${planet.position.position}` + const newParentId = idMap.get(`${planet.parentPlanetId}_${posKey}`) + if (newParentId) { + planet.parentPlanetId = newParentId + needsSave = true + } + } + }) + } + } + // 迁移温度数据:为没有温度的星球生成温度 // 玩家星球 if (oldData.player?.planets && Array.isArray(oldData.player.planets)) { diff --git a/src/views/DiplomacyView.vue b/src/views/DiplomacyView.vue index 1a44377..d1d0939 100644 --- a/src/views/DiplomacyView.vue +++ b/src/views/DiplomacyView.vue @@ -181,10 +181,37 @@ - -
- - + +
+ +
+ + +
+ + +
+ + + +
@@ -379,6 +406,13 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' + import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue + } from '@/components/ui/select' import { Dialog, DialogDescription, DialogTitle } from '@/components/ui/dialog' import ScrollableDialogContent from '@/components/ui/dialog/ScrollableDialogContent.vue' import { @@ -403,7 +437,8 @@ Swords, Activity, LayoutGrid, - List + List, + ArrowUpDown } from 'lucide-vue-next' import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty' @@ -435,6 +470,52 @@ // 搜索功能 const searchQuery = ref('') + // 排序状态 + const sortBy = ref('reputation') + const sortOrder = ref<'asc' | 'desc'>('desc') + + // 排序函数 + const sortNpcs = (npcs: typeof npcStore.npcs) => { + return [...npcs].sort((a, b) => { + let valA = 0 + let valB = 0 + + switch (sortBy.value) { + case 'reputation': + valA = getRelation(a.id)?.reputation || 0 + valB = getRelation(b.id)?.reputation || 0 + break + case 'planets': + valA = a.planets.length + valB = b.planets.length + break + case 'difficulty': + // 简单=1, 普通=2, 困难=3 + // eslint-disable-next-line no-case-declarations + const getDifficultyVal = (diff: string) => { + if (diff === 'hard') return 3 + if (diff === 'medium') return 2 + return 1 + } + valA = a.difficultyLevel || getDifficultyVal(a.difficulty) + valB = b.difficultyLevel || getDifficultyVal(b.difficulty) + break + case 'allies': + valA = a.allies?.length || 0 + valB = b.allies?.length || 0 + break + default: + return 0 + } + + if (sortOrder.value === 'asc') { + return valA - valB + } else { + return valB - valA + } + }) + } + // NPC诊断功能 const npcDiagnosticOpen = ref(false) const npcDiagnostics = ref([]) @@ -627,30 +708,30 @@ } // 按关系状态分类NPC(同时应用搜索过滤) - const allNpcs = computed(() => npcStore.npcs.filter(matchesSearch)) + const allNpcs = computed(() => sortNpcs(npcStore.npcs.filter(matchesSearch))) const friendlyNpcs = computed(() => { - return npcStore.npcs.filter(npc => { + return sortNpcs(npcStore.npcs.filter(npc => { if (!matchesSearch(npc)) return false const relation = getRelation(npc.id) return relation?.status === RelationStatus.Friendly - }) + })) }) const neutralNpcs = computed(() => { - return npcStore.npcs.filter(npc => { + return sortNpcs(npcStore.npcs.filter(npc => { if (!matchesSearch(npc)) return false const relation = getRelation(npc.id) return !relation || relation.status === RelationStatus.Neutral - }) + })) }) const hostileNpcs = computed(() => { - return npcStore.npcs.filter(npc => { + return sortNpcs(npcStore.npcs.filter(npc => { if (!matchesSearch(npc)) return false const relation = getRelation(npc.id) return relation?.status === RelationStatus.Hostile - }) + })) }) // 分页辅助函数 diff --git a/src/views/GMView.vue b/src/views/GMView.vue index 7e4eed8..9f2add6 100644 --- a/src/views/GMView.vue +++ b/src/views/GMView.vue @@ -79,6 +79,35 @@ + + + + {{ t('gmView.presets') || 'Presets' }} + + +
+
+ + +
+
+ + +
+
+
+
+ {{ t(section.titleKey) }} @@ -263,8 +292,157 @@ import { BuildingType, TechnologyType, ShipType, DefenseType, OfficerType } from '@/types/game' import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic' import * as publicLogic from '@/logic/publicLogic' + import { calculateMaxFleetStorage } from '@/logic/fleetStorageLogic' import { Home } from 'lucide-vue-next' + // --- 预设系统 --- + interface GMPreset { + id: string + name: string + values: Record + } + + const getPresets = (type: string): GMPreset[] => { + const data = localStorage.getItem(`gm_presets_${type}`) + return data ? JSON.parse(data) : [] + } + + const savePresets = (type: string, presets: GMPreset[]) => { + localStorage.setItem(`gm_presets_${type}`, JSON.stringify(presets)) + } + + const presetNames = ref>({ + buildings: '', + research: '', + ships: '', + defense: '' + }) + + const selectedPresets = ref>({ + buildings: 'default', + research: 'default', + ships: 'default', + defense: 'default' + }) + + const customPresets = ref>({ + buildings: getPresets('buildings'), + research: getPresets('research'), + ships: getPresets('ships'), + defense: getPresets('defense') + }) + + const handleSavePreset = (section: any) => { + const name = presetNames.value[section.tabValue]?.trim() + if (!name) { + toast.error(t('gmView.presetNameRequired') || '请输入预设名称') + return + } + + const values: Record = {} + section.items.forEach((item: string) => { + values[item] = section.getValue(item) + }) + + const newPreset: GMPreset = { + id: Date.now().toString(), + name, + values + } + + if (customPresets.value[section.tabValue]) { + customPresets.value[section.tabValue]!.push(newPreset) + savePresets(section.tabValue, customPresets.value[section.tabValue]!) + presetNames.value[section.tabValue] = '' + selectedPresets.value[section.tabValue] = newPreset.id + toast.success(t('gmView.presetSaved') || '预设保存成功') + } + } + + const handleApplyPreset = (section: any) => { + const presetId = selectedPresets.value[section.tabValue] + if (!presetId) return + + if (presetId === 'default') { + if (section.tabValue === 'buildings') { + const explicitMax: Record = { + [BuildingType.NaniteFactory]: 10, + [BuildingType.MissileSilo]: 10, + [BuildingType.JumpGate]: 5, + [BuildingType.PlanetDestroyerFactory]: 3, + [BuildingType.GeoResearchStation]: 10, + [BuildingType.DeepDrillingFacility]: 10, + [BuildingType.University]: 10 + } + section.items.forEach((item: string) => { + section.setValue(item, explicitMax[item] || 50) + }) + } else if (section.tabValue === 'research') { + const explicitMax: Record = { + [TechnologyType.ComputerTechnology]: 10, + [TechnologyType.GravitonTechnology]: 1, + [TechnologyType.PlanetDestructionTech]: 10, + [TechnologyType.MiningTechnology]: 15, + [TechnologyType.IntergalacticResearchNetwork]: 10, + [TechnologyType.MineralResearch]: 20, + [TechnologyType.CrystalResearch]: 20, + [TechnologyType.FuelResearch]: 20 + } + section.items.forEach((item: string) => { + section.setValue(item, explicitMax[item] || 100) + }) + } else if (section.tabValue === 'ships') { + if (!selectedPlanet.value) return + + // 重新计算最大舰队仓储,确保数据是最新的 + const maxStorage = calculateMaxFleetStorage(selectedPlanet.value, gameStore.player.technologies) + + const shipTypes = Object.values(ShipType) + // 将总容量平均分配给每种舰船 + const storagePerShip = maxStorage / shipTypes.length + + shipTypes.forEach(type => { + const usage = SHIPS.value[type]?.storageUsage || 1 + // 如果 usage 为 0 (如某些特殊单位),则给予一个默认数量,或者跳过 + if (usage <= 0) { + section.setValue(type, 100) // 防止除以0,给予固定值 + } else { + section.setValue(type, Math.floor(storagePerShip / usage)) + } + }) + } else if (section.tabValue === 'defense') { + if (!selectedPlanet.value) return + const siloLevel = selectedPlanet.value.buildings[BuildingType.MissileSilo] || 0 + const missileCapacity = siloLevel * 10 + const halfCapacity = missileCapacity / 2 + + section.items.forEach((item: string) => { + if (item === DefenseType.AntiBallisticMissile) { + // 反弹道导弹占用1个空间,分配一半容量 + section.setValue(item, Math.floor(halfCapacity)) + } else if (item === DefenseType.InterplanetaryMissile) { + // 星际导弹占用2个空间,分配一半容量 + section.setValue(item, Math.floor(halfCapacity)) + } else { + section.setValue(item, 10000) + } + }) + } + toast.success(t('gmView.presetApplied') || '默认预设应用成功') + } else { + if (customPresets.value[section.tabValue]) { + const customPreset = customPresets.value[section.tabValue]!.find((p: GMPreset) => p.id === presetId) + if (customPreset) { + Object.entries(customPreset.values).forEach(([k, v]) => { + section.setValue(k, v as number) + }) + toast.success(t('gmView.presetApplied') || '预设应用成功') + } + } + } + } + // --- 预设系统结束 --- + const router = useRouter() const gameStore = useGameStore() const npcStore = useNPCStore() @@ -281,7 +459,8 @@ router.push('/') } - const selectedPlanetId = ref(gameStore.player.planets[0]?.id || '') + // 默认选中当前正在游玩的星球 + const selectedPlanetId = ref(gameStore.currentPlanetId || gameStore.player.planets[0]?.id || '') const officerDays = ref>({} as Record) const selectedNPCId = ref(npcStore.npcs[0]?.id || '') const targetPlanetIndex = ref('0') @@ -659,11 +838,13 @@ const maxAllResources = () => { if (!selectedPlanet.value) return - const maxAmount = 1000000000000000000 - selectedPlanet.value.resources.metal = maxAmount - selectedPlanet.value.resources.crystal = maxAmount - selectedPlanet.value.resources.deuterium = maxAmount - selectedPlanet.value.resources.darkMatter = maxAmount + // 计算当前星球的资源存储上限 + const capacity = publicLogic.getResourceCapacity(selectedPlanet.value, gameStore.player.officers) + + selectedPlanet.value.resources.metal = capacity.metal + selectedPlanet.value.resources.crystal = capacity.crystal + selectedPlanet.value.resources.deuterium = capacity.deuterium + selectedPlanet.value.resources.darkMatter = capacity.darkMatter toast.success(t('gmView.maxAllResourcesSuccess')) }