From 705ee8c3dbd35100469097259d8b5e902686ab0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A6=E5=90=9B?= <73606411+setube@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:49:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=E4=B8=8E=E6=A0=B8=E5=BF=83=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引入项目基础目录结构,包含多语言支持、主要页面与组件、核心游戏逻辑、UI 组件库、加密与本地持久化、自动化 Docker 构建流程、GitHub issue 模板(中英文)、README(中英文)、LICENSE 及开发配置文件。实现 OGame 单机版主要功能模块,为后续开发和扩展奠定基础。 --- .github/ISSUE_TEMPLATE/BUG反馈.md | 31 + .github/ISSUE_TEMPLATE/bug-report.md | 31 + .../documentation-improvement.md | 19 + .github/ISSUE_TEMPLATE/feature-request.md | 19 + .github/ISSUE_TEMPLATE/feedback-suggestion.md | 19 + .github/ISSUE_TEMPLATE/功能请求.md | 19 + .github/ISSUE_TEMPLATE/反馈建议.md | 19 + .github/ISSUE_TEMPLATE/文档改进.md | 19 + .github/workflows/ogame-vue-ts.yml | 105 ++ .gitignore | 26 + .vscode/extensions.json | 3 + Dockerfile | 17 + LICENSE | 32 + README.md | 241 +++ README.zh-CN.md | 243 +++ components.json | 21 + docs/assets/AlertDialog-BbpHh-RO.js | 1 + docs/assets/BattleSimulatorView-DI2C9KJc.js | 1 + docs/assets/BuildingsView-B00w_iux.js | 1 + docs/assets/CardDescription-Cb9v68Xa.js | 1 + docs/assets/CardUnlockOverlay-tiSNfyF3.js | 2 + docs/assets/DefenseView-4OcM0vVE.js | 1 + docs/assets/FleetView-DY7yR25i.js | 1 + docs/assets/GalaxyView-C-Ue9wLo.js | 1 + docs/assets/MessagesView-sKxpYFTS.js | 1 + docs/assets/OfficersView-D9JTCZE2.js | 1 + docs/assets/OverviewView-BBQfV6K0.js | 1 + docs/assets/ResearchView-Do1WxfNH.js | 1 + docs/assets/SettingsView-IZn-5kW1.js | 1 + docs/assets/ShipyardView-BGb4XhA2.js | 1 + docs/assets/UnlockRequirement-DeOUxO_t.js | 1 + docs/assets/eye-BrZrKlSB.js | 1 + docs/assets/index-BXmZ_V9Y.css | 2 + docs/assets/index-BpOElaf9.js | 53 + docs/assets/logo-Cz0cNqhe.svg | 1 + docs/assets/shipValidation-BZIVAW1v.js | 1 + docs/assets/useGameConfig-DqswvIth.js | 1 + docs/index.html | 17 + docs/logo.svg | 1 + index.html | 20 + package.json | 48 + pnpm-lock.yaml | 1443 +++++++++++++++++ public/logo.svg | 1 + src/App.vue | 556 +++++++ src/assets/logo.svg | 1 + src/components/AlertDialog.vue | 51 + src/components/CardUnlockOverlay.vue | 77 + src/components/ConfirmDialog.vue | 51 + src/components/DetailDialog.vue | 84 + src/components/ResourceIcon.vue | 28 + src/components/UnlockRequirement.vue | 73 + .../detail-views/BuildingDetailView.vue | 195 +++ .../detail-views/DefenseDetailView.vue | 168 ++ .../detail-views/ShipDetailView.vue | 204 +++ .../detail-views/TechnologyDetailView.vue | 154 ++ .../ui/alert-dialog/AlertDialog.vue | 15 + .../ui/alert-dialog/AlertDialogAction.vue | 18 + .../ui/alert-dialog/AlertDialogCancel.vue | 18 + .../ui/alert-dialog/AlertDialogContent.vue | 39 + .../alert-dialog/AlertDialogDescription.vue | 21 + .../ui/alert-dialog/AlertDialogFooter.vue | 14 + .../ui/alert-dialog/AlertDialogHeader.vue | 14 + .../ui/alert-dialog/AlertDialogTitle.vue | 17 + .../ui/alert-dialog/AlertDialogTrigger.vue | 12 + src/components/ui/alert-dialog/index.ts | 9 + src/components/ui/badge/Badge.vue | 24 + src/components/ui/badge/index.ts | 23 + src/components/ui/button/Button.vue | 24 + src/components/ui/button/index.ts | 35 + src/components/ui/card/Card.vue | 14 + src/components/ui/card/CardAction.vue | 14 + src/components/ui/card/CardContent.vue | 14 + src/components/ui/card/CardDescription.vue | 14 + src/components/ui/card/CardFooter.vue | 14 + src/components/ui/card/CardHeader.vue | 22 + src/components/ui/card/CardTitle.vue | 14 + src/components/ui/card/index.ts | 7 + src/components/ui/dialog/Dialog.vue | 15 + src/components/ui/dialog/DialogClose.vue | 12 + src/components/ui/dialog/DialogContent.vue | 49 + .../ui/dialog/DialogDescription.vue | 19 + src/components/ui/dialog/DialogFooter.vue | 12 + src/components/ui/dialog/DialogHeader.vue | 14 + src/components/ui/dialog/DialogOverlay.vue | 26 + .../ui/dialog/DialogScrollContent.vue | 51 + src/components/ui/dialog/DialogTitle.vue | 19 + src/components/ui/dialog/DialogTrigger.vue | 12 + src/components/ui/dialog/index.ts | 10 + src/components/ui/input/Input.vue | 35 + src/components/ui/input/index.ts | 1 + src/components/ui/label/Label.vue | 26 + src/components/ui/label/index.ts | 1 + src/components/ui/popover/Popover.vue | 19 + src/components/ui/popover/PopoverAnchor.vue | 15 + src/components/ui/popover/PopoverContent.vue | 45 + src/components/ui/popover/PopoverTrigger.vue | 15 + src/components/ui/popover/index.ts | 4 + src/components/ui/progress/Progress.vue | 27 + src/components/ui/progress/index.ts | 1 + src/components/ui/select/Select.vue | 15 + src/components/ui/select/SelectContent.vue | 51 + src/components/ui/select/SelectGroup.vue | 12 + src/components/ui/select/SelectItem.vue | 39 + src/components/ui/select/SelectItemText.vue | 12 + src/components/ui/select/SelectLabel.vue | 14 + .../ui/select/SelectScrollDownButton.vue | 26 + .../ui/select/SelectScrollUpButton.vue | 26 + src/components/ui/select/SelectSeparator.vue | 19 + src/components/ui/select/SelectTrigger.vue | 34 + src/components/ui/select/SelectValue.vue | 12 + src/components/ui/select/index.ts | 11 + src/components/ui/sonner/Sonner.vue | 42 + src/components/ui/sonner/index.ts | 1 + src/components/ui/table/Table.vue | 16 + src/components/ui/table/TableBody.vue | 14 + src/components/ui/table/TableCaption.vue | 14 + src/components/ui/table/TableCell.vue | 17 + src/components/ui/table/TableEmpty.vue | 29 + src/components/ui/table/TableFooter.vue | 14 + src/components/ui/table/TableHead.vue | 22 + src/components/ui/table/TableHeader.vue | 14 + src/components/ui/table/TableRow.vue | 14 + src/components/ui/table/index.ts | 9 + src/components/ui/table/utils.ts | 8 + src/composables/useGameConfig.ts | 173 ++ src/composables/useI18n.ts | 38 + src/composables/useTheme.ts | 49 + src/config/gameConfig.ts | 570 +++++++ src/env.d.ts | 7 + src/lib/utils.ts | 7 + src/locales/de.ts | 536 ++++++ src/locales/en.ts | 533 ++++++ src/locales/index.ts | 53 + src/locales/ja.ts | 531 ++++++ src/locales/ko.ts | 532 ++++++ src/locales/ru.ts | 536 ++++++ src/locales/zh-CN.ts | 531 ++++++ src/locales/zh-TW.ts | 531 ++++++ src/logic/battleLogic.ts | 353 ++++ src/logic/buildingLogic.ts | 194 +++ src/logic/buildingValidation.ts | 161 ++ src/logic/fleetLogic.ts | 374 +++++ src/logic/gameLogic.ts | 132 ++ src/logic/moonLogic.ts | 111 ++ src/logic/moonValidation.ts | 58 + src/logic/officerLogic.ts | 96 ++ src/logic/planetLogic.ts | 193 +++ src/logic/pointsLogic.ts | 137 ++ src/logic/publicLogic.ts | 102 ++ src/logic/researchLogic.ts | 96 ++ src/logic/researchValidation.ts | 81 + src/logic/resourceLogic.ts | 161 ++ src/logic/shipLogic.ts | 231 +++ src/logic/shipValidation.ts | 168 ++ src/main.ts | 16 + src/router/index.ts | 20 + src/stores/detailDialogStore.ts | 52 + src/stores/gameStore.ts | 47 + src/style.css | 193 +++ src/types/game.ts | 340 ++++ src/utils/battleSimulator.ts | 447 +++++ src/utils/crypto.ts | 25 + src/utils/format.ts | 71 + src/views/BattleSimulatorView.vue | 569 +++++++ src/views/BuildingsView.vue | 254 +++ src/views/DefenseView.vue | 263 +++ src/views/FleetView.vue | 569 +++++++ src/views/GalaxyView.vue | 264 +++ src/views/MessagesView.vue | 252 +++ src/views/OfficersView.vue | 269 +++ src/views/OverviewView.vue | 166 ++ src/views/ResearchView.vue | 161 ++ src/views/SettingsView.vue | 281 ++++ src/views/ShipyardView.vue | 239 +++ tsconfig.app.json | 18 + tsconfig.json | 10 + tsconfig.node.json | 26 + vite.config.ts | 11 + 178 files changed, 17258 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/BUG反馈.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/documentation-improvement.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/feedback-suggestion.md create mode 100644 .github/ISSUE_TEMPLATE/功能请求.md create mode 100644 .github/ISSUE_TEMPLATE/反馈建议.md create mode 100644 .github/ISSUE_TEMPLATE/文档改进.md create mode 100644 .github/workflows/ogame-vue-ts.yml create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README.zh-CN.md create mode 100644 components.json create mode 100644 docs/assets/AlertDialog-BbpHh-RO.js create mode 100644 docs/assets/BattleSimulatorView-DI2C9KJc.js create mode 100644 docs/assets/BuildingsView-B00w_iux.js create mode 100644 docs/assets/CardDescription-Cb9v68Xa.js create mode 100644 docs/assets/CardUnlockOverlay-tiSNfyF3.js create mode 100644 docs/assets/DefenseView-4OcM0vVE.js create mode 100644 docs/assets/FleetView-DY7yR25i.js create mode 100644 docs/assets/GalaxyView-C-Ue9wLo.js create mode 100644 docs/assets/MessagesView-sKxpYFTS.js create mode 100644 docs/assets/OfficersView-D9JTCZE2.js create mode 100644 docs/assets/OverviewView-BBQfV6K0.js create mode 100644 docs/assets/ResearchView-Do1WxfNH.js create mode 100644 docs/assets/SettingsView-IZn-5kW1.js create mode 100644 docs/assets/ShipyardView-BGb4XhA2.js create mode 100644 docs/assets/UnlockRequirement-DeOUxO_t.js create mode 100644 docs/assets/eye-BrZrKlSB.js create mode 100644 docs/assets/index-BXmZ_V9Y.css create mode 100644 docs/assets/index-BpOElaf9.js create mode 100644 docs/assets/logo-Cz0cNqhe.svg create mode 100644 docs/assets/shipValidation-BZIVAW1v.js create mode 100644 docs/assets/useGameConfig-DqswvIth.js create mode 100644 docs/index.html create mode 100644 docs/logo.svg create mode 100644 index.html create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 public/logo.svg create mode 100644 src/App.vue create mode 100644 src/assets/logo.svg create mode 100644 src/components/AlertDialog.vue create mode 100644 src/components/CardUnlockOverlay.vue create mode 100644 src/components/ConfirmDialog.vue create mode 100644 src/components/DetailDialog.vue create mode 100644 src/components/ResourceIcon.vue create mode 100644 src/components/UnlockRequirement.vue create mode 100644 src/components/detail-views/BuildingDetailView.vue create mode 100644 src/components/detail-views/DefenseDetailView.vue create mode 100644 src/components/detail-views/ShipDetailView.vue create mode 100644 src/components/detail-views/TechnologyDetailView.vue create mode 100644 src/components/ui/alert-dialog/AlertDialog.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogAction.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogCancel.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogContent.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogDescription.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogFooter.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogHeader.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogTitle.vue create mode 100644 src/components/ui/alert-dialog/AlertDialogTrigger.vue create mode 100644 src/components/ui/alert-dialog/index.ts create mode 100644 src/components/ui/badge/Badge.vue create mode 100644 src/components/ui/badge/index.ts create mode 100644 src/components/ui/button/Button.vue create mode 100644 src/components/ui/button/index.ts create mode 100644 src/components/ui/card/Card.vue create mode 100644 src/components/ui/card/CardAction.vue create mode 100644 src/components/ui/card/CardContent.vue create mode 100644 src/components/ui/card/CardDescription.vue create mode 100644 src/components/ui/card/CardFooter.vue create mode 100644 src/components/ui/card/CardHeader.vue create mode 100644 src/components/ui/card/CardTitle.vue create mode 100644 src/components/ui/card/index.ts create mode 100644 src/components/ui/dialog/Dialog.vue create mode 100644 src/components/ui/dialog/DialogClose.vue create mode 100644 src/components/ui/dialog/DialogContent.vue create mode 100644 src/components/ui/dialog/DialogDescription.vue create mode 100644 src/components/ui/dialog/DialogFooter.vue create mode 100644 src/components/ui/dialog/DialogHeader.vue create mode 100644 src/components/ui/dialog/DialogOverlay.vue create mode 100644 src/components/ui/dialog/DialogScrollContent.vue create mode 100644 src/components/ui/dialog/DialogTitle.vue create mode 100644 src/components/ui/dialog/DialogTrigger.vue create mode 100644 src/components/ui/dialog/index.ts create mode 100644 src/components/ui/input/Input.vue create mode 100644 src/components/ui/input/index.ts create mode 100644 src/components/ui/label/Label.vue create mode 100644 src/components/ui/label/index.ts create mode 100644 src/components/ui/popover/Popover.vue create mode 100644 src/components/ui/popover/PopoverAnchor.vue create mode 100644 src/components/ui/popover/PopoverContent.vue create mode 100644 src/components/ui/popover/PopoverTrigger.vue create mode 100644 src/components/ui/popover/index.ts create mode 100644 src/components/ui/progress/Progress.vue create mode 100644 src/components/ui/progress/index.ts create mode 100644 src/components/ui/select/Select.vue create mode 100644 src/components/ui/select/SelectContent.vue create mode 100644 src/components/ui/select/SelectGroup.vue create mode 100644 src/components/ui/select/SelectItem.vue create mode 100644 src/components/ui/select/SelectItemText.vue create mode 100644 src/components/ui/select/SelectLabel.vue create mode 100644 src/components/ui/select/SelectScrollDownButton.vue create mode 100644 src/components/ui/select/SelectScrollUpButton.vue create mode 100644 src/components/ui/select/SelectSeparator.vue create mode 100644 src/components/ui/select/SelectTrigger.vue create mode 100644 src/components/ui/select/SelectValue.vue create mode 100644 src/components/ui/select/index.ts create mode 100644 src/components/ui/sonner/Sonner.vue create mode 100644 src/components/ui/sonner/index.ts create mode 100644 src/components/ui/table/Table.vue create mode 100644 src/components/ui/table/TableBody.vue create mode 100644 src/components/ui/table/TableCaption.vue create mode 100644 src/components/ui/table/TableCell.vue create mode 100644 src/components/ui/table/TableEmpty.vue create mode 100644 src/components/ui/table/TableFooter.vue create mode 100644 src/components/ui/table/TableHead.vue create mode 100644 src/components/ui/table/TableHeader.vue create mode 100644 src/components/ui/table/TableRow.vue create mode 100644 src/components/ui/table/index.ts create mode 100644 src/components/ui/table/utils.ts create mode 100644 src/composables/useGameConfig.ts create mode 100644 src/composables/useI18n.ts create mode 100644 src/composables/useTheme.ts create mode 100644 src/config/gameConfig.ts create mode 100644 src/env.d.ts create mode 100644 src/lib/utils.ts create mode 100644 src/locales/de.ts create mode 100644 src/locales/en.ts create mode 100644 src/locales/index.ts create mode 100644 src/locales/ja.ts create mode 100644 src/locales/ko.ts create mode 100644 src/locales/ru.ts create mode 100644 src/locales/zh-CN.ts create mode 100644 src/locales/zh-TW.ts create mode 100644 src/logic/battleLogic.ts create mode 100644 src/logic/buildingLogic.ts create mode 100644 src/logic/buildingValidation.ts create mode 100644 src/logic/fleetLogic.ts create mode 100644 src/logic/gameLogic.ts create mode 100644 src/logic/moonLogic.ts create mode 100644 src/logic/moonValidation.ts create mode 100644 src/logic/officerLogic.ts create mode 100644 src/logic/planetLogic.ts create mode 100644 src/logic/pointsLogic.ts create mode 100644 src/logic/publicLogic.ts create mode 100644 src/logic/researchLogic.ts create mode 100644 src/logic/researchValidation.ts create mode 100644 src/logic/resourceLogic.ts create mode 100644 src/logic/shipLogic.ts create mode 100644 src/logic/shipValidation.ts create mode 100644 src/main.ts create mode 100644 src/router/index.ts create mode 100644 src/stores/detailDialogStore.ts create mode 100644 src/stores/gameStore.ts create mode 100644 src/style.css create mode 100644 src/types/game.ts create mode 100644 src/utils/battleSimulator.ts create mode 100644 src/utils/crypto.ts create mode 100644 src/utils/format.ts create mode 100644 src/views/BattleSimulatorView.vue create mode 100644 src/views/BuildingsView.vue create mode 100644 src/views/DefenseView.vue create mode 100644 src/views/FleetView.vue create mode 100644 src/views/GalaxyView.vue create mode 100644 src/views/MessagesView.vue create mode 100644 src/views/OfficersView.vue create mode 100644 src/views/OverviewView.vue create mode 100644 src/views/ResearchView.vue create mode 100644 src/views/SettingsView.vue create mode 100644 src/views/ShipyardView.vue create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.github/ISSUE_TEMPLATE/BUG反馈.md b/.github/ISSUE_TEMPLATE/BUG反馈.md new file mode 100644 index 0000000..217e6f5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG反馈.md @@ -0,0 +1,31 @@ +--- +name: BUG反馈 +about: 报告项目中发现的缺陷或问题 +title: '[BUG] 简要描述问题' +labels: 'bug' +assignees: '' +--- + +**问题描述** +清晰准确地描述遇到的问题 + +**重现步骤** + +1. 第一步操作 +2. 第二步操作 +3. 出现问题的操作 + +**期望行为** +描述您认为正确的行为应该是怎样的 + +**实际行为** +描述实际发生的错误行为 + +**环境信息** + +- 操作系统: +- 浏览器(如适用): +- 项目版本: + +**截图或日志(可选)** +如果有错误截图或日志,请提供 diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..bc8916a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,31 @@ +--- +name: Bug Report +about: Report defects or issues found in the project +title: '[BUG] Brief description of the issue' +labels: 'bug' +assignees: '' +--- + +**Issue Description** +Clearly and accurately describe the problem you encountered + +**Steps to Reproduce** + +1. First operation step +2. Second operation step +3. Operation where the issue occurs + +**Expected Behavior** +Describe what you think the correct behavior should be + +**Actual Behavior** +Describe the actual erroneous behavior that occurred + +**Environment Information** + +- Operating System: +- Browser (if applicable): +- Project Version: + +**Screenshots or Logs (optional)** +If you have error screenshots or logs, please provide them diff --git a/.github/ISSUE_TEMPLATE/documentation-improvement.md b/.github/ISSUE_TEMPLATE/documentation-improvement.md new file mode 100644 index 0000000..68913de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation-improvement.md @@ -0,0 +1,19 @@ +--- +name: Documentation Improvement +about: Report documentation issues or suggest improvements +title: '[Docs] Brief description of the issue' +labels: 'documentation' +assignees: '' +--- + +**Documentation Location** +Point out the documentation path or URL that needs improvement + +**Current Issue** +Describe the current problem or unclear areas in the documentation + +**Improvement Suggestions** +Provide specific improvement suggestions + +**Additional Information (optional)** +Any other information that helps improve the documentation diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..2b12919 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,19 @@ +--- +name: Feature Request +about: Request to add new features or improve existing ones +title: '[Feature] Brief description of the feature' +labels: 'enhancement' +assignees: '' +--- + +**Feature Description** +Clearly describe the feature you would like to see added + +**Feature Background** +Explain why this feature is needed and what problem it solves + +**Suggested Implementation (optional)** +If you have specific implementation ideas, you can describe them here + +**Additional Information** +Any other information that helps understand this feature diff --git a/.github/ISSUE_TEMPLATE/feedback-suggestion.md b/.github/ISSUE_TEMPLATE/feedback-suggestion.md new file mode 100644 index 0000000..6d6fa7f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feedback-suggestion.md @@ -0,0 +1,19 @@ +--- +name: Feedback & Suggestion +about: Propose feature suggestions or improvements for this project +title: '[Suggestion] Brief description of your suggestion' +labels: 'enhancement' +assignees: '' +--- + +**What is your suggestion?** +Please clearly describe the feature or improvement you would like to see + +**Why is this feature/improvement needed?** +Explain what problem this suggestion solves or what value it brings + +**Your expected implementation (optional)** +If you have specific implementation ideas, you can describe them here + +**Additional information (optional)** +Any other information that helps understand this suggestion diff --git a/.github/ISSUE_TEMPLATE/功能请求.md b/.github/ISSUE_TEMPLATE/功能请求.md new file mode 100644 index 0000000..ea39f99 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/功能请求.md @@ -0,0 +1,19 @@ +--- +name: 功能请求 +about: 请求添加新功能或改进现有功能 +title: '[功能] 简要描述功能' +labels: 'enhancement' +assignees: '' +--- + +**功能描述** +清晰描述您希望添加的功能 + +**功能背景** +说明为什么需要这个功能,它能解决什么问题 + +**建议实现方案(可选)** +如果有具体的实现想法,可以在这里描述 + +**附加信息** +任何其他有助于理解这个功能的信息 diff --git a/.github/ISSUE_TEMPLATE/反馈建议.md b/.github/ISSUE_TEMPLATE/反馈建议.md new file mode 100644 index 0000000..493ee03 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/反馈建议.md @@ -0,0 +1,19 @@ +--- +name: 反馈建议 +about: 为这个项目提出功能建议或改进意见 +title: '[建议] 简要描述您的建议' +labels: 'enhancement' +assignees: '' +--- + +**您的建议是什么?** +请清晰描述您希望添加的功能或改进点 + +**为什么需要这个功能/改进?** +说明这个建议会解决什么问题或带来什么价值 + +**您期望的实现方式(可选)** +如果有具体的实现想法,可以在这里描述 + +**附加信息(可选)** +任何其他有助于理解这个建议的信息 diff --git a/.github/ISSUE_TEMPLATE/文档改进.md b/.github/ISSUE_TEMPLATE/文档改进.md new file mode 100644 index 0000000..b89f864 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/文档改进.md @@ -0,0 +1,19 @@ +--- +name: 文档改进 +about: 报告文档问题或建议改进 +title: '[文档] 简要描述问题' +labels: 'documentation' +assignees: '' +--- + +**文档位置** +指出需要改进的文档路径或 URL + +**当前问题** +描述当前文档存在的问题或不清晰的地方 + +**改进建议** +提出具体的改进建议 + +**附加信息(可选)** +任何其他有助于改进文档的信息 diff --git a/.github/workflows/ogame-vue-ts.yml b/.github/workflows/ogame-vue-ts.yml new file mode 100644 index 0000000..60b5022 --- /dev/null +++ b/.github/workflows/ogame-vue-ts.yml @@ -0,0 +1,105 @@ +name: 自动化创建Docker镜像 + +on: + push: + branches: + - main + +jobs: + build-amd64: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: 检查代码 + uses: actions/checkout@v2 + + - name: 登录 GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GHCR_TOKEN }} + + - name: 登录 Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: 构建并推送 amd64 Docker镜像 + uses: docker/build-push-action@v3 + with: + context: . + platforms: linux/amd64 + push: true + tags: | + ghcr.io/${{ github.repository_owner }}/blist:amd64 + ${{ secrets.DOCKERHUB_USERNAME }}/blist:amd64 + + build-arm64: + runs-on: ubuntu-22.04-arm + permissions: + contents: read + packages: write + steps: + - name: 检查代码 + uses: actions/checkout@v2 + + - name: 登录 GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GHCR_TOKEN }} + + - name: 登录 Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: 构建并推送 arm64 Docker镜像 + uses: docker/build-push-action@v3 + with: + context: . + platforms: linux/arm64 + push: true + tags: | + ghcr.io/${{ github.repository_owner }}/blist:arm64 + ${{ secrets.DOCKERHUB_USERNAME }}/blist:arm64 + + create-manifest: + needs: [build-amd64, build-arm64] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: 登录 GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GHCR_TOKEN }} + + - name: 登录 Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: 创建并推送多架构清单 + run: | + # GitHub Container Registry + docker manifest create ghcr.io/${{ github.repository_owner }}/blist:latest \ + ghcr.io/${{ github.repository_owner }}/blist:amd64 \ + ghcr.io/${{ github.repository_owner }}/blist:arm64 + docker manifest push ghcr.io/${{ github.repository_owner }}/blist:latest + + # Docker Hub + docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/blist:latest \ + ${{ secrets.DOCKERHUB_USERNAME }}/blist:amd64 \ + ${{ secrets.DOCKERHUB_USERNAME }}/blist:arm64 + docker manifest push ${{ secrets.DOCKERHUB_USERNAME }}/blist:latest \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73f996e --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +CLAUDE.md + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.claude/* +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6ec9e2a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:latest + +RUN mkdir -p /workspace + +WORKDIR /workspace + +RUN npm config set registry https://registry.npmmirror.com + +RUN cd /workspace + +RUN git clone https://github.com/setube/ogame-vue-ts.git + +RUN mv ./ogame-vue-ts/* . ; rm -rf ./ogame-vue-ts/ + +RUN npm install -g pnpm ; pnpm install ; npx vite build + +CMD ["npx", "vite", "preview", "--host", "0.0.0.0", "--port", "25121"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bf4a0a1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,32 @@ +Creative Commons Attribution-NonCommercial 4.0 International Public License + +This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 +International Public License. To view a copy of this license, visit +https://creativecommons.org/licenses/by-nc/4.0/legalcode. + +You are free to: +Share — copy and redistribute the material in any medium or format +Adapt — remix, transform, and build upon the material + +Under the following terms: +Attribution — You must give appropriate credit, provide a link to the license, +and indicate if changes were made. You may do so in any reasonable manner, +but not in any way that suggests the licensor endorses you or your use. + +NonCommercial — You may not use the material for commercial purposes. + +No additional restrictions — You may not apply legal terms or technological +measures that legally restrict others from doing anything the license permits. + +Notice: +This work is the original creation of Jun Qian (谦君). Source code available at: +https://github.com/setube/ogame-vue-ts +This license does not constitute a waiver of any copyright or related rights. + +When you share adaptations of this work, you must: +- Provide prominent attribution to the original author +- Retain this license document +- Clearly indicate modifications made and dates +- Distribute under the same CC BY-NC 4.0 license + +© 2025 Jun Qian - All rights reserved (except those granted by this license) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a55c80d --- /dev/null +++ b/README.md @@ -0,0 +1,241 @@ +
+ OGame Vue TS Logo + + # OGame Vue TS + + A modern web-based implementation of the classic OGame space strategy game, built with Vue 3 and TypeScript. + + [![License: CC BY-NC 4.0](https://img.shields.io/badge/License-CC%20BY--NC%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc/4.0/) + [![Vue 3](https://img.shields.io/badge/Vue-3.5-brightgreen.svg)](https://vuejs.org/) + [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/) + [![Vite](https://img.shields.io/badge/Vite-7.2-646CFF.svg)](https://vitejs.dev/) + +
+ +## 📖 About + +OGame Vue TS is a single-player, browser-based space strategy game inspired by the classic OGame. Build your empire across the galaxy, research technologies, construct ships, and engage in epic space battles. This project is built with modern web technologies, offering a smooth and responsive gaming experience entirely in your browser with local data persistence. + +## ✨ Features + +- 🌍 **Multi-language Support** - Available in 6 languages: English, Chinese (Simplified & Traditional), German, Russian, and Korean +- 🏗️ **Building Management** - Construct and upgrade various buildings on planets and moons +- 🔬 **Research Technologies** - Unlock advanced technologies to enhance your empire +- 🚀 **Fleet Management** - Build ships, send missions, and engage in tactical space battles +- 🛡️ **Defense Systems** - Deploy defensive structures to protect your colonies +- 👨‍✈️ **Officers System** - Recruit officers to gain strategic advantages +- ⚔️ **Battle Simulator** - Test combat scenarios before committing resources +- 🌌 **Galaxy View** - Explore the universe and interact with other planets +- 💾 **Local Data Persistence** - All game data is encrypted and stored locally in your browser +- 🌓 **Dark/Light Mode** - Choose your preferred visual theme +- 📊 **Queue Management** - Manage multiple build and research queues +- 🌙 **Moon Generation** - Chance-based moon creation from debris fields + +## 🛠️ Tech Stack + +- **Frontend Framework:** [Vue 3](https://vuejs.org/) with Composition API (` + + + + +
+ + + \ No newline at end of file diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 0000000..4e5393d --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..6c5be90 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + OGame-Vue-Ts + + + +
+ + + + + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..3ff44f1 --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "ogame-vue-ts", + "title": "OGame-Vue-Ts", + "qq": "920930589", + "id": "2zBlHPUA6E", + "author": "setube", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite --port 25121", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.17", + "@tanstack/vue-table": "^8.21.3", + "@vueuse/core": "^14.1.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-vue-next": "^0.556.0", + "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", + "reka-ui": "^2.6.1", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^4.1.17", + "vue": "^3.5.24", + "vue-router": "4", + "vue-sonner": "^2.0.9" + }, + "devDependencies": { + "@types/crypto-js": "^4.2.2", + "@types/file-saver": "^2.0.7", + "@types/node": "^24.10.2", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.8.1", + "tw-animate-css": "^1.4.0", + "typescript": "~5.9.3", + "vite": "npm:rolldown-vite@7.2.5", + "vue-tsc": "^3.1.4" + }, + "pnpm": { + "overrides": { + "vite": "npm:rolldown-vite@7.2.5" + } + }, + "packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..bd5d45c --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1443 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + vite: npm:rolldown-vite@7.2.5 + +importers: + + .: + dependencies: + '@tailwindcss/vite': + specifier: ^4.1.17 + version: 4.1.17(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1)) + '@tanstack/vue-table': + specifier: ^8.21.3 + version: 8.21.3(vue@3.5.25(typescript@5.9.3)) + '@vueuse/core': + specifier: ^14.1.0 + version: 14.1.0(vue@3.5.25(typescript@5.9.3)) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-vue-next: + specifier: ^0.556.0 + version: 0.556.0(vue@3.5.25(typescript@5.9.3)) + pinia: + specifier: ^3.0.4 + version: 3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)) + pinia-plugin-persistedstate: + specifier: ^4.7.1 + version: 4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))) + reka-ui: + specifier: ^2.6.1 + version: 2.6.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 + tailwindcss: + specifier: ^4.1.17 + version: 4.1.17 + vue: + specifier: ^3.5.24 + version: 3.5.25(typescript@5.9.3) + vue-router: + specifier: '4' + version: 4.6.3(vue@3.5.25(typescript@5.9.3)) + vue-sonner: + specifier: ^2.0.9 + version: 2.0.9 + devDependencies: + '@types/crypto-js': + specifier: ^4.2.2 + version: 4.2.2 + '@types/file-saver': + specifier: ^2.0.7 + version: 2.0.7 + '@types/node': + specifier: ^24.10.2 + version: 24.10.2 + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.2(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1))(vue@3.5.25(typescript@5.9.3)) + '@vue/tsconfig': + specifier: ^0.8.1 + version: 0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)) + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: npm:rolldown-vite@7.2.5 + version: rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1) + vue-tsc: + specifier: ^3.1.4 + version: 3.1.8(typescript@5.9.3) + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@floating-ui/vue@1.1.9': + resolution: {integrity: sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==} + + '@internationalized/date@3.10.0': + resolution: {integrity: sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==} + + '@internationalized/number@3.6.5': + resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.0': + resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + + '@oxc-project/runtime@0.97.0': + resolution: {integrity: sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.97.0': + resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==} + + '@rolldown/binding-android-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + resolution: {integrity: sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + resolution: {integrity: sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + resolution: {integrity: sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + resolution: {integrity: sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.50': + resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} + + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + + '@tailwindcss/node@4.1.17': + resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} + + '@tailwindcss/oxide-android-arm64@4.1.17': + resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.17': + resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.17': + resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.17': + resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@tanstack/virtual-core@3.13.13': + resolution: {integrity: sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA==} + + '@tanstack/vue-table@8.21.3': + resolution: {integrity: sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==} + engines: {node: '>=12'} + peerDependencies: + vue: '>=3.2' + + '@tanstack/vue-virtual@3.13.13': + resolution: {integrity: sha512-Cf2xIEE8nWAfsX0N5nihkPYMeQRT+pHt4NEkuP8rNCn6lVnLDiV8rC8IeIxbKmQC0yPnj4SIBLwXYVf86xxKTQ==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/crypto-js@4.2.2': + resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} + + '@types/file-saver@2.0.7': + resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} + + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@vitejs/plugin-vue@6.0.2': + resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.26': + resolution: {integrity: sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==} + + '@volar/source-map@2.4.26': + resolution: {integrity: sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==} + + '@volar/typescript@2.4.26': + resolution: {integrity: sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==} + + '@vue/compiler-core@3.5.25': + resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} + + '@vue/compiler-dom@3.5.25': + resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==} + + '@vue/compiler-sfc@3.5.25': + resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==} + + '@vue/compiler-ssr@3.5.25': + resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + + '@vue/language-core@3.1.8': + resolution: {integrity: sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.25': + resolution: {integrity: sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==} + + '@vue/runtime-core@3.5.25': + resolution: {integrity: sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==} + + '@vue/runtime-dom@3.5.25': + resolution: {integrity: sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==} + + '@vue/server-renderer@3.5.25': + resolution: {integrity: sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==} + peerDependencies: + vue: 3.5.25 + + '@vue/shared@3.5.25': + resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==} + + '@vue/tsconfig@0.8.1': + resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==} + peerDependencies: + typescript: 5.x + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + + '@vueuse/core@14.1.0': + resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + + '@vueuse/metadata@14.1.0': + resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==} + + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + + '@vueuse/shared@14.1.0': + resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==} + peerDependencies: + vue: ^3.5.0 + + alien-signals@3.1.1: + resolution: {integrity: sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lucide-vue-next@0.556.0: + resolution: {integrity: sha512-JvdCM2smkWrMDhkfD/FpZiWekkbWD6MZLstIFx/FOVZgULrnMr5hegCB9LlTdgllEFnQYQs8hhHC1WYcAV9HTA==} + peerDependencies: + vue: '>=3.0.1' + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pinia-plugin-persistedstate@4.7.1: + resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==} + peerDependencies: + '@nuxt/kit': '>=3.0.0' + '@pinia/nuxt': '>=0.10.0' + pinia: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@pinia/nuxt': + optional: true + pinia: + optional: true + + pinia@3.0.4: + resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} + peerDependencies: + typescript: '>=4.5.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + reka-ui@2.6.1: + resolution: {integrity: sha512-XK7cJDQoNuGXfCNzBBo/81Yg/OgjPwvbabnlzXG2VsdSgNsT6iIkuPBPr+C0Shs+3bb0x0lbPvgQAhMSCKm5Ww==} + peerDependencies: + vue: '>= 3.2.0' + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rolldown-vite@7.2.5: + resolution: {integrity: sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + esbuild: ^0.25.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + rolldown@1.0.0-beta.50: + resolution: {integrity: sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss@4.1.17: + resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-router@4.6.3: + resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==} + peerDependencies: + vue: ^3.5.0 + + vue-sonner@2.0.9: + resolution: {integrity: sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw==} + peerDependencies: + '@nuxt/kit': ^4.0.3 + '@nuxt/schema': ^4.0.3 + nuxt: ^4.0.3 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@nuxt/schema': + optional: true + nuxt: + optional: true + + vue-tsc@3.1.8: + resolution: {integrity: sha512-deKgwx6exIHeZwF601P1ktZKNF0bepaSN4jBU3AsbldPx9gylUc1JDxYppl82yxgkAgaz0Y0LCLOi+cXe9HMYA==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.25: + resolution: {integrity: sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@emnapi/core@1.7.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@floating-ui/vue@1.1.9(vue@3.5.25(typescript@5.9.3))': + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/utils': 0.2.10 + vue-demi: 0.14.10(vue@3.5.25(typescript@5.9.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@internationalized/date@3.10.0': + dependencies: + '@swc/helpers': 0.5.17 + + '@internationalized/number@3.6.5': + dependencies: + '@swc/helpers': 0.5.17 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.0': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@oxc-project/runtime@0.97.0': {} + + '@oxc-project/types@0.97.0': {} + + '@rolldown/binding-android-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + dependencies: + '@napi-rs/wasm-runtime': 1.1.0 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.50': {} + + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.17': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.17 + + '@tailwindcss/oxide-android-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide@4.1.17': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-x64': 4.1.17 + '@tailwindcss/oxide-freebsd-x64': 4.1.17 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.17 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-x64-musl': 4.1.17 + '@tailwindcss/oxide-wasm32-wasi': 4.1.17 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 + + '@tailwindcss/vite@4.1.17(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1))': + dependencies: + '@tailwindcss/node': 4.1.17 + '@tailwindcss/oxide': 4.1.17 + tailwindcss: 4.1.17 + vite: rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1) + + '@tanstack/table-core@8.21.3': {} + + '@tanstack/virtual-core@3.13.13': {} + + '@tanstack/vue-table@8.21.3(vue@3.5.25(typescript@5.9.3))': + dependencies: + '@tanstack/table-core': 8.21.3 + vue: 3.5.25(typescript@5.9.3) + + '@tanstack/vue-virtual@3.13.13(vue@3.5.25(typescript@5.9.3))': + dependencies: + '@tanstack/virtual-core': 3.13.13 + vue: 3.5.25(typescript@5.9.3) + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/crypto-js@4.2.2': {} + + '@types/file-saver@2.0.7': {} + + '@types/node@24.10.2': + dependencies: + undici-types: 7.16.0 + + '@types/web-bluetooth@0.0.21': {} + + '@vitejs/plugin-vue@6.0.2(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1))(vue@3.5.25(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.50 + vite: rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1) + vue: 3.5.25(typescript@5.9.3) + + '@volar/language-core@2.4.26': + dependencies: + '@volar/source-map': 2.4.26 + + '@volar/source-map@2.4.26': {} + + '@volar/typescript@2.4.26': + dependencies: + '@volar/language-core': 2.4.26 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.25': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.25 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.25': + dependencies: + '@vue/compiler-core': 3.5.25 + '@vue/shared': 3.5.25 + + '@vue/compiler-sfc@3.5.25': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.25 + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-ssr': 3.5.25 + '@vue/shared': 3.5.25 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.25': + dependencies: + '@vue/compiler-dom': 3.5.25 + '@vue/shared': 3.5.25 + + '@vue/devtools-api@6.6.4': {} + + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/language-core@3.1.8(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.26 + '@vue/compiler-dom': 3.5.25 + '@vue/shared': 3.5.25 + alien-signals: 3.1.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + optionalDependencies: + typescript: 5.9.3 + + '@vue/reactivity@3.5.25': + dependencies: + '@vue/shared': 3.5.25 + + '@vue/runtime-core@3.5.25': + dependencies: + '@vue/reactivity': 3.5.25 + '@vue/shared': 3.5.25 + + '@vue/runtime-dom@3.5.25': + dependencies: + '@vue/reactivity': 3.5.25 + '@vue/runtime-core': 3.5.25 + '@vue/shared': 3.5.25 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.25(vue@3.5.25(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.25 + '@vue/shared': 3.5.25 + vue: 3.5.25(typescript@5.9.3) + + '@vue/shared@3.5.25': {} + + '@vue/tsconfig@0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))': + optionalDependencies: + typescript: 5.9.3 + vue: 3.5.25(typescript@5.9.3) + + '@vueuse/core@12.8.2(typescript@5.9.3)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.9.3) + vue: 3.5.25(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + '@vueuse/core@14.1.0(vue@3.5.25(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.1.0 + '@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3)) + vue: 3.5.25(typescript@5.9.3) + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/metadata@14.1.0': {} + + '@vueuse/shared@12.8.2(typescript@5.9.3)': + dependencies: + vue: 3.5.25(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + '@vueuse/shared@14.1.0(vue@3.5.25(typescript@5.9.3))': + dependencies: + vue: 3.5.25(typescript@5.9.3) + + alien-signals@3.1.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + birpc@2.9.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + csstype@3.2.3: {} + + defu@6.1.4: {} + + detect-libc@2.1.2: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fsevents@2.3.3: + optional: true + + graceful-fs@4.2.11: {} + + hookable@5.5.3: {} + + is-what@5.5.0: {} + + jiti@2.6.1: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lucide-vue-next@0.556.0(vue@3.5.25(typescript@5.9.3)): + dependencies: + vue: 3.5.25(typescript@5.9.3) + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mitt@3.0.1: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + ohash@2.0.11: {} + + path-browserify@1.0.1: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pinia-plugin-persistedstate@4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))): + dependencies: + defu: 6.1.4 + optionalDependencies: + pinia: 3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)) + + pinia@3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 7.7.9 + vue: 3.5.25(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + reka-ui@2.6.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)): + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/vue': 1.1.9(vue@3.5.25(typescript@5.9.3)) + '@internationalized/date': 3.10.0 + '@internationalized/number': 3.6.5 + '@tanstack/vue-virtual': 3.13.13(vue@3.5.25(typescript@5.9.3)) + '@vueuse/core': 12.8.2(typescript@5.9.3) + '@vueuse/shared': 12.8.2(typescript@5.9.3) + aria-hidden: 1.2.6 + defu: 6.1.4 + ohash: 2.0.11 + vue: 3.5.25(typescript@5.9.3) + transitivePeerDependencies: + - '@vue/composition-api' + - typescript + + rfdc@1.4.1: {} + + rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1): + dependencies: + '@oxc-project/runtime': 0.97.0 + fdir: 6.5.0(picomatch@4.0.3) + lightningcss: 1.30.2 + picomatch: 4.0.3 + postcss: 8.5.6 + rolldown: 1.0.0-beta.50 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.2 + fsevents: 2.3.3 + jiti: 2.6.1 + + rolldown@1.0.0-beta.50: + dependencies: + '@oxc-project/types': 0.97.0 + '@rolldown/pluginutils': 1.0.0-beta.50 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-x64': 1.0.0-beta.50 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.50 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.50 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.50 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.50 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.50 + + source-map-js@1.2.1: {} + + speakingurl@14.0.1: {} + + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + + tailwind-merge@3.4.0: {} + + tailwindcss@4.1.17: {} + + tapable@2.3.0: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tslib@2.8.1: {} + + tw-animate-css@1.4.0: {} + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + vscode-uri@3.1.0: {} + + vue-demi@0.14.10(vue@3.5.25(typescript@5.9.3)): + dependencies: + vue: 3.5.25(typescript@5.9.3) + + vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.25(typescript@5.9.3) + + vue-sonner@2.0.9: {} + + vue-tsc@3.1.8(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.26 + '@vue/language-core': 3.1.8(typescript@5.9.3) + typescript: 5.9.3 + + vue@3.5.25(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-sfc': 3.5.25 + '@vue/runtime-dom': 3.5.25 + '@vue/server-renderer': 3.5.25(vue@3.5.25(typescript@5.9.3)) + '@vue/shared': 3.5.25 + optionalDependencies: + typescript: 5.9.3 diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..4e5393d --- /dev/null +++ b/public/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..a7439fb --- /dev/null +++ b/src/App.vue @@ -0,0 +1,556 @@ + + + + + diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..4e5393d --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/AlertDialog.vue b/src/components/AlertDialog.vue new file mode 100644 index 0000000..636d03b --- /dev/null +++ b/src/components/AlertDialog.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/components/CardUnlockOverlay.vue b/src/components/CardUnlockOverlay.vue new file mode 100644 index 0000000..7ef25d1 --- /dev/null +++ b/src/components/CardUnlockOverlay.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/components/ConfirmDialog.vue b/src/components/ConfirmDialog.vue new file mode 100644 index 0000000..ae7d32f --- /dev/null +++ b/src/components/ConfirmDialog.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/components/DetailDialog.vue b/src/components/DetailDialog.vue new file mode 100644 index 0000000..52cdc3e --- /dev/null +++ b/src/components/DetailDialog.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/components/ResourceIcon.vue b/src/components/ResourceIcon.vue new file mode 100644 index 0000000..48bc4a5 --- /dev/null +++ b/src/components/ResourceIcon.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/components/UnlockRequirement.vue b/src/components/UnlockRequirement.vue new file mode 100644 index 0000000..370c2da --- /dev/null +++ b/src/components/UnlockRequirement.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/components/detail-views/BuildingDetailView.vue b/src/components/detail-views/BuildingDetailView.vue new file mode 100644 index 0000000..589b14c --- /dev/null +++ b/src/components/detail-views/BuildingDetailView.vue @@ -0,0 +1,195 @@ + + + diff --git a/src/components/detail-views/DefenseDetailView.vue b/src/components/detail-views/DefenseDetailView.vue new file mode 100644 index 0000000..b2b6f5e --- /dev/null +++ b/src/components/detail-views/DefenseDetailView.vue @@ -0,0 +1,168 @@ + + + diff --git a/src/components/detail-views/ShipDetailView.vue b/src/components/detail-views/ShipDetailView.vue new file mode 100644 index 0000000..3c443e7 --- /dev/null +++ b/src/components/detail-views/ShipDetailView.vue @@ -0,0 +1,204 @@ + + + diff --git a/src/components/detail-views/TechnologyDetailView.vue b/src/components/detail-views/TechnologyDetailView.vue new file mode 100644 index 0000000..0570cbe --- /dev/null +++ b/src/components/detail-views/TechnologyDetailView.vue @@ -0,0 +1,154 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialog.vue b/src/components/ui/alert-dialog/AlertDialog.vue new file mode 100644 index 0000000..7ab70a9 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialog.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogAction.vue b/src/components/ui/alert-dialog/AlertDialogAction.vue new file mode 100644 index 0000000..baa56d9 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogAction.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogCancel.vue b/src/components/ui/alert-dialog/AlertDialogCancel.vue new file mode 100644 index 0000000..944e60e --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogCancel.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogContent.vue b/src/components/ui/alert-dialog/AlertDialogContent.vue new file mode 100644 index 0000000..dad3813 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogContent.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogDescription.vue b/src/components/ui/alert-dialog/AlertDialogDescription.vue new file mode 100644 index 0000000..8f277f3 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogDescription.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogFooter.vue b/src/components/ui/alert-dialog/AlertDialogFooter.vue new file mode 100644 index 0000000..5f41997 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogFooter.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogHeader.vue b/src/components/ui/alert-dialog/AlertDialogHeader.vue new file mode 100644 index 0000000..c33287a --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogHeader.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogTitle.vue b/src/components/ui/alert-dialog/AlertDialogTitle.vue new file mode 100644 index 0000000..effbd5d --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogTitle.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogTrigger.vue b/src/components/ui/alert-dialog/AlertDialogTrigger.vue new file mode 100644 index 0000000..8be0758 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogTrigger.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/ui/alert-dialog/index.ts b/src/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..448d519 --- /dev/null +++ b/src/components/ui/alert-dialog/index.ts @@ -0,0 +1,9 @@ +export { default as AlertDialog } from './AlertDialog.vue' +export { default as AlertDialogAction } from './AlertDialogAction.vue' +export { default as AlertDialogCancel } from './AlertDialogCancel.vue' +export { default as AlertDialogContent } from './AlertDialogContent.vue' +export { default as AlertDialogDescription } from './AlertDialogDescription.vue' +export { default as AlertDialogFooter } from './AlertDialogFooter.vue' +export { default as AlertDialogHeader } from './AlertDialogHeader.vue' +export { default as AlertDialogTitle } from './AlertDialogTitle.vue' +export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue' diff --git a/src/components/ui/badge/Badge.vue b/src/components/ui/badge/Badge.vue new file mode 100644 index 0000000..018dd14 --- /dev/null +++ b/src/components/ui/badge/Badge.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/components/ui/badge/index.ts b/src/components/ui/badge/index.ts new file mode 100644 index 0000000..e4c94d3 --- /dev/null +++ b/src/components/ui/badge/index.ts @@ -0,0 +1,23 @@ +import type { VariantProps } from 'class-variance-authority' +import { cva } from 'class-variance-authority' + +export { default as Badge } from './Badge.vue' + +export const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground' + } + }, + defaultVariants: { + variant: 'default' + } + } +) +export type BadgeVariants = VariantProps diff --git a/src/components/ui/button/Button.vue b/src/components/ui/button/Button.vue new file mode 100644 index 0000000..290a6bc --- /dev/null +++ b/src/components/ui/button/Button.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/components/ui/button/index.ts b/src/components/ui/button/index.ts new file mode 100644 index 0000000..800da66 --- /dev/null +++ b/src/components/ui/button/index.ts @@ -0,0 +1,35 @@ +import type { VariantProps } from 'class-variance-authority' +import { cva } from 'class-variance-authority' + +export { default as Button } from './Button.vue' + +export const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: + 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', + link: 'text-primary underline-offset-4 hover:underline' + }, + size: { + default: 'h-9 px-4 py-2 has-[>svg]:px-3', + sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + icon: 'size-9', + 'icon-sm': 'size-8', + 'icon-lg': 'size-10' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } + } +) +export type ButtonVariants = VariantProps diff --git a/src/components/ui/card/Card.vue b/src/components/ui/card/Card.vue new file mode 100644 index 0000000..e7551f4 --- /dev/null +++ b/src/components/ui/card/Card.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/card/CardAction.vue b/src/components/ui/card/CardAction.vue new file mode 100644 index 0000000..f81536b --- /dev/null +++ b/src/components/ui/card/CardAction.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/card/CardContent.vue b/src/components/ui/card/CardContent.vue new file mode 100644 index 0000000..2940b9c --- /dev/null +++ b/src/components/ui/card/CardContent.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/card/CardDescription.vue b/src/components/ui/card/CardDescription.vue new file mode 100644 index 0000000..05e34a5 --- /dev/null +++ b/src/components/ui/card/CardDescription.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/card/CardFooter.vue b/src/components/ui/card/CardFooter.vue new file mode 100644 index 0000000..1e65ab9 --- /dev/null +++ b/src/components/ui/card/CardFooter.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/card/CardHeader.vue b/src/components/ui/card/CardHeader.vue new file mode 100644 index 0000000..56d7e53 --- /dev/null +++ b/src/components/ui/card/CardHeader.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/ui/card/CardTitle.vue b/src/components/ui/card/CardTitle.vue new file mode 100644 index 0000000..5260c70 --- /dev/null +++ b/src/components/ui/card/CardTitle.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/card/index.ts b/src/components/ui/card/index.ts new file mode 100644 index 0000000..73d985f --- /dev/null +++ b/src/components/ui/card/index.ts @@ -0,0 +1,7 @@ +export { default as Card } from './Card.vue' +export { default as CardAction } from './CardAction.vue' +export { default as CardContent } from './CardContent.vue' +export { default as CardDescription } from './CardDescription.vue' +export { default as CardFooter } from './CardFooter.vue' +export { default as CardHeader } from './CardHeader.vue' +export { default as CardTitle } from './CardTitle.vue' diff --git a/src/components/ui/dialog/Dialog.vue b/src/components/ui/dialog/Dialog.vue new file mode 100644 index 0000000..23ba439 --- /dev/null +++ b/src/components/ui/dialog/Dialog.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/dialog/DialogClose.vue b/src/components/ui/dialog/DialogClose.vue new file mode 100644 index 0000000..c7889c4 --- /dev/null +++ b/src/components/ui/dialog/DialogClose.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/ui/dialog/DialogContent.vue b/src/components/ui/dialog/DialogContent.vue new file mode 100644 index 0000000..b1a4e87 --- /dev/null +++ b/src/components/ui/dialog/DialogContent.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/components/ui/dialog/DialogDescription.vue b/src/components/ui/dialog/DialogDescription.vue new file mode 100644 index 0000000..1a9b973 --- /dev/null +++ b/src/components/ui/dialog/DialogDescription.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/dialog/DialogFooter.vue b/src/components/ui/dialog/DialogFooter.vue new file mode 100644 index 0000000..abeaedf --- /dev/null +++ b/src/components/ui/dialog/DialogFooter.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/ui/dialog/DialogHeader.vue b/src/components/ui/dialog/DialogHeader.vue new file mode 100644 index 0000000..5f27bcb --- /dev/null +++ b/src/components/ui/dialog/DialogHeader.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/dialog/DialogOverlay.vue b/src/components/ui/dialog/DialogOverlay.vue new file mode 100644 index 0000000..6ca56f2 --- /dev/null +++ b/src/components/ui/dialog/DialogOverlay.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/ui/dialog/DialogScrollContent.vue b/src/components/ui/dialog/DialogScrollContent.vue new file mode 100644 index 0000000..c610164 --- /dev/null +++ b/src/components/ui/dialog/DialogScrollContent.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/components/ui/dialog/DialogTitle.vue b/src/components/ui/dialog/DialogTitle.vue new file mode 100644 index 0000000..98cb0e1 --- /dev/null +++ b/src/components/ui/dialog/DialogTitle.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/dialog/DialogTrigger.vue b/src/components/ui/dialog/DialogTrigger.vue new file mode 100644 index 0000000..5d6d9af --- /dev/null +++ b/src/components/ui/dialog/DialogTrigger.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/ui/dialog/index.ts b/src/components/ui/dialog/index.ts new file mode 100644 index 0000000..baa7d61 --- /dev/null +++ b/src/components/ui/dialog/index.ts @@ -0,0 +1,10 @@ +export { default as Dialog } from './Dialog.vue' +export { default as DialogClose } from './DialogClose.vue' +export { default as DialogContent } from './DialogContent.vue' +export { default as DialogDescription } from './DialogDescription.vue' +export { default as DialogFooter } from './DialogFooter.vue' +export { default as DialogHeader } from './DialogHeader.vue' +export { default as DialogOverlay } from './DialogOverlay.vue' +export { default as DialogScrollContent } from './DialogScrollContent.vue' +export { default as DialogTitle } from './DialogTitle.vue' +export { default as DialogTrigger } from './DialogTrigger.vue' diff --git a/src/components/ui/input/Input.vue b/src/components/ui/input/Input.vue new file mode 100644 index 0000000..755e791 --- /dev/null +++ b/src/components/ui/input/Input.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/components/ui/input/index.ts b/src/components/ui/input/index.ts new file mode 100644 index 0000000..a691dd6 --- /dev/null +++ b/src/components/ui/input/index.ts @@ -0,0 +1 @@ +export { default as Input } from './Input.vue' diff --git a/src/components/ui/label/Label.vue b/src/components/ui/label/Label.vue new file mode 100644 index 0000000..f4b2882 --- /dev/null +++ b/src/components/ui/label/Label.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/ui/label/index.ts b/src/components/ui/label/index.ts new file mode 100644 index 0000000..572c2f0 --- /dev/null +++ b/src/components/ui/label/index.ts @@ -0,0 +1 @@ +export { default as Label } from './Label.vue' diff --git a/src/components/ui/popover/Popover.vue b/src/components/ui/popover/Popover.vue new file mode 100644 index 0000000..4efdb98 --- /dev/null +++ b/src/components/ui/popover/Popover.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/popover/PopoverAnchor.vue b/src/components/ui/popover/PopoverAnchor.vue new file mode 100644 index 0000000..49e01db --- /dev/null +++ b/src/components/ui/popover/PopoverAnchor.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/popover/PopoverContent.vue b/src/components/ui/popover/PopoverContent.vue new file mode 100644 index 0000000..cf1e55c --- /dev/null +++ b/src/components/ui/popover/PopoverContent.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/components/ui/popover/PopoverTrigger.vue b/src/components/ui/popover/PopoverTrigger.vue new file mode 100644 index 0000000..fd3b497 --- /dev/null +++ b/src/components/ui/popover/PopoverTrigger.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/popover/index.ts b/src/components/ui/popover/index.ts new file mode 100644 index 0000000..66edf89 --- /dev/null +++ b/src/components/ui/popover/index.ts @@ -0,0 +1,4 @@ +export { default as Popover } from "./Popover.vue" +export { default as PopoverAnchor } from "./PopoverAnchor.vue" +export { default as PopoverContent } from "./PopoverContent.vue" +export { default as PopoverTrigger } from "./PopoverTrigger.vue" diff --git a/src/components/ui/progress/Progress.vue b/src/components/ui/progress/Progress.vue new file mode 100644 index 0000000..8e43493 --- /dev/null +++ b/src/components/ui/progress/Progress.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/components/ui/progress/index.ts b/src/components/ui/progress/index.ts new file mode 100644 index 0000000..eace989 --- /dev/null +++ b/src/components/ui/progress/index.ts @@ -0,0 +1 @@ +export { default as Progress } from './Progress.vue' diff --git a/src/components/ui/select/Select.vue b/src/components/ui/select/Select.vue new file mode 100644 index 0000000..5209c4c --- /dev/null +++ b/src/components/ui/select/Select.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/select/SelectContent.vue b/src/components/ui/select/SelectContent.vue new file mode 100644 index 0000000..a52ece9 --- /dev/null +++ b/src/components/ui/select/SelectContent.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/components/ui/select/SelectGroup.vue b/src/components/ui/select/SelectGroup.vue new file mode 100644 index 0000000..c78a6d4 --- /dev/null +++ b/src/components/ui/select/SelectGroup.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/ui/select/SelectItem.vue b/src/components/ui/select/SelectItem.vue new file mode 100644 index 0000000..200d6da --- /dev/null +++ b/src/components/ui/select/SelectItem.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/ui/select/SelectItemText.vue b/src/components/ui/select/SelectItemText.vue new file mode 100644 index 0000000..44e15ca --- /dev/null +++ b/src/components/ui/select/SelectItemText.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/ui/select/SelectLabel.vue b/src/components/ui/select/SelectLabel.vue new file mode 100644 index 0000000..c297222 --- /dev/null +++ b/src/components/ui/select/SelectLabel.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/select/SelectScrollDownButton.vue b/src/components/ui/select/SelectScrollDownButton.vue new file mode 100644 index 0000000..02e260f --- /dev/null +++ b/src/components/ui/select/SelectScrollDownButton.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/ui/select/SelectScrollUpButton.vue b/src/components/ui/select/SelectScrollUpButton.vue new file mode 100644 index 0000000..c69aa82 --- /dev/null +++ b/src/components/ui/select/SelectScrollUpButton.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/ui/select/SelectSeparator.vue b/src/components/ui/select/SelectSeparator.vue new file mode 100644 index 0000000..80e500b --- /dev/null +++ b/src/components/ui/select/SelectSeparator.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/select/SelectTrigger.vue b/src/components/ui/select/SelectTrigger.vue new file mode 100644 index 0000000..21533b3 --- /dev/null +++ b/src/components/ui/select/SelectTrigger.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/components/ui/select/SelectValue.vue b/src/components/ui/select/SelectValue.vue new file mode 100644 index 0000000..71188e3 --- /dev/null +++ b/src/components/ui/select/SelectValue.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/ui/select/index.ts b/src/components/ui/select/index.ts new file mode 100644 index 0000000..31b9294 --- /dev/null +++ b/src/components/ui/select/index.ts @@ -0,0 +1,11 @@ +export { default as Select } from './Select.vue' +export { default as SelectContent } from './SelectContent.vue' +export { default as SelectGroup } from './SelectGroup.vue' +export { default as SelectItem } from './SelectItem.vue' +export { default as SelectItemText } from './SelectItemText.vue' +export { default as SelectLabel } from './SelectLabel.vue' +export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue' +export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue' +export { default as SelectSeparator } from './SelectSeparator.vue' +export { default as SelectTrigger } from './SelectTrigger.vue' +export { default as SelectValue } from './SelectValue.vue' diff --git a/src/components/ui/sonner/Sonner.vue b/src/components/ui/sonner/Sonner.vue new file mode 100644 index 0000000..6830896 --- /dev/null +++ b/src/components/ui/sonner/Sonner.vue @@ -0,0 +1,42 @@ + + + diff --git a/src/components/ui/sonner/index.ts b/src/components/ui/sonner/index.ts new file mode 100644 index 0000000..6673112 --- /dev/null +++ b/src/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./Sonner.vue" diff --git a/src/components/ui/table/Table.vue b/src/components/ui/table/Table.vue new file mode 100644 index 0000000..5574082 --- /dev/null +++ b/src/components/ui/table/Table.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/ui/table/TableBody.vue b/src/components/ui/table/TableBody.vue new file mode 100644 index 0000000..12e3c13 --- /dev/null +++ b/src/components/ui/table/TableBody.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/table/TableCaption.vue b/src/components/ui/table/TableCaption.vue new file mode 100644 index 0000000..dbc628a --- /dev/null +++ b/src/components/ui/table/TableCaption.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/table/TableCell.vue b/src/components/ui/table/TableCell.vue new file mode 100644 index 0000000..56a84f6 --- /dev/null +++ b/src/components/ui/table/TableCell.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/ui/table/TableEmpty.vue b/src/components/ui/table/TableEmpty.vue new file mode 100644 index 0000000..1464751 --- /dev/null +++ b/src/components/ui/table/TableEmpty.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/components/ui/table/TableFooter.vue b/src/components/ui/table/TableFooter.vue new file mode 100644 index 0000000..40f2541 --- /dev/null +++ b/src/components/ui/table/TableFooter.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/table/TableHead.vue b/src/components/ui/table/TableHead.vue new file mode 100644 index 0000000..b6bf080 --- /dev/null +++ b/src/components/ui/table/TableHead.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/ui/table/TableHeader.vue b/src/components/ui/table/TableHeader.vue new file mode 100644 index 0000000..c7a92f1 --- /dev/null +++ b/src/components/ui/table/TableHeader.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/table/TableRow.vue b/src/components/ui/table/TableRow.vue new file mode 100644 index 0000000..273c4b3 --- /dev/null +++ b/src/components/ui/table/TableRow.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/table/index.ts b/src/components/ui/table/index.ts new file mode 100644 index 0000000..2b4ce39 --- /dev/null +++ b/src/components/ui/table/index.ts @@ -0,0 +1,9 @@ +export { default as Table } from './Table.vue' +export { default as TableBody } from './TableBody.vue' +export { default as TableCaption } from './TableCaption.vue' +export { default as TableCell } from './TableCell.vue' +export { default as TableEmpty } from './TableEmpty.vue' +export { default as TableFooter } from './TableFooter.vue' +export { default as TableHead } from './TableHead.vue' +export { default as TableHeader } from './TableHeader.vue' +export { default as TableRow } from './TableRow.vue' diff --git a/src/components/ui/table/utils.ts b/src/components/ui/table/utils.ts new file mode 100644 index 0000000..fe7b2b8 --- /dev/null +++ b/src/components/ui/table/utils.ts @@ -0,0 +1,8 @@ +import type { Updater } from '@tanstack/vue-table' + +import type { Ref } from 'vue' +import { isFunction } from '@tanstack/vue-table' + +export const valueUpdater = (updaterOrValue: Updater, ref: Ref) => { + ref.value = isFunction(updaterOrValue) ? updaterOrValue(ref.value) : updaterOrValue +} diff --git a/src/composables/useGameConfig.ts b/src/composables/useGameConfig.ts new file mode 100644 index 0000000..33501df --- /dev/null +++ b/src/composables/useGameConfig.ts @@ -0,0 +1,173 @@ +import { computed } from 'vue' +import { useI18n } from './useI18n' +import { + BUILDINGS as ORIGINAL_BUILDINGS, + SHIPS as ORIGINAL_SHIPS, + DEFENSES as ORIGINAL_DEFENSES, + TECHNOLOGIES as ORIGINAL_TECHNOLOGIES, + OFFICERS as ORIGINAL_OFFICERS +} from '@/config/gameConfig' + +import { BuildingType, ShipType, DefenseType, TechnologyType, OfficerType } from '@/types/game' + +import type { BuildingConfig, ShipConfig, DefenseConfig, TechnologyConfig, OfficerConfig } from '@/types/game' + +/** + * 提供翻译后的游戏配置对象的 Composable + * 将 i18n 系统的翻译覆盖到原始 gameConfig 上 + */ +export const useGameConfig = () => { + const { t } = useI18n() + + // 建筑类型枚举值到翻译键的映射 + const buildingKeyMap: Record = { + [BuildingType.MetalMine]: 'metalMine', + [BuildingType.CrystalMine]: 'crystalMine', + [BuildingType.DeuteriumSynthesizer]: 'deuteriumSynthesizer', + [BuildingType.SolarPlant]: 'solarPlant', + [BuildingType.RoboticsFactory]: 'roboticsFactory', + [BuildingType.NaniteFactory]: 'naniteFactory', + [BuildingType.Shipyard]: 'shipyard', + [BuildingType.ResearchLab]: 'researchLab', + [BuildingType.MetalStorage]: 'metalStorage', + [BuildingType.CrystalStorage]: 'crystalStorage', + [BuildingType.DeuteriumTank]: 'deuteriumTank', + [BuildingType.DarkMatterCollector]: 'darkMatterCollector', + [BuildingType.LunarBase]: 'lunarBase', + [BuildingType.SensorPhalanx]: 'sensorPhalanx', + [BuildingType.JumpGate]: 'jumpGate' + } + + // 舰船类型枚举值到翻译键的映射 + const shipKeyMap: Record = { + [ShipType.LightFighter]: 'lightFighter', + [ShipType.HeavyFighter]: 'heavyFighter', + [ShipType.Cruiser]: 'cruiser', + [ShipType.Battleship]: 'battleship', + [ShipType.SmallCargo]: 'smallCargo', + [ShipType.LargeCargo]: 'largeCargo', + [ShipType.ColonyShip]: 'colonyShip', + [ShipType.Recycler]: 'recycler', + [ShipType.EspionageProbe]: 'espionageProbe', + [ShipType.DarkMatterHarvester]: 'darkMatterHarvester' + } + + // 防御设施类型枚举值到翻译键的映射 + const defenseKeyMap: Record = { + [DefenseType.RocketLauncher]: 'rocketLauncher', + [DefenseType.LightLaser]: 'lightLaser', + [DefenseType.HeavyLaser]: 'heavyLaser', + [DefenseType.GaussCannon]: 'gaussCannon', + [DefenseType.IonCannon]: 'ionCannon', + [DefenseType.PlasmaTurret]: 'plasmaTurret', + [DefenseType.SmallShieldDome]: 'smallShieldDome', + [DefenseType.LargeShieldDome]: 'largeShieldDome' + } + + // 科技类型枚举值到翻译键的映射 + const technologyKeyMap: Record = { + [TechnologyType.EnergyTechnology]: 'energyTechnology', + [TechnologyType.LaserTechnology]: 'laserTechnology', + [TechnologyType.IonTechnology]: 'ionTechnology', + [TechnologyType.HyperspaceTechnology]: 'hyperspaceTechnology', + [TechnologyType.PlasmaTechnology]: 'plasmaTechnology', + [TechnologyType.ComputerTechnology]: 'computerTechnology', + [TechnologyType.CombustionDrive]: 'combustionDrive', + [TechnologyType.ImpulseDrive]: 'impulseDrive', + [TechnologyType.HyperspaceDrive]: 'hyperspaceDrive', + [TechnologyType.DarkMatterTechnology]: 'darkMatterTechnology' + } + + // 军官类型枚举值到翻译键的映射 + const officerKeyMap: Record = { + [OfficerType.Commander]: 'commander', + [OfficerType.Admiral]: 'admiral', + [OfficerType.Engineer]: 'engineer', + [OfficerType.Geologist]: 'geologist', + [OfficerType.Technocrat]: 'technocrat', + [OfficerType.DarkMatterSpecialist]: 'darkMatterSpecialist' + } + + // 翻译后的建筑配置 + const BUILDINGS = computed(() => { + const translated: Record = {} as Record + for (const [key, config] of Object.entries(ORIGINAL_BUILDINGS)) { + const buildingType = key as BuildingType + const translationKey = buildingKeyMap[buildingType] + translated[buildingType] = { + ...config, + name: t(`buildings.${translationKey}`), + description: t(`buildingDescriptions.${translationKey}`) + } + } + return translated + }) + + // 翻译后的舰船配置 + const SHIPS = computed(() => { + const translated: Record = {} as Record + for (const [key, config] of Object.entries(ORIGINAL_SHIPS)) { + const shipType = key as ShipType + const translationKey = shipKeyMap[shipType] + translated[shipType] = { + ...config, + name: t(`ships.${translationKey}`), + description: t(`shipDescriptions.${translationKey}`) + } + } + return translated + }) + + // 翻译后的防御设施配置 + const DEFENSES = computed(() => { + const translated: Record = {} as Record + for (const [key, config] of Object.entries(ORIGINAL_DEFENSES)) { + const defenseType = key as DefenseType + const translationKey = defenseKeyMap[defenseType] + translated[defenseType] = { + ...config, + name: t(`defenses.${translationKey}`), + description: t(`defenseDescriptions.${translationKey}`) + } + } + return translated + }) + + // 翻译后的科技配置 + const TECHNOLOGIES = computed(() => { + const translated: Record = {} as Record + for (const [key, config] of Object.entries(ORIGINAL_TECHNOLOGIES)) { + const technologyType = key as TechnologyType + const translationKey = technologyKeyMap[technologyType] + translated[technologyType] = { + ...config, + name: t(`technologies.${translationKey}`), + description: t(`technologyDescriptions.${translationKey}`) + } + } + return translated + }) + + // 翻译后的军官配置 + const OFFICERS = computed(() => { + const translated: Record = {} as Record + for (const [key, config] of Object.entries(ORIGINAL_OFFICERS)) { + const officerType = key as OfficerType + const translationKey = officerKeyMap[officerType] + translated[officerType] = { + ...config, + name: t(`officers.${translationKey}`), + description: t(`officerDescriptions.${translationKey}`) + } + } + return translated + }) + + return { + BUILDINGS, + SHIPS, + DEFENSES, + TECHNOLOGIES, + OFFICERS + } +} diff --git a/src/composables/useI18n.ts b/src/composables/useI18n.ts new file mode 100644 index 0000000..e1ec719 --- /dev/null +++ b/src/composables/useI18n.ts @@ -0,0 +1,38 @@ +import { computed } from 'vue' +import { useGameStore } from '@/stores/gameStore' +import { locales, type Locale } from '@/locales' + +export const useI18n = () => { + const gameStore = useGameStore() + + const currentLocale = computed(() => gameStore.locale) + + const messages = computed(() => locales[currentLocale.value]) + + // 获取翻译文本的辅助函数 + const t = (key: string): string => { + const keys = key.split('.') + let value: any = messages.value + + for (const k of keys) { + if (value && typeof value === 'object' && k in value) { + value = value[k] + } else { + return key // 如果找不到翻译,返回原始 key + } + } + + return typeof value === 'string' ? value : key + } + + const setLocale = (locale: Locale) => { + gameStore.locale = locale + } + + return { + t, + locale: currentLocale, + setLocale, + messages + } +} diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts new file mode 100644 index 0000000..6f7ace3 --- /dev/null +++ b/src/composables/useTheme.ts @@ -0,0 +1,49 @@ +import { ref, onMounted, watch } from 'vue' +import { useGameStore } from '@/stores/gameStore' + +type Theme = 'light' | 'dark' + +const isDark = ref(false) + +export const useTheme = () => { + const gameStore = useGameStore() + + // 初始化主题 + onMounted(() => { + if (!gameStore.isDark) { + // 首次访问,使用系统主题偏好 + isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches + gameStore.isDark = isDark.value ? 'dark' : 'light' + } else { + // 从 gameStore 读取保存的主题(Pinia会自动从localStorage恢复) + const savedTheme = gameStore.isDark as Theme + isDark.value = savedTheme === 'dark' + } + applyTheme() + }) + + // 监听主题变化 + watch(isDark, () => { + applyTheme() + gameStore.isDark = isDark.value ? 'dark' : 'light' + }) + + // 应用主题 + const applyTheme = () => { + if (isDark.value) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + } + + // 切换主题 + const toggleTheme = () => { + isDark.value = !isDark.value + } + + return { + isDark, + toggleTheme + } +} diff --git a/src/config/gameConfig.ts b/src/config/gameConfig.ts new file mode 100644 index 0000000..554d529 --- /dev/null +++ b/src/config/gameConfig.ts @@ -0,0 +1,570 @@ +import { BuildingType, TechnologyType, ShipType, DefenseType, OfficerType } from '@/types/game' +import type { BuildingConfig, TechnologyConfig, ShipConfig, DefenseConfig, OfficerConfig } from '@/types/game' + +// 建筑配置数据 +export const BUILDINGS: Record = { + [BuildingType.MetalMine]: { + id: BuildingType.MetalMine, + name: '金属矿', + description: '开采金属资源', + baseCost: { metal: 60, crystal: 15, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 15, // 减少建造时间:30→15秒 + costMultiplier: 1.5, + spaceUsage: 3, + planetOnly: true + }, + [BuildingType.CrystalMine]: { + id: BuildingType.CrystalMine, + name: '晶体矿', + description: '开采晶体资源', + baseCost: { metal: 48, crystal: 24, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 15, // 减少建造时间:30→15秒 + costMultiplier: 1.6, + spaceUsage: 3, + planetOnly: true + }, + [BuildingType.DeuteriumSynthesizer]: { + id: BuildingType.DeuteriumSynthesizer, + name: '重氢合成器', + description: '合成重氢资源', + baseCost: { metal: 225, crystal: 75, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 20, // 减少建造时间:30→20秒 + costMultiplier: 1.5, + spaceUsage: 4, + planetOnly: true + }, + [BuildingType.SolarPlant]: { + id: BuildingType.SolarPlant, + name: '太阳能电站', + description: '提供能源', + baseCost: { metal: 75, crystal: 30, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 15, // 减少建造时间:30→15秒 + costMultiplier: 1.5, + spaceUsage: 5 + }, + [BuildingType.RoboticsFactory]: { + id: BuildingType.RoboticsFactory, + name: '机器人工厂', + description: '加快建造速度', + baseCost: { metal: 400, crystal: 120, deuterium: 200, darkMatter: 0, energy: 0 }, + baseTime: 40, // 减少建造时间:60→40秒 + costMultiplier: 2, + spaceUsage: 6 + }, + [BuildingType.NaniteFactory]: { + id: BuildingType.NaniteFactory, + name: '纳米工厂', + description: '增加建造队列数量,每级+1队列', + baseCost: { metal: 1000000, crystal: 500000, deuterium: 100000, darkMatter: 0, energy: 0 }, + baseTime: 240, // 减少建造时间:300→240秒 + costMultiplier: 2, + spaceUsage: 15, + requirements: { [BuildingType.RoboticsFactory]: 10 } + }, + [BuildingType.Shipyard]: { + id: BuildingType.Shipyard, + name: '船坞', + description: '建造舰船', + baseCost: { metal: 400, crystal: 200, deuterium: 100, darkMatter: 0, energy: 0 }, + baseTime: 30, // 减少建造时间:60→30秒 + costMultiplier: 2, + spaceUsage: 8 + }, + [BuildingType.ResearchLab]: { + id: BuildingType.ResearchLab, + name: '研究实验室', + description: '研究科技', + baseCost: { metal: 200, crystal: 400, deuterium: 200, darkMatter: 0, energy: 0 }, + baseTime: 30, // 减少建造时间:60→30秒 + costMultiplier: 2, + spaceUsage: 5 + }, + [BuildingType.MetalStorage]: { + id: BuildingType.MetalStorage, + name: '金属仓库', + description: '增加金属存储上限', + baseCost: { metal: 1000, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 15, // 减少建造时间:30→15秒 + costMultiplier: 2, + spaceUsage: 2 + }, + [BuildingType.CrystalStorage]: { + id: BuildingType.CrystalStorage, + name: '晶体仓库', + description: '增加晶体存储上限', + baseCost: { metal: 1000, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 15, // 减少建造时间:30→15秒 + costMultiplier: 2, + spaceUsage: 2 + }, + [BuildingType.DeuteriumTank]: { + id: BuildingType.DeuteriumTank, + name: '重氢罐', + description: '增加重氢存储上限', + baseCost: { metal: 1000, crystal: 1000, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 15, // 减少建造时间:30→15秒 + costMultiplier: 2, + spaceUsage: 2 + }, + [BuildingType.DarkMatterCollector]: { + id: BuildingType.DarkMatterCollector, + name: '暗物质收集器', + description: '收集稀有的暗物质资源', + baseCost: { metal: 50000, crystal: 100000, deuterium: 50000, darkMatter: 0, energy: 0 }, + baseTime: 90, // 减少建造时间:120→90秒 + costMultiplier: 2, + spaceUsage: 10, + planetOnly: true + }, + // 月球专属建筑 + [BuildingType.LunarBase]: { + id: BuildingType.LunarBase, + name: '月球基地', + description: '增加月球可用空间', + baseCost: { metal: 20000, crystal: 40000, deuterium: 20000, darkMatter: 0, energy: 0 }, + baseTime: 45, // 减少建造时间:60→45秒 + costMultiplier: 2, + spaceUsage: 0, // 月球基地本身不占用空间,反而增加空间 + moonOnly: true + }, + [BuildingType.SensorPhalanx]: { + id: BuildingType.SensorPhalanx, + name: '传感器阵列', + description: '侦测周围星系的舰队活动', + baseCost: { metal: 20000, crystal: 40000, deuterium: 20000, darkMatter: 0, energy: 0 }, + baseTime: 60, // 减少建造时间:90→60秒 + costMultiplier: 2, + spaceUsage: 10, + moonOnly: true + }, + [BuildingType.JumpGate]: { + id: BuildingType.JumpGate, + name: '跳跃门', + description: '瞬间传送舰队到其他月球', + baseCost: { metal: 2000000, crystal: 4000000, deuterium: 2000000, darkMatter: 0, energy: 0 }, + baseTime: 240, // 减少建造时间:300→240秒 + costMultiplier: 2, + spaceUsage: 20, + moonOnly: true + } +} + +// 科技配置数据 +export const TECHNOLOGIES: Record = { + [TechnologyType.EnergyTechnology]: { + id: TechnologyType.EnergyTechnology, + name: '能源技术', + description: '提高能源利用效率', + baseCost: { metal: 0, crystal: 800, deuterium: 400, darkMatter: 0, energy: 0 }, + baseTime: 30, // 减少研究时间:60→30秒 + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 1 } + }, + [TechnologyType.LaserTechnology]: { + id: TechnologyType.LaserTechnology, + name: '激光技术', + description: '开发激光武器', + baseCost: { metal: 200, crystal: 100, deuterium: 0, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 1, [TechnologyType.EnergyTechnology]: 2 } + }, + [TechnologyType.IonTechnology]: { + id: TechnologyType.IonTechnology, + name: '离子技术', + description: '开发离子武器', + baseCost: { metal: 1000, crystal: 300, deuterium: 100, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 4, [TechnologyType.LaserTechnology]: 5, [TechnologyType.EnergyTechnology]: 4 } + }, + [TechnologyType.HyperspaceTechnology]: { + id: TechnologyType.HyperspaceTechnology, + name: '超空间技术', + description: '研究超空间跳跃', + baseCost: { metal: 0, crystal: 4000, deuterium: 2000, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 7, [TechnologyType.EnergyTechnology]: 5 } + }, + [TechnologyType.PlasmaTechnology]: { + id: TechnologyType.PlasmaTechnology, + name: '等离子技术', + description: '开发等离子武器', + baseCost: { metal: 2000, crystal: 4000, deuterium: 1000, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { + [BuildingType.ResearchLab]: 4, + [TechnologyType.EnergyTechnology]: 8, + [TechnologyType.LaserTechnology]: 10, + [TechnologyType.IonTechnology]: 5 + } + }, + [TechnologyType.ComputerTechnology]: { + id: TechnologyType.ComputerTechnology, + name: '计算机技术', + description: '增加研究队列数量,每级+1队列', + baseCost: { metal: 0, crystal: 400, deuterium: 600, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 1 } + }, + [TechnologyType.CombustionDrive]: { + id: TechnologyType.CombustionDrive, + name: '燃烧引擎', + description: '基础推进系统', + baseCost: { metal: 400, crystal: 0, deuterium: 600, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 1, [TechnologyType.EnergyTechnology]: 1 } + }, + [TechnologyType.ImpulseDrive]: { + id: TechnologyType.ImpulseDrive, + name: '脉冲引擎', + description: '高级推进系统', + baseCost: { metal: 2000, crystal: 4000, deuterium: 600, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 2, [TechnologyType.EnergyTechnology]: 1 } + }, + [TechnologyType.HyperspaceDrive]: { + id: TechnologyType.HyperspaceDrive, + name: '超空间引擎', + description: '超空间推进系统', + baseCost: { metal: 10000, crystal: 20000, deuterium: 6000, darkMatter: 0, energy: 0 }, + baseTime: 60, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 7, [TechnologyType.HyperspaceTechnology]: 3 } + }, + [TechnologyType.DarkMatterTechnology]: { + id: TechnologyType.DarkMatterTechnology, + name: '暗物质技术', + description: '研究暗物质的性质和应用', + baseCost: { metal: 100000, crystal: 200000, deuterium: 100000, darkMatter: 0, energy: 0 }, + baseTime: 180, + costMultiplier: 2, + requirements: { [BuildingType.ResearchLab]: 8, [TechnologyType.HyperspaceTechnology]: 5 } + } +} + +// 舰船配置数据 +export const SHIPS: Record = { + [ShipType.LightFighter]: { + id: ShipType.LightFighter, + name: '轻型战斗机', + description: '基础战斗单位', + cost: { metal: 3000, crystal: 1000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 20, + cargoCapacity: 50, + attack: 50, + shield: 10, + armor: 400, + speed: 12500, + fuelConsumption: 20, + requirements: { [BuildingType.Shipyard]: 1, [TechnologyType.CombustionDrive]: 1 } + }, + [ShipType.HeavyFighter]: { + id: ShipType.HeavyFighter, + name: '重型战斗机', + description: '强力战斗单位', + cost: { metal: 6000, crystal: 4000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 30, + cargoCapacity: 100, + attack: 150, + shield: 25, + armor: 1000, + speed: 10000, + fuelConsumption: 75, + requirements: { [BuildingType.Shipyard]: 3, [TechnologyType.ImpulseDrive]: 2 } + }, + [ShipType.Cruiser]: { + id: ShipType.Cruiser, + name: '巡洋舰', + description: '中型战舰', + cost: { metal: 20000, crystal: 7000, deuterium: 2000, darkMatter: 0, energy: 0 }, + buildTime: 60, + cargoCapacity: 800, + attack: 400, + shield: 50, + armor: 2700, + speed: 15000, + fuelConsumption: 300, + requirements: { [BuildingType.Shipyard]: 5, [TechnologyType.ImpulseDrive]: 4, [TechnologyType.IonTechnology]: 2 } + }, + [ShipType.Battleship]: { + id: ShipType.Battleship, + name: '战列舰', + description: '重型战舰', + cost: { metal: 45000, crystal: 15000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 90, + cargoCapacity: 1500, + attack: 1000, + shield: 200, + armor: 6000, + speed: 10000, + fuelConsumption: 500, + requirements: { [BuildingType.Shipyard]: 7, [TechnologyType.HyperspaceDrive]: 4 } + }, + [ShipType.SmallCargo]: { + id: ShipType.SmallCargo, + name: '小型运输船', + description: '运输资源', + cost: { metal: 2000, crystal: 2000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 15, + cargoCapacity: 5000, + attack: 5, + shield: 10, + armor: 400, + speed: 5000, + fuelConsumption: 10, + requirements: { [BuildingType.Shipyard]: 2, [TechnologyType.CombustionDrive]: 2 } + }, + [ShipType.LargeCargo]: { + id: ShipType.LargeCargo, + name: '大型运输船', + description: '大量运输资源', + cost: { metal: 6000, crystal: 6000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 30, + cargoCapacity: 25000, + attack: 5, + shield: 25, + armor: 1200, + speed: 7500, + fuelConsumption: 50, + requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.CombustionDrive]: 6 } + }, + [ShipType.ColonyShip]: { + id: ShipType.ColonyShip, + name: '殖民船', + description: '建立新殖民地', + cost: { metal: 10000, crystal: 20000, deuterium: 10000, darkMatter: 0, energy: 0 }, + buildTime: 120, + cargoCapacity: 7500, + attack: 50, + shield: 100, + armor: 3000, + speed: 2500, + fuelConsumption: 1000, + requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.ImpulseDrive]: 3 } + }, + [ShipType.Recycler]: { + id: ShipType.Recycler, + name: '回收船', + description: '回收废墟资源', + cost: { metal: 10000, crystal: 6000, deuterium: 2000, darkMatter: 0, energy: 0 }, + buildTime: 60, + cargoCapacity: 20000, + attack: 1, + shield: 10, + armor: 1600, + speed: 2000, + fuelConsumption: 300, + requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.CombustionDrive]: 6 } + }, + [ShipType.EspionageProbe]: { + id: ShipType.EspionageProbe, + name: '间谍探测器', + description: '侦察敌方星球', + cost: { metal: 0, crystal: 1000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 5, + cargoCapacity: 5, + attack: 0, + shield: 0, + armor: 100, + speed: 100000000, + fuelConsumption: 1, + requirements: { [BuildingType.Shipyard]: 3, [TechnologyType.CombustionDrive]: 3 } + }, + [ShipType.DarkMatterHarvester]: { + id: ShipType.DarkMatterHarvester, + name: '暗物质采集船', + description: '专门用于采集暗物质的特殊飞船', + cost: { metal: 100000, crystal: 150000, deuterium: 50000, darkMatter: 0, energy: 0 }, + buildTime: 120, + cargoCapacity: 1000, // 暗物质专用储存 + attack: 10, + shield: 50, + armor: 2000, + speed: 5000, + fuelConsumption: 500, + requirements: { + [BuildingType.Shipyard]: 8, + [TechnologyType.HyperspaceDrive]: 5, + [TechnologyType.DarkMatterTechnology]: 1 + } + } +} + +// 防御设施配置数据 +export const DEFENSES: Record = { + [DefenseType.RocketLauncher]: { + id: DefenseType.RocketLauncher, + name: '火箭发射器', + description: '基础防御设施', + cost: { metal: 2000, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 10, + attack: 80, + shield: 20, + armor: 200, + requirements: { [BuildingType.Shipyard]: 1 } + }, + [DefenseType.LightLaser]: { + id: DefenseType.LightLaser, + name: '轻型激光炮', + description: '激光防御武器', + cost: { metal: 1500, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 12, + attack: 100, + shield: 25, + armor: 200, + requirements: { [BuildingType.Shipyard]: 2, [TechnologyType.LaserTechnology]: 3 } + }, + [DefenseType.HeavyLaser]: { + id: DefenseType.HeavyLaser, + name: '重型激光炮', + description: '强力激光武器', + cost: { metal: 6000, crystal: 2000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 20, + attack: 250, + shield: 100, + armor: 800, + requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.LaserTechnology]: 6 } + }, + [DefenseType.GaussCannon]: { + id: DefenseType.GaussCannon, + name: '高斯炮', + description: '电磁加速武器', + cost: { metal: 20000, crystal: 15000, deuterium: 2000, darkMatter: 0, energy: 0 }, + buildTime: 35, + attack: 1100, + shield: 200, + armor: 3500, + requirements: { [BuildingType.Shipyard]: 6, [TechnologyType.EnergyTechnology]: 6 } + }, + [DefenseType.IonCannon]: { + id: DefenseType.IonCannon, + name: '离子炮', + description: '离子武器系统', + cost: { metal: 2000, crystal: 6000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 30, + attack: 150, + shield: 500, + armor: 800, + requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.IonTechnology]: 4 } + }, + [DefenseType.PlasmaTurret]: { + id: DefenseType.PlasmaTurret, + name: '等离子炮台', + description: '最强防御武器', + cost: { metal: 50000, crystal: 50000, deuterium: 30000, darkMatter: 0, energy: 0 }, + buildTime: 60, + attack: 3000, + shield: 300, + armor: 10000, + requirements: { [BuildingType.Shipyard]: 8, [TechnologyType.PlasmaTechnology]: 7 } + }, + [DefenseType.SmallShieldDome]: { + id: DefenseType.SmallShieldDome, + name: '小型护盾罩', + description: '保护星球的能量护盾', + cost: { metal: 10000, crystal: 10000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 30, + attack: 1, + shield: 2000, + armor: 2000, + requirements: { [BuildingType.Shipyard]: 6, [TechnologyType.EnergyTechnology]: 3 } + }, + [DefenseType.LargeShieldDome]: { + id: DefenseType.LargeShieldDome, + name: '大型护盾罩', + description: '强大的星球护盾', + cost: { metal: 50000, crystal: 50000, deuterium: 0, darkMatter: 0, energy: 0 }, + buildTime: 60, + attack: 1, + shield: 10000, + armor: 10000, + requirements: { [BuildingType.Shipyard]: 6, [TechnologyType.EnergyTechnology]: 6 } + } +} + +// 军官配置数据 +export const OFFICERS: Record = { + [OfficerType.Commander]: { + id: OfficerType.Commander, + name: '指挥官', + description: '提升建筑速度和管理能力', + cost: { metal: 0, crystal: 50000, deuterium: 25000, darkMatter: 0, energy: 0 }, + weeklyMaintenance: { metal: 0, crystal: 5000, deuterium: 2500, darkMatter: 0, energy: 0 }, + benefits: { + buildingSpeedBonus: 10, // 建筑速度 +10% + additionalBuildQueue: 1, // 额外1个建筑队列 + storageCapacityBonus: 10 // 仓储容量 +10% + } + }, + [OfficerType.Admiral]: { + id: OfficerType.Admiral, + name: '上将', + description: '提升舰队作战能力', + cost: { metal: 50000, crystal: 25000, deuterium: 0, darkMatter: 0, energy: 0 }, + weeklyMaintenance: { metal: 5000, crystal: 2500, deuterium: 0, darkMatter: 0, energy: 0 }, + benefits: { + additionalFleetSlots: 2, // 额外2个舰队槽位 + fleetSpeedBonus: 10, // 舰队速度 +10% + fuelConsumptionReduction: 10 // 燃料消耗 -10% + } + }, + [OfficerType.Engineer]: { + id: OfficerType.Engineer, + name: '工程师', + description: '增强防御和能量系统', + cost: { metal: 40000, crystal: 20000, deuterium: 10000, darkMatter: 0, energy: 0 }, + weeklyMaintenance: { metal: 4000, crystal: 2000, deuterium: 1000, darkMatter: 0, energy: 0 }, + benefits: { + defenseBonus: 15, // 防御力 +15% + energyProductionBonus: 10, // 电量产出 +10% + buildingSpeedBonus: 5 // 建筑速度 +5% + } + }, + [OfficerType.Geologist]: { + id: OfficerType.Geologist, + name: '地质学家', + description: '提高资源开采效率', + cost: { metal: 30000, crystal: 30000, deuterium: 20000, darkMatter: 0, energy: 0 }, + weeklyMaintenance: { metal: 3000, crystal: 3000, deuterium: 2000, darkMatter: 0, energy: 0 }, + benefits: { + resourceProductionBonus: 15, // 资源产量 +15% + storageCapacityBonus: 10 // 仓储容量 +10% + } + }, + [OfficerType.Technocrat]: { + id: OfficerType.Technocrat, + name: '技术专家', + description: '加快科技研究速度', + cost: { metal: 20000, crystal: 40000, deuterium: 20000, darkMatter: 0, energy: 0 }, + weeklyMaintenance: { metal: 2000, crystal: 4000, deuterium: 2000, darkMatter: 0, energy: 0 }, + benefits: { + researchSpeedBonus: 15 // 研究速度 +15% + } + }, + [OfficerType.DarkMatterSpecialist]: { + id: OfficerType.DarkMatterSpecialist, + name: '暗物质专家', + description: '提升暗物质采集效率', + cost: { metal: 50000, crystal: 100000, deuterium: 50000, darkMatter: 100, energy: 0 }, + weeklyMaintenance: { metal: 5000, crystal: 10000, deuterium: 5000, darkMatter: 10, energy: 0 }, + benefits: { + darkMatterProductionBonus: 25 // 暗物质产量 +25% + } + } +} + +// 月球配置 +export const MOON_CONFIG = { + minDebrisField: 100000, // 最小残骸场 (金属+晶体) + baseChance: 1, // 基础1%概率 + maxChance: 20, // 最大20%概率 + chancePerDebris: 100000, // 每10万资源增加1%概率 + baseSize: 100, // 月球基础空间 + lunarBaseSpaceBonus: 3 // 每级月球基地增加的空间 +} diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..323c78a --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..2aec90c --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,7 @@ +import type { ClassValue } from 'clsx' +import { clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export const cn = (...inputs: ClassValue[]) => { + return twMerge(clsx(inputs)) +} diff --git a/src/locales/de.ts b/src/locales/de.ts new file mode 100644 index 0000000..6756164 --- /dev/null +++ b/src/locales/de.ts @@ -0,0 +1,536 @@ +export default { + common: { + confirm: 'Bestätigen', + cancel: 'Abbrechen', + delete: 'Löschen', + edit: 'Bearbeiten', + save: 'Speichern', + close: 'Schließen', + back: 'Zurück', + next: 'Weiter', + previous: 'Vorherige', + submit: 'Absenden', + reset: 'Zurücksetzen', + search: 'Suchen', + filter: 'Filtern', + loading: 'Laden...', + noData: 'Keine Daten', + error: 'Fehler', + success: 'Erfolg', + warning: 'Warnung', + info: 'Info', + resourceType: 'Ressourcentyp', + playerName: 'Kommandant', + timeHour: 'Std', + timeMinute: 'Min', + timeSecond: 'Sek', + featureLocked: 'Funktion gesperrt', + unlockRequired: 'Gebäude erforderlich', + requiredBuilding: 'Erforderliches Gebäude', + currentLevel: 'Aktuelles Level', + goToBuildings: 'Zu Gebäuden', + locked: 'Gesperrt', + viewRequirements: 'Anforderungen anzeigen', + requirementsNotMet: 'Anforderungen nicht erfüllt', + current: 'Aktuell' + }, + errors: { + requirementsNotMet: 'Anforderungen nicht erfüllt', + insufficientResources: 'Unzureichende Ressourcen', + shieldDomeLimit: 'Schildkuppel-Limit erreicht', + fleetMissionsFull: 'Flottenmissionsplätze voll', + insufficientFleet: 'Unzureichende Flotte', + insufficientFuel: 'Unzureichender Treibstoff', + planetOnly: 'Dieses Gebäude kann nur auf Planeten gebaut werden', + moonOnly: 'Dieses Gebäude kann nur auf Monden gebaut werden', + buildQueueFull: 'Bauauftrag voll', + insufficientSpace: 'Unzureichender Platz', + buildingLevelZero: 'Gebäudelevel ist 0, kann nicht abgerissen werden', + researchQueueFull: 'Forschungsauftrag voll', + moonExists: 'Mond existiert bereits', + insufficientDebris: 'Unzureichendes Trümmerfeld' + }, + nav: { + overview: 'Übersicht', + buildings: 'Gebäude', + research: 'Forschung', + shipyard: 'Raumschiffwerft', + defense: 'Verteidigung', + fleet: 'Flotte', + officers: 'Offiziere', + simulator: 'Simulator', + galaxy: 'Galaxie', + messages: 'Nachrichten', + settings: 'Einstellungen' + }, + sidebar: { + language: 'Sprache', + lightMode: 'Heller Modus', + darkMode: 'Dunkler Modus', + collapse: 'Einklappen', + expand: 'Ausklappen' + }, + resources: { + metal: 'Metall', + crystal: 'Kristall', + deuterium: 'Deuterium', + darkMatter: 'Dunkle Materie', + energy: 'Energie', + production: 'Produktion', + capacity: 'Kapazität', + current: 'Aktuell', + max: 'Max. Kapazität', + perHour: 'Stunde' + }, + planet: { + planet: 'Planet', + moon: 'Mond', + colony: 'Kolonie', + position: 'Position', + coordinates: 'Koordinaten', + switchToMoon: 'Zum Mond', + backToPlanet: 'Zurück zum Planeten', + fields: 'Felder', + temperature: 'Temperatur', + homePlanet: 'Heimatplanet', + planetPrefix: 'Planet', + moonSuffix: 's Mond', + colonyPrefix: 'Kolonie' + }, + player: { + points: 'Gesamtpunkte' + }, + buildings: { + metalMine: 'Metallmine', + crystalMine: 'Kristallmine', + deuteriumSynthesizer: 'Deuterium-Synthesizer', + solarPlant: 'Solarkraftwerk', + roboticsFactory: 'Roboterfabrik', + naniteFactory: 'Nanitenfabrik', + shipyard: 'Raumschiffwerft', + researchLab: 'Forschungslabor', + metalStorage: 'Metallspeicher', + crystalStorage: 'Kristallspeicher', + deuteriumTank: 'Deuteriumtank', + darkMatterCollector: 'Dunkle-Materie-Kollektor', + lunarBase: 'Mondbasis', + sensorPhalanx: 'Sensorphalanx', + jumpGate: 'Sprungtor', + buildTime: 'Bauzeit', + production: 'Produktion', + consumption: 'Verbrauch', + totalCost: 'Gesamtkosten', + totalPoints: 'Gesamtpunkte', + levelRange: 'Stufenbereich' + }, + buildingDescriptions: { + metalMine: 'Fördert Metallressourcen', + crystalMine: 'Fördert Kristallressourcen', + deuteriumSynthesizer: 'Synthesiert Deuteriumressourcen', + solarPlant: 'Liefert Energie', + roboticsFactory: 'Beschleunigt Baugeschwindigkeit', + naniteFactory: 'Erhöht Bauauftragskapazität, +1 pro Stufe (max 10)', + shipyard: 'Baut Schiffe', + researchLab: 'Erforscht Technologien', + metalStorage: 'Erhöht Metallspeicherkapazität', + crystalStorage: 'Erhöht Kristallspeicherkapazität', + deuteriumTank: 'Erhöht Deuteriumspeicherkapazität', + darkMatterCollector: 'Sammelt seltene Dunkle-Materie-Ressourcen', + lunarBase: 'Erhöht verfügbaren Platz auf dem Mond', + sensorPhalanx: 'Erkennt Flottenaktivitäten in umliegenden Systemen', + jumpGate: 'Überträgt Flotten sofort zu anderen Monden' + }, + ships: { + lightFighter: 'Leichter Jäger', + heavyFighter: 'Schwerer Jäger', + cruiser: 'Kreuzer', + battleship: 'Schlachtschiff', + smallCargo: 'Kleiner Transporter', + largeCargo: 'Großer Transporter', + colonyShip: 'Kolonieschiff', + recycler: 'Recycler', + espionageProbe: 'Spionagesonde', + darkMatterHarvester: 'Dunkle-Materie-Ernter' + }, + shipDescriptions: { + lightFighter: 'Grundlegende Kampfeinheit', + heavyFighter: 'Schwer gepanzerter Jäger', + cruiser: 'Mittleres Kriegsschiff, ausgewogene Offensive und Defensive', + battleship: 'Mächtiges Kriegsschiff', + smallCargo: 'Transportiert kleine Mengen Ressourcen', + largeCargo: 'Transportiert große Mengen Ressourcen', + colonyShip: 'Zur Kolonisierung neuer Planeten', + recycler: 'Sammelt Trümmerfeld-Ressourcen', + espionageProbe: 'Späht feindliche Planeten aus', + darkMatterHarvester: 'Spezielles Schiff zum Ernten von Dunkler Materie' + }, + defenses: { + rocketLauncher: 'Raketenwerfer', + lightLaser: 'Leichtes Lasergeschütz', + heavyLaser: 'Schweres Lasergeschütz', + gaussCannon: 'Gaußkanone', + ionCannon: 'Ionengeschütz', + plasmaTurret: 'Plasmawerfer', + smallShieldDome: 'Kleine Schildkuppel', + largeShieldDome: 'Große Schildkuppel' + }, + defenseDescriptions: { + rocketLauncher: 'Grundlegende Verteidigungsanlage', + lightLaser: 'Leichte Energiewaffe', + heavyLaser: 'Schwere Energiewaffe', + gaussCannon: 'Hochgeschwindigkeits-Kinetikwaffe', + ionCannon: 'Effektiv gegen Schilde', + plasmaTurret: 'Mächtige Verteidigungsanlage', + smallShieldDome: 'Kleiner Schild zum Schutz des gesamten Planeten', + largeShieldDome: 'Großer Schild zum Schutz des gesamten Planeten' + }, + research: { + researchTime: 'Forschungszeit', + totalCost: 'Gesamtkosten', + totalPoints: 'Gesamtpunkte', + levelRange: 'Stufenbereich' + }, + technologies: { + energyTechnology: 'Energietechnik', + laserTechnology: 'Lasertechnik', + ionTechnology: 'Ionentechnik', + hyperspaceTechnology: 'Hyperraumtechnik', + plasmaTechnology: 'Plasmatechnik', + computerTechnology: 'Computertechnologie', + combustionDrive: 'Verbrennungsantrieb', + impulseDrive: 'Impulsantrieb', + hyperspaceDrive: 'Hyperraumantrieb', + darkMatterTechnology: 'Dunkle-Materie-Technologie' + }, + technologyDescriptions: { + energyTechnology: 'Verbessert Energieeffizienz', + laserTechnology: 'Grundlage für Laserwaffen und -verteidigung', + ionTechnology: 'Ionenwaffentechnologie', + hyperspaceTechnology: 'Hyperraumsprung-Technologie', + plasmaTechnology: 'Plasmawaffentechnologie', + computerTechnology: 'Erhöht Forschungsauftragskapazität, +1 pro Stufe (max 10)', + combustionDrive: 'Grundlegende Antriebstechnologie', + impulseDrive: 'Mittlere Antriebstechnologie', + hyperspaceDrive: 'Fortgeschrittene Antriebstechnologie', + darkMatterTechnology: 'Forschung zu Eigenschaften und Anwendungen von Dunkler Materie' + }, + officers: { + commander: 'Kommandant', + admiral: 'Admiral', + engineer: 'Ingenieur', + geologist: 'Geologe', + technocrat: 'Technokrat', + darkMatterSpecialist: 'Dunkle-Materie-Spezialist' + }, + officerDescriptions: { + commander: 'Verbessert Baugeschwindigkeit und Management', + admiral: 'Verbessert Flottenkampf und Geschwindigkeit', + engineer: 'Verbessert Energie und Verteidigung', + geologist: 'Verbessert Ressourcenproduktion', + technocrat: 'Verbessert Forschungsgeschwindigkeit und Spionage', + darkMatterSpecialist: 'Verbessert Dunkle-Materie-Sammlungseffizienz' + }, + queue: { + buildQueue: 'Bauauftrag', + researchQueue: 'Forschungsauftrag', + building: 'Im Bau', + researching: 'In Forschung', + remaining: 'Verbleibend', + cancel: 'Abbrechen', + cancelBuild: 'Bau abbrechen', + cancelResearch: 'Forschung abbrechen', + confirmCancel: 'Möchten Sie wirklich abbrechen? 50% der Ressourcen werden zurückerstattet.', + level: 'Stufe', + upgradeToLevel: 'Auf Stufe aufrüsten' + }, + overview: { + title: 'Planetenübersicht', + resourceOverview: 'Ressourcen', + fleetInfo: 'Flotte', + currentShips: 'Schiffe auf diesem Planeten' + }, + buildingsView: { + title: 'Gebäude', + usedSpace: 'Verwendeter Platz', + spaceUsage: 'Platzbedarf', + level: 'Stufe', + upgradeCost: 'Ausbaukosten', + buildTime: 'Bauzeit', + upgrade: 'Ausbauen', + upgradeFailed: 'Ausbau fehlgeschlagen', + upgradeFailedMessage: 'Bitte überprüfen Sie, ob Sie genügend Ressourcen, Platz oder keine anderen Bauaufträge haben.', + demolish: 'Abreißen', + demolishRefund: 'Abriss-Rückerstattung', + demolishFailed: 'Abriss fehlgeschlagen', + demolishFailedMessage: 'Abriss nicht möglich. Bitte überprüfen Sie, ob die Bauqueue voll ist oder die Gebäudestufe 0 ist.' + }, + researchView: { + title: 'Forschung', + researchCost: 'Forschungskosten', + research: 'Forschen', + researchFailed: 'Forschung fehlgeschlagen', + researchFailedMessage: + 'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben, die Voraussetzungen erfüllt sind oder keine anderen Forschungsaufträge vorhanden sind.' + }, + shipyard: { + attack: 'Angriff', + shield: 'Schild', + armor: 'Panzerung', + speed: 'Geschwindigkeit', + cargoCapacity: 'Ladekapazität', + fuelConsumption: 'Treibstoffverbrauch', + buildCost: 'Baukosten', + buildTime: 'Bauzeit', + perUnit: 'Pro Einheit', + batchCalculator: 'Batch-Rechner', + quantity: 'Menge', + totalCost: 'Gesamtkosten', + totalTime: 'Gesamtzeit' + }, + shipyardView: { + title: 'Raumschiffwerft', + attack: 'Angriff', + shield: 'Schild', + speed: 'Geschwindigkeit', + cargoCapacity: 'Ladekapazität', + unitCost: 'Stückkosten', + buildQuantity: 'Baumenge', + totalCost: 'Gesamtkosten', + build: 'Bauen', + inputError: 'Eingabefehler', + inputErrorMessage: 'Bitte Baumenge eingeben!', + buildFailed: 'Bau fehlgeschlagen', + buildFailedMessage: 'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben oder die Voraussetzungen erfüllt sind.' + }, + defense: { + attack: 'Angriff', + shield: 'Schild', + armor: 'Panzerung', + buildCost: 'Baukosten', + buildTime: 'Bauzeit', + perUnit: 'Pro Einheit', + batchCalculator: 'Batch-Rechner', + quantity: 'Menge', + totalCost: 'Gesamtkosten', + totalTime: 'Gesamtzeit' + }, + defenseView: { + title: 'Verteidigung', + attack: 'Angriff', + shield: 'Schild', + armor: 'Panzerung', + buildTime: 'Bauzeit', + seconds: 's', + unitCost: 'Stückkosten', + buildQuantity: 'Baumenge', + totalCost: 'Gesamtkosten', + build: 'Bauen', + shieldDomeBuilt: 'Schildkuppel bereits gebaut', + inputError: 'Eingabefehler', + inputErrorMessage: 'Bitte Baumenge eingeben!', + buildFailed: 'Bau fehlgeschlagen', + buildFailedMessage: + 'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben oder die Voraussetzungen erfüllt sind. Schildkuppeln können nur einmal gebaut werden.' + }, + fleetView: { + title: 'Flottenverwaltung', + fleetOverview: 'Flottenübersicht', + sendFleet: 'Flotte senden', + flightMissions: 'Flugmissionen', + currentPlanetFleet: 'Flotte auf diesem Planeten', + attack: 'Angriff', + shield: 'Schild', + armor: 'Panzerung', + speed: 'Geschwindigkeit', + cargo: 'Fracht', + selectFleet: 'Flotte auswählen', + selectFleetDescription: 'Wählen Sie die Anzahl der zu sendenden Schiffe', + available: 'Verfügbar', + all: 'Alle', + targetCoordinates: 'Zielkoordinaten', + galaxy: 'Galaxie', + system: 'System', + position: 'Position', + missionType: 'Missionstyp', + missionInfo: 'Missionsinfo', + fuelConsumption: 'Treibstoffverbrauch', + flightTime: 'Flugzeit', + attackMission: 'Angriff', + transport: 'Transport', + colonize: 'Kolonisieren', + spy: 'Spionage', + deploy: 'Stationieren', + transportResources: 'Ressourcen transportieren', + totalCargoCapacity: 'Gesamtladekapazität', + used: 'Verwendet', + noFlightMissions: 'Keine Flugmissionen', + outbound: 'Hinflug', + returning: 'Rückflug', + fleetComposition: 'Flottenzusammensetzung', + carryingResources: 'Transportierte Ressourcen', + arrivalTime: 'Ankunftszeit', + returnTime: 'Rückkehrzeit', + recallFleet: 'Flotte zurückrufen', + sendFailed: 'Senden fehlgeschlagen', + sendFailedMessage: 'Bitte überprüfen Sie Flottenanzahl, Treibstoffverfügbarkeit oder Ladekapazitätsgrenzen.', + recallFailed: 'Zurückrufen fehlgeschlagen', + recallFailedMessage: 'Diese Mission kann nicht zurückgerufen werden.', + unknownPlanet: 'Unbekannter Planet', + fleetMissionSlots: 'Flottenmissionsplätze' + }, + officersView: { + title: 'Offiziere', + activated: 'Aktiviert', + inactive: 'Inaktiv', + activeStatus: 'Aktivierungsstatus', + expirationTime: 'Ablaufzeit', + remainingTime: 'Verbleibende Zeit', + recruitCost: 'Rekrutierungskosten', + days: 'Tage', + benefitsBonus: 'Vorteile', + resourceProduction: 'Ressourcenproduktion', + darkMatterProduction: 'Dunkle-Materie-Produktion', + energyProduction: 'Energieproduktion', + buildingSpeed: 'Baugeschwindigkeit', + researchSpeed: 'Forschungsgeschwindigkeit', + fleetSpeed: 'Flottengeschwindigkeit', + fuelConsumption: 'Treibstoffverbrauch', + defense: 'Verteidigung', + storageCapacity: 'Lagerkapazität', + buildQueue: 'Bauauftrag', + fleetSlots: 'Flottenslots', + hire: 'Rekrutieren', + renew: 'Verlängern', + dismiss: 'Entlassen', + hireTitle: 'Offizier rekrutieren', + hireMessage: 'Möchten Sie wirklich {name} rekrutieren? Gültig für 7 Tage.', + renewTitle: 'Offizier verlängern', + renewMessage: 'Möchten Sie wirklich {name} für 7 Tage verlängern?', + dismissTitle: 'Offizier entlassen', + dismissMessage: 'Möchten Sie wirklich {name} entlassen? Es werden keine Kosten zurückerstattet.', + hireFailed: 'Rekrutierung fehlgeschlagen', + renewFailed: 'Verlängerung fehlgeschlagen', + insufficientResources: 'Nicht genug Ressourcen!' + }, + galaxyView: { + title: 'Galaxie', + selectCoordinates: 'Koordinaten auswählen', + galaxy: 'Galaxie', + selectGalaxy: 'Galaxie auswählen', + system: 'System', + selectSystem: 'System auswählen', + view: 'Anzeigen', + myPlanet: 'Mein Planet', + totalPositions: 'Insgesamt 10 Planetenpositionen', + mine: 'Mein', + hostile: 'Feindlich', + emptySlot: 'Leer - Kolonisierbar', + scout: 'Spähen', + attack: 'Angriff', + colonize: 'Kolonisieren', + switch: 'Wechseln', + scoutPlanetTitle: 'Planet ausspionieren', + attackPlanetTitle: 'Planet angreifen', + colonizePlanetTitle: 'Planet kolonisieren', + scoutPlanetMessage: + 'Möchten Sie wirklich Spionagesonden senden, um Planet [{coordinates}] auszuspionieren?\n\nBitte gehen Sie zur Flottenseite, um Schiffe auszuwählen und zu senden.', + attackPlanetMessage: + 'Möchten Sie wirklich Planet [{coordinates}] angreifen?\n\nBitte gehen Sie zur Flottenseite, um Schiffe auszuwählen und zu senden.', + colonizePlanetMessage: + 'Möchten Sie wirklich Position [{coordinates}] kolonisieren?\n\nBitte gehen Sie zur Flottenseite, um ein Kolonieschiff zu senden.' + }, + messagesView: { + title: 'Nachrichten', + battleReports: 'Kampfberichte', + spyReports: 'Spionageberichte', + noBattleReports: 'Keine Kampfberichte', + noSpyReports: 'Keine Spionageberichte', + battleReport: 'Kampfbericht', + spyReport: 'Spionagebericht', + victory: 'Sieg', + defeat: 'Niederlage', + draw: 'Unentschieden', + attackerFleet: 'Angreiferflotte', + defenderFleet: 'Verteidigerflotte', + defenderDefense: 'Verteidigerverteidigung', + attackerLosses: 'Angreiferverluste', + defenderLosses: 'Verteidigerverluste', + noLosses: 'Keine Verluste', + plunder: 'Beute', + debrisField: 'Trümmerfeld', + resources: 'Ressourcen', + fleet: 'Flotte', + defense: 'Verteidigung', + buildings: 'Gebäude' + }, + simulatorView: { + title: 'Kampfsimulator', + attacker: 'Angreifer', + defender: 'Verteidiger', + attackerConfig: 'Angreiferkonfiguration', + attackerConfigDesc: 'Angreiferflotte und Technologiestufen konfigurieren', + defenderConfig: 'Verteidigerkonfiguration', + defenderConfigDesc: 'Verteidigerflotte, Verteidigung und Technologiestufen konfigurieren', + fleet: 'Flotte', + defenseStructures: 'Verteidigungsanlagen', + techLevels: 'Technologiestufen', + weapon: 'Waffe', + shield: 'Schild', + armor: 'Panzerung', + defenderResources: 'Verteidigerressourcen (für Beuteberechnung)', + startSimulation: 'Simulation starten', + reset: 'Zurücksetzen', + battleResult: 'Kampfergebnis', + attackerVictory: 'Angreifer Sieg', + defenderVictory: 'Verteidiger Sieg', + draw: 'Unentschieden', + afterRounds: 'Nach {rounds} Runden', + attackerLosses: 'Angreiferverluste', + defenderLosses: 'Verteidigerverluste', + noLosses: 'Keine Verluste', + attackerRemaining: 'Angreifer verblieben', + defenderRemaining: 'Verteidiger verblieben', + allDestroyed: 'Alle zerstört', + plunderableResources: 'Erbeutbare Ressourcen', + debrisField: 'Trümmerfeld', + moonChance: 'Mondchance', + showRoundDetails: 'Rundendetails anzeigen', + hideRoundDetails: 'Rundendetails ausblenden', + round: 'Runde {round}', + attackerRemainingPower: 'Verbleibende Angreiferkraft', + defenderRemainingPower: 'Verbleibende Verteidigerkraft' + }, + settings: { + dataManagement: 'Datenverwaltung', + dataManagementDesc: 'Spieldaten exportieren, importieren oder löschen', + exportData: 'Daten exportieren', + exportDataDesc: 'Spielfortschritt als JSON-Datei exportieren', + export: 'Exportieren', + exporting: 'Exportieren...', + exportSuccess: 'Export erfolgreich', + exportFailed: 'Export fehlgeschlagen, bitte erneut versuchen', + importData: 'Daten importieren', + importDataDesc: 'Spielfortschritt aus JSON-Datei wiederherstellen', + selectFile: 'Datei auswählen', + importSuccess: 'Import erfolgreich', + importConfirmTitle: 'Import bestätigen', + importConfirmMessage: 'Beim Importieren wird der aktuelle Spielfortschritt überschrieben. Diese Aktion kann nicht rückgängig gemacht werden. Fortfahren?', + importFailed: 'Import fehlgeschlagen, bitte Dateiformat überprüfen', + clearData: 'Daten löschen', + clearDataDesc: 'Alle Spieldaten löschen und zurücksetzen', + clear: 'Löschen', + clearConfirmTitle: 'Löschen bestätigen', + clearConfirmMessage: 'Alle Spieldaten werden gelöscht und von vorne begonnen. Diese Aktion kann nicht rückgängig gemacht werden. Fortfahren?', + gameSettings: 'Spieleinstellungen', + gameSettingsDesc: 'Spielparameter und Einstellungen anpassen', + playerName: 'Spielername', + gameSpeed: 'Spielgeschwindigkeit', + gameSpeedDesc: 'Aktueller Spielgeschwindigkeitsmultiplikator', + about: 'Über', + version: 'Version', + buildDate: 'Build-Datum', + community: 'Community', + github: 'GitHub-Repository', + qqGroup: 'QQ-Gruppe' + } +} diff --git a/src/locales/en.ts b/src/locales/en.ts new file mode 100644 index 0000000..77fef15 --- /dev/null +++ b/src/locales/en.ts @@ -0,0 +1,533 @@ +export default { + common: { + confirm: 'Confirm', + cancel: 'Cancel', + delete: 'Delete', + edit: 'Edit', + save: 'Save', + close: 'Close', + back: 'Back', + next: 'Next', + previous: 'Previous', + submit: 'Submit', + reset: 'Reset', + search: 'Search', + filter: 'Filter', + loading: 'Loading...', + noData: 'No Data', + error: 'Error', + success: 'Success', + warning: 'Warning', + info: 'Info', + resourceType: 'Resource Type', + playerName: 'Commander', + timeHour: 'h', + timeMinute: 'm', + timeSecond: 's', + featureLocked: 'Feature Locked', + unlockRequired: 'Building Required', + requiredBuilding: 'Required Building', + currentLevel: 'Current Level', + goToBuildings: 'Go to Buildings', + locked: 'Locked', + viewRequirements: 'View Requirements', + requirementsNotMet: 'Requirements Not Met', + current: 'Current' + }, + errors: { + requirementsNotMet: 'Requirements not met', + insufficientResources: 'Insufficient resources', + shieldDomeLimit: 'Shield dome limit reached', + fleetMissionsFull: 'Fleet mission slots full', + insufficientFleet: 'Insufficient fleet', + insufficientFuel: 'Insufficient fuel', + planetOnly: 'This building can only be built on planets', + moonOnly: 'This building can only be built on moons', + buildQueueFull: 'Build queue full', + insufficientSpace: 'Insufficient space', + buildingLevelZero: 'Building level is 0, cannot demolish', + researchQueueFull: 'Research queue full', + moonExists: 'Moon already exists', + insufficientDebris: 'Insufficient debris field' + }, + nav: { + overview: 'Overview', + buildings: 'Buildings', + research: 'Research', + shipyard: 'Shipyard', + defense: 'Defense', + fleet: 'Fleet', + officers: 'Officers', + simulator: 'Simulator', + galaxy: 'Galaxy', + messages: 'Messages', + settings: 'Settings' + }, + sidebar: { + language: 'Language', + lightMode: 'Light Mode', + darkMode: 'Dark Mode', + collapse: 'Collapse', + expand: 'Expand' + }, + resources: { + metal: 'Metal', + crystal: 'Crystal', + deuterium: 'Deuterium', + darkMatter: 'Dark Matter', + energy: 'Energy', + production: 'Production', + capacity: 'Capacity', + current: 'Current', + max: 'Max Capacity', + perHour: 'hour' + }, + planet: { + planet: 'Planet', + moon: 'Moon', + colony: 'Colony', + position: 'Position', + coordinates: 'Coordinates', + switchToMoon: 'View Moon', + backToPlanet: 'Back to Planet', + fields: 'Fields', + temperature: 'Temperature', + homePlanet: 'Home Planet', + planetPrefix: 'Planet', + moonSuffix: "'s Moon", + colonyPrefix: 'Colony' + }, + player: { + points: 'Total Points' + }, + buildings: { + metalMine: 'Metal Mine', + crystalMine: 'Crystal Mine', + deuteriumSynthesizer: 'Deuterium Synthesizer', + solarPlant: 'Solar Plant', + roboticsFactory: 'Robotics Factory', + naniteFactory: 'Nanite Factory', + shipyard: 'Shipyard', + researchLab: 'Research Lab', + metalStorage: 'Metal Storage', + crystalStorage: 'Crystal Storage', + deuteriumTank: 'Deuterium Tank', + darkMatterCollector: 'Dark Matter Collector', + lunarBase: 'Lunar Base', + sensorPhalanx: 'Sensor Phalanx', + jumpGate: 'Jump Gate', + buildTime: 'Build Time', + production: 'Production', + consumption: 'Consumption', + totalCost: 'Total Cost', + totalPoints: 'Total Points', + levelRange: 'Level Range' + }, + buildingDescriptions: { + metalMine: 'Extracts metal resources', + crystalMine: 'Extracts crystal resources', + deuteriumSynthesizer: 'Synthesizes deuterium resources', + solarPlant: 'Provides energy', + roboticsFactory: 'Accelerates construction speed', + naniteFactory: 'Increases build queue capacity, +1 per level (max 10)', + shipyard: 'Constructs ships', + researchLab: 'Researches technologies', + metalStorage: 'Increases metal storage capacity', + crystalStorage: 'Increases crystal storage capacity', + deuteriumTank: 'Increases deuterium storage capacity', + darkMatterCollector: 'Collects rare dark matter resources', + lunarBase: 'Increases available space on the moon', + sensorPhalanx: 'Detects fleet activities in surrounding systems', + jumpGate: 'Instantly transfers fleets to other moons' + }, + ships: { + lightFighter: 'Light Fighter', + heavyFighter: 'Heavy Fighter', + cruiser: 'Cruiser', + battleship: 'Battleship', + smallCargo: 'Small Cargo', + largeCargo: 'Large Cargo', + colonyShip: 'Colony Ship', + recycler: 'Recycler', + espionageProbe: 'Espionage Probe', + darkMatterHarvester: 'Dark Matter Harvester' + }, + shipDescriptions: { + lightFighter: 'Basic combat unit', + heavyFighter: 'Heavily armored fighter', + cruiser: 'Medium warship, balanced offense and defense', + battleship: 'Powerful warship', + smallCargo: 'Transports small amounts of resources', + largeCargo: 'Transports large amounts of resources', + colonyShip: 'Used to colonize new planets', + recycler: 'Collects debris field resources', + espionageProbe: 'Scouts enemy planets', + darkMatterHarvester: 'Special ship for harvesting dark matter' + }, + defenses: { + rocketLauncher: 'Rocket Launcher', + lightLaser: 'Light Laser', + heavyLaser: 'Heavy Laser', + gaussCannon: 'Gauss Cannon', + ionCannon: 'Ion Cannon', + plasmaTurret: 'Plasma Turret', + smallShieldDome: 'Small Shield Dome', + largeShieldDome: 'Large Shield Dome' + }, + defenseDescriptions: { + rocketLauncher: 'Basic defense facility', + lightLaser: 'Light energy weapon', + heavyLaser: 'Heavy energy weapon', + gaussCannon: 'High-speed kinetic weapon', + ionCannon: 'Effective against shields', + plasmaTurret: 'Powerful defense facility', + smallShieldDome: 'Small shield protecting the entire planet', + largeShieldDome: 'Large shield protecting the entire planet' + }, + research: { + researchTime: 'Research Time', + totalCost: 'Total Cost', + totalPoints: 'Total Points', + levelRange: 'Level Range' + }, + technologies: { + energyTechnology: 'Energy Technology', + laserTechnology: 'Laser Technology', + ionTechnology: 'Ion Technology', + hyperspaceTechnology: 'Hyperspace Technology', + plasmaTechnology: 'Plasma Technology', + computerTechnology: 'Computer Technology', + combustionDrive: 'Combustion Drive', + impulseDrive: 'Impulse Drive', + hyperspaceDrive: 'Hyperspace Drive', + darkMatterTechnology: 'Dark Matter Technology' + }, + technologyDescriptions: { + energyTechnology: 'Improves energy efficiency', + laserTechnology: 'Foundation of laser weapons and defense', + ionTechnology: 'Ion weapon technology', + hyperspaceTechnology: 'Hyperspace jump technology', + plasmaTechnology: 'Plasma weapon technology', + computerTechnology: 'Increases research queue capacity, +1 per level (max 10)', + combustionDrive: 'Basic propulsion technology', + impulseDrive: 'Intermediate propulsion technology', + hyperspaceDrive: 'Advanced propulsion technology', + darkMatterTechnology: 'Research into dark matter properties and applications' + }, + officers: { + commander: 'Commander', + admiral: 'Admiral', + engineer: 'Engineer', + geologist: 'Geologist', + technocrat: 'Technocrat', + darkMatterSpecialist: 'Dark Matter Specialist' + }, + officerDescriptions: { + commander: 'Improves building speed and management', + admiral: 'Improves fleet combat and speed', + engineer: 'Improves energy and defense', + geologist: 'Improves resource production', + technocrat: 'Improves research speed and espionage', + darkMatterSpecialist: 'Improves dark matter collection efficiency' + }, + queue: { + buildQueue: 'Build Queue', + researchQueue: 'Research Queue', + building: 'Building', + researching: 'Researching', + remaining: 'Remaining', + cancel: 'Cancel', + cancelBuild: 'Cancel Build', + cancelResearch: 'Cancel Research', + confirmCancel: 'Are you sure you want to cancel? 50% of resources will be refunded.', + level: 'Level', + upgradeToLevel: 'Upgrade to Level' + }, + overview: { + title: 'Planet Overview', + resourceOverview: 'Resources', + fleetInfo: 'Fleet', + currentShips: 'Ships on this planet' + }, + buildingsView: { + title: 'Buildings', + usedSpace: 'Used Space', + spaceUsage: 'Space Usage', + level: 'Level', + upgradeCost: 'Upgrade Cost', + buildTime: 'Build Time', + upgrade: 'Upgrade', + upgradeFailed: 'Upgrade Failed', + upgradeFailedMessage: 'Please check if you have enough resources, space, or if there are other build tasks.', + demolish: 'Demolish', + demolishRefund: 'Demolish Refund', + demolishFailed: 'Demolish Failed', + demolishFailedMessage: 'Unable to demolish this building. Please check if the build queue is full or the building level is 0.' + }, + researchView: { + title: 'Research', + researchCost: 'Research Cost', + research: 'Research', + researchFailed: 'Research Failed', + researchFailedMessage: 'Please check if you have enough resources, prerequisites are met, or if there are other research tasks.' + }, + shipyard: { + attack: 'Attack', + shield: 'Shield', + armor: 'Armor', + speed: 'Speed', + cargoCapacity: 'Cargo Capacity', + fuelConsumption: 'Fuel Consumption', + buildCost: 'Build Cost', + buildTime: 'Build Time', + perUnit: 'Per Unit', + batchCalculator: 'Batch Calculator', + quantity: 'Quantity', + totalCost: 'Total Cost', + totalTime: 'Total Time' + }, + shipyardView: { + title: 'Shipyard', + attack: 'Attack', + shield: 'Shield', + speed: 'Speed', + cargoCapacity: 'Cargo Capacity', + unitCost: 'Unit Cost', + buildQuantity: 'Build Quantity', + totalCost: 'Total Cost', + build: 'Build', + inputError: 'Input Error', + inputErrorMessage: 'Please enter build quantity!', + buildFailed: 'Build Failed', + buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met.' + }, + defense: { + attack: 'Attack', + shield: 'Shield', + armor: 'Armor', + buildCost: 'Build Cost', + buildTime: 'Build Time', + perUnit: 'Per Unit', + batchCalculator: 'Batch Calculator', + quantity: 'Quantity', + totalCost: 'Total Cost', + totalTime: 'Total Time' + }, + defenseView: { + title: 'Defense', + attack: 'Attack', + shield: 'Shield', + armor: 'Armor', + buildTime: 'Build Time', + seconds: 's', + unitCost: 'Unit Cost', + buildQuantity: 'Build Quantity', + totalCost: 'Total Cost', + build: 'Build', + shieldDomeBuilt: 'Shield dome already built', + inputError: 'Input Error', + inputErrorMessage: 'Please enter build quantity!', + buildFailed: 'Build Failed', + buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met. Shield domes can only be built once.' + }, + fleetView: { + title: 'Fleet Management', + fleetOverview: 'Fleet Overview', + sendFleet: 'Send Fleet', + flightMissions: 'Flight Missions', + currentPlanetFleet: 'Current Planet Fleet', + attack: 'Attack', + shield: 'Shield', + armor: 'Armor', + speed: 'Speed', + cargo: 'Cargo', + selectFleet: 'Select Fleet', + selectFleetDescription: 'Select the number of ships to send', + available: 'Available', + all: 'All', + targetCoordinates: 'Target Coordinates', + galaxy: 'Galaxy', + system: 'System', + position: 'Position', + missionType: 'Mission Type', + missionInfo: 'Mission Info', + fuelConsumption: 'Fuel Consumption', + flightTime: 'Flight Time', + attackMission: 'Attack', + transport: 'Transport', + colonize: 'Colonize', + spy: 'Spy', + deploy: 'Deploy', + transportResources: 'Transport Resources', + totalCargoCapacity: 'Total Cargo Capacity', + used: 'Used', + noFlightMissions: 'No flight missions', + outbound: 'Outbound', + returning: 'Returning', + fleetComposition: 'Fleet Composition', + carryingResources: 'Carrying Resources', + arrivalTime: 'Arrival Time', + returnTime: 'Return Time', + recallFleet: 'Recall Fleet', + sendFailed: 'Send Failed', + sendFailedMessage: 'Please check fleet count, fuel availability, or cargo capacity limits.', + recallFailed: 'Recall Failed', + recallFailedMessage: 'This mission cannot be recalled.', + unknownPlanet: 'Unknown Planet', + fleetMissionSlots: 'Fleet Mission Slots' + }, + officersView: { + title: 'Officers', + activated: 'Activated', + inactive: 'Inactive', + activeStatus: 'Active Status', + expirationTime: 'Expiration Time', + remainingTime: 'Remaining Time', + recruitCost: 'Recruitment Cost', + days: 'days', + benefitsBonus: 'Benefits Bonus', + resourceProduction: 'Resource Production', + darkMatterProduction: 'Dark Matter Production', + energyProduction: 'Energy Production', + buildingSpeed: 'Building Speed', + researchSpeed: 'Research Speed', + fleetSpeed: 'Fleet Speed', + fuelConsumption: 'Fuel Consumption', + defense: 'Defense', + storageCapacity: 'Storage Capacity', + buildQueue: 'Build Queue', + fleetSlots: 'Fleet Slots', + hire: 'Hire', + renew: 'Renew', + dismiss: 'Dismiss', + hireTitle: 'Hire Officer', + hireMessage: 'Are you sure you want to hire {name}? Valid for 7 days.', + renewTitle: 'Renew Officer', + renewMessage: 'Are you sure you want to renew {name} for 7 days?', + dismissTitle: 'Dismiss Officer', + dismissMessage: 'Are you sure you want to dismiss {name}? No refunds will be given.', + hireFailed: 'Hire Failed', + renewFailed: 'Renew Failed', + insufficientResources: 'Insufficient resources!' + }, + galaxyView: { + title: 'Galaxy', + selectCoordinates: 'Select Coordinates', + galaxy: 'Galaxy', + selectGalaxy: 'Select Galaxy', + system: 'System', + selectSystem: 'Select System', + view: 'View', + myPlanet: 'My Planet', + totalPositions: '10 planet positions total', + mine: 'Mine', + hostile: 'Hostile', + emptySlot: 'Empty - Colonizable', + scout: 'Scout', + attack: 'Attack', + colonize: 'Colonize', + switch: 'Switch', + scoutPlanetTitle: 'Scout Planet', + attackPlanetTitle: 'Attack Planet', + colonizePlanetTitle: 'Colonize Planet', + scoutPlanetMessage: + 'Are you sure you want to send espionage probes to scout planet [{coordinates}]?\n\nPlease go to the fleet page to select ships and send.', + attackPlanetMessage: 'Are you sure you want to attack planet [{coordinates}]?\n\nPlease go to the fleet page to select ships and send.', + colonizePlanetMessage: + 'Are you sure you want to colonize position [{coordinates}]?\n\nPlease go to the fleet page to send a colony ship.' + }, + messagesView: { + title: 'Messages', + battleReports: 'Battle Reports', + spyReports: 'Spy Reports', + noBattleReports: 'No battle reports', + noSpyReports: 'No spy reports', + battleReport: 'Battle Report', + spyReport: 'Spy Report', + victory: 'Victory', + defeat: 'Defeat', + draw: 'Draw', + attackerFleet: 'Attacker Fleet', + defenderFleet: 'Defender Fleet', + defenderDefense: 'Defender Defense', + attackerLosses: 'Attacker Losses', + defenderLosses: 'Defender Losses', + noLosses: 'No losses', + plunder: 'Plunder', + debrisField: 'Debris Field', + resources: 'Resources', + fleet: 'Fleet', + defense: 'Defense', + buildings: 'Buildings' + }, + simulatorView: { + title: 'Battle Simulator', + attacker: 'Attacker', + defender: 'Defender', + attackerConfig: 'Attacker Configuration', + attackerConfigDesc: 'Configure attacker fleet and technology levels', + defenderConfig: 'Defender Configuration', + defenderConfigDesc: 'Configure defender fleet, defense, and technology levels', + fleet: 'Fleet', + defenseStructures: 'Defense Structures', + techLevels: 'Technology Levels', + weapon: 'Weapon', + shield: 'Shield', + armor: 'Armor', + defenderResources: 'Defender Resources (for plunder calculation)', + startSimulation: 'Start Simulation', + reset: 'Reset', + battleResult: 'Battle Result', + attackerVictory: 'Attacker Victory', + defenderVictory: 'Defender Victory', + draw: 'Draw', + afterRounds: 'After {rounds} rounds', + attackerLosses: 'Attacker Losses', + defenderLosses: 'Defender Losses', + noLosses: 'No losses', + attackerRemaining: 'Attacker Remaining', + defenderRemaining: 'Defender Remaining', + allDestroyed: 'All destroyed', + plunderableResources: 'Plunderable Resources', + debrisField: 'Debris Field', + moonChance: 'Moon chance', + showRoundDetails: 'Show round details', + hideRoundDetails: 'Hide round details', + round: 'Round {round}', + attackerRemainingPower: 'Attacker remaining power', + defenderRemainingPower: 'Defender remaining power' + }, + settings: { + dataManagement: 'Data Management', + dataManagementDesc: 'Export, import, or clear game data', + exportData: 'Export Data', + exportDataDesc: 'Export game progress as JSON file', + export: 'Export', + exporting: 'Exporting...', + exportSuccess: 'Export successful', + exportFailed: 'Export failed, please try again', + importData: 'Import Data', + importDataDesc: 'Restore game progress from JSON file', + selectFile: 'Select File', + importSuccess: 'Import successful', + importConfirmTitle: 'Confirm Import', + importConfirmMessage: 'Importing will overwrite current game progress. This action cannot be undone. Continue?', + importFailed: 'Import failed, please check file format', + clearData: 'Clear Data', + clearDataDesc: 'Delete all game data and reset', + clear: 'Clear', + clearConfirmTitle: 'Confirm Clear Data', + clearConfirmMessage: 'This will delete all game data and start over. This action cannot be undone. Continue?', + gameSettings: 'Game Settings', + gameSettingsDesc: 'Adjust game parameters and preferences', + playerName: 'Player Name', + gameSpeed: 'Game Speed', + gameSpeedDesc: 'Current game speed multiplier', + about: 'About', + version: 'Version', + buildDate: 'Build Date', + community: 'Community', + github: 'GitHub Repository', + qqGroup: 'QQ Group' + } +} diff --git a/src/locales/index.ts b/src/locales/index.ts new file mode 100644 index 0000000..95b5f7c --- /dev/null +++ b/src/locales/index.ts @@ -0,0 +1,53 @@ +import zhCN from './zh-CN' +import zhTW from './zh-TW' +import en from './en' +import de from './de' +import ru from './ru' +import ko from './ko' +import ja from './ja' + +export type Locale = 'zh-CN' | 'zh-TW' | 'en' | 'de' | 'ru' | 'ko' | 'ja' + +export const locales = { 'zh-CN': zhCN, 'zh-TW': zhTW, en, de, ru, ko, ja } + +export const localeNames: Record = { + 'zh-CN': '简体中文', + 'zh-TW': '繁體中文', + en: 'English', + de: 'Deutsch', + ru: 'Русский', + ko: '한국어', + ja: '日本語' +} + +/** + * 根据浏览器语言检测并返回应用支持的语言 + * @returns 检测到的语言代码 + */ +export const detectBrowserLocale = (): Locale => { + // 获取浏览器语言 + const browserLang = navigator.language || (navigator.languages && navigator.languages[0]) || 'zh-CN' + const lang = browserLang.toLowerCase() + + // 映射浏览器语言到应用支持的语言 + if (lang.startsWith('zh-tw') || lang.startsWith('zh-hant') || lang.startsWith('zh-hk') || lang.startsWith('zh-mo')) { + return 'zh-TW' + } else if (lang.startsWith('zh')) { + return 'zh-CN' + } else if (lang.startsWith('ja')) { + return 'ja' + } else if (lang.startsWith('ko')) { + return 'ko' + } else if (lang.startsWith('en')) { + return 'en' + } else if (lang.startsWith('de')) { + return 'de' + } else if (lang.startsWith('ru')) { + return 'ru' + } + + // 默认返回简体中文 + return 'zh-CN' +} + +export type TranslationSchema = typeof zhCN diff --git a/src/locales/ja.ts b/src/locales/ja.ts new file mode 100644 index 0000000..5f3f22f --- /dev/null +++ b/src/locales/ja.ts @@ -0,0 +1,531 @@ +export default { + common: { + confirm: '確認', + cancel: 'キャンセル', + delete: '削除', + edit: '編集', + save: '保存', + close: '閉じる', + back: '戻る', + next: '次へ', + previous: '前へ', + submit: '送信', + reset: 'リセット', + search: '検索', + filter: 'フィルター', + loading: '読み込み中...', + noData: 'データなし', + error: 'エラー', + success: '成功', + warning: '警告', + info: '情報', + resourceType: '資源タイプ', + playerName: '司令官', + timeHour: '時間', + timeMinute: '分', + timeSecond: '秒', + featureLocked: '機能がロックされています', + unlockRequired: '建物が必要です', + requiredBuilding: '必要な建物', + currentLevel: '現在のレベル', + goToBuildings: '建物へ移動', + locked: 'ロック済み', + viewRequirements: '必要条件を表示', + requirementsNotMet: '必要条件が満たされていません', + current: '現在' + }, + errors: { + requirementsNotMet: '前提条件を満たしていません', + insufficientResources: '資源が不足しています', + shieldDomeLimit: 'シールドドームの上限に達しました', + fleetMissionsFull: '艦隊ミッションスロットが満杯です', + insufficientFleet: '艦隊が不足しています', + insufficientFuel: '燃料が不足しています', + planetOnly: 'この建物は惑星でのみ建設できます', + moonOnly: 'この建物は月でのみ建設できます', + buildQueueFull: '建設キューが満杯です', + insufficientSpace: 'スペースが不足しています', + buildingLevelZero: '建物レベルが0のため、解体できません', + researchQueueFull: '研究キューが満杯です', + moonExists: '月は既に存在します', + insufficientDebris: '残骸フィールドが不足しています' + }, + nav: { + overview: '概要', + buildings: '建物', + research: '研究', + shipyard: '造船所', + defense: '防衛', + fleet: '艦隊', + officers: '士官', + simulator: 'シミュレーター', + galaxy: '銀河', + messages: 'メッセージ', + settings: '設定' + }, + sidebar: { + language: '言語', + lightMode: 'ライトモード', + darkMode: 'ダークモード', + collapse: 'メニューを閉じる', + expand: 'メニューを開く' + }, + resources: { + metal: '金属', + crystal: 'クリスタル', + deuterium: '重水素', + darkMatter: 'ダークマター', + energy: 'エネルギー', + production: '生産量', + capacity: '容量', + current: '現在の貯蔵量', + max: '最大容量', + perHour: '時間' + }, + planet: { + planet: '惑星', + moon: '月', + colony: 'コロニー', + position: '位置', + coordinates: '座標', + switchToMoon: '月を表示', + backToPlanet: '母星に戻る', + fields: 'フィールド', + temperature: '温度', + homePlanet: '母星', + planetPrefix: '惑星', + moonSuffix: 'の月', + colonyPrefix: 'コロニー' + }, + player: { + points: '総ポイント' + }, + buildings: { + metalMine: '金属鉱山', + crystalMine: 'クリスタル鉱山', + deuteriumSynthesizer: '重水素合成装置', + solarPlant: '太陽光発電所', + roboticsFactory: 'ロボット工場', + naniteFactory: 'ナノマシン工場', + shipyard: '造船所', + researchLab: '研究所', + metalStorage: '金属倉庫', + crystalStorage: 'クリスタル倉庫', + deuteriumTank: '重水素タンク', + darkMatterCollector: 'ダークマター採取装置', + lunarBase: '月面基地', + sensorPhalanx: 'センサーファランクス', + jumpGate: 'ジャンプゲート', + buildTime: '建設時間', + production: '生産量', + consumption: '消費', + totalCost: '総コスト', + totalPoints: '総ポイント', + levelRange: 'レベル範囲' + }, + buildingDescriptions: { + metalMine: '金属資源を採掘', + crystalMine: 'クリスタル資源を採掘', + deuteriumSynthesizer: '重水素資源を合成', + solarPlant: 'エネルギーを供給', + roboticsFactory: '建設速度を向上', + naniteFactory: '建設キュー数を増加、レベル毎に+1(最大10)', + shipyard: '艦船を建造', + researchLab: '技術を研究', + metalStorage: '金属の貯蔵上限を増加', + crystalStorage: 'クリスタルの貯蔵上限を増加', + deuteriumTank: '重水素の貯蔵上限を増加', + darkMatterCollector: '希少なダークマター資源を収集', + lunarBase: '月の利用可能スペースを増加', + sensorPhalanx: '周辺星系の艦隊活動を探知', + jumpGate: '他の月へ艦隊を瞬間移動' + }, + ships: { + lightFighter: '軽戦闘機', + heavyFighter: '重戦闘機', + cruiser: '巡洋艦', + battleship: '戦艦', + smallCargo: '小型輸送船', + largeCargo: '大型輸送船', + colonyShip: 'コロニーシップ', + recycler: 'リサイクラー', + espionageProbe: 'スパイプローブ', + darkMatterHarvester: 'ダークマター採取船' + }, + shipDescriptions: { + lightFighter: '基本戦闘ユニット', + heavyFighter: '重装甲戦闘機', + cruiser: '中型戦艦、攻守バランス型', + battleship: '強力な戦艦', + smallCargo: '少量の資源を輸送', + largeCargo: '大量の資源を輸送', + colonyShip: '新惑星の植民に使用', + recycler: 'デブリフィールドの資源を回収', + espionageProbe: '敵惑星を偵察', + darkMatterHarvester: 'ダークマター採取専用の特殊艦' + }, + defenses: { + rocketLauncher: 'ロケットランチャー', + lightLaser: 'ライトレーザー', + heavyLaser: 'ヘビーレーザー', + gaussCannon: 'ガウスキャノン', + ionCannon: 'イオンキャノン', + plasmaTurret: 'プラズマタレット', + smallShieldDome: '小型シールドドーム', + largeShieldDome: '大型シールドドーム' + }, + defenseDescriptions: { + rocketLauncher: '基本防衛施設', + lightLaser: '軽量エネルギー兵器', + heavyLaser: '重型エネルギー兵器', + gaussCannon: '高速運動エネルギー兵器', + ionCannon: 'シールド破壊に効果的', + plasmaTurret: '強力な防衛施設', + smallShieldDome: '惑星全体を保護する小型シールド', + largeShieldDome: '惑星全体を保護する大型シールド' + }, + research: { + researchTime: '研究時間', + totalCost: '総コスト', + totalPoints: '総ポイント', + levelRange: 'レベル範囲' + }, + technologies: { + energyTechnology: 'エネルギー技術', + laserTechnology: 'レーザー技術', + ionTechnology: 'イオン技術', + hyperspaceTechnology: 'ハイパースペース技術', + plasmaTechnology: 'プラズマ技術', + computerTechnology: 'コンピューター技術', + combustionDrive: '燃焼ドライブ', + impulseDrive: 'インパルスドライブ', + hyperspaceDrive: 'ハイパースペースドライブ', + darkMatterTechnology: 'ダークマター技術' + }, + technologyDescriptions: { + energyTechnology: 'エネルギー利用効率を向上', + laserTechnology: 'レーザー兵器と防衛の基礎', + ionTechnology: 'イオン兵器技術', + hyperspaceTechnology: 'ハイパースペースジャンプ技術', + plasmaTechnology: 'プラズマ兵器技術', + computerTechnology: '研究キュー数を増加、レベル毎に+1(最大10)', + combustionDrive: '基本推進技術', + impulseDrive: '中級推進技術', + hyperspaceDrive: '高級推進技術', + darkMatterTechnology: 'ダークマターの性質と応用を研究' + }, + officers: { + commander: '司令官', + admiral: '提督', + engineer: 'エンジニア', + geologist: '地質学者', + technocrat: '技術専門家', + darkMatterSpecialist: 'ダークマター専門家' + }, + officerDescriptions: { + commander: '建設速度と管理能力を向上', + admiral: '艦隊戦闘力と速度を向上', + engineer: 'エネルギーと防御力を向上', + geologist: '資源生産量を向上', + technocrat: '研究速度と偵察能力を向上', + darkMatterSpecialist: 'ダークマター採取効率を向上' + }, + queue: { + buildQueue: '建設キュー', + researchQueue: '研究キュー', + building: '建設中', + researching: '研究中', + remaining: '残り時間', + cancel: 'キャンセル', + cancelBuild: '建設キャンセル', + cancelResearch: '研究キャンセル', + confirmCancel: 'キャンセルしますか?資源の50%が返還されます。', + level: 'レベル', + upgradeToLevel: 'レベルにアップグレード' + }, + shipyard: { + attack: '攻撃力', + shield: 'シールド', + armor: '装甲', + speed: '速度', + cargoCapacity: '貨物容量', + fuelConsumption: '燃料消費', + buildCost: '建設コスト', + buildTime: '建設時間', + perUnit: 'ユニットあたり', + batchCalculator: '一括計算機', + quantity: '数量', + totalCost: '総コスト', + totalTime: '総時間' + }, + overview: { + title: '惑星概要', + resourceOverview: '資源概要', + fleetInfo: '艦隊', + currentShips: '現在の惑星の艦船数' + }, + buildingsView: { + title: '建物', + usedSpace: '使用済みスペース', + spaceUsage: 'スペース使用量', + level: 'レベル', + upgradeCost: 'アップグレードコスト', + buildTime: '建設時間', + upgrade: 'アップグレード', + upgradeFailed: 'アップグレード失敗', + upgradeFailedMessage: '資源が十分か、スペースが十分か、または他の建設タスクがないか確認してください。', + demolish: '解体', + demolishRefund: '解体返還', + demolishFailed: '解体失敗', + demolishFailedMessage: 'この建物を解体できません。建設キューが満杯か、建物レベルが0でないか確認してください。' + }, + researchView: { + title: '研究', + researchCost: '研究コスト', + research: '研究', + researchFailed: '研究失敗', + researchFailedMessage: '資源が十分か、前提条件が満たされているか、または他の研究タスクがないか確認してください。' + }, + defense: { + attack: '攻撃力', + shield: 'シールド', + armor: '装甲', + buildCost: '建設コスト', + buildTime: '建設時間', + perUnit: 'ユニットあたり', + batchCalculator: '一括計算機', + quantity: '数量', + totalCost: '総コスト', + totalTime: '総時間' + }, + shipyardView: { + title: '造船所', + attack: '攻撃力', + shield: 'シールド', + speed: '速度', + cargoCapacity: '積載量', + unitCost: 'ユニットコスト', + buildQuantity: '建造数', + totalCost: '総コスト', + build: '建造', + inputError: '入力エラー', + inputErrorMessage: '建造数を入力してください!', + buildFailed: '建造失敗', + buildFailedMessage: '資源が十分か、前提条件が満たされているか確認してください。' + }, + defenseView: { + title: '防衛施設', + attack: '攻撃力', + shield: 'シールド', + armor: '装甲', + buildTime: '建設時間', + seconds: '秒', + unitCost: 'ユニットコスト', + buildQuantity: '建造数', + totalCost: '総コスト', + build: '建造', + shieldDomeBuilt: 'シールドドーム建設済み', + inputError: '入力エラー', + inputErrorMessage: '建造数を入力してください!', + buildFailed: '建造失敗', + buildFailedMessage: '資源が十分か、前提条件が満たされているか確認してください。シールドドームは1つのみ建設できます。' + }, + fleetView: { + title: '艦隊管理', + fleetOverview: '艦隊概要', + sendFleet: '艦隊派遣', + flightMissions: '飛行ミッション', + currentPlanetFleet: '現在の惑星艦隊', + attack: '攻撃', + shield: 'シールド', + armor: '装甲', + speed: '速度', + cargo: '貨物', + selectFleet: '艦隊選択', + selectFleetDescription: '派遣する艦船数を選択', + available: '利用可能', + all: '全て', + targetCoordinates: '目標座標', + galaxy: '銀河', + system: '星系', + position: '位置', + missionType: 'ミッションタイプ', + missionInfo: 'ミッション情報', + fuelConsumption: '燃料消費', + flightTime: '飛行時間', + attackMission: '攻撃', + transport: '輸送', + colonize: '植民', + spy: '偵察', + deploy: '配備', + transportResources: '資源輸送', + totalCargoCapacity: '総積載量', + used: '使用済み', + noFlightMissions: '飛行ミッションなし', + outbound: '往路', + returning: '帰路', + fleetComposition: '艦隊構成', + carryingResources: '運搬資源', + arrivalTime: '到着時刻', + returnTime: '帰還時刻', + recallFleet: '艦隊召還', + sendFailed: '派遣失敗', + sendFailedMessage: '艦隊数、燃料の充足、または積載量の制限を確認してください。', + recallFailed: '召還失敗', + recallFailedMessage: 'このミッションは召還できません。', + unknownPlanet: '未知の惑星', + fleetMissionSlots: '艦隊ミッションスロット' + }, + officersView: { + title: '士官', + activated: 'アクティブ', + inactive: '非アクティブ', + activeStatus: 'アクティブ状態', + expirationTime: '期限切れ時刻', + remainingTime: '残り時間', + recruitCost: '募集コスト', + days: '日', + benefitsBonus: '効果ボーナス', + resourceProduction: '資源生産量', + darkMatterProduction: 'ダークマター生産量', + energyProduction: 'エネルギー生産量', + buildingSpeed: '建設速度', + researchSpeed: '研究速度', + fleetSpeed: '艦隊速度', + fuelConsumption: '燃料消費', + defense: '防御力', + storageCapacity: '貯蔵容量', + buildQueue: '建設キュー', + fleetSlots: '艦隊スロット', + hire: '雇用', + renew: '更新', + dismiss: '解雇', + hireTitle: '士官雇用', + hireMessage: '{name}を雇用しますか?有効期限は7日間です。', + renewTitle: '士官更新', + renewMessage: '{name}を7日間更新しますか?', + dismissTitle: '士官解雇', + dismissMessage: '{name}を解雇しますか?費用は返金されません。', + hireFailed: '雇用失敗', + renewFailed: '更新失敗', + insufficientResources: '資源不足!' + }, + galaxyView: { + title: '銀河', + selectCoordinates: '座標選択', + galaxy: '銀河', + selectGalaxy: '銀河を選択', + system: '星系', + selectSystem: '星系を選択', + view: '表示', + myPlanet: '自分の惑星', + totalPositions: '全10惑星位置', + mine: '自分', + hostile: '敵対', + emptySlot: '空き - 植民可能', + scout: '偵察', + attack: '攻撃', + colonize: '植民', + switch: '切り替え', + scoutPlanetTitle: '惑星偵察', + attackPlanetTitle: '惑星攻撃', + colonizePlanetTitle: '惑星植民', + scoutPlanetMessage: '惑星[{coordinates}]にスパイプローブを送りますか?\n\n艦隊ページに移動して艦船を選択して派遣してください。', + attackPlanetMessage: '惑星[{coordinates}]を攻撃しますか?\n\n艦隊ページに移動して艦船を選択して派遣してください。', + colonizePlanetMessage: '位置[{coordinates}]を植民しますか?\n\n艦隊ページに移動してコロニーシップを派遣してください。' + }, + messagesView: { + title: 'メッセージセンター', + battleReports: '戦闘レポート', + spyReports: 'スパイレポート', + noBattleReports: '戦闘レポートなし', + noSpyReports: 'スパイレポートなし', + battleReport: '戦闘レポート', + spyReport: 'スパイレポート', + victory: '勝利', + defeat: '敗北', + draw: '引き分け', + attackerFleet: '攻撃側艦隊', + defenderFleet: '防御側艦隊', + defenderDefense: '防御側防衛', + attackerLosses: '攻撃側損失', + defenderLosses: '防御側損失', + noLosses: '損失なし', + plunder: '略奪資源', + debrisField: 'デブリフィールド', + resources: '資源', + fleet: '艦隊', + defense: '防衛', + buildings: '建物' + }, + simulatorView: { + title: '戦闘シミュレーター', + attacker: '攻撃側', + defender: '防御側', + attackerConfig: '攻撃側設定', + attackerConfigDesc: '攻撃側の艦隊と技術レベルを設定', + defenderConfig: '防御側設定', + defenderConfigDesc: '防御側の艦隊、防衛、技術レベルを設定', + fleet: '艦隊', + defenseStructures: '防衛施設', + techLevels: '技術レベル', + weapon: '武器', + shield: 'シールド', + armor: '装甲', + defenderResources: '防御側資源(略奪計算用)', + startSimulation: 'シミュレーション開始', + reset: 'リセット', + battleResult: '戦闘結果', + attackerVictory: '攻撃側勝利', + defenderVictory: '防御側勝利', + draw: '引き分け', + afterRounds: '{rounds}ラウンド後', + attackerLosses: '攻撃側損失', + defenderLosses: '防御側損失', + noLosses: '損失なし', + attackerRemaining: '攻撃側残存', + defenderRemaining: '防御側残存', + allDestroyed: '全て破壊', + plunderableResources: '略奪可能資源', + debrisField: 'デブリフィールド', + moonChance: '月生成確率', + showRoundDetails: 'ラウンド詳細表示', + hideRoundDetails: 'ラウンド詳細非表示', + round: '第{round}ラウンド', + attackerRemainingPower: '攻撃側残存火力', + defenderRemainingPower: '防御側残存火力' + }, + settings: { + dataManagement: 'データ管理', + dataManagementDesc: 'ゲームデータのエクスポート、インポート、またはクリア', + exportData: 'データエクスポート', + exportDataDesc: 'ゲームの進行状況をJSONファイルとしてエクスポート', + export: 'エクスポート', + exporting: 'エクスポート中...', + exportSuccess: 'エクスポート成功', + exportFailed: 'エクスポートに失敗しました。もう一度お試しください', + importData: 'データインポート', + importDataDesc: 'JSONファイルからゲームの進行状況を復元', + selectFile: 'ファイルを選択', + importSuccess: 'インポート成功', + importConfirmTitle: 'インポート確認', + importConfirmMessage: 'インポートすると現在のゲームの進行状況が上書きされます。この操作は元に戻せません。続行しますか?', + importFailed: 'インポートに失敗しました。ファイル形式を確認してください', + clearData: 'データクリア', + clearDataDesc: 'すべてのゲームデータを削除してリセット', + clear: 'クリア', + clearConfirmTitle: 'データクリア確認', + clearConfirmMessage: 'すべてのゲームデータが削除され、最初からやり直します。この操作は元に戻せません。続行しますか?', + gameSettings: 'ゲーム設定', + gameSettingsDesc: 'ゲームパラメータと設定を調整', + playerName: 'プレイヤー名', + gameSpeed: 'ゲーム速度', + gameSpeedDesc: '現在のゲーム速度倍率', + about: 'について', + version: 'バージョン', + buildDate: 'ビルド日', + community: 'コミュニティ', + github: 'GitHubリポジトリ', + qqGroup: 'QQグループ' + } +} diff --git a/src/locales/ko.ts b/src/locales/ko.ts new file mode 100644 index 0000000..09b7b8d --- /dev/null +++ b/src/locales/ko.ts @@ -0,0 +1,532 @@ +export default { + common: { + confirm: '확인', + cancel: '취소', + delete: '삭제', + edit: '편집', + save: '저장', + close: '닫기', + back: '돌아가기', + next: '다음', + previous: '이전', + submit: '제출', + reset: '초기화', + search: '검색', + filter: '필터', + loading: '로딩 중...', + noData: '데이터 없음', + error: '오류', + success: '성공', + warning: '경고', + info: '정보', + resourceType: '자원 유형', + playerName: '사령관', + timeHour: '시간', + timeMinute: '분', + timeSecond: '초', + featureLocked: '기능 잠김', + unlockRequired: '건물 필요', + requiredBuilding: '필요한 건물', + currentLevel: '현재 레벨', + goToBuildings: '건물로 이동', + locked: '잠김', + viewRequirements: '요구사항 보기', + requirementsNotMet: '요구사항 미충족', + current: '현재' + }, + errors: { + requirementsNotMet: '전제 조건 미충족', + insufficientResources: '자원 부족', + shieldDomeLimit: '실드 돔 한도 도달', + fleetMissionsFull: '함대 임무 슬롯 가득 참', + insufficientFleet: '함대 부족', + insufficientFuel: '연료 부족', + planetOnly: '이 건물은 행성에서만 지을 수 있습니다', + moonOnly: '이 건물은 위성에서만 지을 수 있습니다', + buildQueueFull: '건설 대기열 가득 참', + insufficientSpace: '공간 부족', + buildingLevelZero: '건물 레벨이 0이므로 철거할 수 없습니다', + researchQueueFull: '연구 대기열 가득 참', + moonExists: '위성이 이미 존재합니다', + insufficientDebris: '잔해장 부족' + }, + nav: { + overview: '개요', + buildings: '건물', + research: '연구', + shipyard: '조선소', + defense: '방어', + fleet: '함대', + officers: '장교', + simulator: '시뮬레이터', + galaxy: '은하계', + messages: '메시지', + settings: '설정' + }, + sidebar: { + language: '언어', + lightMode: '라이트 모드', + darkMode: '다크 모드', + collapse: '메뉴 접기', + expand: '메뉴 펼치기' + }, + resources: { + metal: '금속', + crystal: '크리스탈', + deuterium: '중수소', + darkMatter: '암흑 물질', + energy: '에너지', + production: '생산량', + capacity: '용량', + current: '현재 저장량', + max: '최대 용량', + perHour: '시간' + }, + planet: { + planet: '행성', + moon: '위성', + colony: '식민지', + position: '위치', + coordinates: '좌표', + switchToMoon: '위성 보기', + backToPlanet: '모행성으로 돌아가기', + fields: '필드', + temperature: '온도', + homePlanet: '모행성', + planetPrefix: '행성', + moonSuffix: '의 위성', + colonyPrefix: '식민지' + }, + player: { + points: '총 점수' + }, + buildings: { + metalMine: '금속 광산', + crystalMine: '크리스탈 광산', + deuteriumSynthesizer: '중수소 합성기', + solarPlant: '태양광 발전소', + roboticsFactory: '로봇 공장', + naniteFactory: '나노 공장', + shipyard: '조선소', + researchLab: '연구소', + metalStorage: '금속 창고', + crystalStorage: '크리스탈 창고', + deuteriumTank: '중수소 탱크', + darkMatterCollector: '암흑 물질 수집기', + lunarBase: '달 기지', + sensorPhalanx: '센서 팔랑크스', + jumpGate: '점프 게이트', + buildTime: '건설 시간', + production: '생산량', + consumption: '소비', + totalCost: '총 비용', + totalPoints: '총 점수', + levelRange: '레벨 범위' + }, + buildingDescriptions: { + metalMine: '금속 자원 채굴', + crystalMine: '크리스탈 자원 채굴', + deuteriumSynthesizer: '중수소 자원 합성', + solarPlant: '에너지 제공', + roboticsFactory: '건설 속도 향상', + naniteFactory: '건설 대기열 수 증가, 레벨당 +1 (최대 10개)', + shipyard: '함선 건조', + researchLab: '기술 연구', + metalStorage: '금속 저장 용량 증가', + crystalStorage: '크리스탈 저장 용량 증가', + deuteriumTank: '중수소 저장 용량 증가', + darkMatterCollector: '희귀한 암흑 물질 자원 수집', + lunarBase: '달 가용 공간 증가', + sensorPhalanx: '주변 행성계의 함대 활동 감지', + jumpGate: '다른 위성으로 함대 순간 이동' + }, + ships: { + lightFighter: '경전투기', + heavyFighter: '중전투기', + cruiser: '순양함', + battleship: '전함', + smallCargo: '소형 수송선', + largeCargo: '대형 수송선', + colonyShip: '식민선', + recycler: '재활용선', + espionageProbe: '정찰기', + darkMatterHarvester: '암흑 물질 채취선' + }, + shipDescriptions: { + lightFighter: '기본 전투 유닛', + heavyFighter: '중장갑 전투기', + cruiser: '중형 전함, 공격과 방어 균형', + battleship: '강력한 전함', + smallCargo: '소량의 자원 운송', + largeCargo: '대량의 자원 운송', + colonyShip: '새로운 행성 식민에 사용', + recycler: '잔해장 자원 수집', + espionageProbe: '적 행성 정찰', + darkMatterHarvester: '암흑 물질 채취 전용 특수 함선' + }, + defenses: { + rocketLauncher: '로켓 발사대', + lightLaser: '경량 레이저포', + heavyLaser: '중형 레이저포', + gaussCannon: '가우스 캐논', + ionCannon: '이온 캐논', + plasmaTurret: '플라즈마 포탑', + smallShieldDome: '소형 실드 돔', + largeShieldDome: '대형 실드 돔' + }, + defenseDescriptions: { + rocketLauncher: '기본 방어 시설', + lightLaser: '경량 에너지 무기', + heavyLaser: '중형 에너지 무기', + gaussCannon: '고속 운동 에너지 무기', + ionCannon: '실드 파괴의 이기', + plasmaTurret: '강력한 방어 시설', + smallShieldDome: '행성 전체를 보호하는 소형 실드', + largeShieldDome: '행성 전체를 보호하는 대형 실드' + }, + research: { + researchTime: '연구 시간', + totalCost: '총 비용', + totalPoints: '총 점수', + levelRange: '레벨 범위' + }, + technologies: { + energyTechnology: '에너지 기술', + laserTechnology: '레이저 기술', + ionTechnology: '이온 기술', + hyperspaceTechnology: '초공간 기술', + plasmaTechnology: '플라즈마 기술', + computerTechnology: '컴퓨터 기술', + combustionDrive: '연소 엔진', + impulseDrive: '임펄스 엔진', + hyperspaceDrive: '초공간 엔진', + darkMatterTechnology: '암흑 물질 기술' + }, + technologyDescriptions: { + energyTechnology: '에너지 이용 효율 향상', + laserTechnology: '레이저 무기와 방어의 기초', + ionTechnology: '이온 무기 기술', + hyperspaceTechnology: '초공간 점프 기술', + plasmaTechnology: '플라즈마 무기 기술', + computerTechnology: '연구 대기열 수 증가, 레벨당 +1 (최대 10개)', + combustionDrive: '기본 추진 기술', + impulseDrive: '중급 추진 기술', + hyperspaceDrive: '고급 추진 기술', + darkMatterTechnology: '암흑 물질의 성질과 응용 연구' + }, + officers: { + commander: '사령관', + admiral: '제독', + engineer: '엔지니어', + geologist: '지질학자', + technocrat: '기술 전문가', + darkMatterSpecialist: '암흑 물질 전문가' + }, + officerDescriptions: { + commander: '건설 속도 및 관리 능력 향상', + admiral: '함대 전투력 및 속도 향상', + engineer: '에너지 및 방어력 향상', + geologist: '자원 생산량 향상', + technocrat: '연구 속도 및 정찰 능력 향상', + darkMatterSpecialist: '암흑 물질 수집 효율 향상' + }, + queue: { + buildQueue: '건설 대기열', + researchQueue: '연구 대기열', + building: '건설 중', + researching: '연구 중', + remaining: '남은 시간', + cancel: '취소', + cancelBuild: '건설 취소', + cancelResearch: '연구 취소', + confirmCancel: '취소하시겠습니까? 자원의 50%가 환불됩니다.', + level: '레벨', + upgradeToLevel: '레벨로 업그레이드' + }, + overview: { + title: '행성 개요', + resourceOverview: '자원 개요', + fleetInfo: '함대', + currentShips: '현재 행성의 함선 수' + }, + buildingsView: { + title: '건물', + usedSpace: '사용된 공간', + spaceUsage: '공간 사용', + level: '레벨', + upgradeCost: '업그레이드 비용', + buildTime: '건설 시간', + upgrade: '업그레이드', + upgradeFailed: '업그레이드 실패', + upgradeFailedMessage: '자원이 충분한지, 공간이 충분한지, 또는 다른 건설 작업이 있는지 확인하세요.', + demolish: '철거', + demolishRefund: '철거 환불', + demolishFailed: '철거 실패', + demolishFailedMessage: '이 건물을 철거할 수 없습니다. 건설 대기열이 가득 찼거나 건물 레벨이 0인지 확인하세요.' + }, + researchView: { + title: '연구', + researchCost: '연구 비용', + research: '연구', + researchFailed: '연구 실패', + researchFailedMessage: '자원이 충분한지, 전제 조건이 충족되었는지, 또는 다른 연구 작업이 있는지 확인하세요.' + }, + shipyard: { + attack: '공격력', + shield: '쉴드', + armor: '장갑', + speed: '속도', + cargoCapacity: '화물 용량', + fuelConsumption: '연료 소비', + buildCost: '건설 비용', + buildTime: '건설 시간', + perUnit: '단위당', + batchCalculator: '일괄 계산기', + quantity: '수량', + totalCost: '총 비용', + totalTime: '총 시간' + }, + shipyardView: { + title: '조선소', + attack: '공격력', + shield: '실드', + speed: '속도', + cargoCapacity: '적재량', + unitCost: '단위 비용', + buildQuantity: '건조 수량', + totalCost: '총 비용', + build: '건조', + inputError: '입력 오류', + inputErrorMessage: '건조 수량을 입력하세요!', + buildFailed: '건조 실패', + buildFailedMessage: '자원이 충분한지 또는 전제 조건이 충족되었는지 확인하세요.' + }, + defense: { + attack: '공격력', + shield: '쉴드', + armor: '장갑', + buildCost: '건설 비용', + buildTime: '건설 시간', + perUnit: '단위당', + batchCalculator: '일괄 계산기', + quantity: '수량', + totalCost: '총 비용', + totalTime: '총 시간' + }, + defenseView: { + title: '방어 시설', + attack: '공격력', + shield: '실드', + armor: '장갑', + buildTime: '건설 시간', + seconds: '초', + unitCost: '단위 비용', + buildQuantity: '건조 수량', + totalCost: '총 비용', + build: '건조', + shieldDomeBuilt: '실드 돔이 이미 건설됨', + inputError: '입력 오류', + inputErrorMessage: '건조 수량을 입력하세요!', + buildFailed: '건조 실패', + buildFailedMessage: '자원이 충분한지 또는 전제 조건이 충족되었는지 확인하세요. 실드 돔은 하나만 건설할 수 있습니다.' + }, + fleetView: { + title: '함대 관리', + fleetOverview: '함대 개요', + sendFleet: '함대 파견', + flightMissions: '비행 임무', + currentPlanetFleet: '현재 행성 함대', + attack: '공격', + shield: '실드', + armor: '장갑', + speed: '속도', + cargo: '화물', + selectFleet: '함대 선택', + selectFleetDescription: '파견할 함선 수 선택', + available: '사용 가능', + all: '전체', + targetCoordinates: '목표 좌표', + galaxy: '은하계', + system: '행성계', + position: '위치', + missionType: '임무 유형', + missionInfo: '임무 정보', + fuelConsumption: '연료 소비', + flightTime: '비행 시간', + attackMission: '공격', + transport: '수송', + colonize: '식민', + spy: '정찰', + deploy: '배치', + transportResources: '자원 수송', + totalCargoCapacity: '총 적재량', + used: '사용됨', + noFlightMissions: '비행 임무 없음', + outbound: '이동 중', + returning: '귀환 중', + fleetComposition: '함대 구성', + carryingResources: '운반 자원', + arrivalTime: '도착 시간', + returnTime: '귀환 시간', + recallFleet: '함대 소환', + sendFailed: '파견 실패', + sendFailedMessage: '함대 수, 연료 충분 여부 또는 적재량 한계를 확인하세요.', + recallFailed: '소환 실패', + recallFailedMessage: '이 임무는 소환할 수 없습니다.', + unknownPlanet: '알 수 없는 행성', + fleetMissionSlots: '함대 임무 슬롯' + }, + officersView: { + title: '장교', + activated: '활성화됨', + inactive: '비활성', + activeStatus: '활성 상태', + expirationTime: '만료 시간', + remainingTime: '남은 시간', + recruitCost: '모집 비용', + days: '일', + benefitsBonus: '효과 보너스', + resourceProduction: '자원 생산량', + darkMatterProduction: '암흑 물질 생산량', + energyProduction: '에너지 생산량', + buildingSpeed: '건설 속도', + researchSpeed: '연구 속도', + fleetSpeed: '함대 속도', + fuelConsumption: '연료 소비', + defense: '방어력', + storageCapacity: '저장 용량', + buildQueue: '건설 대기열', + fleetSlots: '함대 슬롯', + hire: '고용', + renew: '갱신', + dismiss: '해고', + hireTitle: '장교 고용', + hireMessage: '{name}을(를) 고용하시겠습니까? 유효 기간은 7일입니다.', + renewTitle: '장교 갱신', + renewMessage: '{name}을(를) 7일간 갱신하시겠습니까?', + dismissTitle: '장교 해고', + dismissMessage: '{name}을(를) 해고하시겠습니까? 비용은 환불되지 않습니다.', + hireFailed: '고용 실패', + renewFailed: '갱신 실패', + insufficientResources: '자원 부족!' + }, + galaxyView: { + title: '은하계', + selectCoordinates: '좌표 선택', + galaxy: '은하계', + selectGalaxy: '은하계 선택', + system: '행성계', + selectSystem: '행성계 선택', + view: '보기', + myPlanet: '내 행성', + totalPositions: '총 10개 행성 위치', + mine: '내 것', + hostile: '적대', + emptySlot: '빈 자리 - 식민 가능', + scout: '정찰', + attack: '공격', + colonize: '식민', + switch: '전환', + scoutPlanetTitle: '행성 정찰', + attackPlanetTitle: '행성 공격', + colonizePlanetTitle: '행성 식민', + scoutPlanetMessage: + '행성 [{coordinates}]을(를) 정찰하기 위해 정찰기를 보내시겠습니까?\n\n함대 페이지로 이동하여 함선을 선택하고 파견하세요.', + attackPlanetMessage: '행성 [{coordinates}]을(를) 공격하시겠습니까?\n\n함대 페이지로 이동하여 함선을 선택하고 파견하세요.', + colonizePlanetMessage: '위치 [{coordinates}]을(를) 식민하시겠습니까?\n\n함대 페이지로 이동하여 식민선을 파견하세요.' + }, + messagesView: { + title: '메시지 센터', + battleReports: '전투 보고서', + spyReports: '정찰 보고서', + noBattleReports: '전투 보고서 없음', + noSpyReports: '정찰 보고서 없음', + battleReport: '전투 보고서', + spyReport: '정찰 보고서', + victory: '승리', + defeat: '패배', + draw: '무승부', + attackerFleet: '공격자 함대', + defenderFleet: '방어자 함대', + defenderDefense: '방어자 방어', + attackerLosses: '공격자 손실', + defenderLosses: '방어자 손실', + noLosses: '손실 없음', + plunder: '약탈 자원', + debrisField: '잔해장', + resources: '자원', + fleet: '함대', + defense: '방어', + buildings: '건물' + }, + simulatorView: { + title: '전투 시뮬레이터', + attacker: '공격자', + defender: '방어자', + attackerConfig: '공격자 설정', + attackerConfigDesc: '공격자의 함대와 기술 레벨 설정', + defenderConfig: '방어자 설정', + defenderConfigDesc: '방어자의 함대, 방어 및 기술 레벨 설정', + fleet: '함대', + defenseStructures: '방어 시설', + techLevels: '기술 레벨', + weapon: '무기', + shield: '실드', + armor: '장갑', + defenderResources: '방어자 자원 (약탈 계산용)', + startSimulation: '시뮬레이션 시작', + reset: '초기화', + battleResult: '전투 결과', + attackerVictory: '공격자 승리', + defenderVictory: '방어자 승리', + draw: '무승부', + afterRounds: '{rounds}회 전투 후', + attackerLosses: '공격자 손실', + defenderLosses: '방어자 손실', + noLosses: '손실 없음', + attackerRemaining: '공격자 잔여', + defenderRemaining: '방어자 잔여', + allDestroyed: '모두 파괴됨', + plunderableResources: '약탈 가능 자원', + debrisField: '잔해장', + moonChance: '위성 생성 확률', + showRoundDetails: '라운드 상세 표시', + hideRoundDetails: '라운드 상세 숨기기', + round: '제 {round} 라운드', + attackerRemainingPower: '공격자 잔여 화력', + defenderRemainingPower: '방어자 잔여 화력' + }, + settings: { + dataManagement: '데이터 관리', + dataManagementDesc: '게임 데이터 내보내기, 가져오기 또는 지우기', + exportData: '데이터 내보내기', + exportDataDesc: '게임 진행 상황을 JSON 파일로 내보내기', + export: '내보내기', + exporting: '내보내는 중...', + exportSuccess: '내보내기 성공', + exportFailed: '내보내기 실패, 다시 시도해주세요', + importData: '데이터 가져오기', + importDataDesc: 'JSON 파일에서 게임 진행 상황 복원', + selectFile: '파일 선택', + importSuccess: '가져오기 성공', + importConfirmTitle: '가져오기 확인', + importConfirmMessage: '가져오기를 하면 현재 게임 진행 상황이 덮어쓰기됩니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?', + importFailed: '가져오기 실패, 파일 형식을 확인해주세요', + clearData: '데이터 지우기', + clearDataDesc: '모든 게임 데이터 삭제 및 초기화', + clear: '지우기', + clearConfirmTitle: '데이터 지우기 확인', + clearConfirmMessage: '모든 게임 데이터가 삭제되고 처음부터 시작됩니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?', + gameSettings: '게임 설정', + gameSettingsDesc: '게임 매개변수 및 설정 조정', + playerName: '플레이어 이름', + gameSpeed: '게임 속도', + gameSpeedDesc: '현재 게임 속도 배율', + about: '정보', + version: '버전', + buildDate: '빌드 날짜', + community: '커뮤니티', + github: 'GitHub 저장소', + qqGroup: 'QQ 그룹' + } +} diff --git a/src/locales/ru.ts b/src/locales/ru.ts new file mode 100644 index 0000000..1b3ef40 --- /dev/null +++ b/src/locales/ru.ts @@ -0,0 +1,536 @@ +export default { + common: { + confirm: 'Подтвердить', + cancel: 'Отмена', + delete: 'Удалить', + edit: 'Редактировать', + save: 'Сохранить', + close: 'Закрыть', + back: 'Назад', + next: 'Далее', + previous: 'Предыдущий', + submit: 'Отправить', + reset: 'Сбросить', + search: 'Поиск', + filter: 'Фильтр', + loading: 'Загрузка...', + noData: 'Нет данных', + error: 'Ошибка', + success: 'Успешно', + warning: 'Предупреждение', + info: 'Информация', + resourceType: 'Тип ресурса', + playerName: 'Командир', + timeHour: 'ч', + timeMinute: 'мин', + timeSecond: 'сек', + featureLocked: 'Функция заблокирована', + unlockRequired: 'Требуется здание', + requiredBuilding: 'Необходимое здание', + currentLevel: 'Текущий уровень', + goToBuildings: 'К зданиям', + locked: 'Заблокировано', + viewRequirements: 'Просмотр требований', + requirementsNotMet: 'Требования не выполнены', + current: 'Текущий' + }, + errors: { + requirementsNotMet: 'Требования не выполнены', + insufficientResources: 'Недостаточно ресурсов', + shieldDomeLimit: 'Достигнут лимит щитовых куполов', + fleetMissionsFull: 'Слоты миссий флота заполнены', + insufficientFleet: 'Недостаточно флота', + insufficientFuel: 'Недостаточно топлива', + planetOnly: 'Это здание можно построить только на планетах', + moonOnly: 'Это здание можно построить только на лунах', + buildQueueFull: 'Очередь строительства заполнена', + insufficientSpace: 'Недостаточно места', + buildingLevelZero: 'Уровень здания 0, нельзя снести', + researchQueueFull: 'Очередь исследований заполнена', + moonExists: 'Луна уже существует', + insufficientDebris: 'Недостаточно обломков' + }, + nav: { + overview: 'Обзор', + buildings: 'Здания', + research: 'Исследования', + shipyard: 'Верфь', + defense: 'Оборона', + fleet: 'Флот', + officers: 'Офицеры', + simulator: 'Симулятор', + galaxy: 'Галактика', + messages: 'Сообщения', + settings: 'Настройки' + }, + sidebar: { + language: 'Язык', + lightMode: 'Светлая тема', + darkMode: 'Тёмная тема', + collapse: 'Свернуть', + expand: 'Развернуть' + }, + resources: { + metal: 'Металл', + crystal: 'Кристалл', + deuterium: 'Дейтерий', + darkMatter: 'Тёмная материя', + energy: 'Энергия', + production: 'Производство', + capacity: 'Вместимость', + current: 'Текущий', + max: 'Макс. вместимость', + perHour: 'час' + }, + planet: { + planet: 'Планета', + moon: 'Луна', + colony: 'Колония', + position: 'Позиция', + coordinates: 'Координаты', + switchToMoon: 'На луну', + backToPlanet: 'Вернуться на планету', + fields: 'Поля', + temperature: 'Температура', + homePlanet: 'Родная планета', + planetPrefix: 'Планета', + moonSuffix: 'я луна', + colonyPrefix: 'Колония' + }, + player: { + points: 'Всего очков' + }, + buildings: { + metalMine: 'Рудник металла', + crystalMine: 'Рудник кристалла', + deuteriumSynthesizer: 'Синтезатор дейтерия', + solarPlant: 'Солнечная электростанция', + roboticsFactory: 'Фабрика роботов', + naniteFactory: 'Нанитная фабрика', + shipyard: 'Верфь', + researchLab: 'Исследовательская лаборатория', + metalStorage: 'Хранилище металла', + crystalStorage: 'Хранилище кристалла', + deuteriumTank: 'Цистерна дейтерия', + darkMatterCollector: 'Коллектор тёмной материи', + lunarBase: 'Лунная база', + sensorPhalanx: 'Сенсорная фаланга', + jumpGate: 'Прыжковые ворота', + buildTime: 'Время строительства', + production: 'Производство', + consumption: 'Потребление', + totalCost: 'Общая стоимость', + totalPoints: 'Общие очки', + levelRange: 'Диапазон уровней' + }, + buildingDescriptions: { + metalMine: 'Добывает металлические ресурсы', + crystalMine: 'Добывает кристаллические ресурсы', + deuteriumSynthesizer: 'Синтезирует дейтериевые ресурсы', + solarPlant: 'Обеспечивает энергией', + roboticsFactory: 'Ускоряет скорость строительства', + naniteFactory: 'Увеличивает вместимость очереди строительства, +1 за уровень (макс 10)', + shipyard: 'Строит корабли', + researchLab: 'Исследует технологии', + metalStorage: 'Увеличивает ёмкость хранилища металла', + crystalStorage: 'Увеличивает ёмкость хранилища кристалла', + deuteriumTank: 'Увеличивает ёмкость хранилища дейтерия', + darkMatterCollector: 'Собирает редкие ресурсы тёмной материи', + lunarBase: 'Увеличивает доступное пространство на луне', + sensorPhalanx: 'Обнаруживает активность флота в окружающих системах', + jumpGate: 'Мгновенно переносит флоты на другие луны' + }, + ships: { + lightFighter: 'Лёгкий истребитель', + heavyFighter: 'Тяжёлый истребитель', + cruiser: 'Крейсер', + battleship: 'Линкор', + smallCargo: 'Малый транспорт', + largeCargo: 'Большой транспорт', + colonyShip: 'Колонизатор', + recycler: 'Переработчик', + espionageProbe: 'Шпионский зонд', + darkMatterHarvester: 'Сборщик тёмной материи' + }, + shipDescriptions: { + lightFighter: 'Базовая боевая единица', + heavyFighter: 'Тяжелобронированный истребитель', + cruiser: 'Средний боевой корабль, сбалансированная атака и защита', + battleship: 'Мощный боевой корабль', + smallCargo: 'Транспортирует небольшое количество ресурсов', + largeCargo: 'Транспортирует большое количество ресурсов', + colonyShip: 'Используется для колонизации новых планет', + recycler: 'Собирает ресурсы с поля обломков', + espionageProbe: 'Разведывает вражеские планеты', + darkMatterHarvester: 'Специальный корабль для сбора тёмной материи' + }, + defenses: { + rocketLauncher: 'Ракетная установка', + lightLaser: 'Лёгкий лазер', + heavyLaser: 'Тяжёлый лазер', + gaussCannon: 'Гауссова пушка', + ionCannon: 'Ионное орудие', + plasmaTurret: 'Плазменная турель', + smallShieldDome: 'Малый щитовой купол', + largeShieldDome: 'Большой щитовой купол' + }, + defenseDescriptions: { + rocketLauncher: 'Базовое оборонительное сооружение', + lightLaser: 'Лёгкое энергетическое оружие', + heavyLaser: 'Тяжёлое энергетическое оружие', + gaussCannon: 'Высокоскоростное кинетическое оружие', + ionCannon: 'Эффективно против щитов', + plasmaTurret: 'Мощное оборонительное сооружение', + smallShieldDome: 'Малый щит, защищающий всю планету', + largeShieldDome: 'Большой щит, защищающий всю планету' + }, + research: { + researchTime: 'Время исследования', + totalCost: 'Общая стоимость', + totalPoints: 'Общие очки', + levelRange: 'Диапазон уровней' + }, + technologies: { + energyTechnology: 'Энергетическая технология', + laserTechnology: 'Лазерная технология', + ionTechnology: 'Ионная технология', + hyperspaceTechnology: 'Гиперпространственная технология', + plasmaTechnology: 'Плазменная технология', + computerTechnology: 'Компьютерная технология', + combustionDrive: 'Реактивный двигатель', + impulseDrive: 'Импульсный двигатель', + hyperspaceDrive: 'Гиперпространственный двигатель', + darkMatterTechnology: 'Технология тёмной материи' + }, + technologyDescriptions: { + energyTechnology: 'Улучшает энергоэффективность', + laserTechnology: 'Основа лазерного оружия и обороны', + ionTechnology: 'Технология ионного оружия', + hyperspaceTechnology: 'Технология гиперпространственных прыжков', + plasmaTechnology: 'Технология плазменного оружия', + computerTechnology: 'Увеличивает вместимость очереди исследований, +1 за уровень (макс 10)', + combustionDrive: 'Базовая технология двигателей', + impulseDrive: 'Средняя технология двигателей', + hyperspaceDrive: 'Продвинутая технология двигателей', + darkMatterTechnology: 'Исследование свойств и применения тёмной материи' + }, + officers: { + commander: 'Командир', + admiral: 'Адмирал', + engineer: 'Инженер', + geologist: 'Геолог', + technocrat: 'Технократ', + darkMatterSpecialist: 'Специалист по тёмной материи' + }, + officerDescriptions: { + commander: 'Улучшает скорость строительства и управление', + admiral: 'Улучшает боевую мощь и скорость флота', + engineer: 'Улучшает энергию и оборону', + geologist: 'Улучшает производство ресурсов', + technocrat: 'Улучшает скорость исследований и шпионаж', + darkMatterSpecialist: 'Улучшает эффективность сбора тёмной материи' + }, + queue: { + buildQueue: 'Очередь строительства', + researchQueue: 'Очередь исследований', + building: 'Строится', + researching: 'Исследуется', + remaining: 'Осталось', + cancel: 'Отменить', + cancelBuild: 'Отменить строительство', + cancelResearch: 'Отменить исследование', + confirmCancel: 'Вы уверены, что хотите отменить? 50% ресурсов будет возвращено.', + level: 'Уровень', + upgradeToLevel: 'Улучшить до уровня' + }, + overview: { + title: 'Обзор планеты', + resourceOverview: 'Ресурсы', + fleetInfo: 'Флот', + currentShips: 'Корабли на этой планете' + }, + buildingsView: { + title: 'Здания', + usedSpace: 'Использовано полей', + spaceUsage: 'Использование полей', + level: 'Уровень', + upgradeCost: 'Стоимость улучшения', + buildTime: 'Время строительства', + upgrade: 'Улучшить', + upgradeFailed: 'Улучшение не удалось', + upgradeFailedMessage: 'Пожалуйста, проверьте, достаточно ли у вас ресурсов, места или нет других задач строительства.', + demolish: 'Снести', + demolishRefund: 'Возврат от сноса', + demolishFailed: 'Снос не удался', + demolishFailedMessage: 'Невозможно снести это здание. Проверьте, не заполнена ли очередь строительства или уровень здания не равен 0.' + }, + researchView: { + title: 'Исследования', + researchCost: 'Стоимость исследования', + research: 'Исследовать', + researchFailed: 'Исследование не удалось', + researchFailedMessage: + 'Пожалуйста, проверьте, достаточно ли у вас ресурсов, выполнены ли предварительные условия или нет других исследовательских задач.' + }, + shipyard: { + attack: 'Атака', + shield: 'Щит', + armor: 'Броня', + speed: 'Скорость', + cargoCapacity: 'Грузоподъёмность', + fuelConsumption: 'Расход топлива', + buildCost: 'Стоимость постройки', + buildTime: 'Время строительства', + perUnit: 'За единицу', + batchCalculator: 'Калькулятор партий', + quantity: 'Количество', + totalCost: 'Общая стоимость', + totalTime: 'Общее время' + }, + shipyardView: { + title: 'Верфь', + attack: 'Атака', + shield: 'Щит', + speed: 'Скорость', + cargoCapacity: 'Грузоподъёмность', + unitCost: 'Стоимость единицы', + buildQuantity: 'Количество для постройки', + totalCost: 'Общая стоимость', + build: 'Построить', + inputError: 'Ошибка ввода', + inputErrorMessage: 'Пожалуйста, введите количество для постройки!', + buildFailed: 'Постройка не удалась', + buildFailedMessage: 'Пожалуйста, проверьте, достаточно ли у вас ресурсов или выполнены ли предварительные условия.' + }, + defense: { + attack: 'Атака', + shield: 'Щит', + armor: 'Броня', + buildCost: 'Стоимость постройки', + buildTime: 'Время строительства', + perUnit: 'За единицу', + batchCalculator: 'Калькулятор партий', + quantity: 'Количество', + totalCost: 'Общая стоимость', + totalTime: 'Общее время' + }, + defenseView: { + title: 'Оборона', + attack: 'Атака', + shield: 'Щит', + armor: 'Броня', + buildTime: 'Время постройки', + seconds: 'с', + unitCost: 'Стоимость единицы', + buildQuantity: 'Количество для постройки', + totalCost: 'Общая стоимость', + build: 'Построить', + shieldDomeBuilt: 'Щитовой купол уже построен', + inputError: 'Ошибка ввода', + inputErrorMessage: 'Пожалуйста, введите количество для постройки!', + buildFailed: 'Постройка не удалась', + buildFailedMessage: + 'Пожалуйста, проверьте, достаточно ли у вас ресурсов или выполнены ли предварительные условия. Щитовые купола можно построить только один раз.' + }, + fleetView: { + title: 'Управление флотом', + fleetOverview: 'Обзор флота', + sendFleet: 'Отправить флот', + flightMissions: 'Полетные миссии', + currentPlanetFleet: 'Флот на этой планете', + attack: 'Атака', + shield: 'Щит', + armor: 'Броня', + speed: 'Скорость', + cargo: 'Груз', + selectFleet: 'Выбрать флот', + selectFleetDescription: 'Выберите количество кораблей для отправки', + available: 'Доступно', + all: 'Все', + targetCoordinates: 'Целевые координаты', + galaxy: 'Галактика', + system: 'Система', + position: 'Позиция', + missionType: 'Тип миссии', + missionInfo: 'Информация о миссии', + fuelConsumption: 'Расход топлива', + flightTime: 'Время полета', + attackMission: 'Атака', + transport: 'Транспорт', + colonize: 'Колонизация', + spy: 'Разведка', + deploy: 'Размещение', + transportResources: 'Транспортировка ресурсов', + totalCargoCapacity: 'Общая грузоподъёмность', + used: 'Использовано', + noFlightMissions: 'Нет полетных миссий', + outbound: 'Туда', + returning: 'Возвращение', + fleetComposition: 'Состав флота', + carryingResources: 'Перевозимые ресурсы', + arrivalTime: 'Время прибытия', + returnTime: 'Время возврата', + recallFleet: 'Отозвать флот', + sendFailed: 'Отправка не удалась', + sendFailedMessage: 'Пожалуйста, проверьте количество флота, наличие топлива или ограничения грузоподъёмности.', + recallFailed: 'Отзыв не удался', + recallFailedMessage: 'Эта миссия не может быть отозвана.', + unknownPlanet: 'Неизвестная планета', + fleetMissionSlots: 'Слоты миссий флота' + }, + officersView: { + title: 'Офицеры', + activated: 'Активирован', + inactive: 'Неактивен', + activeStatus: 'Статус активации', + expirationTime: 'Время истечения', + remainingTime: 'Оставшееся время', + recruitCost: 'Стоимость найма', + days: 'дн.', + benefitsBonus: 'Бонусы', + resourceProduction: 'Производство ресурсов', + darkMatterProduction: 'Производство тёмной материи', + energyProduction: 'Производство энергии', + buildingSpeed: 'Скорость строительства', + researchSpeed: 'Скорость исследований', + fleetSpeed: 'Скорость флота', + fuelConsumption: 'Расход топлива', + defense: 'Защита', + storageCapacity: 'Вместимость хранилища', + buildQueue: 'Очередь строительства', + fleetSlots: 'Слоты флота', + hire: 'Нанять', + renew: 'Продлить', + dismiss: 'Уволить', + hireTitle: 'Нанять офицера', + hireMessage: 'Вы уверены, что хотите нанять {name}? Действует 7 дней.', + renewTitle: 'Продлить офицера', + renewMessage: 'Вы уверены, что хотите продлить {name} на 7 дней?', + dismissTitle: 'Уволить офицера', + dismissMessage: 'Вы уверены, что хотите уволить {name}? Средства не возвращаются.', + hireFailed: 'Найм не удался', + renewFailed: 'Продление не удалось', + insufficientResources: 'Недостаточно ресурсов!' + }, + galaxyView: { + title: 'Галактика', + selectCoordinates: 'Выбрать координаты', + galaxy: 'Галактика', + selectGalaxy: 'Выбрать галактику', + system: 'Система', + selectSystem: 'Выбрать систему', + view: 'Показать', + myPlanet: 'Моя планета', + totalPositions: 'Всего 10 позиций планет', + mine: 'Моя', + hostile: 'Враждебная', + emptySlot: 'Пусто - можно колонизировать', + scout: 'Разведка', + attack: 'Атака', + colonize: 'Колонизация', + switch: 'Переключить', + scoutPlanetTitle: 'Разведать планету', + attackPlanetTitle: 'Атаковать планету', + colonizePlanetTitle: 'Колонизировать планету', + scoutPlanetMessage: + 'Вы уверены, что хотите отправить шпионские зонды для разведки планеты [{coordinates}]?\n\nПерейдите на страницу флота, чтобы выбрать корабли и отправить.', + attackPlanetMessage: + 'Вы уверены, что хотите атаковать планету [{coordinates}]?\n\nПерейдите на страницу флота, чтобы выбрать корабли и отправить.', + colonizePlanetMessage: + 'Вы уверены, что хотите колонизировать позицию [{coordinates}]?\n\nПерейдите на страницу флота, чтобы отправить колонизационный корабль.' + }, + messagesView: { + title: 'Сообщения', + battleReports: 'Отчёты о боях', + spyReports: 'Отчёты разведки', + noBattleReports: 'Нет отчётов о боях', + noSpyReports: 'Нет отчётов разведки', + battleReport: 'Отчёт о бое', + spyReport: 'Отчёт разведки', + victory: 'Победа', + defeat: 'Поражение', + draw: 'Ничья', + attackerFleet: 'Флот нападающего', + defenderFleet: 'Флот защитника', + defenderDefense: 'Оборона защитника', + attackerLosses: 'Потери нападающего', + defenderLosses: 'Потери защитника', + noLosses: 'Без потерь', + plunder: 'Добыча', + debrisField: 'Поле обломков', + resources: 'Ресурсы', + fleet: 'Флот', + defense: 'Оборона', + buildings: 'Здания' + }, + simulatorView: { + title: 'Симулятор боя', + attacker: 'Нападающий', + defender: 'Защитник', + attackerConfig: 'Настройки нападающего', + attackerConfigDesc: 'Настроить флот и уровни технологий нападающего', + defenderConfig: 'Настройки защитника', + defenderConfigDesc: 'Настроить флот, оборону и уровни технологий защитника', + fleet: 'Флот', + defenseStructures: 'Оборонительные сооружения', + techLevels: 'Уровни технологий', + weapon: 'Оружие', + shield: 'Щит', + armor: 'Броня', + defenderResources: 'Ресурсы защитника (для расчёта добычи)', + startSimulation: 'Начать симуляцию', + reset: 'Сбросить', + battleResult: 'Результат боя', + attackerVictory: 'Победа нападающего', + defenderVictory: 'Победа защитника', + draw: 'Ничья', + afterRounds: 'После {rounds} раундов', + attackerLosses: 'Потери нападающего', + defenderLosses: 'Потери защитника', + noLosses: 'Без потерь', + attackerRemaining: 'Осталось у нападающего', + defenderRemaining: 'Осталось у защитника', + allDestroyed: 'Всё уничтожено', + plunderableResources: 'Доступная добыча', + debrisField: 'Поле обломков', + moonChance: 'Шанс появления луны', + showRoundDetails: 'Показать детали раундов', + hideRoundDetails: 'Скрыть детали раундов', + round: 'Раунд {round}', + attackerRemainingPower: 'Оставшаяся мощь нападающего', + defenderRemainingPower: 'Оставшаяся мощь защитника' + }, + settings: { + dataManagement: 'Управление данными', + dataManagementDesc: 'Экспорт, импорт или очистка игровых данных', + exportData: 'Экспорт данных', + exportDataDesc: 'Экспортировать прогресс игры в JSON файл', + export: 'Экспорт', + exporting: 'Экспорт...', + exportSuccess: 'Экспорт успешен', + exportFailed: 'Экспорт не удался, попробуйте еще раз', + importData: 'Импорт данных', + importDataDesc: 'Восстановить прогресс игры из JSON файла', + selectFile: 'Выбрать файл', + importSuccess: 'Импорт успешен', + importConfirmTitle: 'Подтвердить импорт', + importConfirmMessage: 'Импорт перезапишет текущий прогресс игры. Это действие невозможно отменить. Продолжить?', + importFailed: 'Импорт не удался, проверьте формат файла', + clearData: 'Очистить данные', + clearDataDesc: 'Удалить все игровые данные и сбросить', + clear: 'Очистить', + clearConfirmTitle: 'Подтвердить очистку данных', + clearConfirmMessage: 'Все игровые данные будут удалены и игра начнется заново. Это действие невозможно отменить. Продолжить?', + gameSettings: 'Настройки игры', + gameSettingsDesc: 'Настроить параметры и предпочтения игры', + playerName: 'Имя игрока', + gameSpeed: 'Скорость игры', + gameSpeedDesc: 'Текущий множитель скорости игры', + about: 'О программе', + version: 'Версия', + buildDate: 'Дата сборки', + community: 'Сообщество', + github: 'Репозиторий GitHub', + qqGroup: 'Группа QQ' + } +} diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts new file mode 100644 index 0000000..ab82635 --- /dev/null +++ b/src/locales/zh-CN.ts @@ -0,0 +1,531 @@ +export default { + common: { + confirm: '确认', + cancel: '取消', + delete: '删除', + edit: '编辑', + save: '保存', + close: '关闭', + back: '返回', + next: '下一步', + previous: '上一步', + submit: '提交', + reset: '重置', + search: '搜索', + filter: '筛选', + loading: '加载中...', + noData: '暂无数据', + error: '错误', + success: '成功', + warning: '警告', + info: '信息', + resourceType: '资源类型', + playerName: '指挥官', + timeHour: '时', + timeMinute: '分', + timeSecond: '秒', + featureLocked: '功能已锁定', + unlockRequired: '需要解锁前置建筑', + requiredBuilding: '所需建筑', + currentLevel: '当前等级', + goToBuildings: '前往建筑页面', + locked: '已锁定', + viewRequirements: '查看前置条件', + requirementsNotMet: '前置条件未满足', + current: '当前' + }, + errors: { + requirementsNotMet: '不满足前置条件', + insufficientResources: '资源不足', + shieldDomeLimit: '护盾罩数量限制', + fleetMissionsFull: '舰队任务槽位已满', + insufficientFleet: '舰队数量不足', + insufficientFuel: '燃料不足', + planetOnly: '该建筑只能在行星上建造', + moonOnly: '该建筑只能在月球上建造', + buildQueueFull: '建造队列已满', + insufficientSpace: '空间不足', + buildingLevelZero: '建筑等级为0,无法拆除', + researchQueueFull: '研究队列已满', + moonExists: '已存在月球', + insufficientDebris: '残骸场不足' + }, + nav: { + overview: '总览', + buildings: '建筑', + research: '研究', + shipyard: '船坞', + defense: '防御', + fleet: '舰队', + officers: '军官', + simulator: '模拟', + galaxy: '星系', + messages: '消息', + settings: '设置' + }, + sidebar: { + language: '语言', + lightMode: '日间模式', + darkMode: '夜间模式', + collapse: '收起菜单', + expand: '展开菜单' + }, + resources: { + metal: '金属', + crystal: '晶体', + deuterium: '重氢', + darkMatter: '暗物质', + energy: '能量', + production: '产量', + capacity: '容量', + current: '当前储量', + max: '最大容量', + perHour: '小时' + }, + planet: { + planet: '星球', + moon: '月球', + colony: '殖民地', + position: '位置', + coordinates: '坐标', + switchToMoon: '查看月球', + backToPlanet: '返回母星', + fields: '场地', + temperature: '温度', + homePlanet: '母星', + planetPrefix: '星球', + moonSuffix: '的月球', + colonyPrefix: '殖民地' + }, + player: { + points: '总积分' + }, + buildings: { + metalMine: '金属矿', + crystalMine: '晶体矿', + deuteriumSynthesizer: '重氢合成器', + solarPlant: '太阳能电站', + roboticsFactory: '机器人工厂', + naniteFactory: '纳米工厂', + shipyard: '船坞', + researchLab: '研究实验室', + metalStorage: '金属仓库', + crystalStorage: '晶体仓库', + deuteriumTank: '重氢罐', + darkMatterCollector: '暗物质收集器', + lunarBase: '月球基地', + sensorPhalanx: '传感器阵列', + jumpGate: '跳跃门', + buildTime: '建造时间', + production: '产量', + consumption: '消耗', + totalCost: '累积成本', + totalPoints: '累积积分', + levelRange: '等级范围' + }, + buildingDescriptions: { + metalMine: '开采金属资源', + crystalMine: '开采晶体资源', + deuteriumSynthesizer: '合成重氢资源', + solarPlant: '提供能源', + roboticsFactory: '加快建造速度', + naniteFactory: '增加建造队列数量,每级+1队列(最多10个)', + shipyard: '建造舰船', + researchLab: '研究科技', + metalStorage: '增加金属存储上限', + crystalStorage: '增加晶体存储上限', + deuteriumTank: '增加重氢存储上限', + darkMatterCollector: '收集稀有的暗物质资源', + lunarBase: '增加月球可用空间', + sensorPhalanx: '侦测周围星系的舰队活动', + jumpGate: '瞬间传送舰队到其他月球' + }, + ships: { + lightFighter: '轻型战斗机', + heavyFighter: '重型战斗机', + cruiser: '巡洋舰', + battleship: '战列舰', + smallCargo: '小型运输船', + largeCargo: '大型运输船', + colonyShip: '殖民船', + recycler: '回收船', + espionageProbe: '间谍探测器', + darkMatterHarvester: '暗物质采集船' + }, + shipDescriptions: { + lightFighter: '基础战斗单位', + heavyFighter: '重装战斗机', + cruiser: '中型战舰,攻守平衡', + battleship: '强力战舰', + smallCargo: '运输少量资源', + largeCargo: '运输大量资源', + colonyShip: '用于殖民新星球', + recycler: '收集残骸场资源', + espionageProbe: '侦察敌方星球', + darkMatterHarvester: '专门用于采集暗物质的特殊飞船' + }, + defenses: { + rocketLauncher: '火箭发射器', + lightLaser: '轻型激光炮', + heavyLaser: '重型激光炮', + gaussCannon: '高斯炮', + ionCannon: '离子炮', + plasmaTurret: '等离子炮塔', + smallShieldDome: '小型护盾罩', + largeShieldDome: '大型护盾罩' + }, + defenseDescriptions: { + rocketLauncher: '基础防御设施', + lightLaser: '轻型能量武器', + heavyLaser: '重型能量武器', + gaussCannon: '高速动能武器', + ionCannon: '破坏护盾的利器', + plasmaTurret: '强力防御设施', + smallShieldDome: '保护整个星球的小型护盾', + largeShieldDome: '保护整个星球的大型护盾' + }, + research: { + researchTime: '研究时间', + totalCost: '累积成本', + totalPoints: '累积积分', + levelRange: '等级范围' + }, + technologies: { + energyTechnology: '能源技术', + laserTechnology: '激光技术', + ionTechnology: '离子技术', + hyperspaceTechnology: '超空间技术', + plasmaTechnology: '等离子技术', + computerTechnology: '计算机技术', + combustionDrive: '燃烧引擎', + impulseDrive: '脉冲引擎', + hyperspaceDrive: '超空间引擎', + darkMatterTechnology: '暗物质技术' + }, + technologyDescriptions: { + energyTechnology: '提高能源利用效率', + laserTechnology: '激光武器和防御的基础', + ionTechnology: '离子武器技术', + hyperspaceTechnology: '超空间跳跃技术', + plasmaTechnology: '等离子武器技术', + computerTechnology: '增加研究队列数量,每级+1队列(最多10个)', + combustionDrive: '基础推进技术', + impulseDrive: '中级推进技术', + hyperspaceDrive: '高级推进技术', + darkMatterTechnology: '研究暗物质的性质和应用' + }, + officers: { + commander: '指挥官', + admiral: '上将', + engineer: '工程师', + geologist: '地质学家', + technocrat: '技术专家', + darkMatterSpecialist: '暗物质专家' + }, + officerDescriptions: { + commander: '提升建筑速度和管理能力', + admiral: '提升舰队战斗力和速度', + engineer: '提升能源和防御能力', + geologist: '提升资源产量', + technocrat: '提升研究速度和间谍能力', + darkMatterSpecialist: '提升暗物质采集效率' + }, + queue: { + buildQueue: '建造队列', + researchQueue: '研究队列', + building: '建造中', + researching: '研究中', + remaining: '剩余时间', + cancel: '取消', + cancelBuild: '取消建造', + cancelResearch: '取消研究', + confirmCancel: '确定要取消吗?将返还50%的资源。', + level: '等级', + upgradeToLevel: '升级到等级' + }, + overview: { + title: '星球总览', + resourceOverview: '资源概览', + fleetInfo: '舰队', + currentShips: '当前星球的舰船数量' + }, + buildingsView: { + title: '建筑', + usedSpace: '已用空间', + spaceUsage: '占用空间', + level: '等级', + upgradeCost: '升级消耗', + buildTime: '建造时间', + upgrade: '升级', + upgradeFailed: '升级失败', + upgradeFailedMessage: '请检查资源是否足够、空间是否充足或是否有其他建造任务。', + demolish: '拆除', + demolishRefund: '拆除返还', + demolishFailed: '拆除失败', + demolishFailedMessage: '无法拆除该建筑,请检查建造队列是否已满或建筑等级是否为0。' + }, + researchView: { + title: '研究', + researchCost: '研究消耗', + research: '研究', + researchFailed: '研究失败', + researchFailedMessage: '请检查资源是否足够、前置条件是否满足,或是否有其他研究任务。' + }, + shipyard: { + attack: '攻击力', + shield: '护盾', + armor: '装甲', + speed: '速度', + cargoCapacity: '载货量', + fuelConsumption: '燃料消耗', + buildCost: '建造成本', + buildTime: '建造时间', + perUnit: '每个单位', + batchCalculator: '批量建造计算器', + quantity: '数量', + totalCost: '总成本', + totalTime: '总时间' + }, + shipyardView: { + title: '船坞', + attack: '攻击力', + shield: '护盾', + speed: '速度', + cargoCapacity: '载货量', + unitCost: '单位成本', + buildQuantity: '建造数量', + totalCost: '总成本', + build: '建造', + inputError: '输入错误', + inputErrorMessage: '请输入建造数量!', + buildFailed: '建造失败', + buildFailedMessage: '请检查资源是否足够或前置条件是否满足。' + }, + defense: { + attack: '攻击力', + shield: '护盾', + armor: '装甲', + buildCost: '建造成本', + buildTime: '建造时间', + perUnit: '每个单位', + batchCalculator: '批量建造计算器', + quantity: '数量', + totalCost: '总成本', + totalTime: '总时间' + }, + defenseView: { + title: '防御设施', + attack: '攻击力', + shield: '护盾', + armor: '装甲', + buildTime: '建造时间', + seconds: '秒', + unitCost: '单位成本', + buildQuantity: '建造数量', + totalCost: '总成本', + build: '建造', + shieldDomeBuilt: '护盾罩已建造', + inputError: '输入错误', + inputErrorMessage: '请输入建造数量!', + buildFailed: '建造失败', + buildFailedMessage: '请检查资源是否足够或前置条件是否满足。护盾罩只能建造一个。' + }, + fleetView: { + title: '舰队管理', + fleetOverview: '舰队总览', + sendFleet: '派遣舰队', + flightMissions: '飞行任务', + currentPlanetFleet: '当前星球舰队', + attack: '攻击', + shield: '护盾', + armor: '装甲', + speed: '速度', + cargo: '载货', + selectFleet: '选择舰队', + selectFleetDescription: '选择要派遣的舰船数量', + available: '可用', + all: '全部', + targetCoordinates: '目标坐标', + galaxy: '银河系', + system: '星系', + position: '位置', + missionType: '任务类型', + missionInfo: '任务信息', + fuelConsumption: '燃料消耗', + flightTime: '飞行时间', + attackMission: '攻击', + transport: '运输', + colonize: '殖民', + spy: '侦察', + deploy: '部署', + transportResources: '运输资源', + totalCargoCapacity: '总载货量', + used: '已用', + noFlightMissions: '暂无飞行任务', + outbound: '前往', + returning: '返回', + fleetComposition: '舰队组成', + carryingResources: '携带资源', + arrivalTime: '到达时间', + returnTime: '返回时间', + recallFleet: '召回舰队', + sendFailed: '派遣失败', + sendFailedMessage: '请检查舰队数量、燃料是否充足,或载货量是否超出限制。', + recallFailed: '召回失败', + recallFailedMessage: '该任务无法召回。', + unknownPlanet: '未知星球', + fleetMissionSlots: '舰队任务槽位' + }, + officersView: { + title: '军官', + activated: '已激活', + inactive: '未激活', + activeStatus: '激活状态', + expirationTime: '到期时间', + remainingTime: '剩余时间', + recruitCost: '招募成本', + days: '天', + benefitsBonus: '效果加成', + resourceProduction: '资源产量', + darkMatterProduction: '暗物质产量', + energyProduction: '电量产出', + buildingSpeed: '建筑速度', + researchSpeed: '研究速度', + fleetSpeed: '舰队速度', + fuelConsumption: '燃料消耗', + defense: '防御力', + storageCapacity: '仓储容量', + buildQueue: '建筑队列', + fleetSlots: '舰队槽位', + hire: '招募', + renew: '续约', + dismiss: '解雇', + hireTitle: '招募军官', + hireMessage: '确定要招募 {name} 吗?有效期为7天。', + renewTitle: '续约军官', + renewMessage: '确定要为 {name} 续约7天吗?', + dismissTitle: '解雇军官', + dismissMessage: '确定要解雇 {name} 吗?不会返还任何费用。', + hireFailed: '招募失败', + renewFailed: '续约失败', + insufficientResources: '资源不足!' + }, + galaxyView: { + title: '星系', + selectCoordinates: '选择坐标', + galaxy: '银河系', + selectGalaxy: '选择银河系', + system: '星系', + selectSystem: '选择星系', + view: '查看', + myPlanet: '我的星球', + totalPositions: '共10个星球位置', + mine: '我的', + hostile: '敌对', + emptySlot: '空位 - 可殖民', + scout: '侦察', + attack: '攻击', + colonize: '殖民', + switch: '切换', + scoutPlanetTitle: '侦察星球', + attackPlanetTitle: '攻击星球', + colonizePlanetTitle: '殖民星球', + scoutPlanetMessage: '确定要派遣间谍探测器侦察星球 [{coordinates}] 吗?\n\n请前往舰队页面选择舰船并派遣。', + attackPlanetMessage: '确定要攻击星球 [{coordinates}] 吗?\n\n请前往舰队页面选择舰船并派遣。', + colonizePlanetMessage: '确定要殖民位置 [{coordinates}] 吗?\n\n请前往舰队页面派遣殖民船。' + }, + messagesView: { + title: '消息中心', + battleReports: '战斗报告', + spyReports: '间谍报告', + noBattleReports: '暂无战斗报告', + noSpyReports: '暂无间谍报告', + battleReport: '战斗报告', + spyReport: '间谍报告', + victory: '胜利', + defeat: '失败', + draw: '平局', + attackerFleet: '攻击方舰队', + defenderFleet: '防守方舰队', + defenderDefense: '防守方防御', + attackerLosses: '攻击方损失', + defenderLosses: '防守方损失', + noLosses: '无损失', + plunder: '掠夺资源', + debrisField: '残骸场', + resources: '资源', + fleet: '舰队', + defense: '防御', + buildings: '建筑' + }, + simulatorView: { + title: '战斗模拟器', + attacker: '攻击方', + defender: '防守方', + attackerConfig: '攻击方配置', + attackerConfigDesc: '设置攻击方的舰队和科技等级', + defenderConfig: '防守方配置', + defenderConfigDesc: '设置防守方的舰队、防御和科技等级', + fleet: '舰队', + defenseStructures: '防御设施', + techLevels: '科技等级', + weapon: '武器', + shield: '护盾', + armor: '装甲', + defenderResources: '防守方资源(用于掠夺计算)', + startSimulation: '开始模拟', + reset: '重置', + battleResult: '战斗结果', + attackerVictory: '攻击方胜利', + defenderVictory: '防守方胜利', + draw: '平局', + afterRounds: '经过 {rounds} 回合战斗', + attackerLosses: '攻击方损失', + defenderLosses: '防守方损失', + noLosses: '无损失', + attackerRemaining: '攻击方剩余', + defenderRemaining: '防守方剩余', + allDestroyed: '全部摧毁', + plunderableResources: '可掠夺资源', + debrisField: '残骸场', + moonChance: '月球生成概率', + showRoundDetails: '显示回合详情', + hideRoundDetails: '隐藏回合详情', + round: '第 {round} 回合', + attackerRemainingPower: '攻击方剩余火力', + defenderRemainingPower: '防守方剩余火力' + }, + settings: { + dataManagement: '数据管理', + dataManagementDesc: '导出、导入或清除游戏数据', + exportData: '导出数据', + exportDataDesc: '将游戏进度导出为JSON文件', + export: '导出', + exporting: '导出中...', + exportSuccess: '导出成功', + exportFailed: '导出失败,请重试', + importData: '导入数据', + importDataDesc: '从JSON文件恢复游戏进度', + selectFile: '导入', + importSuccess: '导入成功', + importConfirmTitle: '确认导入数据', + importConfirmMessage: '导入数据将覆盖当前游戏进度,此操作不可撤销。确定要继续吗?', + importFailed: '导入失败,请检查文件格式', + clearData: '清除数据', + clearDataDesc: '删除所有游戏数据并重置游戏', + clear: '清除', + clearConfirmTitle: '确认清除数据', + clearConfirmMessage: '这将删除所有游戏数据并重新开始,此操作不可撤销。确定要继续吗?', + gameSettings: '游戏设置', + gameSettingsDesc: '调整游戏参数和偏好设置', + playerName: '玩家名称', + gameSpeed: '游戏速度', + gameSpeedDesc: '当前游戏速度倍率', + about: '关于', + version: '版本', + buildDate: '构建日期', + community: '社区', + github: 'GitHub 仓库', + qqGroup: 'QQ 交流群' + } +} diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts new file mode 100644 index 0000000..f699ca2 --- /dev/null +++ b/src/locales/zh-TW.ts @@ -0,0 +1,531 @@ +export default { + common: { + confirm: '確認', + cancel: '取消', + delete: '刪除', + edit: '編輯', + save: '儲存', + close: '關閉', + back: '返回', + next: '下一步', + previous: '上一步', + submit: '提交', + reset: '重置', + search: '搜尋', + filter: '篩選', + loading: '載入中...', + noData: '暫無資料', + error: '錯誤', + success: '成功', + warning: '警告', + info: '資訊', + resourceType: '資源類型', + playerName: '指揮官', + timeHour: '時', + timeMinute: '分', + timeSecond: '秒', + featureLocked: '功能已鎖定', + unlockRequired: '需要解鎖前置建築', + requiredBuilding: '所需建築', + currentLevel: '當前等級', + goToBuildings: '前往建築頁面', + locked: '已鎖定', + viewRequirements: '查看前置條件', + requirementsNotMet: '前置條件未滿足', + current: '當前' + }, + errors: { + requirementsNotMet: '不滿足前置條件', + insufficientResources: '資源不足', + shieldDomeLimit: '護盾罩數量限制', + fleetMissionsFull: '艦隊任務槽位已滿', + insufficientFleet: '艦隊數量不足', + insufficientFuel: '燃料不足', + planetOnly: '該建築只能在行星上建造', + moonOnly: '該建築只能在月球上建造', + buildQueueFull: '建造隊列已滿', + insufficientSpace: '空間不足', + buildingLevelZero: '建築等級為0,無法拆除', + researchQueueFull: '研究隊列已滿', + moonExists: '已存在月球', + insufficientDebris: '殘骸場不足' + }, + nav: { + overview: '總覽', + buildings: '建築', + research: '研究', + shipyard: '船塢', + defense: '防禦', + fleet: '艦隊', + officers: '軍官', + simulator: '模擬', + galaxy: '星系', + messages: '訊息', + settings: '設定' + }, + sidebar: { + language: '語言', + lightMode: '日間模式', + darkMode: '夜間模式', + collapse: '收起選單', + expand: '展開選單' + }, + resources: { + metal: '金屬', + crystal: '晶體', + deuterium: '重氫', + darkMatter: '暗物質', + energy: '能量', + production: '產量', + capacity: '容量', + current: '當前儲量', + max: '最大容量', + perHour: '小時' + }, + planet: { + planet: '星球', + moon: '月球', + colony: '殖民地', + position: '位置', + coordinates: '座標', + switchToMoon: '查看月球', + backToPlanet: '返回母星', + fields: '場地', + temperature: '溫度', + homePlanet: '母星', + planetPrefix: '星球', + moonSuffix: '的月球', + colonyPrefix: '殖民地' + }, + player: { + points: '總積分' + }, + buildings: { + metalMine: '金屬礦', + crystalMine: '晶體礦', + deuteriumSynthesizer: '重氫合成器', + solarPlant: '太陽能電站', + roboticsFactory: '機器人工廠', + naniteFactory: '納米工廠', + shipyard: '船塢', + researchLab: '研究實驗室', + metalStorage: '金屬倉庫', + crystalStorage: '晶體倉庫', + deuteriumTank: '重氫罐', + darkMatterCollector: '暗物質收集器', + lunarBase: '月球基地', + sensorPhalanx: '傳感器陣列', + jumpGate: '跳躍門', + buildTime: '建造時間', + production: '產量', + consumption: '消耗', + totalCost: '累積成本', + totalPoints: '累積積分', + levelRange: '等級範圍' + }, + buildingDescriptions: { + metalMine: '開採金屬資源', + crystalMine: '開採晶體資源', + deuteriumSynthesizer: '合成重氫資源', + solarPlant: '提供能源', + roboticsFactory: '加快建造速度', + naniteFactory: '增加建造佇列數量,每級+1佇列(最多10個)', + shipyard: '建造艦船', + researchLab: '研究科技', + metalStorage: '增加金屬儲存上限', + crystalStorage: '增加晶體儲存上限', + deuteriumTank: '增加重氫儲存上限', + darkMatterCollector: '收集稀有的暗物質資源', + lunarBase: '增加月球可用空間', + sensorPhalanx: '偵測周圍星系的艦隊活動', + jumpGate: '瞬間傳送艦隊到其他月球' + }, + ships: { + lightFighter: '輕型戰鬥機', + heavyFighter: '重型戰鬥機', + cruiser: '巡洋艦', + battleship: '戰列艦', + smallCargo: '小型運輸船', + largeCargo: '大型運輸船', + colonyShip: '殖民船', + recycler: '回收船', + espionageProbe: '間諜探測器', + darkMatterHarvester: '暗物質採集船' + }, + shipDescriptions: { + lightFighter: '基礎戰鬥單位', + heavyFighter: '重裝戰鬥機', + cruiser: '中型戰艦,攻守平衡', + battleship: '強力戰艦', + smallCargo: '運輸少量資源', + largeCargo: '運輸大量資源', + colonyShip: '用於殖民新星球', + recycler: '收集殘骸場資源', + espionageProbe: '偵察敵方星球', + darkMatterHarvester: '專門用於採集暗物質的特殊飛船' + }, + defenses: { + rocketLauncher: '火箭發射器', + lightLaser: '輕型激光炮', + heavyLaser: '重型激光炮', + gaussCannon: '高斯炮', + ionCannon: '離子炮', + plasmaTurret: '等離子炮塔', + smallShieldDome: '小型護盾罩', + largeShieldDome: '大型護盾罩' + }, + defenseDescriptions: { + rocketLauncher: '基礎防禦設施', + lightLaser: '輕型能量武器', + heavyLaser: '重型能量武器', + gaussCannon: '高速動能武器', + ionCannon: '破壞護盾的利器', + plasmaTurret: '強力防禦設施', + smallShieldDome: '保護整個星球的小型護盾', + largeShieldDome: '保護整個星球的大型護盾' + }, + research: { + researchTime: '研究時間', + totalCost: '累積成本', + totalPoints: '累積積分', + levelRange: '等級範圍' + }, + technologies: { + energyTechnology: '能源技術', + laserTechnology: '激光技術', + ionTechnology: '離子技術', + hyperspaceTechnology: '超空間技術', + plasmaTechnology: '等離子技術', + computerTechnology: '計算機技術', + combustionDrive: '燃燒引擎', + impulseDrive: '脈衝引擎', + hyperspaceDrive: '超空間引擎', + darkMatterTechnology: '暗物質技術' + }, + technologyDescriptions: { + energyTechnology: '提高能源利用效率', + laserTechnology: '激光武器和防禦的基礎', + ionTechnology: '離子武器技術', + hyperspaceTechnology: '超空間跳躍技術', + plasmaTechnology: '等離子武器技術', + computerTechnology: '增加研究佇列數量,每級+1佇列(最多10個)', + combustionDrive: '基礎推進技術', + impulseDrive: '中級推進技術', + hyperspaceDrive: '高級推進技術', + darkMatterTechnology: '研究暗物質的性質和應用' + }, + officers: { + commander: '指揮官', + admiral: '上將', + engineer: '工程師', + geologist: '地質學家', + technocrat: '技術專家', + darkMatterSpecialist: '暗物質專家' + }, + officerDescriptions: { + commander: '提升建築速度和管理能力', + admiral: '提升艦隊戰鬥力和速度', + engineer: '提升能源和防禦能力', + geologist: '提升資源產量', + technocrat: '提升研究速度和間諜能力', + darkMatterSpecialist: '提升暗物質採集效率' + }, + queue: { + buildQueue: '建造佇列', + researchQueue: '研究佇列', + building: '建造中', + researching: '研究中', + remaining: '剩餘時間', + cancel: '取消', + cancelBuild: '取消建造', + cancelResearch: '取消研究', + confirmCancel: '確定要取消嗎?將返還50%的資源。', + level: '等級', + upgradeToLevel: '升級到等級' + }, + overview: { + title: '星球總覽', + resourceOverview: '資源概覽', + fleetInfo: '艦隊資訊', + currentShips: '當前星球的艦船數量' + }, + buildingsView: { + title: '建築', + usedSpace: '已用空間', + spaceUsage: '佔用空間', + level: '等級', + upgradeCost: '升級消耗', + buildTime: '建造時間', + upgrade: '升級', + upgradeFailed: '升級失敗', + upgradeFailedMessage: '請檢查資源是否足夠、空間是否充足或是否有其他建造任務。', + demolish: '拆除', + demolishRefund: '拆除返還', + demolishFailed: '拆除失敗', + demolishFailedMessage: '無法拆除該建築,請檢查建造隊列是否已滿或建築等級是否為0。' + }, + researchView: { + title: '研究', + researchCost: '研究消耗', + research: '研究', + researchFailed: '研究失敗', + researchFailedMessage: '請檢查資源是否足夠、前置條件是否滿足,或是否有其他研究任務。' + }, + shipyard: { + attack: '攻擊力', + shield: '護盾', + armor: '裝甲', + speed: '速度', + cargoCapacity: '載貨量', + fuelConsumption: '燃料消耗', + buildCost: '建造成本', + buildTime: '建造時間', + perUnit: '每個單位', + batchCalculator: '批量建造計算器', + quantity: '數量', + totalCost: '總成本', + totalTime: '總時間' + }, + shipyardView: { + title: '船塢', + attack: '攻擊力', + shield: '護盾', + speed: '速度', + cargoCapacity: '載貨量', + unitCost: '單位成本', + buildQuantity: '建造數量', + totalCost: '總成本', + build: '建造', + inputError: '輸入錯誤', + inputErrorMessage: '請輸入建造數量!', + buildFailed: '建造失敗', + buildFailedMessage: '請檢查資源是否足夠或前置條件是否滿足。' + }, + defense: { + attack: '攻擊力', + shield: '護盾', + armor: '裝甲', + buildCost: '建造成本', + buildTime: '建造時間', + perUnit: '每個單位', + batchCalculator: '批量建造計算器', + quantity: '數量', + totalCost: '總成本', + totalTime: '總時間' + }, + defenseView: { + title: '防禦設施', + attack: '攻擊力', + shield: '護盾', + armor: '裝甲', + buildTime: '建造時間', + seconds: '秒', + unitCost: '單位成本', + buildQuantity: '建造數量', + totalCost: '總成本', + build: '建造', + shieldDomeBuilt: '護盾罩已建造', + inputError: '輸入錯誤', + inputErrorMessage: '請輸入建造數量!', + buildFailed: '建造失敗', + buildFailedMessage: '請檢查資源是否足夠或前置條件是否滿足。護盾罩只能建造一個。' + }, + fleetView: { + title: '艦隊管理', + fleetOverview: '艦隊總覽', + sendFleet: '派遣艦隊', + flightMissions: '飛行任務', + currentPlanetFleet: '當前星球艦隊', + attack: '攻擊', + shield: '護盾', + armor: '裝甲', + speed: '速度', + cargo: '載貨', + selectFleet: '選擇艦隊', + selectFleetDescription: '選擇要派遣的艦船數量', + available: '可用', + all: '全部', + targetCoordinates: '目標座標', + galaxy: '銀河系', + system: '星系', + position: '位置', + missionType: '任務類型', + missionInfo: '任務資訊', + fuelConsumption: '燃料消耗', + flightTime: '飛行時間', + attackMission: '攻擊', + transport: '運輸', + colonize: '殖民', + spy: '偵察', + deploy: '部署', + transportResources: '運輸資源', + totalCargoCapacity: '總載貨量', + used: '已用', + noFlightMissions: '暫無飛行任務', + outbound: '前往', + returning: '返回', + fleetComposition: '艦隊組成', + carryingResources: '攜帶資源', + arrivalTime: '到達時間', + returnTime: '返回時間', + recallFleet: '召回艦隊', + sendFailed: '派遣失敗', + sendFailedMessage: '請檢查艦隊數量、燃料是否充足,或載貨量是否超出限制。', + recallFailed: '召回失敗', + recallFailedMessage: '該任務無法召回。', + unknownPlanet: '未知星球', + fleetMissionSlots: '艦隊任務槽位' + }, + officersView: { + title: '軍官', + activated: '已啟用', + inactive: '未啟用', + activeStatus: '啟用狀態', + expirationTime: '到期時間', + remainingTime: '剩餘時間', + recruitCost: '招募成本', + days: '天', + benefitsBonus: '效果加成', + resourceProduction: '資源產量', + darkMatterProduction: '暗物質產量', + energyProduction: '電量產出', + buildingSpeed: '建築速度', + researchSpeed: '研究速度', + fleetSpeed: '艦隊速度', + fuelConsumption: '燃料消耗', + defense: '防禦力', + storageCapacity: '倉儲容量', + buildQueue: '建築佇列', + fleetSlots: '艦隊槽位', + hire: '招募', + renew: '續約', + dismiss: '解雇', + hireTitle: '招募軍官', + hireMessage: '確定要招募 {name} 嗎?有效期為7天。', + renewTitle: '續約軍官', + renewMessage: '確定要為 {name} 續約7天嗎?', + dismissTitle: '解雇軍官', + dismissMessage: '確定要解雇 {name} 嗎?不會返還任何費用。', + hireFailed: '招募失敗', + renewFailed: '續約失敗', + insufficientResources: '資源不足!' + }, + galaxyView: { + title: '星系', + selectCoordinates: '選擇座標', + galaxy: '銀河系', + selectGalaxy: '選擇銀河系', + system: '星系', + selectSystem: '選擇星系', + view: '查看', + myPlanet: '我的星球', + totalPositions: '共10個星球位置', + mine: '我的', + hostile: '敵對', + emptySlot: '空位 - 可殖民', + scout: '偵察', + attack: '攻擊', + colonize: '殖民', + switch: '切換', + scoutPlanetTitle: '偵察星球', + attackPlanetTitle: '攻擊星球', + colonizePlanetTitle: '殖民星球', + scoutPlanetMessage: '確定要派遣間諜探測器偵察星球 [{coordinates}] 嗎?\n\n請前往艦隊頁面選擇艦船並派遣。', + attackPlanetMessage: '確定要攻擊星球 [{coordinates}] 嗎?\n\n請前往艦隊頁面選擇艦船並派遣。', + colonizePlanetMessage: '確定要殖民位置 [{coordinates}] 嗎?\n\n請前往艦隊頁面派遣殖民船。' + }, + messagesView: { + title: '訊息中心', + battleReports: '戰鬥報告', + spyReports: '間諜報告', + noBattleReports: '暫無戰鬥報告', + noSpyReports: '暫無間諜報告', + battleReport: '戰鬥報告', + spyReport: '間諜報告', + victory: '勝利', + defeat: '失敗', + draw: '平局', + attackerFleet: '攻擊方艦隊', + defenderFleet: '防守方艦隊', + defenderDefense: '防守方防禦', + attackerLosses: '攻擊方損失', + defenderLosses: '防守方損失', + noLosses: '無損失', + plunder: '掠奪資源', + debrisField: '殘骸場', + resources: '資源', + fleet: '艦隊', + defense: '防禦', + buildings: '建築' + }, + simulatorView: { + title: '戰鬥模擬器', + attacker: '攻擊方', + defender: '防守方', + attackerConfig: '攻擊方配置', + attackerConfigDesc: '設置攻擊方的艦隊和科技等級', + defenderConfig: '防守方配置', + defenderConfigDesc: '設置防守方的艦隊、防禦和科技等級', + fleet: '艦隊', + defenseStructures: '防禦設施', + techLevels: '科技等級', + weapon: '武器', + shield: '護盾', + armor: '裝甲', + defenderResources: '防守方資源(用於掠奪計算)', + startSimulation: '開始模擬', + reset: '重置', + battleResult: '戰鬥結果', + attackerVictory: '攻擊方勝利', + defenderVictory: '防守方勝利', + draw: '平局', + afterRounds: '經過 {rounds} 回合戰鬥', + attackerLosses: '攻擊方損失', + defenderLosses: '防守方損失', + noLosses: '無損失', + attackerRemaining: '攻擊方剩餘', + defenderRemaining: '防守方剩餘', + allDestroyed: '全部摧毀', + plunderableResources: '可掠奪資源', + debrisField: '殘骸場', + moonChance: '月球生成機率', + showRoundDetails: '顯示回合詳情', + hideRoundDetails: '隱藏回合詳情', + round: '第 {round} 回合', + attackerRemainingPower: '攻擊方剩餘火力', + defenderRemainingPower: '防守方剩餘火力' + }, + settings: { + dataManagement: '資料管理', + dataManagementDesc: '匯出、匯入或清除遊戲資料', + exportData: '匯出資料', + exportDataDesc: '將遊戲進度匯出為JSON檔案', + export: '匯出', + exporting: '匯出中...', + exportSuccess: '匯出成功', + exportFailed: '匯出失敗,請重試', + importData: '匯入資料', + importDataDesc: '從JSON檔案恢復遊戲進度', + selectFile: '匯入', + importSuccess: '匯入成功', + importConfirmTitle: '確認匯入資料', + importConfirmMessage: '匯入資料將覆蓋目前遊戲進度,此操作不可撤銷。確定要繼續嗎?', + importFailed: '匯入失敗,請檢查檔案格式', + clearData: '清除資料', + clearDataDesc: '刪除所有遊戲資料並重置遊戲', + clear: '清除', + clearConfirmTitle: '確認清除資料', + clearConfirmMessage: '這將刪除所有遊戲資料並重新開始,此操作不可撤銷。確定要繼續嗎?', + gameSettings: '遊戲設定', + gameSettingsDesc: '調整遊戲參數和偏好設定', + playerName: '玩家名稱', + gameSpeed: '遊戲速度', + gameSpeedDesc: '目前遊戲速度倍率', + about: '關於', + version: '版本', + buildDate: '建置日期', + community: '社群', + github: 'GitHub 儲存庫', + qqGroup: 'QQ 交流群' + } +} diff --git a/src/logic/battleLogic.ts b/src/logic/battleLogic.ts new file mode 100644 index 0000000..99d8fba --- /dev/null +++ b/src/logic/battleLogic.ts @@ -0,0 +1,353 @@ +import type { Fleet, Resources, BattleResult, Officer } from '@/types/game' +import { DefenseType, ShipType, OfficerType } from '@/types/game' +import { SHIPS, DEFENSES } from '@/config/gameConfig' +import * as officerLogic from './officerLogic' + +/** + * 战斗单位(舰船或防御) + */ +interface BattleUnit { + type: ShipType | DefenseType + count: number + attack: number + shield: number + armor: number + isShip: boolean +} + +/** + * 战斗方 + */ +interface BattleSide { + fleet: BattleUnit[] + defense: BattleUnit[] + totalShields: number + totalArmor: number +} + +/** + * 准备战斗方数据 + */ +const prepareBattleSide = (fleet: Partial, defense: Partial>, defenseBonus: number = 0): BattleSide => { + const side: BattleSide = { + fleet: [], + defense: [], + totalShields: 0, + totalArmor: 0 + } + + // 添加舰船 + Object.entries(fleet).forEach(([shipType, count]) => { + if (count > 0) { + const config = SHIPS[shipType as ShipType] + const unit: BattleUnit = { + type: shipType as ShipType, + count, + attack: config.attack, + shield: config.shield * (1 + defenseBonus / 100), + armor: config.armor * (1 + defenseBonus / 100), + isShip: true + } + side.fleet.push(unit) + side.totalShields += unit.shield * count + side.totalArmor += unit.armor * count + } + }) + + // 添加防御 + Object.entries(defense).forEach(([defenseType, count]) => { + if (count > 0) { + const config = DEFENSES[defenseType as DefenseType] + const unit: BattleUnit = { + type: defenseType as DefenseType, + count, + attack: config.attack, + shield: config.shield * (1 + defenseBonus / 100), + armor: config.armor * (1 + defenseBonus / 100), + isShip: false + } + side.defense.push(unit) + side.totalShields += unit.shield * count + side.totalArmor += unit.armor * count + } + }) + + return side +} + +/** + * 计算一方的总攻击力 + */ +const calculateTotalAttack = (side: BattleSide): number => { + let total = 0 + side.fleet.forEach(unit => { + total += unit.attack * unit.count + }) + side.defense.forEach(unit => { + total += unit.attack * unit.count + }) + return total +} + +/** + * 执行一轮战斗 + */ +const executeBattleRound = (attacker: BattleSide, defender: BattleSide): void => { + // 攻击方对防御方造成伤害 + const attackerDamage = calculateTotalAttack(attacker) + applyDamage(defender, attackerDamage) + + // 防御方对攻击方造成伤害 + const defenderDamage = calculateTotalAttack(defender) + applyDamage(attacker, defenderDamage) +} + +/** + * 对一方施加伤害 + */ +const applyDamage = (side: BattleSide, totalDamage: number): void => { + let remainingDamage = totalDamage + + // 先消耗护盾 + const totalShields = side.totalShields + if (totalShields > 0) { + const shieldAbsorption = Math.min(remainingDamage, totalShields) + remainingDamage -= shieldAbsorption + side.totalShields -= shieldAbsorption + } + + // 剩余伤害穿透护盾,破坏单位 + if (remainingDamage > 0) { + destroyUnits(side, remainingDamage) + } +} + +/** + * 根据伤害摧毁单位 + */ +const destroyUnits = (side: BattleSide, damage: number): void => { + let remainingDamage = damage + + // 随机选择单位摧毁 + const allUnits = [...side.fleet, ...side.defense] + + while (remainingDamage > 0 && allUnits.some(u => u.count > 0)) { + // 随机选择一个有数量的单位 + const availableUnits = allUnits.filter(u => u.count > 0) + if (availableUnits.length === 0) break + + const targetUnit = availableUnits[Math.floor(Math.random() * availableUnits.length)] + if (!targetUnit) break // 安全检查 + + // 计算破坏概率(伤害 / 装甲) + const destructionChance = Math.min(remainingDamage / targetUnit.armor, 1) + + if (Math.random() < destructionChance) { + targetUnit.count-- + side.totalArmor -= targetUnit.armor + remainingDamage -= targetUnit.armor + } else { + // 未破坏,但消耗一部分伤害 + remainingDamage -= targetUnit.armor * destructionChance + } + } +} + +/** + * 检查战斗是否结束 + */ +const isBattleOver = (attacker: BattleSide, defender: BattleSide): boolean => { + const attackerHasUnits = attacker.fleet.some(u => u.count > 0) || attacker.defense.some(u => u.count > 0) + const defenderHasUnits = defender.fleet.some(u => u.count > 0) || defender.defense.some(u => u.count > 0) + + return !attackerHasUnits || !defenderHasUnits +} + +/** + * 计算损失 + */ +const calculateLosses = ( + initialSide: BattleSide, + finalSide: BattleSide +): { fleet: Partial; defense: Partial> } => { + const losses: { fleet: Partial; defense: Partial> } = { + fleet: {}, + defense: {} + } + + // 计算舰船损失 + initialSide.fleet.forEach((initialUnit, index) => { + const finalUnit = finalSide.fleet[index] + const lost = initialUnit.count - (finalUnit?.count || 0) + if (lost > 0) { + losses.fleet[initialUnit.type as ShipType] = lost + } + }) + + // 计算防御损失 + initialSide.defense.forEach((initialUnit, index) => { + const finalUnit = finalSide.defense[index] + const lost = initialUnit.count - (finalUnit?.count || 0) + if (lost > 0) { + losses.defense[initialUnit.type as DefenseType] = lost + } + }) + + return losses +} + +/** + * 计算残骸场 + */ +const calculateDebrisField = ( + attackerLosses: Partial, + defenderLosses: { fleet: Partial; defense: Partial> } +): Resources => { + const debris: Resources = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 } + const debrisRate = 0.3 // 30%的残骸回收率 + + // 攻击方舰船损失 + Object.entries(attackerLosses).forEach(([shipType, count]) => { + const config = SHIPS[shipType as ShipType] + debris.metal += config.cost.metal * count * debrisRate + debris.crystal += config.cost.crystal * count * debrisRate + }) + + // 防御方舰船损失 + Object.entries(defenderLosses.fleet).forEach(([shipType, count]) => { + const config = SHIPS[shipType as ShipType] + debris.metal += config.cost.metal * count * debrisRate + debris.crystal += config.cost.crystal * count * debrisRate + }) + + // 防御设施不产生残骸场(或产生较少) + + return debris +} + +/** + * 计算掠夺资源 + */ +const calculatePlunder = (availableResources: Resources, attackerFleet: Partial, cargoCapacity: number): Resources => { + // 计算攻击方剩余运载能力 + let totalCapacity = 0 + Object.entries(attackerFleet).forEach(([shipType, count]) => { + const config = SHIPS[shipType as ShipType] + totalCapacity += config.cargoCapacity * count + }) + + // 最多掠夺50%的资源 + const maxPlunder = Math.min(totalCapacity, cargoCapacity) + const plunderRate = 0.5 + + const plunder: Resources = { + metal: Math.min(availableResources.metal * plunderRate, maxPlunder * 0.5), + crystal: Math.min(availableResources.crystal * plunderRate, maxPlunder * 0.3), + deuterium: Math.min(availableResources.deuterium * plunderRate, maxPlunder * 0.2), + darkMatter: 0, // 暗物质无法掠夺 + energy: 0 + } + + return plunder +} + +/** + * 执行战斗模拟 + */ +export const simulateBattle = ( + attackerFleet: Partial, + defenderFleet: Partial, + defenderDefense: Partial>, + defenderResources: Resources, + attackerOfficers: Record, + defenderOfficers: Record +): BattleResult => { + // 计算军官加成 + const attackerBonuses = officerLogic.calculateActiveBonuses(attackerOfficers, Date.now()) + const defenderBonuses = officerLogic.calculateActiveBonuses(defenderOfficers, Date.now()) + + // 准备战斗方 + const initialAttacker = prepareBattleSide(attackerFleet, {}, attackerBonuses.defenseBonus) + const initialDefender = prepareBattleSide(defenderFleet, defenderDefense, defenderBonuses.defenseBonus) + + // 复制战斗方用于战斗 + const attacker = JSON.parse(JSON.stringify(initialAttacker)) as BattleSide + const defender = JSON.parse(JSON.stringify(initialDefender)) as BattleSide + + // 战斗回合(最多6回合) + let rounds = 0 + const maxRounds = 6 + + while (rounds < maxRounds && !isBattleOver(attacker, defender)) { + executeBattleRound(attacker, defender) + rounds++ + } + + // 计算损失 + const attackerLosses = calculateLosses(initialAttacker, attacker).fleet + const defenderLosses = calculateLosses(initialDefender, defender) + + // 判断胜负 + let winner: 'attacker' | 'defender' | 'draw' = 'draw' + const attackerSurvived = attacker.fleet.some(u => u.count > 0) + const defenderSurvived = defender.fleet.some(u => u.count > 0) || defender.defense.some(u => u.count > 0) + + if (attackerSurvived && !defenderSurvived) { + winner = 'attacker' + } else if (!attackerSurvived && defenderSurvived) { + winner = 'defender' + } + + // 计算残骸场 + const debrisField = calculateDebrisField(attackerLosses, defenderLosses) + + // 计算掠夺(仅攻击方胜利时) + const plunder = + winner === 'attacker' + ? calculatePlunder(defenderResources, attackerFleet, 10000) + : { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 } + + // 生成战斗报告 + const battleResult: BattleResult = { + id: `battle_${Date.now()}`, + timestamp: Date.now(), + attackerId: '', + defenderId: '', + attackerPlanetId: '', + defenderPlanetId: '', + attackerFleet, + defenderFleet, + defenderDefense, + attackerLosses, + defenderLosses, + winner, + plunder, + debrisField + } + + return battleResult +} + +/** + * 计算防御设施修复(防御有70%概率修复) + */ +export const repairDefense = ( + defenseBeforeBattle: Partial>, + defenseAfterBattle: Partial> +): Partial> => { + const repaired: Partial> = { ...defenseAfterBattle } + + Object.keys(defenseBeforeBattle).forEach(defenseType => { + const before = defenseBeforeBattle[defenseType as DefenseType] || 0 + const after = defenseAfterBattle[defenseType as DefenseType] || 0 + const lost = before - after + + if (lost > 0) { + // 70%修复概率 + const repairedCount = Math.floor(lost * 0.7) + repaired[defenseType as DefenseType] = after + repairedCount + } + }) + + return repaired +} diff --git a/src/logic/buildingLogic.ts b/src/logic/buildingLogic.ts new file mode 100644 index 0000000..112f4ac --- /dev/null +++ b/src/logic/buildingLogic.ts @@ -0,0 +1,194 @@ +import type { Planet, Resources, BuildQueueItem } from '@/types/game' +import { BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game' +import { BUILDINGS } from '@/config/gameConfig' +import * as pointsLogic from './pointsLogic' + +/** + * 计算建筑升级成本 + */ +export const calculateBuildingCost = (buildingType: BuildingType, targetLevel: number): Resources => { + const config = BUILDINGS[buildingType] + const multiplier = Math.pow(config.costMultiplier, targetLevel - 1) + return { + metal: Math.floor(config.baseCost.metal * multiplier), + crystal: Math.floor(config.baseCost.crystal * multiplier), + deuterium: Math.floor(config.baseCost.deuterium * multiplier), + darkMatter: Math.floor(config.baseCost.darkMatter * multiplier), + energy: 0 + } +} + +/** + * 计算建筑升级时间 + */ +export const calculateBuildingTime = (buildingType: BuildingType, targetLevel: number, buildingSpeedBonus: number = 0): number => { + const config = BUILDINGS[buildingType] + const multiplier = Math.pow(config.costMultiplier, targetLevel - 1) + const baseTime = config.baseTime * multiplier + const speedMultiplier = 1 - buildingSpeedBonus / 100 + return Math.floor(baseTime * speedMultiplier) +} + +/** + * 计算已用空间 + */ +export const calculateUsedSpace = (planet: Planet): number => { + let usedSpace = 0 + Object.entries(planet.buildings).forEach(([buildingType, level]) => { + if (level > 0) { + const config = BUILDINGS[buildingType as BuildingType] + usedSpace += config.spaceUsage * level + } + }) + return usedSpace +} + +/** + * 检查建筑升级条件 + */ +export const checkBuildingRequirements = ( + buildingType: BuildingType, + planet: Planet, + technologies: Partial> +): boolean => { + const config = BUILDINGS[buildingType] + const requirements = (config as any).requirements + if (!requirements) return true + + for (const [key, level] of Object.entries(requirements)) { + const requiredLevel = level as number + if (Object.values(BuildingType).includes(key as BuildingType)) { + if ((planet.buildings[key as BuildingType] || 0) < requiredLevel) { + return false + } + } else if (Object.values(TechnologyType).includes(key as TechnologyType)) { + if ((technologies[key as TechnologyType] || 0) < requiredLevel) { + return false + } + } + } + return true +} + +/** + * 检查是否有足够空间建造 + */ +export const checkSpaceAvailable = (planet: Planet, buildingType: BuildingType): boolean => { + const usedSpace = calculateUsedSpace(planet) + const buildingConfig = BUILDINGS[buildingType] + const requiredSpace = buildingConfig.spaceUsage + return usedSpace + requiredSpace <= planet.maxSpace +} + +/** + * 创建建造队列项 + */ +export const createBuildQueueItem = (buildingType: BuildingType, targetLevel: number, buildTime: number): BuildQueueItem => { + const now = Date.now() + return { + id: `build_${now}`, + type: 'building', + itemType: buildingType, + targetLevel, + startTime: now, + endTime: now + buildTime * 1000 + } +} + +/** + * 处理建造完成 + */ +export const completeBuildQueue = (planet: Planet, now: number, onPointsEarned?: (points: number, type: 'building' | 'ship' | 'defense', itemType: string, level?: number, quantity?: number) => void): void => { + planet.buildQueue = planet.buildQueue.filter(item => { + if (now >= item.endTime) { + // 建造完成 + if (item.type === 'building') { + const oldLevel = planet.buildings[item.itemType as BuildingType] || 0 + const newLevel = item.targetLevel || 0 + planet.buildings[item.itemType as BuildingType] = newLevel + + // 计算并累积积分 + if (onPointsEarned && newLevel > oldLevel) { + const points = pointsLogic.calculateBuildingPoints(item.itemType as BuildingType, oldLevel, newLevel) + onPointsEarned(points, 'building', item.itemType, newLevel) + } + } else if (item.type === 'ship') { + const shipType = item.itemType as ShipType + const quantity = item.quantity || 0 + planet.fleet[shipType] = (planet.fleet[shipType] || 0) + quantity + + // 计算并累积积分 + if (onPointsEarned && quantity > 0) { + const points = pointsLogic.calculateShipPoints(shipType, quantity) + onPointsEarned(points, 'ship', item.itemType, undefined, quantity) + } + } else if (item.type === 'defense') { + const defenseType = item.itemType as DefenseType + const quantity = item.quantity || 0 + planet.defense[defenseType] = (planet.defense[defenseType] || 0) + quantity + + // 计算并累积积分 + if (onPointsEarned && quantity > 0) { + const points = pointsLogic.calculateDefensePoints(defenseType, quantity) + onPointsEarned(points, 'defense', item.itemType, undefined, quantity) + } + } else if (item.type === 'demolish') { + // 拆除完成,降低建筑等级 + // 注意:拆除不会扣除积分,积分只增不减 + const buildingType = item.itemType as BuildingType + const currentLevel = planet.buildings[buildingType] || 0 + planet.buildings[buildingType] = Math.max(0, currentLevel - 1) + } + return false + } + return true + }) +} + +/** + * 计算拆除返还资源 + * @param buildingType 建筑类型 + * @param currentLevel 当前等级 + * @returns 返还50%的当前等级建造成本 + */ +export const calculateDemolishRefund = (buildingType: BuildingType, currentLevel: number): Resources => { + const cost = calculateBuildingCost(buildingType, currentLevel) + return { + metal: Math.floor(cost.metal * 0.5), + crystal: Math.floor(cost.crystal * 0.5), + deuterium: Math.floor(cost.deuterium * 0.5), + darkMatter: Math.floor(cost.darkMatter * 0.5), + energy: 0 + } +} + +/** + * 计算拆除时间 + * @param buildingType 建筑类型 + * @param currentLevel 当前等级 + * @param buildingSpeedBonus 建筑速度加成 + * @returns 拆除时间(建造时间的50%) + */ +export const calculateDemolishTime = (buildingType: BuildingType, currentLevel: number, buildingSpeedBonus: number = 0): number => { + const buildTime = calculateBuildingTime(buildingType, currentLevel, buildingSpeedBonus) + return Math.floor(buildTime * 0.5) +} + +/** + * 创建拆除队列项 + * @param buildingType 建筑类型 + * @param currentLevel 当前等级 + * @param demolishTime 拆除时间 + * @returns 拆除队列项 + */ +export const createDemolishQueueItem = (buildingType: BuildingType, currentLevel: number, demolishTime: number): BuildQueueItem => { + const now = Date.now() + return { + id: `demolish_${now}`, + type: 'demolish', + itemType: buildingType, + targetLevel: currentLevel - 1, // 目标等级为当前等级-1 + startTime: now, + endTime: now + demolishTime * 1000 + } +} diff --git a/src/logic/buildingValidation.ts b/src/logic/buildingValidation.ts new file mode 100644 index 0000000..2b30a91 --- /dev/null +++ b/src/logic/buildingValidation.ts @@ -0,0 +1,161 @@ +import type { Planet, Resources, BuildQueueItem, Officer } from '@/types/game' +import { BuildingType, TechnologyType, OfficerType } from '@/types/game' +import * as buildingLogic from './buildingLogic' +import * as resourceLogic from './resourceLogic' +import * as publicLogic from './publicLogic' +import * as officerLogic from './officerLogic' +import { BUILDINGS } from '@/config/gameConfig' + +/** + * 验证建筑升级的所有条件 + */ +export const validateBuildingUpgrade = ( + planet: Planet, + buildingType: BuildingType, + technologies: Partial>, + officers: Record +): { + valid: boolean + reason?: string +} => { + const currentLevel = planet.buildings[buildingType] || 0 + const targetLevel = currentLevel + 1 + const cost = buildingLogic.calculateBuildingCost(buildingType, targetLevel) + const buildingConfig = BUILDINGS[buildingType] + + // 检查星球/月球限制 + if (buildingConfig.planetOnly && planet.isMoon) { + return { valid: false, reason: 'errors.planetOnly' } + } + if (buildingConfig.moonOnly && !planet.isMoon) { + return { valid: false, reason: 'errors.moonOnly' } + } + + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + + // 检查建造队列是否已满 + const maxQueue = publicLogic.getMaxBuildQueue(planet, bonuses.additionalBuildQueue) + if (planet.buildQueue.length >= maxQueue) { + return { valid: false, reason: 'errors.buildQueueFull' } + } + + // 检查空间 + if (!buildingLogic.checkSpaceAvailable(planet, buildingType)) { + return { valid: false, reason: 'errors.insufficientSpace' } + } + + // 检查资源 + if (!resourceLogic.checkResourcesAvailable(planet.resources, cost)) { + return { valid: false, reason: 'errors.insufficientResources' } + } + + // 检查前置条件 + if (!buildingLogic.checkBuildingRequirements(buildingType, planet, technologies)) { + return { valid: false, reason: 'errors.requirementsNotMet' } + } + + return { valid: true } +} + +/** + * 执行建筑升级(扣除资源,添加到队列) + */ +export const executeBuildingUpgrade = (planet: Planet, buildingType: BuildingType, officers: Record): BuildQueueItem => { + const currentLevel = planet.buildings[buildingType] || 0 + const targetLevel = currentLevel + 1 + const cost = buildingLogic.calculateBuildingCost(buildingType, targetLevel) + + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + const time = buildingLogic.calculateBuildingTime(buildingType, targetLevel, bonuses.buildingSpeedBonus) + + // 扣除资源 + resourceLogic.deductResources(planet.resources, cost) + + // 创建队列项 + return buildingLogic.createBuildQueueItem(buildingType, targetLevel, time) +} + +/** + * 取消建造并计算返还资源 + */ +export const cancelBuildingUpgrade = (_planet: Planet, queueItem: BuildQueueItem): Resources => { + const cost = buildingLogic.calculateBuildingCost(queueItem.itemType as BuildingType, queueItem.targetLevel || 1) + + return { + metal: Math.floor(cost.metal * 0.5), + crystal: Math.floor(cost.crystal * 0.5), + deuterium: Math.floor(cost.deuterium * 0.5), + darkMatter: Math.floor(cost.darkMatter * 0.5), + energy: 0 + } +} + +/** + * 查找队列项 + */ +export const findQueueItem = ( + queue: BuildQueueItem[], + queueId: string +): { + item: BuildQueueItem | null + index: number +} => { + const index = queue.findIndex(q => q.id === queueId) + if (index === -1) { + return { item: null, index: -1 } + } + return { item: queue[index] || null, index } +} + +/** + * 验证建筑拆除的所有条件 + */ +export const validateBuildingDemolish = ( + planet: Planet, + buildingType: BuildingType, + officers: Record +): { + valid: boolean + reason?: string +} => { + const currentLevel = planet.buildings[buildingType] || 0 + + // 检查建筑等级 + if (currentLevel <= 0) { + return { valid: false, reason: 'errors.buildingLevelZero' } + } + + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + + // 检查建造队列是否已满 + const maxQueue = publicLogic.getMaxBuildQueue(planet, bonuses.additionalBuildQueue) + if (planet.buildQueue.length >= maxQueue) { + return { valid: false, reason: 'errors.buildQueueFull' } + } + + return { valid: true } +} + +/** + * 执行建筑拆除(返还资源,添加到队列) + */ +export const executeBuildingDemolish = (planet: Planet, buildingType: BuildingType, officers: Record): BuildQueueItem => { + const currentLevel = planet.buildings[buildingType] || 0 + + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + const demolishTime = buildingLogic.calculateDemolishTime(buildingType, currentLevel, bonuses.buildingSpeedBonus) + + // 返还50%资源 + const refund = buildingLogic.calculateDemolishRefund(buildingType, currentLevel) + planet.resources.metal += refund.metal + planet.resources.crystal += refund.crystal + planet.resources.deuterium += refund.deuterium + planet.resources.darkMatter += refund.darkMatter + + // 创建拆除队列项 + return buildingLogic.createDemolishQueueItem(buildingType, currentLevel, demolishTime) +} diff --git a/src/logic/fleetLogic.ts b/src/logic/fleetLogic.ts new file mode 100644 index 0000000..9746751 --- /dev/null +++ b/src/logic/fleetLogic.ts @@ -0,0 +1,374 @@ +import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer } from '@/types/game' +import { ShipType, DefenseType, MissionType, BuildingType, OfficerType } from '@/types/game' +import * as battleLogic from './battleLogic' +import * as moonLogic from './moonLogic' +import * as moonValidation from './moonValidation' + +/** + * 计算两个星球之间的距离 + */ +export const calculateDistance = ( + from: { galaxy: number; system: number; position: number }, + to: { galaxy: number; system: number; position: number } +): number => { + return Math.sqrt(Math.pow(to.galaxy - from.galaxy, 2) + Math.pow(to.system - from.system, 2) + Math.pow(to.position - from.position, 2)) +} + +/** + * 计算飞行时间 + */ +export const calculateFlightTime = (distance: number, minSpeed: number): number => { + return Math.max(10, Math.floor((distance * 10000) / minSpeed)) // 至少10秒 +} + +/** + * 创建舰队任务 + */ +export const createFleetMission = ( + playerId: string, + originPlanetId: string, + targetPosition: { galaxy: number; system: number; position: number }, + missionType: MissionType, + fleet: Partial, + cargo: Resources, + flightTime: number +): FleetMission => { + const now = Date.now() + return { + id: `mission_${now}`, + playerId, + originPlanetId, + targetPosition, + missionType, + fleet, + cargo, + departureTime: now, + arrivalTime: now + flightTime * 1000, + returnTime: now + flightTime * 2 * 1000, + status: 'outbound' + } +} + +/** + * 处理运输任务到达 + */ +export const processTransportArrival = (mission: FleetMission, targetPlanet: Planet | undefined): void => { + if (targetPlanet) { + targetPlanet.resources.metal += mission.cargo.metal + targetPlanet.resources.crystal += mission.cargo.crystal + targetPlanet.resources.deuterium += mission.cargo.deuterium + targetPlanet.resources.darkMatter += mission.cargo.darkMatter + } + mission.status = 'returning' + mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 } +} + +/** + * 处理攻击任务到达 + */ +export const processAttackArrival = ( + mission: FleetMission, + targetPlanet: Planet | undefined, + attacker: Player, + defender: Player | null, + allPlanets: Planet[] +): { battleResult: BattleResult; moon: Planet | null } | null => { + if (!targetPlanet || targetPlanet.ownerId === attacker.id) { + mission.status = 'returning' + return null + } + + // 执行战斗 + const battleResult = battleLogic.simulateBattle( + mission.fleet, + targetPlanet.fleet, + targetPlanet.defense, + targetPlanet.resources, + attacker.officers, + defender?.officers || ({} as Record) + ) + + // 更新战斗报告ID + battleResult.id = `battle_${Date.now()}` + battleResult.attackerId = attacker.id + battleResult.defenderId = targetPlanet.ownerId || 'unknown' + battleResult.attackerPlanetId = mission.originPlanetId + battleResult.defenderPlanetId = targetPlanet.id + + // 如果攻击方获胜,掠夺资源已经在战斗模拟中计算 + mission.cargo = battleResult.plunder + + // 更新舰队 - 计算幸存舰船 + const survivingFleet: Partial = {} + Object.entries(mission.fleet).forEach(([shipType, initialCount]) => { + const lost = battleResult.attackerLosses[shipType as ShipType] || 0 + const surviving = initialCount - lost + if (surviving > 0) { + survivingFleet[shipType as ShipType] = surviving + } + }) + mission.fleet = survivingFleet + + // 更新目标星球舰队和防御 + Object.entries(battleResult.defenderLosses.fleet).forEach(([shipType, lost]) => { + targetPlanet.fleet[shipType as ShipType] = Math.max(0, targetPlanet.fleet[shipType as ShipType] - lost) + }) + + Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => { + targetPlanet.defense[defenseType as DefenseType] = Math.max(0, targetPlanet.defense[defenseType as DefenseType] - lost) + }) + + // 防御设施修复(70%概率) + const defenseBeforeBattle: Partial> = { ...targetPlanet.defense } + Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => { + defenseBeforeBattle[defenseType as DefenseType] = (defenseBeforeBattle[defenseType as DefenseType] || 0) + lost + }) + targetPlanet.defense = battleLogic.repairDefense(defenseBeforeBattle, targetPlanet.defense) as Record + + // 扣除掠夺的资源 + targetPlanet.resources.metal -= battleResult.plunder.metal + targetPlanet.resources.crystal -= battleResult.plunder.crystal + targetPlanet.resources.deuterium -= battleResult.plunder.deuterium + + mission.status = 'returning' + + // 尝试生成月球(如果该位置还没有月球) + let moon: Planet | null = null + const moonCheck = moonValidation.canCreateMoon(allPlanets, targetPlanet.position, battleResult.debrisField) + if (moonCheck.canCreate && moonCheck.chance) { + if (moonValidation.shouldGenerateMoon(moonCheck.chance)) { + moon = moonLogic.tryGenerateMoon(battleResult.debrisField, targetPlanet.position, targetPlanet.id, targetPlanet.ownerId || 'unknown') + } + } + + return { battleResult, moon } +} + +/** + * 处理殖民任务到达 + */ +export const processColonizeArrival = ( + mission: FleetMission, + targetPlanet: Planet | undefined, + playerId: string, + colonyNameTemplate: string = 'Colony' +): Planet | null => { + if (targetPlanet) { + // 位置已被占用 + mission.status = 'returning' + return null + } + + // 创建新殖民地 + const newPlanet: Planet = { + id: `planet_${Date.now()}`, + name: `${colonyNameTemplate} ${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`, + ownerId: playerId, + position: mission.targetPosition, + resources: { metal: 500, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 }, + buildings: {} as Record, + fleet: { + [ShipType.LightFighter]: 0, + [ShipType.HeavyFighter]: 0, + [ShipType.Cruiser]: 0, + [ShipType.Battleship]: 0, + [ShipType.SmallCargo]: 0, + [ShipType.LargeCargo]: 0, + [ShipType.ColonyShip]: 0, + [ShipType.Recycler]: 0, + [ShipType.EspionageProbe]: 0, + [ShipType.DarkMatterHarvester]: 0 + }, + defense: { + [DefenseType.RocketLauncher]: 0, + [DefenseType.LightLaser]: 0, + [DefenseType.HeavyLaser]: 0, + [DefenseType.GaussCannon]: 0, + [DefenseType.IonCannon]: 0, + [DefenseType.PlasmaTurret]: 0, + [DefenseType.SmallShieldDome]: 0, + [DefenseType.LargeShieldDome]: 0 + }, + buildQueue: [], + lastUpdate: Date.now(), + maxSpace: 200, + isMoon: false + } + + Object.values(BuildingType).forEach(building => { + newPlanet.buildings[building] = 0 + }) + + // 殖民船被消耗 + mission.fleet[ShipType.ColonyShip] = (mission.fleet[ShipType.ColonyShip] || 1) - 1 + mission.status = 'returning' + + return newPlanet +} + +/** + * 处理间谍任务到达 + */ +export const processSpyArrival = (mission: FleetMission, targetPlanet: Planet | undefined, playerId: string): SpyReport | null => { + if (!targetPlanet) { + mission.status = 'returning' + return null + } + + const spyReport: SpyReport = { + id: `spy_${Date.now()}`, + timestamp: Date.now(), + spyId: playerId, + targetPlanetId: targetPlanet.id, + targetPlayerId: targetPlanet.ownerId || 'unknown', + resources: { ...targetPlanet.resources }, + fleet: { ...targetPlanet.fleet }, + defense: { ...targetPlanet.defense }, + buildings: { ...targetPlanet.buildings }, + technologies: {}, + detectionChance: 0.3 + } + + mission.status = 'returning' + return spyReport +} + +/** + * 处理部署任务到达 + */ +export const processDeployArrival = (mission: FleetMission, targetPlanet: Planet | undefined, playerId: string): boolean => { + if (!targetPlanet || targetPlanet.ownerId !== playerId) { + mission.status = 'returning' + return false + } + + for (const [shipType, count] of Object.entries(mission.fleet)) { + targetPlanet.fleet[shipType as ShipType] += count + } + + // 部署任务直接完成,不返回 + return true +} + +/** + * 处理舰队任务返回 + */ +export const processFleetReturn = (mission: FleetMission, originPlanet: Planet): void => { + // 舰船返回 + Object.entries(mission.fleet).forEach(([shipType, count]) => { + if (count > 0) { + originPlanet.fleet[shipType as ShipType] += count + } + }) + + // 资源返回(掠夺物或运输货物) + originPlanet.resources.metal += mission.cargo.metal + originPlanet.resources.crystal += mission.cargo.crystal + originPlanet.resources.deuterium += mission.cargo.deuterium + originPlanet.resources.darkMatter += mission.cargo.darkMatter +} + +/** + * 更新舰队任务状态 + */ +export const updateFleetMissions = ( + missions: FleetMission[], + planets: Map, + attacker: Player, + defender: Player | null, + now: number +): { + completedMissions: string[] + battleReports: BattleResult[] + spyReports: SpyReport[] + newColonies: Planet[] + newMoons: Planet[] +} => { + const completedMissions: string[] = [] + const battleReports: BattleResult[] = [] + const spyReports: SpyReport[] = [] + const newColonies: Planet[] = [] + const newMoons: Planet[] = [] + + // 获取所有星球列表(用于月球生成检查) + const allPlanets = Array.from(planets.values()) + + missions.forEach(mission => { + const originPlanet = attacker.planets.find(p => p.id === mission.originPlanetId) + + if (mission.status === 'outbound' && now >= mission.arrivalTime) { + // 任务到达目标 + const targetKey = `${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}` + const targetPlanet = planets.get(targetKey) + + switch (mission.missionType) { + case MissionType.Transport: + processTransportArrival(mission, targetPlanet) + break + + case MissionType.Attack: + const attackResult = processAttackArrival(mission, targetPlanet, attacker, defender, allPlanets) + if (attackResult) { + battleReports.push(attackResult.battleResult) + if (attackResult.moon) { + newMoons.push(attackResult.moon) + // 将月球添加到planets map中 + const moonKey = `${attackResult.moon.position.galaxy}:${attackResult.moon.position.system}:${attackResult.moon.position.position}` + planets.set(moonKey, attackResult.moon) + } + } + break + + case MissionType.Colonize: + const newColony = processColonizeArrival(mission, targetPlanet, attacker.id) + if (newColony) { + newColonies.push(newColony) + planets.set(targetKey, newColony) + } + break + + case MissionType.Spy: + const spyReport = processSpyArrival(mission, targetPlanet, attacker.id) + if (spyReport) { + spyReports.push(spyReport) + } + break + + case MissionType.Deploy: + const deployed = processDeployArrival(mission, targetPlanet, attacker.id) + if (deployed) { + completedMissions.push(mission.id) + } + break + } + } + + if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) { + // 舰队返回 + if (originPlanet) { + processFleetReturn(mission, originPlanet) + } + completedMissions.push(mission.id) + } + }) + + return { completedMissions, battleReports, spyReports, newColonies, newMoons } +} + +/** + * 召回舰队 + */ +export const recallFleetMission = (mission: FleetMission, now: number): boolean => { + if (mission.status !== 'outbound') return false + + const elapsedTime = now - mission.departureTime + + // 如果还在飞行途中,立即返回 + if (now < mission.arrivalTime) { + mission.status = 'returning' + mission.returnTime = now + elapsedTime // 返回时间等于已飞行的时间 + return true + } + + return false +} diff --git a/src/logic/gameLogic.ts b/src/logic/gameLogic.ts new file mode 100644 index 0000000..e31f72d --- /dev/null +++ b/src/logic/gameLogic.ts @@ -0,0 +1,132 @@ +import type { Planet, Player, BuildQueueItem, Officer } from '@/types/game' +import { TechnologyType, OfficerType } from '@/types/game' +import * as officerLogic from './officerLogic' +import * as buildingLogic from './buildingLogic' +import * as researchLogic from './researchLogic' +import * as resourceLogic from './resourceLogic' +import * as pointsLogic from './pointsLogic' + +/** + * 初始化玩家数据 + */ +export const initializePlayer = (playerId: string, playerName: string = 'Commander'): Player => { + const player: Player = { + id: playerId, + name: playerName, + planets: [], + technologies: {} as Record, + officers: {} as Record, + researchQueue: [], + fleetMissions: [], + battleReports: [], + spyReports: [], + points: 0 + } + + // 初始化科技等级 + Object.values(TechnologyType).forEach(tech => { + player.technologies[tech] = 0 + }) + + // 初始化军官状态 + Object.values(OfficerType).forEach(officer => { + player.officers[officer] = officerLogic.createInactiveOfficer(officer) + }) + + return player +} + +/** + * 检查是否需要初始化游戏 + */ +export const shouldInitializeGame = (planets: Planet[]): boolean => { + return planets.length === 0 +} + +/** + * 更新所有星球的最后更新时间 + */ +export const updatePlanetsLastUpdate = (planets: Planet[], now: number): void => { + planets.forEach(planet => { + planet.lastUpdate = now + }) +} + +/** + * 生成星系位置列表 + */ +export const generateSystemPositions = ( + _galaxy: number, + _system: number, + count: number = 10 +): Array<{ position: number; planet: Planet | null }> => { + const result: Array<{ position: number; planet: Planet | null }> = [] + for (let pos = 1; pos <= count; pos++) { + result.push({ position: pos, planet: null }) + } + return result +} + +/** + * 生成随机NPC星球位置 + */ +export const generateRandomPosition = (): { galaxy: number; system: number; position: number } => { + return { + galaxy: Math.floor(Math.random() * 9) + 1, + system: Math.floor(Math.random() * 10) + 1, + position: Math.floor(Math.random() * 10) + 1 + } +} + +/** + * 生成位置键 + */ +export const generatePositionKey = (galaxy: number, system: number, position: number): string => { + return `${galaxy}:${system}:${position}` +} + +/** + * 更新游戏状态 - 处理所有星球和任务 + */ +export const processGameUpdate = ( + player: Player, + now: number +): { + updatedResearchQueue: BuildQueueItem[] +} => { + // 获取军官加成 + const bonuses = officerLogic.calculateActiveBonuses(player.officers, now) + + // 创建积分回调函数 + const onPointsEarned = (points: number, _type: string, _itemType: string, _level?: number, _quantity?: number) => { + pointsLogic.addPoints(player, points) + } + + // 更新所有星球 + player.planets.forEach(planet => { + // 更新资源 + resourceLogic.updatePlanetResources(planet, now, bonuses) + + // 检查建造队列 + buildingLogic.completeBuildQueue(planet, now, onPointsEarned) + }) + + // 检查研究队列 + const updatedResearchQueue = researchLogic.completeResearchQueue( + player.researchQueue, + player.technologies, + now, + onPointsEarned + ) + + return { + updatedResearchQueue + } +} + +/** + * 检查并返回过期的军官列表 + */ +export const checkOfficersExpiration = (officers: Record, now: number): void => { + officerLogic.checkAndDeactivateExpiredOfficers(officers, now) +} diff --git a/src/logic/moonLogic.ts b/src/logic/moonLogic.ts new file mode 100644 index 0000000..4e2703b --- /dev/null +++ b/src/logic/moonLogic.ts @@ -0,0 +1,111 @@ +import type { Planet, Resources } from '@/types/game' +import { BuildingType, ShipType, DefenseType } from '@/types/game' +import { MOON_CONFIG } from '@/config/gameConfig' + +/** + * 计算月球生成概率 + * @param debrisField 战斗产生的残骸场 + * @returns 生成概率(0-100) + */ +export const calculateMoonGenerationChance = (debrisField: Resources): number => { + const totalDebris = debrisField.metal + debrisField.crystal + + // 残骸不足最小值,无法生成月球 + if (totalDebris < MOON_CONFIG.minDebrisField) { + return 0 + } + + // 计算概率:基础概率 + (残骸量 / 每单位增加量) * 1% + const additionalChance = Math.floor(totalDebris / MOON_CONFIG.chancePerDebris) + const chance = MOON_CONFIG.baseChance + additionalChance + + // 限制在最大概率内 + return Math.min(chance, MOON_CONFIG.maxChance) +} + +/** + * 尝试生成月球 + * @param debrisField 战斗产生的残骸场 + * @param planetPosition 星球坐标 + * @param playerId 玩家ID + * @returns 生成的月球对象,如果未生成则返回null + */ +export const tryGenerateMoon = ( + debrisField: Resources, + planetPosition: { galaxy: number; system: number; position: number }, + planetId: string, + playerId: string +): Planet | null => { + const chance = calculateMoonGenerationChance(debrisField) + + // 无法生成 + if (chance === 0) { + return null + } + + // 随机判断是否生成 + const roll = Math.random() * 100 + if (roll >= chance) { + return null + } + + // 生成月球 + const moon: Planet = { + id: `moon_${Date.now()}`, + name: `Moon [${planetPosition.galaxy}:${planetPosition.system}:${planetPosition.position}]`, + ownerId: playerId, + position: planetPosition, + resources: { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }, + buildings: {} as Record, + fleet: { + [ShipType.LightFighter]: 0, + [ShipType.HeavyFighter]: 0, + [ShipType.Cruiser]: 0, + [ShipType.Battleship]: 0, + [ShipType.SmallCargo]: 0, + [ShipType.LargeCargo]: 0, + [ShipType.ColonyShip]: 0, + [ShipType.Recycler]: 0, + [ShipType.EspionageProbe]: 0, + [ShipType.DarkMatterHarvester]: 0 + }, + defense: { + [DefenseType.RocketLauncher]: 0, + [DefenseType.LightLaser]: 0, + [DefenseType.HeavyLaser]: 0, + [DefenseType.GaussCannon]: 0, + [DefenseType.IonCannon]: 0, + [DefenseType.PlasmaTurret]: 0, + [DefenseType.SmallShieldDome]: 0, + [DefenseType.LargeShieldDome]: 0 + }, + buildQueue: [], + lastUpdate: Date.now(), + maxSpace: MOON_CONFIG.baseSize, + isMoon: true, + parentPlanetId: planetId + } + + // 初始化所有建筑为0级 + Object.values(BuildingType).forEach(building => { + moon.buildings[building] = 0 + }) + + return moon +} + +/** + * 检查坐标是否已有月球 + * @param planets 所有星球列表 + * @param position 坐标 + * @returns 是否已有月球 + */ +export const hasMoonAtPosition = (planets: Planet[], position: { galaxy: number; system: number; position: number }): boolean => { + return planets.some( + p => + p.isMoon && + p.position.galaxy === position.galaxy && + p.position.system === position.system && + p.position.position === position.position + ) +} diff --git a/src/logic/moonValidation.ts b/src/logic/moonValidation.ts new file mode 100644 index 0000000..af995e1 --- /dev/null +++ b/src/logic/moonValidation.ts @@ -0,0 +1,58 @@ +import type { Planet, Resources } from '@/types/game' +import * as planetLogic from './planetLogic' + +/** + * 检查是否可以生成月球 + */ +export const canCreateMoon = ( + planets: Planet[], + position: { galaxy: number; system: number; position: number }, + debrisField: Resources +): { + canCreate: boolean + reason?: string + chance?: number +} => { + // 检查该位置是否已有月球 + const existingMoon = planets.find( + p => + p.position.galaxy === position.galaxy && + p.position.system === position.system && + p.position.position === position.position && + p.isMoon + ) + + if (existingMoon) { + return { canCreate: false, reason: 'errors.moonExists' } + } + + const chance = planetLogic.calculateMoonChance(debrisField) + if (chance === 0) { + return { canCreate: false, reason: 'errors.insufficientDebris', chance } + } + + return { canCreate: true, chance } +} + +/** + * 计算月球生成概率并判断是否生成 + */ +export const shouldGenerateMoon = (chance: number): boolean => { + const random = Math.random() * 100 + return random <= chance +} + +/** + * 查找母星 + */ +export const findParentPlanet = (planets: Planet[], position: { galaxy: number; system: number; position: number }): Planet | null => { + return ( + planets.find( + p => + p.position.galaxy === position.galaxy && + p.position.system === position.system && + p.position.position === position.position && + !p.isMoon + ) || null + ) +} diff --git a/src/logic/officerLogic.ts b/src/logic/officerLogic.ts new file mode 100644 index 0000000..77b4ea8 --- /dev/null +++ b/src/logic/officerLogic.ts @@ -0,0 +1,96 @@ +import type { Officer, Resources } from '@/types/game' +import { OfficerType } from '@/types/game' +import { OFFICERS } from '@/config/gameConfig' + +/** + * 获取军官成本 + */ +export const getOfficerCost = (officerType: OfficerType): Resources => { + const config = OFFICERS[officerType] + return config.cost +} + +/** + * 检查军官是否激活 + */ +export const isOfficerActive = (officer: Officer, now: number): boolean => { + return officer.active && (!officer.expiresAt || officer.expiresAt > now) +} + +/** + * 创建激活的军官 + */ +export const createActiveOfficer = (officerType: OfficerType, duration: number): Officer => { + const now = Date.now() + return { + type: officerType, + active: true, + hiredAt: now, + expiresAt: now + duration * 24 * 60 * 60 * 1000 // duration天后过期 + } +} + +/** + * 创建未激活的军官 + */ +export const createInactiveOfficer = (officerType: OfficerType): Officer => { + return { + type: officerType, + active: false + } +} + +/** + * 续约军官 + */ +export const renewOfficerExpiration = (officer: Officer, duration: number, now: number): Officer => { + const expiresAt = officer.expiresAt && officer.expiresAt > now ? officer.expiresAt : now + return { + ...officer, + active: true, + expiresAt: expiresAt + duration * 24 * 60 * 60 * 1000 + } +} + +/** + * 计算所有激活军官的加成 + */ +export const calculateActiveBonuses = (officers: Record, now: number) => { + const bonuses = { + buildingSpeedBonus: 0, + researchSpeedBonus: 0, + resourceProductionBonus: 0, + darkMatterProductionBonus: 0, + energyProductionBonus: 0, + fleetSpeedBonus: 0, + fuelConsumptionReduction: 0, + defenseBonus: 0, + additionalBuildQueue: 0, + additionalFleetSlots: 0, + storageCapacityBonus: 0 + } + + Object.values(officers).forEach(officer => { + if (isOfficerActive(officer, now)) { + const config = OFFICERS[officer.type] + Object.entries(config.benefits).forEach(([key, value]) => { + if (value !== undefined) { + bonuses[key as keyof typeof bonuses] += value + } + }) + } + }) + + return bonuses +} + +/** + * 检查并停用过期的军官 + */ +export const checkAndDeactivateExpiredOfficers = (officers: Record, now: number): void => { + Object.values(officers).forEach(officer => { + if (officer.active && officer.expiresAt && officer.expiresAt <= now) { + officer.active = false + } + }) +} diff --git a/src/logic/planetLogic.ts b/src/logic/planetLogic.ts new file mode 100644 index 0000000..7c5a442 --- /dev/null +++ b/src/logic/planetLogic.ts @@ -0,0 +1,193 @@ +import type { Planet, Resources } from '@/types/game' +import { ShipType, DefenseType, BuildingType } from '@/types/game' +import { MOON_CONFIG } from '@/config/gameConfig' + +/** + * 创建初始星球 + */ +export const createInitialPlanet = (playerId: string, planetName: string = 'Home Planet'): Planet => { + const initialPlanet: Planet = { + id: 'planet1', + name: planetName, + ownerId: playerId, + position: { galaxy: 1, system: 1, position: 1 }, + resources: { + metal: 500, + crystal: 500, + deuterium: 0, + darkMatter: 0, + energy: 0 + }, + buildings: {} as Record, + fleet: { + [ShipType.LightFighter]: 0, + [ShipType.HeavyFighter]: 0, + [ShipType.Cruiser]: 0, + [ShipType.Battleship]: 0, + [ShipType.SmallCargo]: 0, + [ShipType.LargeCargo]: 0, + [ShipType.ColonyShip]: 0, + [ShipType.Recycler]: 0, + [ShipType.EspionageProbe]: 0, + [ShipType.DarkMatterHarvester]: 0 + }, + defense: { + [DefenseType.RocketLauncher]: 0, + [DefenseType.LightLaser]: 0, + [DefenseType.HeavyLaser]: 0, + [DefenseType.GaussCannon]: 0, + [DefenseType.IonCannon]: 0, + [DefenseType.PlasmaTurret]: 0, + [DefenseType.SmallShieldDome]: 0, + [DefenseType.LargeShieldDome]: 0 + }, + buildQueue: [], + lastUpdate: Date.now(), + maxSpace: 200, + isMoon: false + } + + // 初始化建筑等级 + Object.values(BuildingType).forEach(building => { + initialPlanet.buildings[building] = 0 + }) + + return initialPlanet +} + +/** + * 创建NPC星球 + */ +export const createNPCPlanet = ( + npcId: number, + position: { galaxy: number; system: number; position: number }, + planetPrefix: string = 'Planet' +): Planet => { + const npcPlanet: Planet = { + id: `npc_planet_${npcId}`, + name: `${planetPrefix} ${position.galaxy}:${position.system}:${position.position}`, + ownerId: `npc_${npcId}`, + position, + resources: { + metal: Math.floor(Math.random() * 10000) + 5000, + crystal: Math.floor(Math.random() * 5000) + 2000, + deuterium: Math.floor(Math.random() * 2000) + 500, + darkMatter: Math.floor(Math.random() * 100), + energy: 0 + }, + buildings: {} as Record, + fleet: { + [ShipType.LightFighter]: Math.floor(Math.random() * 50), + [ShipType.HeavyFighter]: Math.floor(Math.random() * 20), + [ShipType.Cruiser]: Math.floor(Math.random() * 10), + [ShipType.Battleship]: Math.floor(Math.random() * 5), + [ShipType.SmallCargo]: Math.floor(Math.random() * 10), + [ShipType.LargeCargo]: Math.floor(Math.random() * 5), + [ShipType.ColonyShip]: 0, + [ShipType.Recycler]: 0, + [ShipType.EspionageProbe]: 0, + [ShipType.DarkMatterHarvester]: 0 + }, + defense: { + [DefenseType.RocketLauncher]: Math.floor(Math.random() * 100), + [DefenseType.LightLaser]: Math.floor(Math.random() * 50), + [DefenseType.HeavyLaser]: Math.floor(Math.random() * 20), + [DefenseType.GaussCannon]: Math.floor(Math.random() * 10), + [DefenseType.IonCannon]: Math.floor(Math.random() * 10), + [DefenseType.PlasmaTurret]: Math.floor(Math.random() * 5), + [DefenseType.SmallShieldDome]: Math.random() > 0.5 ? 1 : 0, + [DefenseType.LargeShieldDome]: Math.random() > 0.8 ? 1 : 0 + }, + buildQueue: [], + lastUpdate: Date.now(), + maxSpace: 200, + isMoon: false + } + + // 随机初始化建筑等级 + Object.values(BuildingType).forEach(building => { + npcPlanet.buildings[building] = Math.floor(Math.random() * 10) + }) + + return npcPlanet +} + +/** + * 计算月球生成概率 + */ +export const calculateMoonChance = (debrisField: Resources): number => { + const totalDebris = debrisField.metal + debrisField.crystal + if (totalDebris < MOON_CONFIG.minDebrisField) return 0 + + const chance = MOON_CONFIG.baseChance + Math.floor(totalDebris / MOON_CONFIG.chancePerDebris) + return Math.min(chance, MOON_CONFIG.maxChance) +} + +/** + * 创建月球 + */ +export const createMoon = ( + parentPlanet: Planet, + position: { galaxy: number; system: number; position: number }, + playerId: string, + moonSuffix: string = "'s Moon" +): Planet => { + const moonId = `moon_${Date.now()}` + const moon: Planet = { + id: moonId, + name: `${parentPlanet.name}${moonSuffix}`, + ownerId: playerId, + position: { ...position }, + resources: { + metal: 0, + crystal: 0, + deuterium: 0, + darkMatter: 0, + energy: 0 + }, + buildings: {} as Record, + fleet: { + [ShipType.LightFighter]: 0, + [ShipType.HeavyFighter]: 0, + [ShipType.Cruiser]: 0, + [ShipType.Battleship]: 0, + [ShipType.SmallCargo]: 0, + [ShipType.LargeCargo]: 0, + [ShipType.ColonyShip]: 0, + [ShipType.Recycler]: 0, + [ShipType.EspionageProbe]: 0, + [ShipType.DarkMatterHarvester]: 0 + }, + defense: { + [DefenseType.RocketLauncher]: 0, + [DefenseType.LightLaser]: 0, + [DefenseType.HeavyLaser]: 0, + [DefenseType.GaussCannon]: 0, + [DefenseType.IonCannon]: 0, + [DefenseType.PlasmaTurret]: 0, + [DefenseType.SmallShieldDome]: 0, + [DefenseType.LargeShieldDome]: 0 + }, + buildQueue: [], + lastUpdate: Date.now(), + maxSpace: MOON_CONFIG.baseSize, + isMoon: true, + parentPlanetId: parentPlanet.id + } + + // 初始化建筑等级 + Object.values(BuildingType).forEach(building => { + moon.buildings[building] = 0 + }) + + return moon +} + +/** + * 计算月球空间上限 + */ +export const calculateMoonMaxSpace = (moon: Planet): number => { + if (!moon.isMoon) return 0 + const lunarBaseLevel = moon.buildings[BuildingType.LunarBase] || 0 + return MOON_CONFIG.baseSize + lunarBaseLevel * MOON_CONFIG.lunarBaseSpaceBonus +} diff --git a/src/logic/pointsLogic.ts b/src/logic/pointsLogic.ts new file mode 100644 index 0000000..6ad621c --- /dev/null +++ b/src/logic/pointsLogic.ts @@ -0,0 +1,137 @@ +import type { Player, Resources, BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game' +import { SHIPS } from '@/config/gameConfig' +import { DEFENSES } from '@/config/gameConfig' +import * as buildingLogic from './buildingLogic' +import * as researchLogic from './researchLogic' + +/** + * 计算资源总和(仅计算金属+晶体+重氢) + * 根据游戏规则:不包括暗物质和能量 + */ +export const calculateResourceCost = (resources: Resources): number => { + return resources.metal + resources.crystal + resources.deuterium +} + +/** + * 将资源总和转换为积分 + * 规则:每1000资源 = 1分 + */ +export const calculatePointsFromResources = (resourceCost: number): number => { + return Math.floor(resourceCost / 1000) +} + +/** + * 为玩家添加积分 + */ +export const addPoints = (player: Player, points: number): void => { + player.points += points +} + +/** + * 计算建筑升级到指定等级所获得的积分 + * @param buildingType 建筑类型 + * @param fromLevel 起始等级(不包括) + * @param toLevel 目标等级(包括) + * @returns 积分数 + */ +export const calculateBuildingPoints = (buildingType: BuildingType, fromLevel: number, toLevel: number): number => { + let totalPoints = 0 + for (let level = fromLevel + 1; level <= toLevel; level++) { + const cost = buildingLogic.calculateBuildingCost(buildingType, level) + const resourceCost = calculateResourceCost(cost) + totalPoints += calculatePointsFromResources(resourceCost) + } + return totalPoints +} + +/** + * 计算科技研究到指定等级所获得的积分 + * @param technologyType 科技类型 + * @param fromLevel 起始等级(不包括) + * @param toLevel 目标等级(包括) + * @returns 积分数 + */ +export const calculateTechnologyPoints = (technologyType: TechnologyType, fromLevel: number, toLevel: number): number => { + let totalPoints = 0 + for (let level = fromLevel + 1; level <= toLevel; level++) { + const cost = researchLogic.calculateTechnologyCost(technologyType, level) + const resourceCost = calculateResourceCost(cost) + totalPoints += calculatePointsFromResources(resourceCost) + } + return totalPoints +} + +/** + * 计算舰船建造所获得的积分 + * @param shipType 舰船类型 + * @param quantity 数量 + * @returns 积分数 + */ +export const calculateShipPoints = (shipType: ShipType, quantity: number): number => { + const config = SHIPS[shipType] + const resourceCost = calculateResourceCost(config.cost) + const pointsPerShip = calculatePointsFromResources(resourceCost) + return pointsPerShip * quantity +} + +/** + * 计算防御建造所获得的积分 + * @param defenseType 防御类型 + * @param quantity 数量 + * @returns 积分数 + */ +export const calculateDefensePoints = (defenseType: DefenseType, quantity: number): number => { + const config = DEFENSES[defenseType] + const resourceCost = calculateResourceCost(config.cost) + const pointsPerDefense = calculatePointsFromResources(resourceCost) + return pointsPerDefense * quantity +} + +/** + * 计算玩家当前的总积分(用于数据迁移或重新计算) + * 会计算所有建筑、科技、舰船、防御的累积积分 + */ +export const calculateTotalPlayerPoints = (player: Player): number => { + let totalPoints = 0 + + // 计算所有星球的建筑积分 + for (const planet of player.planets) { + for (const [buildingType, level] of Object.entries(planet.buildings)) { + if (level > 0) { + totalPoints += calculateBuildingPoints(buildingType as BuildingType, 0, level) + } + } + + // 计算所有星球的舰船积分 + for (const [shipType, quantity] of Object.entries(planet.fleet)) { + if (quantity > 0) { + totalPoints += calculateShipPoints(shipType as ShipType, quantity) + } + } + + // 计算所有星球的防御积分 + for (const [defenseType, quantity] of Object.entries(planet.defense)) { + if (quantity > 0) { + totalPoints += calculateDefensePoints(defenseType as DefenseType, quantity) + } + } + } + + // 计算所有科技的积分 + for (const [technologyType, level] of Object.entries(player.technologies)) { + if (level > 0) { + totalPoints += calculateTechnologyPoints(technologyType as TechnologyType, 0, level) + } + } + + // 计算正在飞行的舰队积分(舰队已经建造,所以也计入积分) + for (const mission of player.fleetMissions) { + for (const [shipType, quantity] of Object.entries(mission.fleet)) { + if (quantity && quantity > 0) { + totalPoints += calculateShipPoints(shipType as ShipType, quantity) + } + } + } + + return totalPoints +} diff --git a/src/logic/publicLogic.ts b/src/logic/publicLogic.ts new file mode 100644 index 0000000..ff5fffa --- /dev/null +++ b/src/logic/publicLogic.ts @@ -0,0 +1,102 @@ +/** + * 公共业务逻辑模块 + * 提供跨模块共享的通用业务逻辑功能 + */ + +import { BuildingType, TechnologyType } from '@/types/game' +import type { Planet, Resources, Officer } from '@/types/game' +import { OfficerType } from '@/types/game' +import * as officerLogic from '@/logic/officerLogic' +import * as resourceLogic from '@/logic/resourceLogic' + +/** + * 检查建造/研发前置条件是否满足 + * @param planet 星球对象 + * @param technologies 已研究的科技等级 + * @param requirements 前置条件要求(建筑等级或科技等级) + * @returns 是否满足前置条件 + */ +export const checkRequirements = ( + planet: Planet | undefined, + technologies: Partial>, + requirements?: Partial> +): boolean => { + // 如果星球不存在或没有前置条件,默认返回 true + if (!planet || !requirements) return true + + // 检查所有前置条件 + for (const [key, requiredLevel] of Object.entries(requirements)) { + // 检查是否为建筑类型 + if (Object.values(BuildingType).includes(key as BuildingType)) { + const currentLevel = planet.buildings[key as BuildingType] || 0 + if (currentLevel < requiredLevel) { + return false + } + } + // 检查是否为科技类型 + else if (Object.values(TechnologyType).includes(key as TechnologyType)) { + const currentLevel = technologies[key as TechnologyType] || 0 + if (currentLevel < requiredLevel) { + return false + } + } + } + + return true +} + +/** + * 计算星球的资源产量(包含军官加成) + * @param planet 星球对象 + * @param officers 玩家的军官对象 + * @returns 每小时各类资源的产量 + */ +export const getResourceProduction = (planet: Planet, officers: Record): Resources => { + // 计算当前激活的军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + // 根据建筑等级和军官加成计算资源产量 + return resourceLogic.calculateResourceProduction(planet, bonuses) +} + +/** + * 计算星球的资源存储容量(包含军官加成) + * @param planet 星球对象 + * @param officers 玩家的军官对象 + * @returns 各类资源的最大存储容量 + */ +export const getResourceCapacity = (planet: Planet, officers: Record): Resources => { + // 计算当前激活的军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + // 根据仓库建筑等级和军官加成计算存储容量 + return resourceLogic.calculateResourceCapacity(planet, bonuses.storageCapacityBonus) +} + +/** + * 计算最大建造队列数量 + * @param planet 星球对象 + * @param additionalBuildQueue 军官提供的额外队列数量 + * @returns 最大建造队列数量(基础1个 + 纳米工厂等级 + 军官加成,最多10个) + */ +export const getMaxBuildQueue = (planet: Planet, additionalBuildQueue: number = 0): number => { + const naniteFactoryLevel = planet.buildings[BuildingType.NaniteFactory] || 0 + return Math.min(1 + naniteFactoryLevel + additionalBuildQueue, 10) +} + +/** + * 计算最大研究队列数量 + * @param technologies 已研究的科技等级 + * @returns 最大研究队列数量(基础1个 + 计算机技术等级,最多10个) + */ +export const getMaxResearchQueue = (technologies: Partial>): number => { + const computerTechLevel = technologies[TechnologyType.ComputerTechnology] || 0 + return Math.min(1 + computerTechLevel, 10) +} + +/** + * 计算最大舰队任务数量 + * @param additionalFleetSlots 军官提供的额外槽位数量 + * @returns 最大舰队任务数量(基础1个 + 军官加成,最多10个) + */ +export const getMaxFleetMissions = (additionalFleetSlots: number = 0): number => { + return Math.min(1 + additionalFleetSlots, 10) +} diff --git a/src/logic/researchLogic.ts b/src/logic/researchLogic.ts new file mode 100644 index 0000000..a5c377f --- /dev/null +++ b/src/logic/researchLogic.ts @@ -0,0 +1,96 @@ +import type { Resources, BuildQueueItem } from '@/types/game' +import { TechnologyType, BuildingType } from '@/types/game' +import { TECHNOLOGIES } from '@/config/gameConfig' +import * as pointsLogic from './pointsLogic' + +/** + * 计算科技研究成本 + */ +export const calculateTechnologyCost = (techType: TechnologyType, targetLevel: number): Resources => { + const config = TECHNOLOGIES[techType] + const multiplier = Math.pow(config.costMultiplier, targetLevel - 1) + return { + metal: Math.floor(config.baseCost.metal * multiplier), + crystal: Math.floor(config.baseCost.crystal * multiplier), + deuterium: Math.floor(config.baseCost.deuterium * multiplier), + darkMatter: Math.floor(config.baseCost.darkMatter * multiplier), + energy: 0 + } +} + +/** + * 计算科技研究时间 + */ +export const calculateTechnologyTime = (techType: TechnologyType, currentLevel: number, researchSpeedBonus: number = 0): number => { + const config = TECHNOLOGIES[techType] + const baseTime = config.baseTime * Math.pow(config.costMultiplier, currentLevel) + const speedMultiplier = 1 - researchSpeedBonus / 100 + return Math.floor(baseTime * speedMultiplier) +} + +/** + * 检查科技研究条件 + */ +export const checkTechnologyRequirements = ( + techType: TechnologyType, + buildings: Partial>, + technologies: Partial> +): boolean => { + const config = TECHNOLOGIES[techType] + if (!config.requirements) return true + + for (const [key, level] of Object.entries(config.requirements)) { + if (Object.values(BuildingType).includes(key as BuildingType)) { + if ((buildings[key as BuildingType] || 0) < level) { + return false + } + } else if (Object.values(TechnologyType).includes(key as TechnologyType)) { + if ((technologies[key as TechnologyType] || 0) < level) { + return false + } + } + } + return true +} + +/** + * 创建研究队列项 + */ +export const createResearchQueueItem = (techType: TechnologyType, targetLevel: number, researchTime: number): BuildQueueItem => { + const now = Date.now() + return { + id: `research_${now}`, + type: 'technology', + itemType: techType, + targetLevel, + startTime: now, + endTime: now + researchTime * 1000 + } +} + +/** + * 处理研究完成 + */ +export const completeResearchQueue = ( + researchQueue: BuildQueueItem[], + technologies: Partial>, + now: number, + onPointsEarned?: (points: number, type: 'technology', itemType: string, level: number) => void +): BuildQueueItem[] => { + return researchQueue.filter(item => { + if (now >= item.endTime) { + // 研究完成 + const oldLevel = technologies[item.itemType as TechnologyType] || 0 + const newLevel = item.targetLevel || 0 + technologies[item.itemType as TechnologyType] = newLevel + + // 计算并累积积分 + if (onPointsEarned && newLevel > oldLevel) { + const points = pointsLogic.calculateTechnologyPoints(item.itemType as TechnologyType, oldLevel, newLevel) + onPointsEarned(points, 'technology', item.itemType, newLevel) + } + return false + } + return true + }) +} diff --git a/src/logic/researchValidation.ts b/src/logic/researchValidation.ts new file mode 100644 index 0000000..3a91d10 --- /dev/null +++ b/src/logic/researchValidation.ts @@ -0,0 +1,81 @@ +import type { Planet, Resources, BuildQueueItem, Officer } from '@/types/game' +import { TechnologyType, OfficerType } from '@/types/game' +import * as researchLogic from './researchLogic' +import * as resourceLogic from './resourceLogic' +import * as publicLogic from './publicLogic' +import * as officerLogic from './officerLogic' + +/** + * 验证科技研究的所有条件 + */ +export const validateTechnologyResearch = ( + planet: Planet, + techType: TechnologyType, + technologies: Partial>, + researchQueue: BuildQueueItem[] +): { + valid: boolean + reason?: string +} => { + const currentLevel = technologies[techType] || 0 + const targetLevel = currentLevel + 1 + const cost = researchLogic.calculateTechnologyCost(techType, targetLevel) + + // 检查研究队列是否已满 + const maxQueue = publicLogic.getMaxResearchQueue(technologies) + if (researchQueue.length >= maxQueue) { + return { valid: false, reason: 'errors.researchQueueFull' } + } + + // 检查前置条件 + if (!researchLogic.checkTechnologyRequirements(techType, planet.buildings, technologies)) { + return { valid: false, reason: 'errors.requirementsNotMet' } + } + + // 检查资源 + if (!resourceLogic.checkResourcesAvailable(planet.resources, cost)) { + return { valid: false, reason: 'errors.insufficientResources' } + } + + return { valid: true } +} + +/** + * 执行科技研究(扣除资源,创建队列项) + */ +export const executeTechnologyResearch = ( + planet: Planet, + techType: TechnologyType, + currentLevel: number, + officers: Record +): { queueItem: BuildQueueItem } => { + const targetLevel = currentLevel + 1 + const cost = researchLogic.calculateTechnologyCost(techType, targetLevel) + + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + const time = researchLogic.calculateTechnologyTime(techType, currentLevel, bonuses.researchSpeedBonus) + + // 扣除资源 + resourceLogic.deductResources(planet.resources, cost) + + // 创建队列项 + const queueItem = researchLogic.createResearchQueueItem(techType, targetLevel, time) + + return { queueItem } +} + +/** + * 取消研究并计算返还资源 + */ +export const cancelTechnologyResearch = (queueItem: BuildQueueItem): Resources => { + const cost = researchLogic.calculateTechnologyCost(queueItem.itemType as TechnologyType, queueItem.targetLevel || 1) + + return { + metal: Math.floor(cost.metal * 0.5), + crystal: Math.floor(cost.crystal * 0.5), + deuterium: Math.floor(cost.deuterium * 0.5), + darkMatter: Math.floor(cost.darkMatter * 0.5), + energy: 0 + } +} diff --git a/src/logic/resourceLogic.ts b/src/logic/resourceLogic.ts new file mode 100644 index 0000000..b664501 --- /dev/null +++ b/src/logic/resourceLogic.ts @@ -0,0 +1,161 @@ +import type { Planet, Resources } from '@/types/game' +import { BuildingType } from '@/types/game' + +/** + * 计算电量产出 + */ +export const calculateEnergyProduction = ( + planet: Planet, + bonuses: { + energyProductionBonus: number + } +): number => { + const solarPlantLevel = planet.buildings[BuildingType.SolarPlant] || 0 + const energyBonus = 1 + (bonuses.energyProductionBonus || 0) / 100 + + // 太阳能电站每级产出:50 * 1.1^等级 + return solarPlantLevel * 50 * Math.pow(1.1, solarPlantLevel) * energyBonus +} + +/** + * 计算电量消耗 + */ +export const calculateEnergyConsumption = (planet: Planet): number => { + const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0 + const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0 + const deuteriumSynthesizerLevel = planet.buildings[BuildingType.DeuteriumSynthesizer] || 0 + + // 矿场每级消耗:10 * 1.1^等级 + const metalConsumption = metalMineLevel * 10 * Math.pow(1.1, metalMineLevel) + const crystalConsumption = crystalMineLevel * 10 * Math.pow(1.1, crystalMineLevel) + const deuteriumConsumption = deuteriumSynthesizerLevel * 15 * Math.pow(1.1, deuteriumSynthesizerLevel) + + return metalConsumption + crystalConsumption + deuteriumConsumption +} + +/** + * 计算资源产量(每小时) + */ +export const calculateResourceProduction = ( + planet: Planet, + bonuses: { + resourceProductionBonus: number + darkMatterProductionBonus: number + energyProductionBonus: number + } +): Resources => { + const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0 + const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0 + const deuteriumSynthesizerLevel = planet.buildings[BuildingType.DeuteriumSynthesizer] || 0 + const darkMatterCollectorLevel = planet.buildings[BuildingType.DarkMatterCollector] || 0 + + const resourceBonus = 1 + (bonuses.resourceProductionBonus || 0) / 100 + const darkMatterBonus = 1 + (bonuses.darkMatterProductionBonus || 0) / 100 + + // 计算电量情况 + const energyProduction = calculateEnergyProduction(planet, { energyProductionBonus: bonuses.energyProductionBonus }) + const energyConsumption = calculateEnergyConsumption(planet) + const energyBalance = energyProduction - energyConsumption + + // 如果电量不足,资源产量按比例减少 + const productionEfficiency = energyBalance >= 0 ? 1 : Math.max(0, energyProduction / energyConsumption) + + return { + metal: metalMineLevel * 150 * Math.pow(1.1, metalMineLevel) * resourceBonus * productionEfficiency, + crystal: crystalMineLevel * 100 * Math.pow(1.1, crystalMineLevel) * resourceBonus * productionEfficiency, + deuterium: deuteriumSynthesizerLevel * 50 * Math.pow(1.1, deuteriumSynthesizerLevel) * resourceBonus * productionEfficiency, + darkMatter: darkMatterCollectorLevel * 2.5 * Math.pow(1.1, darkMatterCollectorLevel) * darkMatterBonus, + energy: energyBalance + } +} + +/** + * 计算资源容量 + */ +export const calculateResourceCapacity = (planet: Planet, storageCapacityBonus: number): Resources => { + const metalStorageLevel = planet.buildings[BuildingType.MetalStorage] || 0 + const crystalStorageLevel = planet.buildings[BuildingType.CrystalStorage] || 0 + const deuteriumTankLevel = planet.buildings[BuildingType.DeuteriumTank] || 0 + const darkMatterCollectorLevel = planet.buildings[BuildingType.DarkMatterCollector] || 0 + + const bonus = 1 + (storageCapacityBonus || 0) / 100 + + const baseCapacity = 10000 + return { + metal: baseCapacity * Math.pow(2, metalStorageLevel) * bonus, + crystal: baseCapacity * Math.pow(2, crystalStorageLevel) * bonus, + deuterium: baseCapacity * Math.pow(2, deuteriumTankLevel) * bonus, + darkMatter: 1000 + darkMatterCollectorLevel * 100, // 暗物质容量较小 + energy: 0 // 电量不存储,实时计算 + } +} + +/** + * 更新星球资源 + */ +export const updatePlanetResources = ( + planet: Planet, + now: number, + bonuses: { + resourceProductionBonus: number + darkMatterProductionBonus: number + energyProductionBonus: number + storageCapacityBonus: number + } +): void => { + const timeDiff = (now - planet.lastUpdate) / 1000 // 转换为秒 + + // 计算资源产量(每小时) + const production = calculateResourceProduction(planet, { + resourceProductionBonus: bonuses.resourceProductionBonus, + darkMatterProductionBonus: bonuses.darkMatterProductionBonus, + energyProductionBonus: bonuses.energyProductionBonus + }) + + // 更新资源(转换为每秒产量) + planet.resources.metal += (production.metal * timeDiff) / 3600 + planet.resources.crystal += (production.crystal * timeDiff) / 3600 + planet.resources.deuterium += (production.deuterium * timeDiff) / 3600 + planet.resources.darkMatter += (production.darkMatter * timeDiff) / 3600 + + // 限制资源上限 + const capacity = calculateResourceCapacity(planet, bonuses.storageCapacityBonus) + planet.resources.metal = Math.min(planet.resources.metal, capacity.metal) + planet.resources.crystal = Math.min(planet.resources.crystal, capacity.crystal) + planet.resources.deuterium = Math.min(planet.resources.deuterium, capacity.deuterium) + planet.resources.darkMatter = Math.min(planet.resources.darkMatter, capacity.darkMatter) + + planet.lastUpdate = now +} + +/** + * 检查资源是否足够 + */ +export const checkResourcesAvailable = (currentResources: Resources, cost: Resources): boolean => { + return ( + currentResources.metal >= cost.metal && + currentResources.crystal >= cost.crystal && + currentResources.deuterium >= cost.deuterium && + currentResources.darkMatter >= cost.darkMatter + ) +} + +/** + * 扣除资源 + */ +export const deductResources = (currentResources: Resources, cost: Resources): void => { + currentResources.metal -= cost.metal + currentResources.crystal -= cost.crystal + currentResources.deuterium -= cost.deuterium + currentResources.darkMatter -= cost.darkMatter +} + +/** + * 添加资源 + */ +export const addResources = (currentResources: Resources, amount: Resources): void => { + currentResources.metal += amount.metal + currentResources.crystal += amount.crystal + currentResources.deuterium += amount.deuterium + currentResources.darkMatter += amount.darkMatter +} diff --git a/src/logic/shipLogic.ts b/src/logic/shipLogic.ts new file mode 100644 index 0000000..a0c5ce3 --- /dev/null +++ b/src/logic/shipLogic.ts @@ -0,0 +1,231 @@ +import type { Resources, BuildQueueItem, Fleet } from '@/types/game' +import { ShipType, DefenseType, BuildingType, TechnologyType } from '@/types/game' +import { SHIPS, DEFENSES } from '@/config/gameConfig' + +/** + * 计算舰船建造成本 + */ +export const calculateShipCost = (shipType: ShipType, quantity: number): Resources => { + const config = SHIPS[shipType] + return { + metal: config.cost.metal * quantity, + crystal: config.cost.crystal * quantity, + deuterium: config.cost.deuterium * quantity, + darkMatter: config.cost.darkMatter * quantity, + energy: 0 + } +} + +/** + * 计算防御设施建造成本 + */ +export const calculateDefenseCost = (defenseType: DefenseType, quantity: number): Resources => { + const config = DEFENSES[defenseType] + return { + metal: config.cost.metal * quantity, + crystal: config.cost.crystal * quantity, + deuterium: config.cost.deuterium * quantity, + darkMatter: config.cost.darkMatter * quantity, + energy: 0 + } +} + +/** + * 计算舰船建造时间 + */ +export const calculateShipBuildTime = (shipType: ShipType, quantity: number, buildingSpeedBonus: number = 0): number => { + const config = SHIPS[shipType] + const baseTime = config.buildTime * quantity + const speedMultiplier = 1 - buildingSpeedBonus / 100 + return Math.floor(baseTime * speedMultiplier) +} + +/** + * 计算防御设施建造时间 + */ +export const calculateDefenseBuildTime = (defenseType: DefenseType, quantity: number, buildingSpeedBonus: number = 0): number => { + const config = DEFENSES[defenseType] + const baseTime = config.buildTime * quantity + const speedMultiplier = 1 - buildingSpeedBonus / 100 + return Math.floor(baseTime * speedMultiplier) +} + +/** + * 检查舰船建造条件 + */ +export const checkShipRequirements = ( + shipType: ShipType, + buildings: Partial>, + technologies: Partial> +): boolean => { + const config = SHIPS[shipType] + if (!config.requirements) return true + + for (const [key, level] of Object.entries(config.requirements)) { + if (Object.values(BuildingType).includes(key as BuildingType)) { + if ((buildings[key as BuildingType] || 0) < level) { + return false + } + } else if (Object.values(TechnologyType).includes(key as TechnologyType)) { + if ((technologies[key as TechnologyType] || 0) < level) { + return false + } + } + } + return true +} + +/** + * 检查防御设施建造条件 + */ +export const checkDefenseRequirements = ( + defenseType: DefenseType, + buildings: Partial>, + technologies: Partial> +): boolean => { + const config = DEFENSES[defenseType] + if (!config.requirements) return true + + for (const [key, level] of Object.entries(config.requirements)) { + if (Object.values(BuildingType).includes(key as BuildingType)) { + if ((buildings[key as BuildingType] || 0) < level) { + return false + } + } else if (Object.values(TechnologyType).includes(key as TechnologyType)) { + if ((technologies[key as TechnologyType] || 0) < level) { + return false + } + } + } + return true +} + +/** + * 检查防御罩数量限制 + */ +export const checkShieldDomeLimit = ( + defenseType: DefenseType, + currentDefense: Partial>, + quantity: number +): boolean => { + if (defenseType === DefenseType.SmallShieldDome || defenseType === DefenseType.LargeShieldDome) { + if ((currentDefense[defenseType] || 0) > 0) { + return false + } + if (quantity > 1) { + return false + } + } + return true +} + +/** + * 创建舰船建造队列项 + */ +export const createShipQueueItem = (shipType: ShipType, quantity: number, buildTime: number): BuildQueueItem => { + const now = Date.now() + return { + id: `ship_${now}`, + type: 'ship', + itemType: shipType, + quantity, + startTime: now, + endTime: now + buildTime * 1000 + } +} + +/** + * 创建防御设施建造队列项 + */ +export const createDefenseQueueItem = (defenseType: DefenseType, quantity: number, buildTime: number): BuildQueueItem => { + const now = Date.now() + return { + id: `defense_${now}`, + type: 'defense', + itemType: defenseType, + quantity, + startTime: now, + endTime: now + buildTime * 1000 + } +} + +/** + * 检查舰队是否足够 + */ +export const checkFleetAvailable = (currentFleet: Partial, requiredFleet: Partial): boolean => { + for (const [shipType, count] of Object.entries(requiredFleet)) { + if ((currentFleet[shipType as ShipType] || 0) < count) { + return false + } + } + return true +} + +/** + * 计算舰队燃料消耗(包含货物重量影响) + * @param fleet 舰队组成 + * @param fuelConsumptionReduction 燃料消耗减少百分比 + * @param cargo 携带的货物(可选) + * @returns 总燃料消耗(重氢) + */ +export const calculateFleetFuelConsumption = ( + fleet: Partial, + fuelConsumptionReduction: number = 0, + cargo?: Resources +): number => { + // 计算舰船基础燃料消耗 + let baseFuelNeeded = 0 + for (const [shipType, count] of Object.entries(fleet)) { + const config = SHIPS[shipType as ShipType] + baseFuelNeeded += config.fuelConsumption * count + } + + // 计算货物额外燃料消耗 + // 每1000单位资源增加1点燃料消耗 + let cargoFuelNeeded = 0 + if (cargo) { + const totalCargo = cargo.metal + cargo.crystal + cargo.deuterium + cargo.darkMatter + cargoFuelNeeded = Math.floor(totalCargo / 1000) + } + + // 应用燃料消耗减少加成(仅应用于基础燃料,不影响货物燃料) + const reductionMultiplier = 1 - fuelConsumptionReduction / 100 + const reducedBaseFuel = Math.floor(baseFuelNeeded * reductionMultiplier) + + return reducedBaseFuel + cargoFuelNeeded +} + +/** + * 计算舰队最慢速度 + */ +export const calculateFleetMinSpeed = (fleet: Partial, fleetSpeedBonus: number = 0): number => { + let minSpeed = Infinity + for (const [shipType, count] of Object.entries(fleet)) { + if (count > 0) { + const config = SHIPS[shipType as ShipType] + minSpeed = Math.min(minSpeed, config.speed) + } + } + const speedMultiplier = 1 + fleetSpeedBonus / 100 + return Math.floor(minSpeed * speedMultiplier) +} + +/** + * 扣除舰队 + */ +export const deductFleet = (currentFleet: Fleet, fleet: Partial): void => { + for (const [shipType, count] of Object.entries(fleet)) { + currentFleet[shipType as ShipType] -= count + } +} + +/** + * 添加舰队 + */ +export const addFleet = (currentFleet: Fleet, fleet: Partial): void => { + for (const [shipType, count] of Object.entries(fleet)) { + if (count > 0) { + currentFleet[shipType as ShipType] += count + } + } +} diff --git a/src/logic/shipValidation.ts b/src/logic/shipValidation.ts new file mode 100644 index 0000000..3255b26 --- /dev/null +++ b/src/logic/shipValidation.ts @@ -0,0 +1,168 @@ +import type { Planet, Resources, BuildQueueItem, Fleet, Officer } from '@/types/game' +import { ShipType, DefenseType, TechnologyType, OfficerType } from '@/types/game' +import * as shipLogic from './shipLogic' +import * as resourceLogic from './resourceLogic' +import * as officerLogic from './officerLogic' +import * as publicLogic from './publicLogic' + +/** + * 验证舰船建造的所有条件 + */ +export const validateShipBuild = ( + planet: Planet, + shipType: ShipType, + quantity: number, + technologies: Partial> +): { + valid: boolean + reason?: string +} => { + const totalCost = shipLogic.calculateShipCost(shipType, quantity) + + // 检查前置条件 + if (!shipLogic.checkShipRequirements(shipType, planet.buildings, technologies)) { + return { valid: false, reason: 'errors.requirementsNotMet' } + } + + // 检查资源 + if (!resourceLogic.checkResourcesAvailable(planet.resources, totalCost)) { + return { valid: false, reason: 'errors.insufficientResources' } + } + + return { valid: true } +} + +/** + * 执行舰船建造 + */ +export const executeShipBuild = ( + planet: Planet, + shipType: ShipType, + quantity: number, + officers: Record +): BuildQueueItem => { + const totalCost = shipLogic.calculateShipCost(shipType, quantity) + + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + const buildTime = shipLogic.calculateShipBuildTime(shipType, quantity, bonuses.buildingSpeedBonus) + + // 扣除资源 + resourceLogic.deductResources(planet.resources, totalCost) + + // 创建队列项 + return shipLogic.createShipQueueItem(shipType, quantity, buildTime) +} + +/** + * 验证防御建造的所有条件 + */ +export const validateDefenseBuild = ( + planet: Planet, + defenseType: DefenseType, + quantity: number, + technologies: Partial> +): { + valid: boolean + reason?: string +} => { + const totalCost = shipLogic.calculateDefenseCost(defenseType, quantity) + + // 检查前置条件 + if (!shipLogic.checkDefenseRequirements(defenseType, planet.buildings, technologies)) { + return { valid: false, reason: 'errors.requirementsNotMet' } + } + + // 检查资源 + if (!resourceLogic.checkResourcesAvailable(planet.resources, totalCost)) { + return { valid: false, reason: 'errors.insufficientResources' } + } + + // 护盾罩限制 + if (!shipLogic.checkShieldDomeLimit(defenseType, planet.defense, quantity)) { + return { valid: false, reason: 'errors.shieldDomeLimit' } + } + + return { valid: true } +} + +/** + * 执行防御建造 + */ +export const executeDefenseBuild = ( + planet: Planet, + defenseType: DefenseType, + quantity: number, + officers: Record +): BuildQueueItem => { + const totalCost = shipLogic.calculateDefenseCost(defenseType, quantity) + + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + const buildTime = shipLogic.calculateDefenseBuildTime(defenseType, quantity, bonuses.buildingSpeedBonus) + + // 扣除资源 + resourceLogic.deductResources(planet.resources, totalCost) + + // 创建队列项 + return shipLogic.createDefenseQueueItem(defenseType, quantity, buildTime) +} + +/** + * 验证舰队派遣的所有条件 + */ +export const validateFleetDispatch = ( + planet: Planet, + fleet: Partial, + cargo: Resources, + officers: Record, + currentFleetMissions: number = 0 +): { + valid: boolean + reason?: string + fuelNeeded?: number +} => { + // 计算军官加成 + const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) + + // 检查舰队任务槽位是否已满 + const maxFleetMissions = publicLogic.getMaxFleetMissions(bonuses.additionalFleetSlots) + if (currentFleetMissions >= maxFleetMissions) { + return { valid: false, reason: 'errors.fleetMissionsFull' } + } + + // 检查舰队是否足够 + if (!shipLogic.checkFleetAvailable(planet.fleet, fleet)) { + return { valid: false, reason: 'errors.insufficientFleet' } + } + + // 检查是否有足够的重氢作为燃料(包含货物重量影响) + const fuelNeeded = shipLogic.calculateFleetFuelConsumption(fleet, bonuses.fuelConsumptionReduction, cargo) + if (planet.resources.deuterium < fuelNeeded) { + return { valid: false, reason: 'errors.insufficientFuel', fuelNeeded } + } + + return { valid: true, fuelNeeded } +} + +/** + * 执行舰队派遣(扣除舰队和燃料) + */ +export const executeFleetDispatch = ( + planet: Planet, + fleet: Partial, + fuelNeeded: number, + shouldDeductCargo: boolean, + cargo: Resources +): void => { + // 扣除舰队 + shipLogic.deductFleet(planet.fleet, fleet) + + // 扣除燃料 + planet.resources.deuterium -= fuelNeeded + + // 扣除运输的资源 + if (shouldDeductCargo) { + resourceLogic.deductResources(planet.resources, cargo) + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..a93fd01 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,16 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' +import './style.css' +import App from './App.vue' +import router from './router' + +const app = createApp(App) +const pinia = createPinia() + +pinia.use(piniaPluginPersistedstate) + +app.use(pinia) +app.use(router) + +app.mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..507a039 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,20 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { path: '/', name: 'overview', component: () => import('@/views/OverviewView.vue') }, + { path: '/buildings', name: 'buildings', component: () => import('@/views/BuildingsView.vue') }, + { path: '/research', name: 'research', component: () => import('@/views/ResearchView.vue') }, + { path: '/shipyard', name: 'shipyard', component: () => import('@/views/ShipyardView.vue') }, + { path: '/defense', name: 'defense', component: () => import('@/views/DefenseView.vue') }, + { path: '/fleet', name: 'fleet', component: () => import('@/views/FleetView.vue') }, + { path: '/officers', name: 'officers', component: () => import('@/views/OfficersView.vue') }, + { path: '/battle-simulator', name: 'battle-simulator', component: () => import('@/views/BattleSimulatorView.vue') }, + { path: '/messages', name: 'messages', component: () => import('@/views/MessagesView.vue') }, + { path: '/galaxy', name: 'galaxy', component: () => import('@/views/GalaxyView.vue') }, + { path: '/settings', name: 'settings', component: () => import('@/views/SettingsView.vue') } + ] +}) + +export default router diff --git a/src/stores/detailDialogStore.ts b/src/stores/detailDialogStore.ts new file mode 100644 index 0000000..793f427 --- /dev/null +++ b/src/stores/detailDialogStore.ts @@ -0,0 +1,52 @@ +import { defineStore } from 'pinia' +import type { BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game' + +export type DetailDialogType = 'building' | 'technology' | 'ship' | 'defense' + +export interface DetailDialogState { + isOpen: boolean + type: DetailDialogType | null + itemType: BuildingType | TechnologyType | ShipType | DefenseType | null + currentLevel?: number // 用于建筑和科技 +} + +export const useDetailDialogStore = defineStore('detailDialog', { + state: (): DetailDialogState => ({ + isOpen: false, + type: null, + itemType: null, + currentLevel: undefined + }), + actions: { + openBuilding(buildingType: BuildingType, currentLevel: number) { + this.isOpen = true + this.type = 'building' + this.itemType = buildingType + this.currentLevel = currentLevel + }, + openTechnology(technologyType: TechnologyType, currentLevel: number) { + this.isOpen = true + this.type = 'technology' + this.itemType = technologyType + this.currentLevel = currentLevel + }, + openShip(shipType: ShipType) { + this.isOpen = true + this.type = 'ship' + this.itemType = shipType + this.currentLevel = undefined + }, + openDefense(defenseType: DefenseType) { + this.isOpen = true + this.type = 'defense' + this.itemType = defenseType + this.currentLevel = undefined + }, + close() { + this.isOpen = false + this.type = null + this.itemType = null + this.currentLevel = undefined + } + } +}) diff --git a/src/stores/gameStore.ts b/src/stores/gameStore.ts new file mode 100644 index 0000000..acae16b --- /dev/null +++ b/src/stores/gameStore.ts @@ -0,0 +1,47 @@ +import { defineStore } from 'pinia' +import type { Planet, Player, BuildQueueItem, FleetMission, BattleResult, SpyReport, Officer } from '@/types/game' +import { TechnologyType, OfficerType } from '@/types/game' +import type { Locale } from '@/locales' +import pkg from '../../package.json' +import { encryptData, decryptData } from '@/utils/crypto' + +export const useGameStore = defineStore('game', { + state: () => ({ + gameTime: Date.now(), + isPaused: false, + player: { + id: 'player1', + name: '', + planets: [] as Planet[], + technologies: {} as Record, + officers: {} as Record, + researchQueue: [] as BuildQueueItem[], + fleetMissions: [] as FleetMission[], + battleReports: [] as BattleResult[], + spyReports: [] as SpyReport[] + } as Player, + currentPlanetId: '', + isDark: '', + locale: 'zh-CN' as Locale, + sidebarCollapsed: window.innerWidth < 1024 ? false : true, + universePlanets: {} as Record + }), + getters: { + currentPlanet(): Planet | undefined { + return this.player.planets.find(p => p.id === this.currentPlanetId) + }, + getMoonForPlanet(): (planetId: string) => Planet | undefined { + return (planetId: string) => { + return this.player.planets.find(p => p.parentPlanetId === planetId && p.isMoon) + } + } + }, + persist: { + key: pkg.name, + storage: localStorage, + serializer: { + serialize: state => encryptData(state), + deserialize: value => decryptData(value) + } + } +}) diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..eb654e9 --- /dev/null +++ b/src/style.css @@ -0,0 +1,193 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.625rem; + + /* Light mode colors */ + --background: oklch(0.99 0 0); + --foreground: oklch(0.129 0.042 264.695); + --card: oklch(1 0 0); + --card-foreground: oklch(0.129 0.042 264.695); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.129 0.042 264.695); + --primary: oklch(0.208 0.042 265.755); + --primary-foreground: oklch(0.984 0.003 247.858); + --secondary: oklch(0.968 0.007 247.896); + --secondary-foreground: oklch(0.208 0.042 265.755); + --muted: oklch(0.968 0.007 247.896); + --muted-foreground: oklch(0.554 0.046 257.417); + --accent: oklch(0.968 0.007 247.896); + --accent-foreground: oklch(0.208 0.042 265.755); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.929 0.013 255.508); + --input: oklch(0.929 0.013 255.508); + --ring: oklch(0.704 0.04 256.788); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.984 0.003 247.858); + --sidebar-foreground: oklch(0.129 0.042 264.695); + --sidebar-primary: oklch(0.208 0.042 265.755); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.968 0.007 247.896); + --sidebar-accent-foreground: oklch(0.208 0.042 265.755); + --sidebar-border: oklch(0.929 0.013 255.508); + --sidebar-ring: oklch(0.704 0.04 256.788); +} + +.dark { + --background: oklch(0.1 0.015 264.695); + --foreground: oklch(0.984 0.003 247.858); + --card: oklch(0.15 0.02 264.695); + --card-foreground: oklch(0.984 0.003 247.858); + --popover: oklch(0.15 0.02 264.695); + --popover-foreground: oklch(0.984 0.003 247.858); + --primary: oklch(0.729 0.113 255.508); + --primary-foreground: oklch(0.984 0.003 247.858); + --secondary: oklch(0.25 0.025 260.031); + --secondary-foreground: oklch(0.984 0.003 247.858); + --muted: oklch(0.25 0.025 260.031); + --muted-foreground: oklch(0.604 0.04 256.788); + --accent: oklch(0.25 0.025 260.031); + --accent-foreground: oklch(0.984 0.003 247.858); + --destructive: oklch(0.604 0.191 22.216); + --border: oklch(0.25 0.025 260.031); + --input: oklch(0.25 0.025 260.031); + --ring: oklch(0.551 0.027 264.364); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.15 0.02 264.695); + --sidebar-foreground: oklch(0.984 0.003 247.858); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.25 0.025 260.031); + --sidebar-accent-foreground: oklch(0.984 0.003 247.858); + --sidebar-border: oklch(0.25 0.025 260.031); + --sidebar-ring: oklch(0.551 0.027 264.364); +} + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + + body { + @apply bg-background text-foreground; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + /* 平滑过渡 */ + html { + transition: background-color 0.3s ease, color 0.3s ease; + } + + /* 滚动条样式 */ + ::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + ::-webkit-scrollbar-track { + @apply bg-transparent; + } + + ::-webkit-scrollbar-thumb { + @apply bg-border rounded-lg; + } + + ::-webkit-scrollbar-thumb:hover { + @apply bg-muted-foreground/50; + } +} + +/* 自定义动画 */ +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fade-in 0.3s ease-out; +} + +/* 侧边栏导航按钮悬停效果 */ +aside nav a { + transition: all 0.2s ease; +} + +aside nav a:hover button { + transform: translateX(4px); +} + +/* 资源数字更新动画 */ +@keyframes pulse-resource { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.8; + } +} + +.resource-pulse { + animation: pulse-resource 2s ease-in-out infinite; +} \ No newline at end of file diff --git a/src/types/game.ts b/src/types/game.ts new file mode 100644 index 0000000..1354d44 --- /dev/null +++ b/src/types/game.ts @@ -0,0 +1,340 @@ +// 资源类型 +export interface Resources { + metal: number + crystal: number + deuterium: number + darkMatter: number // 暗物质 + energy: number // 电量(实时计算,不存储) +} + +// 建筑类型 +export const BuildingType = { + MetalMine: 'metalMine', + CrystalMine: 'crystalMine', + DeuteriumSynthesizer: 'deuteriumSynthesizer', + SolarPlant: 'solarPlant', + RoboticsFactory: 'roboticsFactory', + NaniteFactory: 'naniteFactory', // 纳米工厂 + Shipyard: 'shipyard', + ResearchLab: 'researchLab', + MetalStorage: 'metalStorage', + CrystalStorage: 'crystalStorage', + DeuteriumTank: 'deuteriumTank', + DarkMatterCollector: 'darkMatterCollector', // 暗物质收集器 + // 月球专属建筑 + LunarBase: 'lunarBase', // 月球基地 + SensorPhalanx: 'sensorPhalanx', // 传感器阵列 + JumpGate: 'jumpGate' // 跳跃门 +} as const + +export type BuildingType = (typeof BuildingType)[keyof typeof BuildingType] + +// 建筑配置 +export interface BuildingConfig { + id: BuildingType + name: string + description: string + baseCost: Resources + baseTime: number // 基础建造时间(秒) + costMultiplier: number // 升级成本倍数 + spaceUsage: number // 占用空间 + planetOnly?: boolean // 仅行星可建造 + moonOnly?: boolean // 仅月球可建造 + requirements?: Partial> // 前置条件 +} + +// 建筑实例 +export interface Building { + type: BuildingType + level: number +} + +// 研究科技类型 +export const TechnologyType = { + EnergyTechnology: 'energyTechnology', + LaserTechnology: 'laserTechnology', + IonTechnology: 'ionTechnology', + HyperspaceTechnology: 'hyperspaceTechnology', + PlasmaTechnology: 'plasmaTechnology', + ComputerTechnology: 'computerTechnology', // 计算机技术 + CombustionDrive: 'combustionDrive', + ImpulseDrive: 'impulseDrive', + HyperspaceDrive: 'hyperspaceDrive', + DarkMatterTechnology: 'darkMatterTechnology' // 暗物质技术 +} as const + +export type TechnologyType = (typeof TechnologyType)[keyof typeof TechnologyType] + +// 科技配置 +export interface TechnologyConfig { + id: TechnologyType + name: string + description: string + baseCost: Resources + baseTime: number + costMultiplier: number + requirements?: Partial> +} + +// 科技实例 +export interface Technology { + type: TechnologyType + level: number +} + +// 防御设施类型 +export const DefenseType = { + RocketLauncher: 'rocketLauncher', + LightLaser: 'lightLaser', + HeavyLaser: 'heavyLaser', + GaussCannon: 'gaussCannon', + IonCannon: 'ionCannon', + PlasmaTurret: 'plasmaTurret', + SmallShieldDome: 'smallShieldDome', + LargeShieldDome: 'largeShieldDome' +} as const + +export type DefenseType = (typeof DefenseType)[keyof typeof DefenseType] + +// 防御设施配置 +export interface DefenseConfig { + id: DefenseType + name: string + description: string + cost: Resources + buildTime: number + attack: number + shield: number + armor: number + requirements?: Partial> +} + +// 舰船类型 +export const ShipType = { + LightFighter: 'lightFighter', + HeavyFighter: 'heavyFighter', + Cruiser: 'cruiser', + Battleship: 'battleship', + SmallCargo: 'smallCargo', + LargeCargo: 'largeCargo', + ColonyShip: 'colonyShip', + Recycler: 'recycler', + EspionageProbe: 'espionageProbe', + DarkMatterHarvester: 'darkMatterHarvester' // 暗物质采集船 +} as const + +export type ShipType = (typeof ShipType)[keyof typeof ShipType] + +// 舰船配置 +export interface ShipConfig { + id: ShipType + name: string + description: string + cost: Resources + buildTime: number + cargoCapacity: number + attack: number + shield: number + armor: number + speed: number + fuelConsumption: number + requirements?: Partial> +} + +// 舰船实例 +export interface Fleet { + [ShipType.LightFighter]: number + [ShipType.HeavyFighter]: number + [ShipType.Cruiser]: number + [ShipType.Battleship]: number + [ShipType.SmallCargo]: number + [ShipType.LargeCargo]: number + [ShipType.ColonyShip]: number + [ShipType.Recycler]: number + [ShipType.EspionageProbe]: number + [ShipType.DarkMatterHarvester]: number +} + +// 舰队任务类型 +export const MissionType = { + Attack: 'attack', + Transport: 'transport', + Colonize: 'colonize', + Spy: 'spy', + Deploy: 'deploy', + Expedition: 'expedition', + HarvestDarkMatter: 'harvestDarkMatter' // 暗物质采集 +} as const + +export type MissionType = (typeof MissionType)[keyof typeof MissionType] + +// 舰队任务 +export interface FleetMission { + id: string + playerId: string + originPlanetId: string + targetPosition: { galaxy: number; system: number; position: number } + targetPlanetId?: string + missionType: MissionType + fleet: Partial + cargo: Resources + departureTime: number + arrivalTime: number + returnTime?: number + status: 'outbound' | 'returning' | 'arrived' +} + +// 战斗结果 +export interface BattleResult { + id: string + timestamp: number + attackerId: string + defenderId: string + attackerPlanetId: string + defenderPlanetId: string + attackerFleet: Partial + defenderFleet: Partial + defenderDefense: Partial> + attackerLosses: Partial + defenderLosses: { + fleet: Partial + defense: Partial> + } + winner: 'attacker' | 'defender' | 'draw' + plunder: Resources + debrisField: Resources +} + +// 间谍报告 +export interface SpyReport { + id: string + timestamp: number + spyId: string + targetPlanetId: string + targetPlayerId: string + resources: Resources + fleet?: Partial + defense?: Partial> + buildings?: Partial> + technologies?: Partial> + detectionChance: number +} + +// 建造队列项 +export interface BuildQueueItem { + id: string + type: 'building' | 'technology' | 'ship' | 'defense' | 'demolish' + itemType: BuildingType | TechnologyType | ShipType | DefenseType + targetLevel?: number // 用于建筑和科技 + quantity?: number // 用于舰船和防御 + startTime: number + endTime: number +} + +// 星球 +export interface Planet { + id: string + name: string + ownerId?: string + position: { galaxy: number; system: number; position: number } + resources: Resources + buildings: Record + fleet: Fleet + defense: Record + buildQueue: BuildQueueItem[] + lastUpdate: number + maxSpace: number // 最大空间 + isMoon: boolean // 是否为月球 + parentPlanetId?: string // 如果是月球,指向母星的ID +} + +// 月球特殊配置 +export interface MoonConfig { + minDebrisField: number // 生成月球所需的最小残骸场 + baseChance: number // 基础生成概率 + maxChance: number // 最大生成概率 + chancePerDebris: number // 每单位残骸增加的概率 +} + +// 军官类型 +export const OfficerType = { + Commander: 'commander', // 指挥官 - 增加建筑队列 + Admiral: 'admiral', // 上将 - 增加舰队槽位 + Engineer: 'engineer', // 工程师 - 增加防御和能量 + Geologist: 'geologist', // 地质学家 - 增加资源产量 + Technocrat: 'technocrat', // 技术专家 - 减少研究时间 + DarkMatterSpecialist: 'darkMatterSpecialist' // 暗物质专家 - 增加暗物质产量 +} as const + +export type OfficerType = (typeof OfficerType)[keyof typeof OfficerType] + +// 军官配置 +export interface OfficerConfig { + id: OfficerType + name: string + description: string + cost: Resources // 招募成本 + weeklyMaintenance: Resources // 每周维护费用 + benefits: { + buildingSpeedBonus?: number // 建筑速度加成 (百分比) + researchSpeedBonus?: number // 研究速度加成 (百分比) + resourceProductionBonus?: number // 资源产量加成 (百分比) + darkMatterProductionBonus?: number // 暗物质产量加成 (百分比) + energyProductionBonus?: number // 电量产出加成 (百分比) + fleetSpeedBonus?: number // 舰队速度加成 (百分比) + fuelConsumptionReduction?: number // 燃料消耗减少 (百分比) + defenseBonus?: number // 防御加成 (百分比) + additionalBuildQueue?: number // 额外建筑队列 + additionalFleetSlots?: number // 额外舰队槽位 + storageCapacityBonus?: number // 仓储容量加成 (百分比) + } +} + +// 军官实例 +export interface Officer { + type: OfficerType + active: boolean + hiredAt?: number // 招募时间 + expiresAt?: number // 到期时间 +} + +// 玩家 +export interface Player { + id: string + name: string + planets: Planet[] + technologies: Record + officers: Record + researchQueue: BuildQueueItem[] + fleetMissions: FleetMission[] + battleReports: BattleResult[] + spyReports: SpyReport[] + points: number // 总积分(每1000资源=1分) +} + +// 游戏状态 +export interface GameState { + player: Player + currentPlanetId: string + gameTime: number + isPaused: boolean + universe: Universe +} + +// 宇宙 +export interface Universe { + galaxies: number + systems: number + positions: number + planets: Map // key: "galaxy:system:position" + npcs: NPC[] +} + +// NPC玩家 +export interface NPC { + id: string + name: string + planets: Planet[] + technologies: Record + difficulty: 'easy' | 'medium' | 'hard' +} diff --git a/src/utils/battleSimulator.ts b/src/utils/battleSimulator.ts new file mode 100644 index 0000000..caeb324 --- /dev/null +++ b/src/utils/battleSimulator.ts @@ -0,0 +1,447 @@ +import type { Fleet, Resources } from '@/types/game' +import { ShipType, DefenseType } from '@/types/game' +import { SHIPS, DEFENSES } from '@/config/gameConfig' + +// 战斗单位接口 +interface CombatUnit { + type: ShipType | DefenseType + count: number + attack: number + shield: number + armor: number + rapidFire?: Record // 快速射击 +} + +// 战斗方 +interface BattleSide { + ships: Partial + defense?: Partial> + weaponTech?: number + shieldTech?: number + armorTech?: number +} + +// 战斗轮次结果 +interface RoundResult { + attackerLosses: Partial + defenderLosses: { + fleet: Partial + defense: Partial> + } + attackerRemainingPower: number + defenderRemainingPower: number +} + +/** + * 计算科技加成后的数值 + */ +const applyTechBonus = (baseValue: number, techLevel: number = 0, bonusPerLevel: number = 0.1): number => { + return Math.floor(baseValue * (1 + techLevel * bonusPerLevel)) +} + +/** + * 将舰队和防御转换为战斗单位数组 + */ +const prepareCombatUnits = (side: BattleSide, isDefender: boolean = false): CombatUnit[] => { + const units: CombatUnit[] = [] + + // 处理舰船 + if (side.ships) { + for (const [shipType, count] of Object.entries(side.ships)) { + if (count > 0) { + const config = SHIPS[shipType as ShipType] + units.push({ + type: shipType as ShipType, + count: count, + attack: applyTechBonus(config.attack, side.weaponTech), + shield: applyTechBonus(config.shield, side.shieldTech), + armor: applyTechBonus(config.armor, side.armorTech) + }) + } + } + } + + // 处理防御设施(仅防守方) + if (isDefender && side.defense) { + for (const [defenseType, count] of Object.entries(side.defense)) { + if (count > 0) { + const config = DEFENSES[defenseType as DefenseType] + units.push({ + type: defenseType as DefenseType, + count: count, + attack: applyTechBonus(config.attack, side.weaponTech), + shield: applyTechBonus(config.shield, side.shieldTech), + armor: applyTechBonus(config.armor, side.armorTech) + }) + } + } + } + + return units +} + +/** + * 计算一个单位对另一个单位造成的伤害 + */ +const calculateDamage = (attacker: CombatUnit, defender: CombatUnit): { destroyed: number; damagedShield: number } => { + const attackPower = attacker.attack + const defenderShield = defender.shield + const defenderArmor = defender.armor + + let destroyed = 0 + let damagedShield = 0 + + // 如果攻击力小于护盾的1%,有很大概率无法击穿护盾 + if (attackPower < defenderShield * 0.01) { + if (Math.random() > 0.01) { + return { destroyed: 0, damagedShield: 0 } + } + } + + // 计算伤害 + let remainingDamage = attackPower + + // 先消耗护盾 + if (remainingDamage > defenderShield) { + remainingDamage -= defenderShield + damagedShield = defenderShield + } else { + damagedShield = remainingDamage + return { destroyed: 0, damagedShield } + } + + // 再消耗装甲 + if (remainingDamage > defenderArmor) { + destroyed = 1 + } else { + // 有概率摧毁 + const destroyChance = remainingDamage / defenderArmor + if (Math.random() < destroyChance) { + destroyed = 1 + } + } + + return { destroyed, damagedShield } +} + +/** + * 执行一轮战斗 + */ +const executeRound = (attackerUnits: CombatUnit[], defenderUnits: CombatUnit[]): RoundResult => { + const attackerLosses: Partial = {} + const defenderShipLosses: Partial = {} + const defenderDefenseLosses: Partial> = {} + + // 攻击方向防守方射击 + for (const attacker of attackerUnits) { + for (let i = 0; i < attacker.count; i++) { + // 随机选择一个目标 + if (defenderUnits.length === 0) break + + const targetIndex = Math.floor(Math.random() * defenderUnits.length) + const target = defenderUnits[targetIndex] + + if (!target) continue + + const { destroyed } = calculateDamage(attacker, target) + + if (destroyed > 0) { + target.count -= destroyed + + // 记录损失 + if (Object.values(ShipType).includes(target.type as ShipType)) { + const shipType = target.type as ShipType + defenderShipLosses[shipType] = (defenderShipLosses[shipType] || 0) + destroyed + } else { + const defenseType = target.type as DefenseType + defenderDefenseLosses[defenseType] = (defenderDefenseLosses[defenseType] || 0) + destroyed + } + + // 如果目标被全部摧毁,从列表中移除 + if (target.count <= 0) { + defenderUnits.splice(targetIndex, 1) + } + } + } + } + + // 防守方向攻击方射击 + for (const defender of defenderUnits) { + for (let i = 0; i < defender.count; i++) { + // 随机选择一个目标 + if (attackerUnits.length === 0) break + + const targetIndex = Math.floor(Math.random() * attackerUnits.length) + const target = attackerUnits[targetIndex] + + if (!target) continue + + const { destroyed } = calculateDamage(defender, target) + + if (destroyed > 0) { + target.count -= destroyed + + // 记录损失 + const shipType = target.type as ShipType + attackerLosses[shipType] = (attackerLosses[shipType] || 0) + destroyed + + // 如果目标被全部摧毁,从列表中移除 + if (target.count <= 0) { + attackerUnits.splice(targetIndex, 1) + } + } + } + } + + // 计算剩余战斗力 + const attackerPower = attackerUnits.reduce((sum, unit) => sum + unit.count * unit.attack, 0) + const defenderPower = defenderUnits.reduce((sum, unit) => sum + unit.count * unit.attack, 0) + + return { + attackerLosses, + defenderLosses: { + fleet: defenderShipLosses, + defense: defenderDefenseLosses + }, + attackerRemainingPower: attackerPower, + defenderRemainingPower: defenderPower + } +} + +/** + * 模拟完整战斗 + * @param attacker 攻击方 + * @param defender 防守方 + * @param maxRounds 最大回合数(默认6回合) + */ +export const simulateBattle = ( + attacker: BattleSide, + defender: BattleSide, + maxRounds: number = 6 +): { + winner: 'attacker' | 'defender' | 'draw' + rounds: number + attackerLosses: Partial + defenderLosses: { + fleet: Partial + defense: Partial> + } + attackerRemaining: Partial + defenderRemaining: { + fleet: Partial + defense: Partial> + } + roundDetails: Array<{ + round: number + attackerLosses: Partial + defenderLosses: { + fleet: Partial + defense: Partial> + } + attackerRemainingPower: number + defenderRemainingPower: number + }> +} => { + // 准备战斗单位 + let attackerUnits = prepareCombatUnits(attacker, false) + let defenderUnits = prepareCombatUnits(defender, true) + + const totalAttackerLosses: Partial = {} + const totalDefenderShipLosses: Partial = {} + const totalDefenderDefenseLosses: Partial> = {} + const roundDetails: Array<{ + round: number + attackerLosses: Partial + defenderLosses: { + fleet: Partial + defense: Partial> + } + attackerRemainingPower: number + defenderRemainingPower: number + }> = [] + + let rounds = 0 + + // 执行最多maxRounds轮战斗 + for (let round = 0; round < maxRounds; round++) { + if (attackerUnits.length === 0 || defenderUnits.length === 0) { + break + } + + rounds++ + + const roundResult = executeRound(attackerUnits, defenderUnits) + + // 保存当前回合详情 + roundDetails.push({ + round: rounds, + attackerLosses: { ...roundResult.attackerLosses }, + defenderLosses: { + fleet: { ...roundResult.defenderLosses.fleet }, + defense: { ...roundResult.defenderLosses.defense } + }, + attackerRemainingPower: roundResult.attackerRemainingPower, + defenderRemainingPower: roundResult.defenderRemainingPower + }) + + // 累计损失 + for (const [shipType, count] of Object.entries(roundResult.attackerLosses)) { + totalAttackerLosses[shipType as ShipType] = (totalAttackerLosses[shipType as ShipType] || 0) + count + } + + for (const [shipType, count] of Object.entries(roundResult.defenderLosses.fleet)) { + totalDefenderShipLosses[shipType as ShipType] = (totalDefenderShipLosses[shipType as ShipType] || 0) + count + } + + for (const [defenseType, count] of Object.entries(roundResult.defenderLosses.defense)) { + totalDefenderDefenseLosses[defenseType as DefenseType] = (totalDefenderDefenseLosses[defenseType as DefenseType] || 0) + count + } + } + + // 防御设施有概率修复(70%概率) + const repairedDefense: Partial> = {} + for (const [defenseType, count] of Object.entries(totalDefenderDefenseLosses)) { + const repaired = Math.floor(count * 0.7) + if (repaired > 0) { + repairedDefense[defenseType as DefenseType] = repaired + totalDefenderDefenseLosses[defenseType as DefenseType] = count - repaired + } + } + + // 计算剩余单位 + const attackerRemaining: Partial = {} + for (const unit of attackerUnits) { + if (unit.count > 0) { + attackerRemaining[unit.type as ShipType] = unit.count + } + } + + const defenderShipRemaining: Partial = {} + const defenderDefenseRemaining: Partial> = {} + for (const unit of defenderUnits) { + if (unit.count > 0) { + if (Object.values(ShipType).includes(unit.type as ShipType)) { + defenderShipRemaining[unit.type as ShipType] = unit.count + } else { + defenderDefenseRemaining[unit.type as DefenseType] = unit.count + } + } + } + + // 添加修复的防御设施 + for (const [defenseType, count] of Object.entries(repairedDefense)) { + defenderDefenseRemaining[defenseType as DefenseType] = (defenderDefenseRemaining[defenseType as DefenseType] || 0) + count + } + + // 判断胜负 + let winner: 'attacker' | 'defender' | 'draw' + if (attackerUnits.length === 0 && defenderUnits.length === 0) { + winner = 'draw' + } else if (attackerUnits.length === 0) { + winner = 'defender' + } else if (defenderUnits.length === 0) { + winner = 'attacker' + } else { + winner = 'draw' + } + + return { + winner, + rounds, + attackerLosses: totalAttackerLosses, + defenderLosses: { + fleet: totalDefenderShipLosses, + defense: totalDefenderDefenseLosses + }, + attackerRemaining, + defenderRemaining: { + fleet: defenderShipRemaining, + defense: defenderDefenseRemaining + }, + roundDetails + } +} + +/** + * 计算掠夺的资源 + * 攻击方最多可以掠夺防守方50%的资源,但受运输船载货量限制 + */ +export const calculatePlunder = (defenderResources: Resources, attackerFleet: Partial): Resources => { + // 计算总载货量 + let totalCapacity = 0 + for (const [shipType, count] of Object.entries(attackerFleet)) { + const config = SHIPS[shipType as ShipType] + totalCapacity += config.cargoCapacity * count + } + + // 计算可掠夺资源(50%) + const availableResources = { + metal: Math.floor(defenderResources.metal * 0.5), + crystal: Math.floor(defenderResources.crystal * 0.5), + deuterium: Math.floor(defenderResources.deuterium * 0.5), + darkMatter: Math.floor(defenderResources.darkMatter * 0.5), + energy: 0 + } + + const totalAvailable = + availableResources.metal + availableResources.crystal + availableResources.deuterium + availableResources.darkMatter + + // 如果载货量足够,全部掠夺 + if (totalCapacity >= totalAvailable) { + return availableResources + } + + // 否则按比例分配 + const ratio = totalCapacity / totalAvailable + return { + metal: Math.floor(availableResources.metal * ratio), + crystal: Math.floor(availableResources.crystal * ratio), + deuterium: Math.floor(availableResources.deuterium * ratio), + darkMatter: Math.floor(availableResources.darkMatter * ratio), + energy: 0 + } +} + +/** + * 计算残骸场 + * 被摧毁的舰船和防御会产生30%的金属和晶体残骸 + */ +export const calculateDebrisField = ( + attackerLosses: Partial, + defenderLosses: { + fleet: Partial + defense: Partial> + } +): Resources => { + let totalMetal = 0 + let totalCrystal = 0 + + // 计算攻击方损失产生的残骸 + for (const [shipType, count] of Object.entries(attackerLosses)) { + const config = SHIPS[shipType as ShipType] + totalMetal += config.cost.metal * count * 0.3 + totalCrystal += config.cost.crystal * count * 0.3 + } + + // 计算防守方舰船损失产生的残骸 + for (const [shipType, count] of Object.entries(defenderLosses.fleet)) { + const config = SHIPS[shipType as ShipType] + totalMetal += config.cost.metal * count * 0.3 + totalCrystal += config.cost.crystal * count * 0.3 + } + + // 计算防守方防御损失产生的残骸 + for (const [defenseType, count] of Object.entries(defenderLosses.defense)) { + const config = DEFENSES[defenseType as DefenseType] + totalMetal += config.cost.metal * count * 0.3 + totalCrystal += config.cost.crystal * count * 0.3 + } + + return { + metal: Math.floor(totalMetal), + crystal: Math.floor(totalCrystal), + deuterium: 0, + darkMatter: 0, + energy: 0 + } +} diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts new file mode 100644 index 0000000..04ade57 --- /dev/null +++ b/src/utils/crypto.ts @@ -0,0 +1,25 @@ +import CryptoJS from 'crypto-js' +import pkg from '../../package.json' + +// 数据加密 +export const encryptData = (data: any): string => { + try { + const jsonStr = JSON.stringify(data) + return CryptoJS.AES.encrypt(jsonStr, pkg.name).toString() + } catch (error) { + console.error('数据加密失败:', error) + return '' + } +} + +// 数据解密 +export const decryptData = (data: string): any => { + try { + const bytes = CryptoJS.AES.decrypt(data, pkg.name) + const decryptedStr = bytes.toString(CryptoJS.enc.Utf8) + return JSON.parse(decryptedStr) + } catch (error) { + console.error('数据解密失败:', error) + return {} + } +} \ No newline at end of file diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..dfb8dba --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,71 @@ +/** + * 格式化数字为英文单位(K, M, B) + * @param num 数字 + * @param decimals 小数位数,默认2 + * @returns 格式化后的字符串 + */ +export const formatNumber = (num: number, decimals: number = 2): string => { + if (num >= 1_000_000_000) { + return (num / 1_000_000_000).toFixed(decimals) + 'B' + } else if (num >= 1_000_000) { + return (num / 1_000_000).toFixed(decimals) + 'M' + } else if (num >= 1_000) { + return (num / 1_000).toFixed(decimals) + 'K' + } + return Math.floor(num).toString() +} + +/** + * 获取资源颜色类(根据占用率) + * @param current 当前数量 + * @param max 最大容量 + * @returns Tailwind CSS 类名 + */ +export const getResourceColor = (current: number, max: number): string => { + const ratio = current / max + if (ratio >= 1) return 'text-red-600 dark:text-red-400' + if (ratio >= 0.7) return 'text-yellow-600 dark:text-yellow-400' + return '' +} + +/** + * 格式化时间(秒转为时分秒) + * @param seconds 秒数 + * @param units 时间单位 {hour, minute, second} + * @returns 格式化后的时间字符串(双位数格式,例如 00:05:08) + */ +export const formatTime = (seconds: number): string => { + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = Math.floor(seconds % 60) + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}` +} + +/** + * 格式化日期 + * @param timestamp 时间戳 + * @returns 格式化后的日期字符串 + */ +export const formatDate = (timestamp: number): string => { + const date = new Date(timestamp) + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }) +} + +/** + * 检查资源是否充足并返回对应的颜色类 + * @param available 当前可用资源数量 + * @param required 所需资源数量 + * @returns Tailwind CSS 类名(资源不足时返回红色) + */ +export const getResourceCostColor = (available: number, required: number): string => { + if (available < required) { + return 'text-red-600 dark:text-red-400' + } + return '' +} diff --git a/src/views/BattleSimulatorView.vue b/src/views/BattleSimulatorView.vue new file mode 100644 index 0000000..2833bb1 --- /dev/null +++ b/src/views/BattleSimulatorView.vue @@ -0,0 +1,569 @@ + + + diff --git a/src/views/BuildingsView.vue b/src/views/BuildingsView.vue new file mode 100644 index 0000000..b7656d9 --- /dev/null +++ b/src/views/BuildingsView.vue @@ -0,0 +1,254 @@ + + + diff --git a/src/views/DefenseView.vue b/src/views/DefenseView.vue new file mode 100644 index 0000000..a23e2d0 --- /dev/null +++ b/src/views/DefenseView.vue @@ -0,0 +1,263 @@ + + + diff --git a/src/views/FleetView.vue b/src/views/FleetView.vue new file mode 100644 index 0000000..42cf6b4 --- /dev/null +++ b/src/views/FleetView.vue @@ -0,0 +1,569 @@ + + + diff --git a/src/views/GalaxyView.vue b/src/views/GalaxyView.vue new file mode 100644 index 0000000..7e5d19d --- /dev/null +++ b/src/views/GalaxyView.vue @@ -0,0 +1,264 @@ + + + diff --git a/src/views/MessagesView.vue b/src/views/MessagesView.vue new file mode 100644 index 0000000..ead2ef3 --- /dev/null +++ b/src/views/MessagesView.vue @@ -0,0 +1,252 @@ + + + diff --git a/src/views/OfficersView.vue b/src/views/OfficersView.vue new file mode 100644 index 0000000..3262512 --- /dev/null +++ b/src/views/OfficersView.vue @@ -0,0 +1,269 @@ + + + diff --git a/src/views/OverviewView.vue b/src/views/OverviewView.vue new file mode 100644 index 0000000..0fccae0 --- /dev/null +++ b/src/views/OverviewView.vue @@ -0,0 +1,166 @@ + + + diff --git a/src/views/ResearchView.vue b/src/views/ResearchView.vue new file mode 100644 index 0000000..aec6b94 --- /dev/null +++ b/src/views/ResearchView.vue @@ -0,0 +1,161 @@ + + + diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue new file mode 100644 index 0000000..b9d88c3 --- /dev/null +++ b/src/views/SettingsView.vue @@ -0,0 +1,281 @@ + + + diff --git a/src/views/ShipyardView.vue b/src/views/ShipyardView.vue new file mode 100644 index 0000000..16883df --- /dev/null +++ b/src/views/ShipyardView.vue @@ -0,0 +1,239 @@ + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..db1f651 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + "paths": { + "@/*": ["./src/*"] + }, + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2b78387 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..fee4aea --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' +import path from 'node:path' + +export default defineConfig({ + base: './', + build: { outDir: 'docs' }, + plugins: [vue(), tailwindcss()], + resolve: { alias: { '@': path.resolve(__dirname, './src') } } +})