39 Commits

Author SHA1 Message Date
谦君
d44ea60ae9 1.6.5 2026-01-23 01:38:12 +08:00
谦君
1fc807915f 1.6.5 2026-01-23 01:28:57 +08:00
谦君
bd6c474913 Merge pull request #38 from coolxitech/main
优化Docker镜像构建
2026-01-11 19:25:30 +08:00
谦君
66c0ed8d0e 更新 2026-01-11 19:25:12 +08:00
coolxitech
9634dcb023 build(ci): 优化 Docker 构建流程并添加多平台支持
- 在 Dockerfile 中添加构建参数和标签信息用于缓存破坏
- 使用 --chown 确保正确的文件权限并验证构建产物
- 添加构建产物时间戳检查以确保最新性
- 获取并使用版本号进行镜像标签管理
- 添加清理冲突镜像标签的步骤
- 配置多平台构建支持(linux/amd64,linux/arm64)
- 添加版本标签和构建参数传递
- 配置构建缓存和镜像推送功能
2026-01-08 17:55:40 +08:00
coolxitech
e4c4cdd63c chore(workflow): 更新 GitHub Actions 工作流配置
- 升级 actions/checkout 从 v4 到 v6
- 升级 pnpm/action-setup 从 v3 到 v4 并更新版本到 latest
- 升级 actions/setup-node 从 v4 到 v6
- 升级 actions/setup-java 从 v4 到 v5
- 升级 actions/cache 从 v4 到 v5
- 升级 softprops/action-gh-release 从 v1 到 v2
- 升级 actions/configure-pages 从 v3 到 v5
- 升级 actions/deploy-pages 从 v2 到 v4
- 添加构建产物验证步骤
- 添加缓存 pnpm 依赖的配置
- 优化 Docker 镜像标签和元数据配置
- 改进条件判断逻辑以优化 Docker 推送流程
2026-01-08 17:40:29 +08:00
coolxitech
7279bcbc89 chore(workflow): 更新 github pages 工作流配置
- 为 pnpm action setup 添加版本配置
- 指定使用最新版本的 pnpm
- 保持 nodejs 安装配置不变
2026-01-08 17:18:25 +08:00
coolxitech
d9c708e0ca feat(docker): 添加完整的 Docker 构建支持
- 重构 Dockerfile 支持本地完整源码构建流程
- 添加 CI 专用的 Dockerfile.ci 使用预构建产物
- 创建 .dockerignore 和 .dockerignore.ci 文件优化构建上下文
- 添加 build-docker.sh 和 build-docker.bat 本地构建脚本
- 更新 GitHub Actions 工作流支持 Node.js 环境和 pnpm 依赖管理
- 添加 DOCKER.md 详细说明文档
- 优化 nginx 配置和端口暴露设置
2026-01-08 17:13:46 +08:00
coolxitech
21cf5762d2 chore: 移除 packageManager 配置项
- 从 package.json 中删除了 pnpm 的 packageManager 指定配置
- 保持项目构建配置的简洁性
- 统一依赖管理方式,避免版本冲突问题
- 确保所有开发者使用相同的包管理器版本
- 减少不必要的配置冗余
- 提高项目的可维护性和一致性
2026-01-08 17:01:19 +08:00
coolxitech
8db70ea674 fix(types): 修复类型检查错误
- 添加类型断言以解决类型不匹配问题
- 确保 typeKey 正确映射到 settings.types 的键类型
2026-01-08 17:01:19 +08:00
酷曦科技
136591a3dd 无缓存构建 2026-01-08 15:40:02 +08:00
谦君
72f37aa435 Create FUNDING.yml 2026-01-06 15:14:32 +08:00
谦君
4c54e1b773 撤回 2026-01-06 11:11:18 +08:00
谦君
9e8ceb0414 优化 2026-01-06 08:15:59 +08:00
谦君
ec96d2541e 修复问题 2026-01-06 03:06:14 +08:00
谦君
9e7560cc4b 1.6.0更新 2026-01-06 03:00:02 +08:00
谦君
1ad051cd6d Update ResourceIcon.vue 2025-12-27 04:02:17 +08:00
谦君
fda15646eb Update package.json 2025-12-27 03:58:54 +08:00
谦君
6a9846c6df Update package.json 2025-12-27 03:58:15 +08:00
谦君
49753566c3 优化webdav相关 2025-12-27 01:37:35 +08:00
谦君
66783f896c 补全翻译 2025-12-27 01:04:14 +08:00
谦君
7cc885c62a Delete splash.xml 2025-12-27 00:18:40 +08:00
谦君
5c6404d86a 优化移动端开屏 2025-12-27 00:12:49 +08:00
谦君
010ea137ac perf: 优化安卓WebView性能与调试配置
MainActivity中为WebView启用硬件加速、DOM存储、数据库及默认缓存模式,提升性能与兼容性。capacitor.config.ts开启webContentsDebugging,便于调试排查问题。
2025-12-26 23:44:22 +08:00
谦君
6dbca76252 Update index.html 2025-12-26 23:37:23 +08:00
谦君
c047ffb88e Create favicon.ico 2025-12-26 22:25:12 +08:00
谦君
6f8adfa586 build: 替换autoprefixer为lightningcss并优化依赖
移除autoprefixer,改用lightningcss处理CSS,提升构建兼容性与性能。package.json、pnpm-lock.yaml、vite.config.ts同步调整依赖与配置,支持Android 5+/iOS 10+/Chrome 60+等目标环境。补充PWA苹果图标,删除favicon.ico。多语言任务目标文本细化和丰富,提升本地化体验。安卓端gradle配置补充capacitor-app与capacitor-filesystem依赖。
2025-12-26 22:22:14 +08:00
谦君
94fa2ad57a feat: 多语言完善造船厂与研究相关字段
为de、en、es-LA、ko、ru、zh-CN、zh-TW等多语言文件补充和完善造船厂(shipyard)与研究(research)相关字段,包括攻击、防御、装甲、建造成本、总成本、批量计算等,提升界面一致性与本地化体验。同时优化通知弹窗滚动区域样式,增加overflow-y-auto,提升内容自适应性。
2025-12-26 01:53:19 +08:00
谦君
7ed508945a build: Android版本号自动同步package.json
android/app/build.gradle中通过读取package.json自动设置versionName与versionCode,实现前后端版本号一致,避免手动同步出错。
2025-12-26 01:16:08 +08:00
谦君
fe2e5bfad9 refactor: 优化ResourceIcon样式及兼容性
将ResourceIcon根元素由div改为span,调整样式为inline-block和shrink-0,提升布局灵活性。颜色由渐变改为纯色背景,增强在Android WebView等环境下的显示兼容性。尺寸样式增加min-width/min-height,确保图标不被压缩。
2025-12-25 21:29:38 +08:00
谦君
7f36b6693f style: 优化通知弹窗滚动区域高度样式
将DiplomaticNotifications、EnemyAlertNotifications与QueueNotifications中的ScrollArea高度由固定h-96/h-[420px]调整为h-auto max-h-96,提升内容自适应性,避免内容较少时出现多余空白,增强界面美观与一致性。
2025-12-25 21:17:53 +08:00
谦君
27d60ae71a fix: 禁用WebView文本缩放并修复Portal定位
安卓端MainActivity中强制WebView文本缩放为100%,防止系统字体大小影响布局。capacitor.config.ts同步禁用WebView文本缩放及键盘视口调整。CSS中统一禁用文本大小调整,修复Edge-to-Edge模式下Portal容器定位问题,提升移动端显示一致性。
2025-12-25 20:40:02 +08:00
谦君
ca1aed1e9b style: 优化可滚动Dialog内容与遮罩布局
ScrollableDialogContent重构遮罩与内容结构,遮罩层支持flex居中与内边距,内容容器样式与DialogContent统一,提升弹窗显示一致性与居中效果。DialogContent补充relative定位,便于后续扩展。
2025-12-25 20:12:01 +08:00
谦君
04ee72a33d feat: 安卓端支持沉浸式边到边显示
MainActivity启用Edge-to-Edge,状态栏与导航栏设为透明并强制深色图标,提升沉浸体验。styles.xml同步调整相关颜色为透明。CSS中优化html平滑过渡样式,提升界面切换流畅度。
2025-12-25 20:00:13 +08:00
谦君
d95dffcfcd style: 优化Dialog与AlertDialog居中与间距样式
调整AlertDialogContent、DialogContent及DialogOverlay的布局样式,统一弹窗居中方式,增加flex居中与padding,提升弹窗在不同屏幕下的显示效果与一致性。
2025-12-25 19:51:15 +08:00
谦君
b6bcae3294 fix: 统一APK文件扩展名为小写.apk
将构建产物及相关CI流程中的APK文件扩展名由大写.APK统一为小写.apk,提升平台兼容性并避免文件识别问题。
2025-12-25 19:26:44 +08:00
谦君
ebed10b82f feat: 优化Dialog内容样式并完善多语言“建造”文案
调整AlertDialogContent与DialogContent的宽度与定位样式,提升弹窗显示效果。多语言文件中buildingsView部分新增“build”字段,完善德语、英语、韩语、俄语、简体中文的“建造”相关文案,提升界面一致性与本地化体验。
2025-12-25 19:24:11 +08:00
谦君
f4f5a719f5 ci: 构建流程切换为官方Gradle Action
将原有自定义Gradle缓存步骤替换为gradle/actions/setup-gradle官方Action,简化配置并利用内置智能缓存,提升CI流程维护性与稳定性。
2025-12-25 18:46:52 +08:00
谦君
1686622013 chore: 优化CI缓存与YAML格式统一
构建流程中Gradle缓存新增build-cache目录,并在assembleRelease时启用--build-cache参数,提升构建效率。统一GitHub Actions YAML文件中分支、标签、条件判断等格式,增强可读性与一致性。
2025-12-25 18:38:54 +08:00
91 changed files with 4052 additions and 1275 deletions

31
.dockerignore Normal file
View File

@@ -0,0 +1,31 @@
# 排除不需要的文件和目录,减少 Docker 构建上下文大小
# 开发工具
.vscode/
.idea/
*.swp
*.swo
# Git
.git/
.gitignore
# 构建产物(本地构建时会重新生成)
docs/
pkg/
# 临时文件
*.tmp
*.temp
.DS_Store
Thumbs.db
# CI 相关文件
.github/
Dockerfile.ci
# 其他不需要的目录
android/app/build/
android/.gradle/
resources/
electron/dist/

38
.dockerignore.ci Normal file
View File

@@ -0,0 +1,38 @@
# CI 构建专用的 dockerignore
# 只保留构建产物和必要的配置文件
# 排除所有源代码和开发文件
src/
public/
electron/
node_modules/
.vscode/
.idea/
.git/
.github/
# 排除构建工具配置
vite.config.ts
tsconfig*.json
*.config.js
*.config.ts
package.json
package-lock.json
pnpm-lock.yaml
# 排除其他构建产物
pkg/
android/
resources/
# 排除临时文件
*.tmp
*.temp
.DS_Store
Thumbs.db
*.log
# 只保留以下文件:
# - docs/ (构建产物)
# - nginx.conf (nginx配置)
# - Dockerfile.ci

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: ['https://afdian.com/a/setube'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -23,12 +23,12 @@ jobs:
goarch: arm64 goarch: arm64
executable: OGame-Vue-Ts-server-linux-arm64 executable: OGame-Vue-Ts-server-linux-arm64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v4
with: with:
version: 8 version: latest
- name: Setup Node & Go - name: Setup Node & Go
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: 20 node-version: 20
cache: 'pnpm' cache: 'pnpm'
@@ -55,33 +55,28 @@ jobs:
name: Build Android APK name: Build Android APK
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v4
with: with:
version: 8 version: latest
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '21' java-version: '21'
- name: Setup Android SDK - name: Setup Android SDK
uses: android-actions/setup-android@v3 uses: android-actions/setup-android@v3
# 缓存 Gradle 依赖 (可节省 3-5 分钟) # 使用官方 Gradle Action内置智能缓存
- name: Cache Gradle - name: Setup Gradle
uses: actions/cache@v4 uses: gradle/actions/setup-gradle@v4
with: with:
path: | cache-read-only: false
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('android/**/*.gradle*', 'android/**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Build Frontend - name: Build Frontend
run: | run: |
@@ -118,13 +113,13 @@ jobs:
working-directory: android working-directory: android
run: | run: |
chmod +x ./gradlew chmod +x ./gradlew
./gradlew assembleRelease --no-daemon ./gradlew assembleRelease --build-cache
- name: Upload APK Artifacts - name: Upload APK Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: android-apk name: android-apk
path: android/app/build/outputs/apk/release/*.APK path: android/app/build/outputs/apk/release/*.apk
# 3. 构建 Electron 客户端 # 3. 构建 Electron 客户端
build-electron: build-electron:
@@ -141,18 +136,18 @@ jobs:
- os: ubuntu-latest - os: ubuntu-latest
platform: linux platform: linux
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v4
with: with:
version: 8 version: latest
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: 20 node-version: 20
cache: 'pnpm' cache: 'pnpm'
- name: Cache Electron Builder - name: Cache Electron Builder
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: | path: |
~/.cache/electron ~/.cache/electron
@@ -180,12 +175,12 @@ jobs:
# 4. 发布 Release # 4. 发布 Release
release: release:
needs: [ build-server, build-android, build-electron ] needs: [build-server, build-android, build-electron]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Get Version - name: Get Version
id: get_version id: get_version
@@ -201,18 +196,18 @@ jobs:
- name: Flatten and Rename Assets - name: Flatten and Rename Assets
run: | run: |
mkdir -p ./final-release mkdir -p ./final-release
# 移动 Server 文件并确保名字唯一 # 移动 Server 文件并确保名字唯一
cp ./raw-assets/server-windows-amd64/OGame-Vue-Ts-server-win.exe ./final-release/OGame-Vue-Ts-server-win.exe || cp ./raw-assets/server-windows-amd64/server-windows-amd64.exe ./final-release/OGame-Vue-Ts-server-win.exe || true cp ./raw-assets/server-windows-amd64/OGame-Vue-Ts-server-win.exe ./final-release/OGame-Vue-Ts-server-win.exe || cp ./raw-assets/server-windows-amd64/server-windows-amd64.exe ./final-release/OGame-Vue-Ts-server-win.exe || true
cp ./raw-assets/server-linux-amd64/OGame-Vue-Ts-server-linux ./final-release/OGame-Vue-Ts-server-linux || true cp ./raw-assets/server-linux-amd64/OGame-Vue-Ts-server-linux ./final-release/OGame-Vue-Ts-server-linux || true
cp ./raw-assets/server-linux-arm64/OGame-Vue-Ts-server-linux-arm64 ./final-release/OGame-Vue-Ts-server-linux-arm64 || true cp ./raw-assets/server-linux-arm64/OGame-Vue-Ts-server-linux-arm64 ./final-release/OGame-Vue-Ts-server-linux-arm64 || true
# 移动 Electron 安装包 (排除 unpacked 目录) # 移动 Electron 安装包 (排除 unpacked 目录)
find ./raw-assets/electron-* -type f \( -name "*.exe" -o -name "*.dmg" -o -name "*.AppImage" -o -name "*.zip" \) -exec cp {} ./final-release/ \; find ./raw-assets/electron-* -type f \( -name "*.exe" -o -name "*.dmg" -o -name "*.AppImage" -o -name "*.zip" \) -exec cp {} ./final-release/ \;
# 移动 Android APK # 移动 Android APK
find ./raw-assets/android-apk -type f -name "*.APK" -exec cp {} ./final-release/ \; || true find ./raw-assets/android-apk -type f -name "*.apk" -exec cp {} ./final-release/ \; || true
# 检查结果 # 检查结果
echo "Final assets to upload:" echo "Final assets to upload:"
ls -R ./final-release ls -R ./final-release
@@ -222,7 +217,7 @@ jobs:
run: | run: |
VERSION=${{ steps.get_version.outputs.VERSION }} VERSION=${{ steps.get_version.outputs.VERSION }}
# 获取 release 中的现有 assets 并删除 APK 文件 # 获取 release 中的现有 assets 并删除 APK 文件
gh release view "$VERSION" --json assets -q '.assets[].name' 2>/dev/null | grep -i '\.APK$' | while read asset; do gh release view "$VERSION" --json assets -q '.assets[].name' 2>/dev/null | grep -i '\.apk$' | while read asset; do
echo "Deleting existing asset: $asset" echo "Deleting existing asset: $asset"
gh release delete-asset "$VERSION" "$asset" -y || true gh release delete-asset "$VERSION" "$asset" -y || true
done done
@@ -231,7 +226,7 @@ jobs:
# 4. 一次性上传,禁止重复匹配 # 4. 一次性上传,禁止重复匹配
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ steps.get_version.outputs.VERSION }} tag_name: ${{ steps.get_version.outputs.VERSION }}
name: Release ${{ steps.get_version.outputs.VERSION }} name: Release ${{ steps.get_version.outputs.VERSION }}
@@ -239,4 +234,4 @@ jobs:
files: ./final-release/* files: ./final-release/*
generate_release_notes: true generate_release_notes: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,7 +2,7 @@ name: 构建 Github Pages
on: on:
push: push:
branches: [ main ] # 如果你的主分支叫 master请改为 master branches: [main] # 如果你的主分支叫 master请改为 master
permissions: permissions:
contents: read contents: read
@@ -19,21 +19,45 @@ jobs:
- name: 检出代码 - name: 检出代码
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: 安装 pnpm - name: 设置 pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with:
version: latest
- name: 安装 Nodejs - name: 设置 Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 20 node-version: '20'
cache: 'pnpm'
- name: 缓存 pnpm 依赖
uses: actions/cache@v5
with:
path: |
~/.pnpm-store
node_modules
key: ${{ runner.os }}-pnpm-pages-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-pages-
- name: 安装依赖 - name: 安装依赖
run: pnpm install run: pnpm install --frozen-lockfile
- name: 构建前端项目 - name: 构建前端项目
run: pnpm run build run: pnpm run build
- name: 验证构建产物
run: |
if [ ! -d "docs" ]; then
echo "❌ 构建失败docs 目录不存在"
exit 1
fi
if [ ! -f "docs/index.html" ]; then
echo "❌ 构建失败docs/index.html 不存在"
exit 1
fi
echo "✅ 构建产物验证通过"
ls -la docs/
# 关键步骤:告诉 GitHub Actions 跳过 Jekyll 检查 # 关键步骤:告诉 GitHub Actions 跳过 Jekyll 检查
- name: 配置 Github Pages - name: 配置 Github Pages
uses: actions/configure-pages@v5 uses: actions/configure-pages@v5
@@ -44,4 +68,5 @@ jobs:
path: './docs' path: './docs'
- name: 部署到 GitHub Pages - name: 部署到 GitHub Pages
uses: actions/deploy-pages@v4 id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,9 +1,9 @@
name: Docker 多架构构建并发布 name: 构建并发布 Docker 镜像
on: on:
push: push:
branches: [ main ] branches: [main]
tags: [ 'v*.*.*' ] # 打 tag 时也触发 tags: ['v*.*.*'] # 打 tag 时也触发
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@@ -15,10 +15,113 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 检出代码 - name: 检出代码
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
# 设置 Node.js 环境
- name: 设置 Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
# 设置 pnpm
- name: 设置 pnpm
uses: pnpm/action-setup@v4
with:
version: latest
# 缓存 pnpm 依赖
- name: 缓存 pnpm 依赖
uses: actions/cache@v5
with:
path: |
~/.pnpm-store
node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
# 安装依赖
- name: 安装依赖
run: pnpm install --frozen-lockfile
# 构建项目
- name: 构建项目
run: pnpm run build
# 验证构建产物
- name: 验证构建产物
run: |
if [ ! -d "docs" ]; then
echo "❌ 构建失败docs 目录不存在"
exit 1
fi
if [ ! -f "docs/index.html" ]; then
echo "❌ 构建失败docs/index.html 不存在"
exit 1
fi
# 检查构建产物的时间戳,确保是最新的
BUILD_TIME=$(stat -c %Y docs/index.html 2>/dev/null || stat -f %m docs/index.html 2>/dev/null || echo "0")
CURRENT_TIME=$(date +%s)
TIME_DIFF=$((CURRENT_TIME - BUILD_TIME))
echo "📊 构建产物信息:"
echo " 构建时间: $(date -d @$BUILD_TIME 2>/dev/null || date -r $BUILD_TIME 2>/dev/null || echo '未知')"
echo " 当前时间: $(date)"
echo " 时间差: ${TIME_DIFF}秒"
if [ $TIME_DIFF -gt 300 ]; then
echo "⚠️ 警告: 构建产物可能不是最新的超过5分钟"
fi
echo "✅ 构建产物验证通过"
ls -la docs/
# 获取当前日期
- name: 获取当前日期
id: date
run: echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
# 获取版本号
- name: 获取版本号
id: version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "📦 当前版本: $VERSION"
# 准备 CI 构建环境
- name: 准备 CI 构建环境
run: |
# 使用 CI 专用的 dockerignore
cp .dockerignore.ci .dockerignore
echo "✅ 已切换到 CI 构建模式"
echo "📁 当前构建上下文文件:"
ls -la | grep -E "(docs|nginx.conf|Dockerfile.ci|\.dockerignore)$"
# 清理可能冲突的镜像标签
- name: 清理可能冲突的镜像标签
continue-on-error: true
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "🧹 尝试清理可能冲突的镜像标签..."
# 尝试删除 GHCR 中的现有标签(如果存在)
echo "清理 GHCR 标签..."
docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:$VERSION 2>/dev/null && \
echo "发现现有版本标签,将被覆盖" || echo "版本标签不存在,可以安全推送"
# 如果配置了 Docker Hub也尝试检查
if [ -n "${{ vars.DOCKERHUB_USERNAME }}" ]; then
echo "检查 Docker Hub 标签..."
docker buildx imagetools inspect ${{ vars.DOCKERHUB_USERNAME }}/ogame-vue-ts:$VERSION 2>/dev/null && \
echo "发现现有 Docker Hub 版本标签,将被覆盖" || echo "Docker Hub 版本标签不存在,可以安全推送"
fi
echo "✅ 标签冲突检查完成,构建将覆盖任何现有标签"
# QEMU 用于支持多架构构建(必须) # QEMU 用于支持多架构构建(必须)
- name: 设置 QEMU - name: 设置 QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@@ -37,24 +140,40 @@ jobs:
# 登录 Docker Hub只在用户名存在时执行 # 登录 Docker Hub只在用户名存在时执行
- name: 登录 Docker Hub - name: 登录 Docker Hub
if: vars.DOCKERHUB_USERNAME != '' # 只检查 vars忽略 secrets if: vars.DOCKERHUB_USERNAME != '' # 只检查 vars忽略 secrets
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# 真正一键构建 + 推送多架构镜像amd64 + arm64 # 构建并推送多架构镜像(使用构建产物
- name: 构建并推送多架构镜像 - name: 构建并推送多架构镜像
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./Dockerfile.ci
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
no-cache: false
pull: true
tags: | tags: |
ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:latest ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:latest
ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:${{ steps.version.outputs.version }}
ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:${{ github.sha }} ghcr.io/${{ github.repository_owner }}/ogame-vue-ts:${{ github.sha }}
${{ vars.DOCKERHUB_USERNAME != '' && format('docker.io/{0}/ogame-vue-ts:latest', vars.DOCKERHUB_USERNAME) || '' }} ${{ vars.DOCKERHUB_USERNAME && format('{0}/ogame-vue-ts:latest', vars.DOCKERHUB_USERNAME) || '' }}
${{ vars.DOCKERHUB_USERNAME != '' && format('docker.io/{0}/ogame-vue-ts:{1}', vars.DOCKERHUB_USERNAME, github.sha) || '' }} ${{ vars.DOCKERHUB_USERNAME && format('{0}/ogame-vue-ts:{1}', vars.DOCKERHUB_USERNAME, steps.version.outputs.version) || '' }}
${{ vars.DOCKERHUB_USERNAME && format('{0}/ogame-vue-ts:{1}', vars.DOCKERHUB_USERNAME, github.sha) || '' }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=OGame Vue Ts build-args: |
BUILDKIT_INLINE_CACHE=1
BUILD_DATE=${{ steps.date.outputs.date }}
VERSION=${{ steps.version.outputs.version }}
COMMIT_SHA=${{ github.sha }}
labels: |
org.opencontainers.image.title=OGame Vue Ts
org.opencontainers.image.description=OGame Vue TypeScript Implementation
org.opencontainers.image.version=${{ steps.version.outputs.version }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ steps.date.outputs.date }}

89
DOCKER.md Normal file
View File

@@ -0,0 +1,89 @@
# Docker 构建说明
本项目支持两种 Docker 构建方式:
## 🏠 本地构建
### 方式一:使用构建脚本(推荐)
**Linux/macOS:**
```bash
chmod +x build-docker.sh
./build-docker.sh
```
**Windows:**
```cmd
build-docker.bat
```
### 方式二:直接使用 Docker 命令
```bash
# 构建镜像
docker build -t ogame-vue-ts:local .
# 运行容器
docker run -p 8080:80 ogame-vue-ts:local
```
## ☁️ GitHub Actions 自动构建
当代码推送到 `main` 分支或创建 tag 时GitHub Actions 会自动:
1. 在 Actions 环境中构建项目
2. 使用构建产物创建 Docker 镜像
3. 推送到 GitHub Container Registry 和 Docker Hub
### 使用预构建镜像
```bash
# 从 GitHub Container Registry 拉取
docker pull ghcr.io/your-username/ogame-vue-ts:latest
# 从 Docker Hub 拉取(如果配置了)
docker pull your-dockerhub-username/ogame-vue-ts:latest
# 运行
docker run -p 8080:80 ghcr.io/your-username/ogame-vue-ts:latest
```
## 📁 文件说明
- `Dockerfile` - 本地构建用,包含完整的源代码构建流程
- `Dockerfile.ci` - GitHub Actions 构建用,使用预构建产物
- `.dockerignore` - 本地构建时排除的文件
- `.dockerignore.ci` - CI 构建时排除的文件
- `build-docker.sh` / `build-docker.bat` - 本地构建便捷脚本
## 🔧 配置说明
### GitHub Actions 环境变量
需要在 GitHub 仓库设置中配置:
**Variables (公开):**
- `DOCKERHUB_USERNAME` - Docker Hub 用户名(可选)
**Secrets (私密):**
- `DOCKERHUB_TOKEN` - Docker Hub 访问令牌(可选)
- `GITHUB_TOKEN` - 自动提供,用于 GHCR
### 本地构建要求
- Docker
- 足够的磁盘空间(构建过程中会下载 Node.js 依赖)
## 🚀 快速开始
1. **本地开发测试:**
```bash
./build-docker.sh
docker run -p 8080:80 ogame-vue-ts:local
```
2. **访问应用:**
打开浏览器访问 `http://localhost:8080`
3. **生产部署:**
使用 GitHub Actions 自动构建的镜像进行部署

View File

@@ -1,22 +1,40 @@
FROM node:lts-alpine AS builder # 本地构建用的 Dockerfile
# 支持完整的源代码构建流程
RUN mkdir -p /workspace FROM node:20-alpine AS builder
WORKDIR /workspace
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update && apk add git
RUN npm config set registry https://registry.npmmirror.com
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; # 设置工作目录
WORKDIR /app
# 复制 package 文件
COPY package.json pnpm-lock.yaml ./
# 安装 pnpm
RUN npm install -g pnpm
# 安装依赖
RUN pnpm install --frozen-lockfile
# 复制源代码
COPY . .
# 构建项目
RUN pnpm run build RUN pnpm run build
# 生产阶段
FROM nginx:alpine FROM nginx:alpine
# 复制 nginx 配置文件
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
# 清理默认的 nginx 静态文件
RUN rm -rf /usr/share/nginx/html/* RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /workspace/docs /usr/share/nginx/html
# 复制构建产物到 nginx 静态文件目录
COPY --from=builder /app/docs /usr/share/nginx/html
# 暴露端口
EXPOSE 80 EXPOSE 80
# 启动 nginx
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

35
Dockerfile.ci Normal file
View File

@@ -0,0 +1,35 @@
# GitHub Actions 构建用的 Dockerfile
# 使用预构建的产物,不包含源代码构建过程
FROM nginx:alpine
# 添加构建参数用于缓存破坏
ARG BUILD_DATE
ARG VERSION
ARG COMMIT_SHA
# 添加标签信息
LABEL build.date="${BUILD_DATE}" \
build.version="${VERSION}" \
build.commit="${COMMIT_SHA}"
# 复制 nginx 配置文件
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 清理默认的 nginx 静态文件
RUN rm -rf /usr/share/nginx/html/*
# 复制构建产物到 nginx 静态文件目录
# 这里的 docs 目录是在 GitHub Actions 中构建生成的
# 使用 --chown 确保正确的文件权限
COPY --chown=nginx:nginx docs /usr/share/nginx/html
# 验证构建产物
RUN ls -la /usr/share/nginx/html/ && \
test -f /usr/share/nginx/html/index.html || (echo "构建产物验证失败" && exit 1)
# 暴露端口
EXPOSE 80
# 启动 nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,5 +1,15 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
// 从 package.json 读取版本号
def packageJsonFile = file('../../package.json')
def packageJsonText = packageJsonFile.text
// 使用正则提取版本号
def versionMatcher = packageJsonText =~ /"version"\s*:\s*"([^"]+)"/
def appVersionName = versionMatcher ? versionMatcher[0][1] : "1.0.0"
// 将版本号转换为 versionCode例如 "1.5.5" -> 1*10000 + 5*100 + 5 = 10505
def versionParts = appVersionName.split('\\.')
def appVersionCode = versionParts[0].toInteger() * 10000 + versionParts[1].toInteger() * 100 + versionParts[2].toInteger()
android { android {
namespace = "games.wenzi.ogame" namespace = "games.wenzi.ogame"
compileSdk = rootProject.ext.compileSdkVersion compileSdk = rootProject.ext.compileSdkVersion
@@ -7,8 +17,8 @@ android {
applicationId "games.wenzi.ogame" applicationId "games.wenzi.ogame"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 15 versionCode appVersionCode
versionName "1.5.0" versionName appVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -56,7 +66,7 @@ android {
def abi = output.getFilter(com.android.build.OutputFile.ABI) def abi = output.getFilter(com.android.build.OutputFile.ABI)
if (abi != null) { if (abi != null) {
output.versionCodeOverride = abiVersionCode[abi] * 1000 + defaultConfig.versionCode output.versionCodeOverride = abiVersionCode[abi] * 1000 + defaultConfig.versionCode
output.outputFileName = "OGame-Vue-Ts-${abi}.APK" output.outputFileName = "OGame-Vue-Ts-${abi}.apk"
} }
} }
} }

View File

@@ -9,7 +9,8 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-filesystem')
} }

View File

@@ -1,45 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config" android:hardwareAccelerated="true"
android:theme="@style/AppTheme"> android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:theme="@style/AppTheme">
<activity <activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/title_activity_main" android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch" android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask" android:launchMode="singleTask"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data> android:resource="@xml/file_paths"></meta-data>
</provider> </provider>
</application> </application>
<!-- Permissions --> <!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 旧版本存储权限 (Android 10 及以下) -->
</manifest> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="30" />
<!-- Android 11+ (API 30+) 完整外部存储访问权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Android 13+ (API 33+) 细粒度媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
</manifest>

View File

@@ -1,18 +1,100 @@
package games.wenzi.ogame; package games.wenzi.ogame;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.view.Window; import android.view.Window;
import androidx.core.content.ContextCompat; import android.view.animation.AccelerateDecelerateInterpolator;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.core.splashscreen.SplashScreen;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import com.getcapacitor.BridgeActivity; import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity { public class MainActivity extends BridgeActivity {
private boolean isWebViewReady = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
// 安装 SplashScreen必须在 super.onCreate 之前调用
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
// 保持 SplashScreen 直到 WebView 加载完成
splashScreen.setKeepOnScreenCondition(() -> !isWebViewReady);
// 设置淡出退出动画
splashScreen.setOnExitAnimationListener(splashScreenView -> {
// 创建淡出动画
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(
splashScreenView.getView(),
View.ALPHA,
1f,
0f
);
fadeOut.setInterpolator(new AccelerateDecelerateInterpolator());
fadeOut.setDuration(300);
// 动画结束后移除 SplashScreen
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
splashScreenView.remove();
}
});
fadeOut.start();
});
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// 设置状态栏颜色,防止 Capacitor 强制透明
Window window = getWindow(); Window window = getWindow();
window.setStatusBarColor(ContextCompat.getColor(this, R.color.status_bar_color));
window.setNavigationBarColor(ContextCompat.getColor(this, R.color.status_bar_color)); // 启用边到边显示Edge-to-Edge
WindowCompat.setDecorFitsSystemWindows(window, false);
// 设置透明状态栏和导航栏
window.setStatusBarColor(Color.TRANSPARENT);
window.setNavigationBarColor(Color.TRANSPARENT);
// 设置状态栏图标为浅色(因为背景是深色)
WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(window, window.getDecorView());
if (controller != null) {
controller.setAppearanceLightStatusBars(false);
controller.setAppearanceLightNavigationBars(false);
}
}
@Override
public void onStart() {
super.onStart();
WebView webView = getBridge().getWebView();
if (webView != null) {
WebSettings settings = webView.getSettings();
// 禁用 WebView 文本缩放,防止系统字体大小设置影响布局
settings.setTextZoom(100);
// 优化 WebView 性能
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);
// 启用硬件加速渲染
webView.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
// 监听页面加载进度,加载完成后隐藏 SplashScreen
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
// 当页面加载达到 80% 时认为可以显示
if (newProgress >= 80) {
isWebViewReady = true;
}
}
});
}
} }
} }

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 启动画面专用图标,使用较小的尺寸避免模糊 -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:width="96dp"
android:height="96dp"
android:gravity="center"
android:drawable="@mipmap/ic_launcher_foreground" />
</layer-list>

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="status_bar_color">#1a1a2e</color> <color name="status_bar_color">#1a1a2e</color>
</resources> <color name="splash_background">#0f0f1a</color>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#FFFFFF</color> <color name="ic_launcher_background">#FFFFFF</color>
</resources> </resources>

View File

@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<resources> <resources>
<string name="app_name">OGame Vue Ts</string> <string name="app_name">OGame Vue Ts</string>
<string name="title_activity_main">OGame Vue Ts</string> <string name="title_activity_main">OGame Vue Ts</string>
<string name="package_name">games.wenzi.ogame</string> <string name="package_name">games.wenzi.ogame</string>
<string name="custom_url_scheme">games.wenzi.ogame</string> <string name="custom_url_scheme">games.wenzi.ogame</string>
</resources> </resources>

View File

@@ -1,24 +1,33 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">#1a1a2e</item> <item name="colorPrimary">#1a1a2e</item>
<item name="colorPrimaryDark">#0f0f1a</item> <item name="colorPrimaryDark">#0f0f1a</item>
<item name="colorAccent">#6366f1</item> <item name="colorAccent">#6366f1</item>
</style> </style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar"> <style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:statusBarColor">#1a1a2e</item> <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">#1a1a2e</item> <item name="android:navigationBarColor">@android:color/transparent</item>
</style> <item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item>
</style>
<!-- 启动画面主题 - 延长显示直到 WebView 加载完成 -->
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="windowSplashScreenBackground">@color/splash_background</item>
</style> <!-- 使用较小的自定义图标避免模糊 -->
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<!-- 图标大小限制 -->
<item name="windowSplashScreenIconBackgroundColor">@color/splash_background</item>
<!-- 退出动画时长 -->
<item name="android:windowSplashScreenAnimationDuration">500</item>
<item name="postSplashScreenTheme">@style/AppTheme.NoActionBar</item>
</style>
</resources> </resources>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." /> <external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." /> <cache-path name="my_cache_images" path="." />
</paths> </paths>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<base-config cleartextTrafficPermitted="true"> <base-config cleartextTrafficPermitted="true">
<trust-anchors> <trust-anchors>
<certificates src="system" /> <certificates src="system" />
</trust-anchors> </trust-anchors>
</base-config> </base-config>
</network-security-config> </network-security-config>

View File

@@ -1,3 +1,9 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/app/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/filesystem/android')

17
build-docker.bat Normal file
View File

@@ -0,0 +1,17 @@
@echo off
REM 本地 Docker 构建脚本 (Windows)
REM 使用完整的源代码构建流程
echo 🚀 开始本地 Docker 构建...
REM 构建镜像
docker build -t ogame-vue-ts:local .
if %ERRORLEVEL% EQU 0 (
echo ✅ Docker 镜像构建成功!
echo 📦 镜像标签: ogame-vue-ts:local
echo 🏃 运行命令: docker run -p 8080:80 ogame-vue-ts:local
) else (
echo ❌ Docker 镜像构建失败!
exit /b 1
)

18
build-docker.sh Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# 本地 Docker 构建脚本
# 使用完整的源代码构建流程
echo "🚀 开始本地 Docker 构建..."
# 构建镜像
docker build -t ogame-vue-ts:local .
if [ $? -eq 0 ]; then
echo "✅ Docker 镜像构建成功!"
echo "📦 镜像标签: ogame-vue-ts:local"
echo "🏃 运行命令: docker run -p 8080:80 ogame-vue-ts:local"
else
echo "❌ Docker 镜像构建失败!"
exit 1
fi

View File

@@ -4,15 +4,15 @@ const config: CapacitorConfig = {
appId: 'games.wenzi.ogame', appId: 'games.wenzi.ogame',
appName: 'OGame Vue Ts', appName: 'OGame Vue Ts',
webDir: 'docs', webDir: 'docs',
server: { server: { androidScheme: 'https', cacheControl: 'max-age=31536000' },
androidScheme: 'https'
},
android: { android: {
buildOptions: { buildOptions: { keystorePath: undefined, keystoreAlias: undefined },
keystorePath: undefined, webContentsDebuggingEnabled: false,
keystoreAlias: undefined allowMixedContent: false,
} hardwareAcceleration: true
} },
// 禁用键盘自动调整视口
plugins: { Keyboard: { resize: 'none' } }
} }
export default config export default config

View File

@@ -6,6 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" /> <link rel="icon" type="image/svg+xml" href="/logo.svg" />
<link rel="apple-touch-icon" href="/logo.svg" />
<title>OGame-Vue-Ts</title> <title>OGame-Vue-Ts</title>
</head> </head>
@@ -13,8 +14,9 @@
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<!-- 统计勿删 --> <!-- 统计勿删 -->
<script charset="UTF-8" id="LA_COLLECT" src="https://sdk.51.la/js-sdk-pro.min.js"></script> <script>
<script>LA.init({ id: "L298GYqn6JhAO0VU", ck: "L298GYqn6JhAO0VU", autoTrack: true, hashMode: true })</script> !function (p) { "use strict"; !function (t) { var s = window, e = document, i = p, c = "".concat("https:" === e.location.protocol ? "https://" : "http://", "sdk.51.la/js-sdk-pro.min.js"), n = e.createElement("script"), r = e.getElementsByTagName("script")[0]; n.type = "text/javascript", n.setAttribute("charset", "UTF-8"), n.async = !0, n.src = c, n.id = "LA_COLLECT", i.d = n; var o = function () { s.LA.ids.push(i) }; s.LA ? s.LA.ids && o() : (s.LA = p, s.LA.ids = [], o()), r.parentNode.insertBefore(n, r) }() }({ id: "L298GYqn6JhAO0VU", ck: "L298GYqn6JhAO0VU", autoTrack: true, hashMode: true });
</script>
</body> </body>
</html> </html>

75
main.go
View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"bufio"
"embed" "embed"
"flag" "flag"
"fmt" "fmt"
@@ -10,6 +11,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
) )
@@ -20,10 +22,16 @@ var content embed.FS
func main() { func main() {
// --- 1. 命令行参数配置 --- // --- 1. 命令行参数配置 ---
// 定义 -port 参数,默认为 0自动分配 // 定义 -port 参数,默认为 -1表示未指定需要交互选择
portPtr := flag.Int("port", 0, "指定运行端口 (例如: 8080),不指定则自动分配可用端口") portPtr := flag.Int("port", -1, "指定运行端口 (例如: 8080),不指定则显示交互菜单")
flag.Parse() flag.Parse()
// 如果没有通过命令行指定端口,显示交互式菜单
port := *portPtr
if port == -1 {
port = showPortMenu()
}
// --- 2. 静态资源处理 --- // --- 2. 静态资源处理 ---
// 获取 docs 子目录的文件系统句柄 // 获取 docs 子目录的文件系统句柄
distFS, err := fs.Sub(content, "docs") distFS, err := fs.Sub(content, "docs")
@@ -60,10 +68,10 @@ func main() {
}) })
// --- 3. 端口监听逻辑 --- // --- 3. 端口监听逻辑 ---
addr := fmt.Sprintf("0.0.0.0:%d", *portPtr) addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
fmt.Printf("错误: 端口 %d 已被占用或监听失败: %v\n", *portPtr, err) fmt.Printf("错误: 端口 %d 已被占用或监听失败: %v\n", port, err)
// 停留 5 秒让用户看到错误信息 // 停留 5 秒让用户看到错误信息
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
os.Exit(1) os.Exit(1)
@@ -79,8 +87,8 @@ func main() {
fmt.Printf("启动时间: %s\n", time.Now().Format("2006-01-02 15:04:05")) fmt.Printf("启动时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Printf("本地访问: %s\n", localUrl) fmt.Printf("本地访问: %s\n", localUrl)
fmt.Printf("局域网访问: %s\n", lanUrl) fmt.Printf("局域网访问: %s\n", lanUrl)
if *portPtr != 0 { if port != 0 {
fmt.Printf("运行模式: 固定端口 (%d)\n", *portPtr) fmt.Printf("运行模式: 固定端口 (%d)\n", port)
} else { } else {
fmt.Printf("运行模式: 自动分配端口\n") fmt.Printf("运行模式: 自动分配端口\n")
} }
@@ -131,4 +139,59 @@ func openBrowser(url string) {
} }
_ = exec.Command(cmd, args...).Start() _ = exec.Command(cmd, args...).Start()
}
// 显示端口选择菜单
func showPortMenu() int {
reader := bufio.NewReader(os.Stdin)
fmt.Println("=======================================")
fmt.Println(" OGame Vue Ts 服务器启动")
fmt.Println("=======================================")
fmt.Println()
fmt.Println("请选择端口模式:")
fmt.Println(" [1] 随机端口 (自动分配可用端口)")
fmt.Println(" [2] 自定义端口 (指定固定端口)")
fmt.Println()
fmt.Print("请输入选项 (1/2): ")
for {
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
switch input {
case "1", "":
fmt.Println("\n已选择: 随机端口模式")
return 0
case "2":
return inputCustomPort(reader)
default:
fmt.Print("无效输入,请输入 1 或 2: ")
}
}
}
// 输入自定义端口
func inputCustomPort(reader *bufio.Reader) int {
fmt.Print("请输入端口号 (1-65535推荐: 8080): ")
for {
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
// 如果直接回车,使用默认端口 8080
if input == "" {
fmt.Println("\n已选择: 固定端口 8080")
return 8080
}
port, err := strconv.Atoi(input)
if err != nil || port < 1 || port > 65535 {
fmt.Print("无效端口号,请输入 1-65535 之间的数字: ")
continue
}
fmt.Printf("\n已选择: 固定端口 %d\n", port)
return port
}
} }

View File

@@ -8,8 +8,8 @@
"email": "1962257451@qq.com" "email": "1962257451@qq.com"
}, },
"private": true, "private": true,
"version": "1.5.5", "version": "1.6.5",
"buildDate": "2025/12/25 18:23:43", "buildDate": "2026/1/23 01:10:25",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -34,6 +34,7 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"finalhandler": "^2.1.1", "finalhandler": "^2.1.1",
"lightningcss": "^1.30.2",
"lucide-vue-next": "^0.556.0", "lucide-vue-next": "^0.556.0",
"marked": "^17.0.1", "marked": "^17.0.1",
"motion-v": "^1.7.4", "motion-v": "^1.7.4",
@@ -47,12 +48,12 @@
"vue-sonner": "^2.0.9" "vue-sonner": "^2.0.9"
}, },
"devDependencies": { "devDependencies": {
"@csstools/postcss-cascade-layers": "^5.0.2",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/node": "^24.10.2", "@types/node": "^24.10.2",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.23",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@@ -81,7 +82,6 @@
"electron" "electron"
] ]
}, },
"packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad",
"build": { "build": {
"appId": "games.wenzi.ogame", "appId": "games.wenzi.ogame",
"productName": "OGame-Vue-Ts", "productName": "OGame-Vue-Ts",

69
pnpm-lock.yaml generated
View File

@@ -44,6 +44,9 @@ importers:
finalhandler: finalhandler:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
lightningcss:
specifier: ^1.30.2
version: 1.30.2
lucide-vue-next: lucide-vue-next:
specifier: ^0.556.0 specifier: ^0.556.0
version: 0.556.0(vue@3.5.25(typescript@5.9.3)) version: 0.556.0(vue@3.5.25(typescript@5.9.3))
@@ -78,6 +81,9 @@ importers:
specifier: ^2.0.9 specifier: ^2.0.9
version: 2.0.9 version: 2.0.9
devDependencies: devDependencies:
'@csstools/postcss-cascade-layers':
specifier: ^5.0.2
version: 5.0.2(postcss@8.5.6)
'@types/crypto-js': '@types/crypto-js':
specifier: ^4.2.2 specifier: ^4.2.2
version: 4.2.2 version: 4.2.2
@@ -93,9 +99,6 @@ importers:
'@vue/tsconfig': '@vue/tsconfig':
specifier: ^0.8.1 specifier: ^0.8.1
version: 0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)) version: 0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
autoprefixer:
specifier: ^10.4.23
version: 10.4.23(postcss@8.5.6)
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@@ -674,6 +677,18 @@ packages:
'@capacitor/synapse@1.0.4': '@capacitor/synapse@1.0.4':
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==} resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
'@csstools/postcss-cascade-layers@5.0.2':
resolution: {integrity: sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==}
engines: {node: '>=18'}
peerDependencies:
postcss: ^8.4
'@csstools/selector-specificity@5.0.0':
resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==}
engines: {node: '>=18'}
peerDependencies:
postcss-selector-parser: ^7.0.0
'@develar/schema-utils@2.6.5': '@develar/schema-utils@2.6.5':
resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
engines: {node: '>= 8.9.0'} engines: {node: '>= 8.9.0'}
@@ -1672,13 +1687,6 @@ packages:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'} engines: {node: '>= 4.0.0'}
autoprefixer@10.4.23:
resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
postcss: ^8.1.0
available-typed-arrays@1.0.7: available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1922,6 +1930,11 @@ packages:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'} engines: {node: '>=8'}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
csstype@3.2.3: csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -2215,9 +2228,6 @@ packages:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
framer-motion@12.23.12: framer-motion@12.23.12:
resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==} resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==}
peerDependencies: peerDependencies:
@@ -3114,8 +3124,9 @@ packages:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
postcss-value-parser@4.2.0: postcss-selector-parser@7.1.1:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
engines: {node: '>=4'}
postcss@8.5.6: postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
@@ -4659,6 +4670,16 @@ snapshots:
'@capacitor/synapse@1.0.4': {} '@capacitor/synapse@1.0.4': {}
'@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.6)':
dependencies:
'@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1)
postcss: 8.5.6
postcss-selector-parser: 7.1.1
'@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)':
dependencies:
postcss-selector-parser: 7.1.1
'@develar/schema-utils@2.6.5': '@develar/schema-utils@2.6.5':
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
@@ -5680,15 +5701,6 @@ snapshots:
at-least-node@1.0.0: {} at-least-node@1.0.0: {}
autoprefixer@10.4.23(postcss@8.5.6):
dependencies:
browserslist: 4.28.1
caniuse-lite: 1.0.30001760
fraction.js: 5.3.4
picocolors: 1.1.1
postcss: 8.5.6
postcss-value-parser: 4.2.0
available-typed-arrays@1.0.7: available-typed-arrays@1.0.7:
dependencies: dependencies:
possible-typed-array-names: 1.1.0 possible-typed-array-names: 1.1.0
@@ -5977,6 +5989,8 @@ snapshots:
crypto-random-string@2.0.0: {} crypto-random-string@2.0.0: {}
cssesc@3.0.0: {}
csstype@3.2.3: {} csstype@3.2.3: {}
data-view-buffer@1.0.2: data-view-buffer@1.0.2:
@@ -6395,8 +6409,6 @@ snapshots:
hasown: 2.0.2 hasown: 2.0.2
mime-types: 2.1.35 mime-types: 2.1.35
fraction.js@5.3.4: {}
framer-motion@12.23.12: framer-motion@12.23.12:
dependencies: dependencies:
motion-dom: 12.23.12 motion-dom: 12.23.12
@@ -7267,7 +7279,10 @@ snapshots:
possible-typed-array-names@1.1.0: {} possible-typed-array-names@1.1.0: {}
postcss-value-parser@4.2.0: {} postcss-selector-parser@7.1.1:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss@8.5.6: postcss@8.5.6:
dependencies: dependencies:

View File

@@ -1,8 +1,6 @@
<template> <template>
<!-- 首页无侧边栏/头部 --> <!-- 首页无侧边栏/头部 -->
<template v-if="isHomePage"> <RouterView v-if="isHomePage" />
<RouterView />
</template>
<!-- 其他页面完整布局含侧边栏 --> <!-- 其他页面完整布局含侧边栏 -->
<SidebarProvider v-else :open="sidebarOpen" @update:open="handleSidebarOpenChange"> <SidebarProvider v-else :open="sidebarOpen" @update:open="handleSidebarOpenChange">
@@ -47,7 +45,7 @@
<ChevronsUpDown class="h-4 w-4 shrink-0 text-muted-foreground ml-2" /> <ChevronsUpDown class="h-4 w-4 shrink-0 text-muted-foreground ml-2" />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-72 p-0" side="bottom" align="start"> <PopoverContent class="w-70 p-0" side="bottom" align="start">
<div class="p-2"> <div class="p-2">
<div class="px-2 py-1.5 mb-1 text-xs font-semibold text-muted-foreground"> <div class="px-2 py-1.5 mb-1 text-xs font-semibold text-muted-foreground">
{{ t('planet.switchPlanet') }} {{ t('planet.switchPlanet') }}
@@ -133,13 +131,6 @@
<SidebarMenuBadge v-if="item.path === '/fleet' && activeFleetMissionsCount > 0" class="bg-primary text-primary-foreground"> <SidebarMenuBadge v-if="item.path === '/fleet' && activeFleetMissionsCount > 0" class="bg-primary text-primary-foreground">
{{ activeFleetMissionsCount }} {{ activeFleetMissionsCount }}
</SidebarMenuBadge> </SidebarMenuBadge>
<!-- 未读外交报告数量 -->
<SidebarMenuBadge
v-if="item.path === '/diplomacy' && unreadDiplomaticReportsCount > 0"
class="bg-destructive text-destructive-foreground"
>
{{ unreadDiplomaticReportsCount }}
</SidebarMenuBadge>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
@@ -387,7 +378,7 @@
</SidebarInset> </SidebarInset>
<!-- 右下角固定通知按钮 --> <!-- 右下角固定通知按钮 -->
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2"> <div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2" :class="{ 'bottom-15': Capacitor.isNativePlatform() }">
<!-- 返回顶部 --> <!-- 返回顶部 -->
<BackToTop /> <BackToTop />
<!-- 队列通知 --> <!-- 队列通知 -->
@@ -527,7 +518,7 @@
import HintToast from '@/components/notifications/HintToast.vue' import HintToast from '@/components/notifications/HintToast.vue'
import BackToTop from '@/components/common/BackToTop.vue' import BackToTop from '@/components/common/BackToTop.vue'
import Sonner from '@/components/ui/sonner/Sonner.vue' import Sonner from '@/components/ui/sonner/Sonner.vue'
import { MissionType, BuildingType, TechnologyType, DiplomaticEventType } from '@/types/game' import { MissionType, BuildingType, TechnologyType, DiplomaticEventType, ShipType, DefenseType } from '@/types/game'
import type { FleetMission, NPC, MissileAttack } from '@/types/game' import type { FleetMission, NPC, MissileAttack } from '@/types/game'
import { DIPLOMATIC_CONFIG } from '@/config/gameConfig' import { DIPLOMATIC_CONFIG } from '@/config/gameConfig'
import type { VersionInfo } from '@/utils/versionCheck' import type { VersionInfo } from '@/utils/versionCheck'
@@ -738,11 +729,6 @@
return fleetMissions + flyingMissiles return fleetMissions + flyingMissiles
}) })
// 未读外交报告数量
const unreadDiplomaticReportsCount = computed(() => {
return (gameStore.player.diplomaticReports || []).filter(r => !r.read).length
})
// 月球相关 // 月球相关
const moon = computed(() => { const moon = computed(() => {
if (!planet.value || planet.value.isMoon) return null if (!planet.value || planet.value.isMoon) return null
@@ -779,7 +765,7 @@
return return
} }
if (!settings.types[typeKey]) return if (!settings.types[typeKey as keyof typeof settings.types]) return
// 浏览器通知 // 浏览器通知
if (settings.browser && 'Notification' in window && Notification.permission === 'granted') { if (settings.browser && 'Notification' in window && Notification.permission === 'granted') {
@@ -852,8 +838,14 @@
// 计算离线收益(直接同步计算,应用游戏速度) // 计算离线收益(直接同步计算,应用游戏速度)
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now) const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now)
const miningTechLevel = gameStore.player.technologies[TechnologyType.MiningTechnology] || 0
const techBonuses = {
mineralResearchLevel: gameStore.player.technologies[TechnologyType.MineralResearch] || 0,
crystalResearchLevel: gameStore.player.technologies[TechnologyType.CrystalResearch] || 0,
fuelResearchLevel: gameStore.player.technologies[TechnologyType.FuelResearch] || 0
}
gameStore.player.planets.forEach(planet => { gameStore.player.planets.forEach(planet => {
resourceLogic.updatePlanetResources(planet, now, bonuses, gameStore.gameSpeed) resourceLogic.updatePlanetResources(planet, now, bonuses, gameStore.gameSpeed, miningTechLevel, techBonuses)
}) })
// 只在没有NPC星球时才生成首次加载已有玩家数据时 // 只在没有NPC星球时才生成首次加载已有玩家数据时
@@ -861,6 +853,29 @@
generateNPCPlanets() generateNPCPlanets()
} }
// 数据迁移:为没有 bonusPoints 的玩家计算奖励积分
if (gameStore.player.bonusPoints === undefined) {
// 计算基础积分(建筑、科技、舰船、防御)
let totalCost = 0
gameStore.player.planets.forEach(planet => {
Object.entries(planet.buildings).forEach(([buildingType, level]) => {
totalCost += publicLogic.calculateBuildingTotalCost(buildingType as BuildingType, level)
})
Object.entries(planet.fleet).forEach(([shipType, count]) => {
totalCost += publicLogic.calculateShipUnitCost(shipType as ShipType) * count
})
Object.entries(planet.defense).forEach(([defenseType, count]) => {
totalCost += publicLogic.calculateDefenseUnitCost(defenseType as DefenseType) * count
})
})
Object.entries(gameStore.player.technologies).forEach(([techType, level]) => {
totalCost += publicLogic.calculateTechnologyTotalCost(techType as TechnologyType, level)
})
const basePoints = Math.floor(totalCost / 1000)
// bonusPoints = 当前积分 - 基础积分
gameStore.player.bonusPoints = Math.max(0, gameStore.player.points - basePoints)
}
// 初始化或更新玩家积分 // 初始化或更新玩家积分
gameStore.player.points = publicLogic.calculatePlayerPoints(gameStore.player) gameStore.player.points = publicLogic.calculatePlayerPoints(gameStore.player)
@@ -1079,6 +1094,19 @@
const targetNpc = npcStore.npcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id)) const targetNpc = npcStore.npcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
if (targetNpc) { if (targetNpc) {
diplomaticLogic.handleAttackReputation(gameStore.player, targetNpc, attackResult.battleResult, npcStore.npcs, gameStore.locale) diplomaticLogic.handleAttackReputation(gameStore.player, targetNpc, attackResult.battleResult, npcStore.npcs, gameStore.locale)
// 同步战斗损失到NPC的实际星球数据
const npcPlanet = targetNpc.planets.find(p => p.id === targetPlanet.id)
if (npcPlanet) {
// 同步舰队损失
Object.entries(attackResult.battleResult.defenderLosses.fleet).forEach(([shipType, lost]) => {
npcPlanet.fleet[shipType as ShipType] = Math.max(0, (npcPlanet.fleet[shipType as ShipType] || 0) - lost)
})
// 同步防御损失修复后的数据已在targetPlanet中
npcPlanet.defense = { ...targetPlanet.defense }
// 同步资源(被掠夺后的)
npcPlanet.resources = { ...targetPlanet.resources }
}
} }
} }
@@ -1276,8 +1304,41 @@
} }
} }
} else if (mission.missionType === MissionType.Destroy) { } else if (mission.missionType === MissionType.Destroy) {
// 处理行星毁灭任务 // 处理行星毁灭任务(需要先战斗,再计算毁灭概率)
const destroyResult = fleetLogic.processDestroyArrival(mission, targetPlanet, gameStore.player) const destroyResult = await fleetLogic.processDestroyArrival(mission, targetPlanet, gameStore.player, null, gameStore.player.planets)
// 处理战斗报告(如果发生了战斗)
if (destroyResult.battleResult) {
gameStore.player.battleReports.push(destroyResult.battleResult)
// 处理战斗对NPC的影响
if (targetPlanet) {
const targetNpc = npcStore.npcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
if (targetNpc) {
diplomaticLogic.handleAttackReputation(gameStore.player, targetNpc, destroyResult.battleResult, npcStore.npcs, gameStore.locale)
// 同步战斗损失到NPC的实际星球数据
const npcPlanet = targetNpc.planets.find(p => p.id === targetPlanet.id)
if (npcPlanet) {
Object.entries(destroyResult.battleResult.defenderLosses.fleet).forEach(([shipType, lost]) => {
npcPlanet.fleet[shipType as ShipType] = Math.max(0, (npcPlanet.fleet[shipType as ShipType] || 0) - lost)
})
npcPlanet.defense = { ...targetPlanet.defense }
npcPlanet.resources = { ...targetPlanet.resources }
}
}
}
}
// 处理新生成的月球
if (destroyResult.moon) {
gameStore.player.planets.push(destroyResult.moon)
}
// 处理残骸场
if (destroyResult.debrisField) {
universeStore.debrisFields[destroyResult.debrisField.id] = destroyResult.debrisField
}
// 更新成就统计 - 行星毁灭 // 更新成就统计 - 行星毁灭
if (destroyResult.success) { if (destroyResult.success) {
@@ -1317,12 +1378,14 @@
? { ? {
destroyedPlanetName: destroyedPlanetName:
targetPlanet?.name || targetPlanet?.name ||
`[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]` `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
hadBattle: !!destroyResult.battleResult
} }
: { : {
failReason: destroyResult.failReason, failReason: destroyResult.failReason,
destructionChance: destroyResult.destructionChance, destructionChance: destroyResult.destructionChance,
deathstarsLost: destroyResult.deathstarsLost deathstarsLost: destroyResult.deathstarsLost,
hadBattle: !!destroyResult.battleResult
}, },
read: false read: false
}) })
@@ -1401,7 +1464,7 @@
delete universeStore.debrisFields[destroyedDebrisId] delete universeStore.debrisFields[destroyedDebrisId]
} }
} else if (mission.missionType === MissionType.Expedition) { } else if (mission.missionType === MissionType.Expedition) {
// 处理远征任务 // 处理探险任务
const expeditionResult = fleetLogic.processExpeditionArrival(mission) const expeditionResult = fleetLogic.processExpeditionArrival(mission)
// 确保返回时间正确设置(兼容旧版本任务数据) // 确保返回时间正确设置(兼容旧版本任务数据)
@@ -1413,12 +1476,12 @@
mission.returnTime = now + flightDuration mission.returnTime = now + flightDuration
} }
// 更新成就统计 - 远征 // 更新成就统计 - 探险
const isSuccessful = const isSuccessful =
expeditionResult.eventType === 'resources' || expeditionResult.eventType === 'darkMatter' || expeditionResult.eventType === 'fleet' expeditionResult.eventType === 'resources' || expeditionResult.eventType === 'darkMatter' || expeditionResult.eventType === 'fleet'
gameLogic.trackMissionStats(gameStore.player, 'expedition', { successful: isSuccessful }) gameLogic.trackMissionStats(gameStore.player, 'expedition', { successful: isSuccessful })
// 生成远征任务报告 // 生成探险任务报告
if (!gameStore.player.missionReports) { if (!gameStore.player.missionReports) {
gameStore.player.missionReports = [] gameStore.player.missionReports = []
} }
@@ -1472,6 +1535,17 @@
read: false read: false
}) })
} }
// 更新任务状态为返回中
// Deploy任务不需要返回舰队已经留在目标星球
if (mission.missionType !== MissionType.Deploy) {
mission.status = 'returning'
// 确保returnTime已设置如果还没设置的话
if (!mission.returnTime) {
const flightTime = mission.arrivalTime - mission.departureTime
mission.returnTime = Date.now() + flightTime
}
}
} }
const processMissionReturn = (mission: FleetMission) => { const processMissionReturn = (mission: FleetMission) => {
@@ -1690,10 +1764,16 @@
// 应用损失到目标星球 // 应用损失到目标星球
missileLogic.applyMissileAttackResult(targetPlanet, impactResult.defenseLosses) missileLogic.applyMissileAttackResult(targetPlanet, impactResult.defenseLosses)
// 如果目标是NPC的星球扣除外交好感度 // 如果目标是NPC的星球同步损失到NPC实际数据并扣除外交好感度
if (targetPlanet.ownerId && targetPlanet.ownerId !== gameStore.player.id) { if (targetPlanet.ownerId && targetPlanet.ownerId !== gameStore.player.id) {
const targetNpc = npcStore.npcs.find(npc => npc.id === targetPlanet.ownerId) const targetNpc = npcStore.npcs.find(npc => npc.id === targetPlanet.ownerId)
if (targetNpc) { if (targetNpc) {
// 同步防御损失到NPC的实际星球数据
const npcPlanet = targetNpc.planets.find(p => p.id === targetPlanet.id)
if (npcPlanet) {
missileLogic.applyMissileAttackResult(npcPlanet, impactResult.defenseLosses)
}
// 导弹攻击扣除好感度 // 导弹攻击扣除好感度
const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG
const reputationLoss = REPUTATION_CHANGES.ATTACK / 2 // 导弹攻击的好感度惩罚是普通攻击的一半 const reputationLoss = REPUTATION_CHANGES.ATTACK / 2 // 导弹攻击的好感度惩罚是普通攻击的一半
@@ -2234,6 +2314,7 @@
const switchToMoon = () => { const switchToMoon = () => {
if (moon.value) { if (moon.value) {
gameStore.currentPlanetId = moon.value.id gameStore.currentPlanetId = moon.value.id
router.push('/')
} }
} }
@@ -2241,12 +2322,14 @@
const switchToParentPlanet = () => { const switchToParentPlanet = () => {
if (planet.value?.parentPlanetId) { if (planet.value?.parentPlanetId) {
gameStore.currentPlanetId = planet.value.parentPlanetId gameStore.currentPlanetId = planet.value.parentPlanetId
router.push('/')
} }
} }
// 切换到指定星球 // 切换到指定星球
const switchToPlanet = (planetId: string) => { const switchToPlanet = (planetId: string) => {
gameStore.currentPlanetId = planetId gameStore.currentPlanetId = planetId
router.push('/')
} }
// 切换侧边栏 // 切换侧边栏

View File

@@ -72,15 +72,15 @@ html.light {
/* 队列添加动画 - 脉冲效果 */ /* 队列添加动画 - 脉冲效果 */
@keyframes queue-pulse-animation { @keyframes queue-pulse-animation {
0% { 0% {
transform: scale(1); transform: scale3d(1, 1, 1);
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
} }
50% { 50% {
transform: scale(1.1); transform: scale3d(1.1, 1.1, 1);
box-shadow: 0 0 0 8px rgba(34, 197, 94, 0); box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);
} }
100% { 100% {
transform: scale(1); transform: scale3d(1, 1, 1);
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
} }
} }

View File

@@ -9,7 +9,7 @@
<!-- 可缩放内容包装器 --> <!-- 可缩放内容包装器 -->
<div <div
class="map-content origin-top-left transition-transform duration-200" class="map-content origin-top-left transition-transform duration-200"
:style="{ transform: `scale(${zoomLevel})`, minWidth: calculatedMapWidth + 'px', minHeight: calculatedMapHeight + 'px' }" :style="{ transform: `scale3d(${zoomLevel}, ${zoomLevel}, 1)`, minWidth: calculatedMapWidth + 'px', minHeight: calculatedMapHeight + 'px' }"
> >
<!-- SVG连接线 - 位置与节点容器对齐 --> <!-- SVG连接线 - 位置与节点容器对齐 -->
<svg <svg

View File

@@ -176,7 +176,7 @@
} }
.quest-available .quest-node:hover { .quest-available .quest-node:hover {
transform: scale(1.05); transform: scale3d(1.05, 1.05, 1);
} }
@keyframes pulse-glow { @keyframes pulse-glow {

View File

@@ -1,6 +1,12 @@
<template> <template>
<Dialog :open="true" @update:open="handleClose"> <Dialog :open="true" @update:open="handleClose">
<DialogContent class="max-w-2xl p-0 overflow-hidden bg-gradient-to-b from-background to-background/95"> <DialogContent class="max-w-2xl p-0 overflow-hidden bg-gradient-to-b from-background to-background/95">
<!-- 可访问性标题隐藏 -->
<VisuallyHidden>
<DialogTitle>{{ t('campaign.dialogue.title') }}</DialogTitle>
<DialogDescription>{{ t('campaign.dialogue.description') }}</DialogDescription>
</VisuallyHidden>
<!-- 对话框头部 - 星空效果 --> <!-- 对话框头部 - 星空效果 -->
<div class="absolute inset-0 pointer-events-none overflow-hidden"> <div class="absolute inset-0 pointer-events-none overflow-hidden">
<div class="stars-bg" /> <div class="stars-bg" />
@@ -80,7 +86,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue' import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { Dialog, DialogContent } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import { VisuallyHidden } from 'reka-ui'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import type { StoryDialogue, DialogueChoice } from '@/types/game' import type { StoryDialogue, DialogueChoice } from '@/types/game'
import { User, Bot, HelpCircle, MessageCircle, ChevronRight } from 'lucide-vue-next' import { User, Bot, HelpCircle, MessageCircle, ChevronRight } from 'lucide-vue-next'

View File

@@ -491,9 +491,16 @@
return currentPlanet.value.buildings['naniteFactory'] || 0 return currentPlanet.value.buildings['naniteFactory'] || 0
}) })
// 获取研究所等级(用于研究时间计算 // 获取有效研究所等级(考虑星际研究网络
const researchLabLevel = computed(() => { const researchLabLevel = computed(() => {
if (!currentPlanet.value) return 0 if (!currentPlanet.value) return 0
const intergalacticResearchNetworkLevel = gameStore.player.technologies[TechnologyType.IntergalacticResearchNetwork] || 0
// 如果有星际研究网络,计算有效实验室等级
if (intergalacticResearchNetworkLevel > 0) {
return researchLogic.calculateEffectiveLabLevel(gameStore.player.planets, currentPlanet.value.id, intergalacticResearchNetworkLevel)
}
return currentPlanet.value.buildings['researchLab'] || 0 return currentPlanet.value.buildings['researchLab'] || 0
}) })
@@ -502,6 +509,12 @@
return gameStore.player.technologies['energyTechnology'] || 0 return gameStore.player.technologies['energyTechnology'] || 0
}) })
// 获取大学等级(用于研究时间计算)
const universityLevel = computed(() => {
if (!currentPlanet.value) return 0
return currentPlanet.value.buildings['university'] || 0
})
// 翻译键(转换为复数形式) // 翻译键(转换为复数形式)
const typeKey = computed(() => { const typeKey = computed(() => {
const typeMap = { const typeMap = {
@@ -582,11 +595,7 @@
const miningTechLevel = gameStore.player?.technologies?.[TechnologyType.MiningTechnology] || 0 const miningTechLevel = gameStore.player?.technologies?.[TechnologyType.MiningTechnology] || 0
// 使用增强版计算函数获取带加成的储量上限 // 使用增强版计算函数获取带加成的储量上限
const enhancedDeposits = oreDepositLogic.calculateEnhancedDeposits( const enhancedDeposits = oreDepositLogic.calculateEnhancedDeposits(deposits.position, deepDrillingLevel, miningTechLevel)
deposits.position,
deepDrillingLevel,
miningTechLevel
)
const initial = enhancedDeposits[resourceType] const initial = enhancedDeposits[resourceType]
// 百分比基于增强后的上限计算 // 百分比基于增强后的上限计算
@@ -931,7 +940,9 @@
level - 1, level - 1,
activeBonuses.value.researchSpeedBonus, activeBonuses.value.researchSpeedBonus,
researchLabLevel.value, researchLabLevel.value,
energyTechLevel.value energyTechLevel.value,
1,
universityLevel.value
) )
let researchSpeedBonus = 0 let researchSpeedBonus = 0

View File

@@ -1,5 +1,5 @@
<template> <template>
<div :class="[colors[type], sizes[size], 'rounded shadow-sm']" /> <span :class="[colors[type], sizes[size], 'inline-block rounded shrink-0']" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -8,21 +8,22 @@
size?: 'sm' | 'md' | 'lg' size?: 'sm' | 'md' | 'lg'
} }
const props = withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
size: 'md' size: 'md'
}) })
// 使用纯色背景,在 Android WebView 中更可靠
const colors = { const colors = {
metal: 'bg-gradient-to-br from-slate-400 to-slate-600', metal: 'bg-slate-500',
crystal: 'bg-gradient-to-br from-cyan-400 to-blue-600', crystal: 'bg-cyan-500',
deuterium: 'bg-gradient-to-br from-green-400 to-emerald-600', deuterium: 'bg-green-500',
darkMatter: 'bg-gradient-to-br from-purple-600 to-indigo-900', darkMatter: 'bg-purple-700',
energy: 'bg-gradient-to-br from-yellow-400 to-orange-500' energy: 'bg-yellow-500'
} }
const sizes = { const sizes = {
sm: 'w-3 h-3', sm: 'w-3 h-3 min-w-3 min-h-3',
md: 'w-4 h-4', md: 'w-4 h-4 min-w-4 min-h-4',
lg: 'w-5 h-5' lg: 'w-5 h-5 min-w-5 min-h-5'
} }
</script> </script>

View File

@@ -178,6 +178,16 @@
type: 'attacker-loss' | 'defender-loss' | 'info' type: 'attacker-loss' | 'defender-loss' | 'info'
} }
const battleLogs = ref<BattleLog[]>([]) const battleLogs = ref<BattleLog[]>([])
const MAX_LOGS = 100 // 限制日志数量防止100回合战斗导致性能问题
// 添加日志的辅助函数,自动限制数量
const addBattleLog = (log: BattleLog) => {
battleLogs.value.push(log)
// 如果超过最大数量,删除最旧的日志
if (battleLogs.value.length > MAX_LOGS) {
battleLogs.value = battleLogs.value.slice(-MAX_LOGS)
}
}
// 计算属性 // 计算属性
const totalRounds = computed(() => props.report.roundDetails?.length || props.report.rounds || 1) const totalRounds = computed(() => props.report.roundDetails?.length || props.report.rounds || 1)
@@ -367,97 +377,101 @@
if (currentRoundIndex.value >= totalRounds.value) return if (currentRoundIndex.value >= totalRounds.value) return
isPlayingRound = true isPlayingRound = true
const speed = parseFloat(speedMultiplier.value) try {
const roundIndex = currentRoundIndex.value const speed = parseFloat(speedMultiplier.value) || 1
const roundData = props.report.roundDetails?.[roundIndex] const roundIndex = currentRoundIndex.value
const roundData = props.report.roundDetails?.[roundIndex]
// 攻击动画阶段 // 攻击动画阶段
attackAnimationPhase.value = 'attack' attackAnimationPhase.value = 'attack'
// 添加日志 // 添加日志
battleLogs.value.push({ addBattleLog({
round: roundIndex + 1, round: roundIndex + 1,
message: t('messagesView.roundStarted').replace('{round}', String(roundIndex + 1)), message: t('messagesView.roundStarted').replace('{round}', String(roundIndex + 1)),
type: 'info' type: 'info'
}) })
// 等待攻击动画 // 等待攻击动画
await sleep(400 / speed) await sleep(400 / speed)
// 伤害阶段 // 伤害阶段
attackAnimationPhase.value = 'damage' attackAnimationPhase.value = 'damage'
// 计算当前回合的损失数字 // 计算当前回合的损失数字
if (roundData) { if (roundData) {
const attackerLoss = Object.values(roundData.attackerLosses).reduce((sum, count) => sum + count, 0) const attackerLoss = Object.values(roundData.attackerLosses || {}).reduce((sum, count) => sum + count, 0)
const defenderLoss = const defenderLoss =
Object.values(roundData.defenderLosses.fleet || {}).reduce((sum, count) => sum + count, 0) + Object.values(roundData.defenderLosses?.fleet || {}).reduce((sum, count) => sum + count, 0) +
Object.values(roundData.defenderLosses.defense || {}).reduce((sum, count) => sum + count, 0) Object.values(roundData.defenderLosses?.defense || {}).reduce((sum, count) => sum + count, 0)
displayedLosses.value = { attacker: attackerLoss, defender: defenderLoss } displayedLosses.value = { attacker: attackerLoss, defender: defenderLoss }
} else { } else {
displayedLosses.value = { attacker: 0, defender: 0 } displayedLosses.value = { attacker: 0, defender: 0 }
}
showDamageNumbers.value = true
if (roundData) {
// 记录攻击方损失
for (const [shipType, count] of Object.entries(roundData.attackerLosses || {})) {
if (count > 0) {
explodingShips.value.push({ side: 'attacker', type: shipType })
addBattleLog({
round: roundIndex + 1,
message: t('messagesView.shipDestroyed')
.replace('{count}', String(count))
.replace('{ship}', SHIPS.value[shipType as ShipType]?.name || shipType),
type: 'attacker-loss'
})
}
}
// 记录防守方损失
for (const [shipType, count] of Object.entries(roundData.defenderLosses?.fleet || {})) {
if (count > 0) {
explodingShips.value.push({ side: 'defender', type: shipType })
addBattleLog({
round: roundIndex + 1,
message: t('messagesView.shipDestroyed')
.replace('{count}', String(count))
.replace('{ship}', SHIPS.value[shipType as ShipType]?.name || shipType),
type: 'defender-loss'
})
}
}
for (const [defType, count] of Object.entries(roundData.defenderLosses?.defense || {})) {
if (count > 0) {
explodingShips.value.push({ side: 'defender', type: defType })
addBattleLog({
round: roundIndex + 1,
message: t('messagesView.defenseDestroyed')
.replace('{count}', String(count))
.replace('{defense}', DEFENSES.value[defType as DefenseType]?.name || defType),
type: 'defender-loss'
})
}
}
}
// 等待伤害显示
await sleep(600 / speed)
// 清理状态
attackAnimationPhase.value = 'idle'
showDamageNumbers.value = false
explodingShips.value = []
currentRoundIndex.value++
} finally {
// 确保锁始终被释放,即使发生错误
isPlayingRound = false
} }
showDamageNumbers.value = true
if (roundData) {
// 记录攻击方损失
for (const [shipType, count] of Object.entries(roundData.attackerLosses)) {
if (count > 0) {
explodingShips.value.push({ side: 'attacker', type: shipType })
battleLogs.value.push({
round: roundIndex + 1,
message: t('messagesView.shipDestroyed')
.replace('{count}', String(count))
.replace('{ship}', SHIPS.value[shipType as ShipType]?.name || shipType),
type: 'attacker-loss'
})
}
}
// 记录防守方损失
for (const [shipType, count] of Object.entries(roundData.defenderLosses.fleet || {})) {
if (count > 0) {
explodingShips.value.push({ side: 'defender', type: shipType })
battleLogs.value.push({
round: roundIndex + 1,
message: t('messagesView.shipDestroyed')
.replace('{count}', String(count))
.replace('{ship}', SHIPS.value[shipType as ShipType]?.name || shipType),
type: 'defender-loss'
})
}
}
for (const [defType, count] of Object.entries(roundData.defenderLosses.defense || {})) {
if (count > 0) {
explodingShips.value.push({ side: 'defender', type: defType })
battleLogs.value.push({
round: roundIndex + 1,
message: t('messagesView.defenseDestroyed')
.replace('{count}', String(count))
.replace('{defense}', DEFENSES.value[defType as DefenseType]?.name || defType),
type: 'defender-loss'
})
}
}
}
// 等待伤害显示
await sleep(600 / speed)
// 清理状态
attackAnimationPhase.value = 'idle'
showDamageNumbers.value = false
explodingShips.value = []
currentRoundIndex.value++
isPlayingRound = false
} }
const nextRound = () => { const nextRound = async () => {
if (currentRoundIndex.value < totalRounds.value) { if (currentRoundIndex.value < totalRounds.value) {
pause() pause()
playRound() await playRound()
} }
} }

View File

@@ -19,7 +19,7 @@
{{ t('diplomacy.markAllRead') }} {{ t('diplomacy.markAllRead') }}
</Button> </Button>
</div> </div>
<ScrollArea class="h-96"> <ScrollArea class="h-auto max-h-96 overflow-y-auto">
<Empty v-if="allNotifications.length === 0" class="border-0"> <Empty v-if="allNotifications.length === 0" class="border-0">
<EmptyContent> <EmptyContent>
<ScrollText class="h-10 w-10 text-muted-foreground" /> <ScrollText class="h-10 w-10 text-muted-foreground" />

View File

@@ -19,7 +19,7 @@
{{ t('enemyAlert.markAllRead') }} {{ t('enemyAlert.markAllRead') }}
</Button> </Button>
</div> </div>
<ScrollArea class="h-96"> <ScrollArea class="h-auto max-h-96 overflow-y-auto">
<Empty v-if="activeAlerts.length === 0" class="border-0"> <Empty v-if="activeAlerts.length === 0" class="border-0">
<EmptyContent> <EmptyContent>
<Shield class="h-10 w-10 text-muted-foreground" /> <Shield class="h-10 w-10 text-muted-foreground" />

View File

@@ -32,7 +32,7 @@
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<ScrollArea class="h-[420px]"> <ScrollArea class="h-auto max-h-96 overflow-y-auto">
<TabsContent v-for="tab in tabConfig" :key="tab.value" :value="tab.value" class="mt-0"> <TabsContent v-for="tab in tabConfig" :key="tab.value" :value="tab.value" class="mt-0">
<Empty v-if="tab.items.length === 0" class="border-0"> <Empty v-if="tab.items.length === 0" class="border-0">
<EmptyContent> <EmptyContent>
@@ -63,7 +63,9 @@
class="text-[10px] sm:text-xs whitespace-nowrap" class="text-[10px] sm:text-xs whitespace-nowrap"
:class="isWaitingItemResourcesReady(item as WaitingQueueItem) ? 'text-green-500' : 'text-yellow-500'" :class="isWaitingItemResourcesReady(item as WaitingQueueItem) ? 'text-green-500' : 'text-yellow-500'"
> >
{{ isWaitingItemResourcesReady(item as WaitingQueueItem) ? t('queue.resourcesReady') : t('queue.waitingResources') }} {{
isWaitingItemResourcesReady(item as WaitingQueueItem) ? t('queue.resourcesReady') : t('queue.waitingResources')
}}
</span> </span>
<Button <Button
variant="ghost" variant="ghost"
@@ -146,8 +148,8 @@
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { formatTime, formatNumber } from '@/utils/format' import { formatTime, formatNumber } from '@/utils/format'
import type { BuildQueueItem, WaitingQueueItem, BuildingType, ShipType, DefenseType, TechnologyType, Resources } from '@/types/game' import type { BuildQueueItem, WaitingQueueItem, BuildingType, ShipType, DefenseType, TechnologyType, Resources } from '@/types/game'
import * as waitingQueueLogic from '@/logic/waitingQueueLogic' import * as waitingQueueLogic from '@/logic/waitingQueueLogic'
import * as resourceLogic from '@/logic/resourceLogic' import * as resourceLogic from '@/logic/resourceLogic'
const { t } = useI18n() const { t } = useI18n()
const gameStore = useGameStore() const gameStore = useGameStore()

View File

@@ -81,6 +81,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { useGameStore } from '@/stores/gameStore'
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -95,13 +96,11 @@ import { Label } from '@/components/ui/label'
import { Loader2 } from 'lucide-vue-next' import { Loader2 } from 'lucide-vue-next'
import { import {
type WebDAVConfig, type WebDAVConfig,
getWebDAVConfig,
saveWebDAVConfig,
clearWebDAVConfig,
testWebDAVConnection testWebDAVConnection
} from '@/services/webdavService' } from '@/services/webdavService'
const { t } = useI18n() const { t } = useI18n()
const gameStore = useGameStore()
const isOpen = defineModel<boolean>('open', { default: false }) const isOpen = defineModel<boolean>('open', { default: false })
@@ -130,7 +129,7 @@ const isConfigValid = computed(() => {
// 加载已保存的配置 // 加载已保存的配置
watch(isOpen, (open) => { watch(isOpen, (open) => {
if (open) { if (open) {
const saved = getWebDAVConfig() const saved = gameStore.webdavConfig
if (saved) { if (saved) {
config.value = { ...saved } config.value = { ...saved }
hasExistingConfig.value = true hasExistingConfig.value = true
@@ -152,21 +151,25 @@ const handleTest = async () => {
testResult.value = null testResult.value = null
try { try {
testResult.value = await testWebDAVConnection(config.value) const result = await testWebDAVConnection(config.value)
testResult.value = {
success: result.success,
message: t(result.messageKey)
}
} finally { } finally {
isTesting.value = false isTesting.value = false
} }
} }
const handleSave = () => { const handleSave = () => {
saveWebDAVConfig(config.value) gameStore.webdavConfig = { ...config.value }
hasExistingConfig.value = true hasExistingConfig.value = true
emit('saved', config.value) emit('saved', config.value)
isOpen.value = false isOpen.value = false
} }
const handleClear = () => { const handleClear = () => {
clearWebDAVConfig() gameStore.webdavConfig = null
hasExistingConfig.value = false hasExistingConfig.value = false
config.value = { config.value = {
serverUrl: '', serverUrl: '',

View File

@@ -121,7 +121,7 @@ const loadFiles = async () => {
if (result.success && result.files) { if (result.success && result.files) {
files.value = result.files files.value = result.files
} else { } else {
error.value = result.message || t('settings.webdav.loadFailed') error.value = t(result.messageKey) || t('settings.webdav.loadFailed')
} }
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : String(e) error.value = e instanceof Error ? e.message : String(e)
@@ -174,7 +174,7 @@ const handleDelete = async (fileName: string) => {
selectedFile.value = null selectedFile.value = null
} }
} else { } else {
toast.error(result.message || t('settings.webdav.deleteFailed')) toast.error(t(result.messageKey) || t('settings.webdav.deleteFailed'))
} }
} }
</script> </script>

View File

@@ -2,20 +2,21 @@
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay <AlertDialogOverlay
data-slot="alert-dialog-overlay" data-slot="alert-dialog-overlay"
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80" class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4"
/>
<AlertDialogContent
data-slot="alert-dialog-content"
v-bind="{ ...$attrs, ...forwarded }"
:class="
cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
props.class
)
"
> >
<slot /> <AlertDialogContent
</AlertDialogContent> data-slot="alert-dialog-content"
v-bind="{ ...$attrs, ...forwarded }"
:class="
cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-50 grid w-full max-w-lg gap-4 rounded-lg border p-6 shadow-lg duration-200',
props.class
)
"
>
<slot />
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialogPortal> </AlertDialogPortal>
</template> </template>

View File

@@ -71,11 +71,17 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { SpringOptions, Transition } from 'motion-v'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { motion, useMotionValue, useSpring } from 'motion-v' import { motion, useMotionValue, useSpring } from 'motion-v'
import { computed, onMounted, ref, watch } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
// 本地定义类型,因为 motion-v 不导出这些类型
interface SpringOptions {
stiffness?: number
damping?: number
mass?: number
}
interface StarsBackgroundProps { interface StarsBackgroundProps {
factor?: number factor?: number
speed?: number speed?: number
@@ -139,19 +145,19 @@
} }
) )
const starLayer1Transition = computed<Transition>(() => ({ const starLayer1Transition = computed(() => ({
repeat: Infinity, repeat: Infinity,
duration: props.speed, duration: props.speed,
ease: 'linear' as const ease: 'linear' as const
})) }))
const starLayer2Transition = computed<Transition>(() => ({ const starLayer2Transition = computed(() => ({
repeat: Infinity, repeat: Infinity,
duration: props.speed * 2, duration: props.speed * 2,
ease: 'linear' as const ease: 'linear' as const
})) }))
const starLayer3Transition = computed<Transition>(() => ({ const starLayer3Transition = computed(() => ({
repeat: Infinity, repeat: Infinity,
duration: props.speed * 3, duration: props.speed * 3,
ease: 'linear' as const ease: 'linear' as const

View File

@@ -1,27 +1,31 @@
<template> <template>
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay
<DialogContent data-slot="dialog-overlay"
data-slot="dialog-content" class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80 flex items-center justify-center p-4"
v-bind="{ ...$attrs, ...forwarded }"
:class="
cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-1/2 left-1/2 z-60 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-2xl',
props.class
)
"
> >
<slot /> <DialogContent
data-slot="dialog-content"
<DialogClose v-bind="{ ...$attrs, ...forwarded }"
v-if="showCloseButton" :class="
data-slot="dialog-close" cn(
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-60 grid w-full max-w-2xl gap-4 rounded-lg border p-6 shadow-lg duration-200 relative',
props.class
)
"
> >
<X /> <slot />
<span class="sr-only">Close</span>
</DialogClose> <DialogClose
</DialogContent> v-if="showCloseButton"
data-slot="dialog-close"
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<X />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>
</DialogOverlay>
</DialogPortal> </DialogPortal>
</template> </template>
@@ -30,9 +34,8 @@
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core' import { reactiveOmit } from '@vueuse/core'
import { X } from 'lucide-vue-next' import { X } from 'lucide-vue-next'
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui' import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import DialogOverlay from './DialogOverlay.vue'
defineOptions({ defineOptions({
inheritAttrs: false inheritAttrs: false

View File

@@ -4,7 +4,7 @@
v-bind="delegatedProps" v-bind="delegatedProps"
:class=" :class="
cn( cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80', 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80 flex items-center justify-center p-4',
props.class props.class
) )
" "

View File

@@ -1,16 +1,19 @@
<template> <template>
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay
<DialogContent data-slot="dialog-overlay"
data-slot="scrollable-dialog-content" class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80 flex items-center justify-center p-4"
v-bind="{ ...$attrs, ...forwarded }"
:class="
cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-1/2 left-1/2 z-60 w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg border shadow-lg duration-200 sm:w-auto sm:min-w-[764px] flex flex-col p-0',
containerClass
)
"
> >
<DialogContent
data-slot="scrollable-dialog-content"
v-bind="{ ...$attrs, ...forwarded }"
:class="
cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-60 w-full max-w-3xl rounded-lg border shadow-lg duration-200 flex flex-col p-0 relative',
containerClass
)
"
>
<!-- 固定的头部 --> <!-- 固定的头部 -->
<div class="shrink-0 px-4 pt-4 pb-3 sm:px-6 sm:pt-6 sm:pb-4 border-b"> <div class="shrink-0 px-4 pt-4 pb-3 sm:px-6 sm:pt-6 sm:pb-4 border-b">
<slot name="header" /> <slot name="header" />
@@ -35,7 +38,8 @@
<X /> <X />
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>
</DialogOverlay>
</DialogPortal> </DialogPortal>
</template> </template>
@@ -44,9 +48,8 @@
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core' import { reactiveOmit } from '@vueuse/core'
import { X } from 'lucide-vue-next' import { X } from 'lucide-vue-next'
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui' import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import DialogOverlay from './DialogOverlay.vue'
defineOptions({ defineOptions({
inheritAttrs: false inheritAttrs: false

View File

@@ -7,7 +7,7 @@
<ProgressIndicator <ProgressIndicator
data-slot="progress-indicator" data-slot="progress-indicator"
class="bg-primary h-full w-full flex-1 transition-all" class="bg-primary h-full w-full flex-1 transition-all"
:style="`transform: translateX(-${100 - (props.modelValue ?? 0)}%);`" :style="`transform: translate3d(-${100 - (props.modelValue ?? 0)}%, 0, 0);`"
/> />
</ProgressRoot> </ProgressRoot>
</template> </template>

View File

@@ -43,7 +43,8 @@ export const useGameConfig = () => {
[BuildingType.JumpGate]: 'jumpGate', [BuildingType.JumpGate]: 'jumpGate',
[BuildingType.PlanetDestroyerFactory]: 'planetDestroyerFactory', [BuildingType.PlanetDestroyerFactory]: 'planetDestroyerFactory',
[BuildingType.GeoResearchStation]: 'geoResearchStation', [BuildingType.GeoResearchStation]: 'geoResearchStation',
[BuildingType.DeepDrillingFacility]: 'deepDrillingFacility' [BuildingType.DeepDrillingFacility]: 'deepDrillingFacility',
[BuildingType.University]: 'university'
} }
// 舰船类型枚举值到翻译键的映射 // 舰船类型枚举值到翻译键的映射
@@ -100,7 +101,11 @@ export const useGameConfig = () => {
[TechnologyType.DarkMatterTechnology]: 'darkMatterTechnology', [TechnologyType.DarkMatterTechnology]: 'darkMatterTechnology',
[TechnologyType.TerraformingTechnology]: 'terraformingTechnology', [TechnologyType.TerraformingTechnology]: 'terraformingTechnology',
[TechnologyType.PlanetDestructionTech]: 'planetDestructionTech', [TechnologyType.PlanetDestructionTech]: 'planetDestructionTech',
[TechnologyType.MiningTechnology]: 'miningTechnology' [TechnologyType.MiningTechnology]: 'miningTechnology',
[TechnologyType.IntergalacticResearchNetwork]: 'intergalacticResearchNetwork',
[TechnologyType.MineralResearch]: 'mineralResearch',
[TechnologyType.CrystalResearch]: 'crystalResearch',
[TechnologyType.FuelResearch]: 'fuelResearch'
} }
// 军官类型枚举值到翻译键的映射 // 军官类型枚举值到翻译键的映射

View File

@@ -110,7 +110,7 @@ export const triggerQueueAnimation = (event: MouseEvent, type: 'building' | 'tec
el.style.left = `${x}px` el.style.left = `${x}px`
el.style.top = `${y}px` el.style.top = `${y}px`
el.style.transform = `translate(-50%, -50%) scale(${scale})` el.style.transform = `translate3d(-50%, -50%, 0) scale3d(${scale}, ${scale}, 1)`
el.style.opacity = `${opacity}` el.style.opacity = `${opacity}`
if (progress < 1) { if (progress < 1) {

View File

@@ -284,7 +284,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
id: BuildingType.LunarBase, id: BuildingType.LunarBase,
name: '月球基地', name: '月球基地',
description: '增加月球可用空间', description: '增加月球可用空间',
baseCost: { metal: 20000, crystal: 40000, deuterium: 20000, darkMatter: 0, energy: 0 }, baseCost: { metal: 8000, crystal: 8000, deuterium: 4000, darkMatter: 0, energy: 0 },
baseTime: 45, // 减少建造时间60→45秒 baseTime: 45, // 减少建造时间60→45秒
costMultiplier: 2, costMultiplier: 2,
spaceUsage: 0, // 月球基地本身不占用空间,反而增加空间 spaceUsage: 0, // 月球基地本身不占用空间,反而增加空间
@@ -357,7 +357,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
[BuildingType.GeoResearchStation]: { [BuildingType.GeoResearchStation]: {
id: BuildingType.GeoResearchStation, id: BuildingType.GeoResearchStation,
name: '地质研究站', name: '地质研究站',
description: '研究地质结构,提高矿脉自然恢复速度。每级增加50%恢复速率', description: '研究地质结构,提高矿脉自然恢复速度。每级增加10%恢复速率',
baseCost: { metal: 50000, crystal: 30000, deuterium: 20000, darkMatter: 0, energy: 0 }, baseCost: { metal: 50000, crystal: 30000, deuterium: 20000, darkMatter: 0, energy: 0 },
baseTime: 60, baseTime: 60,
costMultiplier: 1.8, costMultiplier: 1.8,
@@ -416,6 +416,34 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
[TechnologyType.PlasmaTechnology]: 5 [TechnologyType.PlasmaTechnology]: 5
} }
} }
},
// 2moons新增建筑
[BuildingType.University]: {
id: BuildingType.University,
name: '大学',
description: '培养科研人员加快研究速度。每级减少研究时间8%(与星际研究网络叠加)',
baseCost: { metal: 200000, crystal: 100000, deuterium: 50000, darkMatter: 0, energy: 0 },
baseTime: 120,
costMultiplier: 2,
spaceUsage: 8,
planetOnly: true,
maxLevel: 10, // 最多10级最高约57%研究时间减少)
requirements: {
[BuildingType.ResearchLab]: 8,
[BuildingType.RoboticsFactory]: 6
},
levelRequirements: {
5: {
[BuildingType.ResearchLab]: 10,
[BuildingType.RoboticsFactory]: 8,
[TechnologyType.EnergyTechnology]: 8
},
8: {
[BuildingType.ResearchLab]: 12,
[BuildingType.NaniteFactory]: 2,
[TechnologyType.ComputerTechnology]: 8
}
}
} }
} }
@@ -510,12 +538,13 @@ export const TECHNOLOGIES: Record<TechnologyType, TechnologyConfig> = {
baseTime: 60, baseTime: 60,
costMultiplier: 2, costMultiplier: 2,
fleetStorageBonus: 500, // 每级全局增加500舰队仓储 fleetStorageBonus: 500, // 每级全局增加500舰队仓储
maxLevel: 10, // 最多10最多11个研究队列和11个舰队槽位) maxLevel: 10, // 最多15最多16个研究队列和16个舰队槽位)
requirements: { [BuildingType.ResearchLab]: 1 }, requirements: { [BuildingType.ResearchLab]: 1 },
levelRequirements: { levelRequirements: {
3: { [BuildingType.ResearchLab]: 5 }, 3: { [BuildingType.ResearchLab]: 5 },
5: { [BuildingType.ResearchLab]: 8, [BuildingType.RoboticsFactory]: 5 }, 5: { [BuildingType.ResearchLab]: 8, [BuildingType.RoboticsFactory]: 5 },
8: { [BuildingType.ResearchLab]: 10, [BuildingType.NaniteFactory]: 2 } 8: { [BuildingType.ResearchLab]: 10, [BuildingType.NaniteFactory]: 2 },
12: { [BuildingType.ResearchLab]: 12, [BuildingType.NaniteFactory]: 5 }
} }
}, },
[TechnologyType.EspionageTechnology]: { [TechnologyType.EspionageTechnology]: {
@@ -726,6 +755,111 @@ export const TECHNOLOGIES: Record<TechnologyType, TechnologyConfig> = {
[TechnologyType.PlasmaTechnology]: 3 [TechnologyType.PlasmaTechnology]: 3
} }
} }
},
// 2moons新增科技
[TechnologyType.IntergalacticResearchNetwork]: {
id: TechnologyType.IntergalacticResearchNetwork,
name: '星际研究网络',
description: '连接多个星球的研究实验室共享研究资源。每级可连接1个额外的研究实验室取等级最高的N个实验室',
baseCost: { metal: 240000, crystal: 400000, deuterium: 160000, darkMatter: 0, energy: 0 },
baseTime: 180,
costMultiplier: 2,
maxLevel: 10,
requirements: {
[BuildingType.ResearchLab]: 10,
[TechnologyType.ComputerTechnology]: 8,
[TechnologyType.HyperspaceTechnology]: 8
},
levelRequirements: {
5: {
[BuildingType.ResearchLab]: 12,
[TechnologyType.ComputerTechnology]: 10,
[TechnologyType.HyperspaceTechnology]: 10
},
8: {
[BuildingType.ResearchLab]: 14,
[TechnologyType.ComputerTechnology]: 10,
[BuildingType.NaniteFactory]: 5
}
}
},
[TechnologyType.MineralResearch]: {
id: TechnologyType.MineralResearch,
name: '矿物研究',
description: '研究更高效的金属提取技术提升金属矿产量。每级增加金属产量2%',
baseCost: { metal: 60000, crystal: 30000, deuterium: 0, darkMatter: 0, energy: 0 },
baseTime: 60,
costMultiplier: 1.75,
maxLevel: 20,
requirements: {
[BuildingType.ResearchLab]: 5,
[BuildingType.MetalMine]: 10,
[TechnologyType.EnergyTechnology]: 3
},
levelRequirements: {
10: {
[BuildingType.ResearchLab]: 8,
[BuildingType.MetalMine]: 20,
[TechnologyType.EnergyTechnology]: 8
},
15: {
[BuildingType.ResearchLab]: 12,
[BuildingType.NaniteFactory]: 2,
[TechnologyType.PlasmaTechnology]: 5
}
}
},
[TechnologyType.CrystalResearch]: {
id: TechnologyType.CrystalResearch,
name: '晶体研究',
description: '研究更高效的晶体提炼技术提升晶体矿产量。每级增加晶体产量2%',
baseCost: { metal: 40000, crystal: 60000, deuterium: 0, darkMatter: 0, energy: 0 },
baseTime: 60,
costMultiplier: 1.75,
maxLevel: 20,
requirements: {
[BuildingType.ResearchLab]: 5,
[BuildingType.CrystalMine]: 10,
[TechnologyType.EnergyTechnology]: 3
},
levelRequirements: {
10: {
[BuildingType.ResearchLab]: 8,
[BuildingType.CrystalMine]: 20,
[TechnologyType.EnergyTechnology]: 8
},
15: {
[BuildingType.ResearchLab]: 12,
[BuildingType.NaniteFactory]: 2,
[TechnologyType.PlasmaTechnology]: 5
}
}
},
[TechnologyType.FuelResearch]: {
id: TechnologyType.FuelResearch,
name: '燃料研究',
description: '研究更高效的重氢合成技术提升重氢产量。每级增加重氢产量2%',
baseCost: { metal: 0, crystal: 50000, deuterium: 50000, darkMatter: 0, energy: 0 },
baseTime: 60,
costMultiplier: 1.75,
maxLevel: 20,
requirements: {
[BuildingType.ResearchLab]: 5,
[BuildingType.DeuteriumSynthesizer]: 10,
[TechnologyType.EnergyTechnology]: 3
},
levelRequirements: {
10: {
[BuildingType.ResearchLab]: 8,
[BuildingType.DeuteriumSynthesizer]: 20,
[TechnologyType.EnergyTechnology]: 8
},
15: {
[BuildingType.ResearchLab]: 12,
[BuildingType.NaniteFactory]: 2,
[TechnologyType.PlasmaTechnology]: 5
}
}
} }
} }
@@ -1007,7 +1141,7 @@ export const SHIPS: Record<ShipType, ShipConfig> = {
id: ShipType.Deathstar, id: ShipType.Deathstar,
name: '死星', name: '死星',
description: '终极武器,能够摧毁整个行星', description: '终极武器,能够摧毁整个行星',
cost: { metal: 5000000, crystal: 4000000, deuterium: 1000000, darkMatter: 20000, energy: 0 }, cost: { metal: 50000000, crystal: 40000000, deuterium: 10000000, darkMatter: 20000, energy: 0 },
buildTime: 600, buildTime: 600,
cargoCapacity: 1000000, cargoCapacity: 1000000,
attack: 200000, attack: 200000,
@@ -1262,7 +1396,7 @@ export const MOON_CONFIG = {
maxChance: 20, // 最大20%概率需要2M残骸 maxChance: 20, // 最大20%概率需要2M残骸
chancePerDebris: 100000, // 每10万资源增加1%概率 chancePerDebris: 100000, // 每10万资源增加1%概率
baseFields: 1, // 月球初始空间OGame规则初始只有1格 baseFields: 1, // 月球初始空间OGame规则初始只有1格
lunarBaseFieldsBonus: 3, // 每级月球基地增加的空间(每级+3格占用1格净增2格) lunarBaseFieldsBonus: 30, // 每级月球基地增加的空间(每级+30格,占用0格)
minDiameter: 3476, // 最小月球直径(km)1%概率时 minDiameter: 3476, // 最小月球直径(km)1%概率时
maxDiameter: 8944, // 最大月球直径(km)20%概率时保证>8000km maxDiameter: 8944, // 最大月球直径(km)20%概率时保证>8000km
baseDiameter: 3000, // 基础直径(km) baseDiameter: 3000, // 基础直径(km)
@@ -1467,9 +1601,9 @@ export const DIPLOMATIC_CONFIG = {
// 贸易检查间隔(秒) // 贸易检查间隔(秒)
CHECK_INTERVAL: 1800, // 30分钟 CHECK_INTERVAL: 1800, // 30分钟
// 贸易概率 // 贸易概率
PROBABILITY: 0.1, PROBABILITY: 0.05,
// 玩家最多同时保留的贸易提议数量 // 玩家最多同时保留的贸易提议数量
MAX_PENDING_OFFERS: 30, MAX_PENDING_OFFERS: 5,
// 汇率配置NPC出售资源的汇率 // 汇率配置NPC出售资源的汇率
EXCHANGE_RATES: { EXCHANGE_RATES: {
// 金属:晶体:重氢 基准比例 3:2:1 // 金属:晶体:重氢 基准比例 3:2:1
@@ -1658,9 +1792,9 @@ export const ORE_DEPOSIT_CONFIG = {
// 矿脉恢复配置 // 矿脉恢复配置
REGENERATION: { REGENERATION: {
ENABLED: true, ENABLED: true,
// 每小时恢复初始储量的百分比(0.1% = 每小时恢复0.1%约42天完全恢复 // 每小时恢复初始储量的百分比1% = 每小时恢复1%约4.2天完全恢复
RATE_PER_HOUR: 0.001, RATE_PER_HOUR: 0.01,
// 最大恢复到初始储量的百分比100%表示可以完全恢复) // 最大恢复到初始储量的百分比100表示可以完全恢复%
MAX_PERCENTAGE: 1.0 MAX_PERCENTAGE: 1.0
} }
} }

View File

@@ -153,10 +153,15 @@ export default {
planetDestroyerFactory: 'Planetenzerstörer-Fabrik', planetDestroyerFactory: 'Planetenzerstörer-Fabrik',
geoResearchStation: 'Geologische Forschungsstation', geoResearchStation: 'Geologische Forschungsstation',
deepDrillingFacility: 'Tiefbohranlage', deepDrillingFacility: 'Tiefbohranlage',
university: 'Universität',
buildTime: 'Bauzeit', buildTime: 'Bauzeit',
build: 'Bauen', build: 'Bauen',
production: 'Produktion', production: 'Produktion',
consumption: 'Verbrauch', consumption: 'Verbrauch',
levelRange: 'Stufenbereich',
totalCost: 'Gesamtkosten',
totalPoints: 'Gesamtpunkte',
researchSpeedBonus: 'Forschungsgeschwindigkeitsbonus',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -200,8 +205,9 @@ export default {
jumpGate: 'Überträgt Flotten sofort zu anderen Monden', jumpGate: 'Überträgt Flotten sofort zu anderen Monden',
planetDestroyerFactory: 'Konstruiert ultimative Waffen zur Zerstörung von Planeten', planetDestroyerFactory: 'Konstruiert ultimative Waffen zur Zerstörung von Planeten',
geoResearchStation: geoResearchStation:
'Erforscht geologische Strukturen und erhöht die natürliche Erzvorkommen-Regenerationsrate. +50% Regeneration pro Stufe', 'Erforscht geologische Strukturen und erhöht die natürliche Erzvorkommen-Regenerationsrate. +10% Regeneration pro Stufe',
deepDrillingFacility: '' deepDrillingFacility: '',
university: 'Bildet Forscher aus, um die Forschungsgeschwindigkeit zu beschleunigen. -8% Forschungszeit pro Stufe'
}, },
ships: { ships: {
lightFighter: 'Leichter Jäger', lightFighter: 'Leichter Jäger',
@@ -265,6 +271,9 @@ export default {
}, },
research: { research: {
researchTime: 'Forschungszeit', researchTime: 'Forschungszeit',
levelRange: 'Stufenbereich',
totalCost: 'Gesamtkosten',
totalPoints: 'Gesamtpunkte',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -280,7 +289,11 @@ export default {
researchQueueBonus: 'Forschungswarteschlange', researchQueueBonus: 'Forschungswarteschlange',
colonySlots: 'Kolonieplätze', colonySlots: 'Kolonieplätze',
forAllPlanets: '(Global)', forAllPlanets: '(Global)',
speedBonus: 'Geschwindigkeitsbonus' speedBonus: 'Geschwindigkeitsbonus',
// Ressourcenproduktionsbonus-Anzeige
mineralResearch: 'Mineralforschung',
crystalResearch: 'Kristallforschung',
fuelResearch: 'Brennstoffforschung'
}, },
technologies: { technologies: {
energyTechnology: 'Energietechnik', energyTechnology: 'Energietechnik',
@@ -301,7 +314,11 @@ export default {
darkMatterTechnology: 'Dunkle-Materie-Technologie', darkMatterTechnology: 'Dunkle-Materie-Technologie',
terraformingTechnology: 'Terraforming-Technologie', terraformingTechnology: 'Terraforming-Technologie',
planetDestructionTech: 'Planetenzerstörungstechnologie', planetDestructionTech: 'Planetenzerstörungstechnologie',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: 'Intergalaktisches Forschungsnetzwerk',
mineralResearch: 'Mineralforschung',
crystalResearch: 'Kristallforschung',
fuelResearch: 'Treibstoffforschung'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: 'Verbessert Energieeffizienz', energyTechnology: 'Verbessert Energieeffizienz',
@@ -323,7 +340,11 @@ export default {
darkMatterTechnology: 'Forschung zu Eigenschaften und Anwendungen von Dunkler Materie', darkMatterTechnology: 'Forschung zu Eigenschaften und Anwendungen von Dunkler Materie',
terraformingTechnology: 'Forschung zur Planeten-Terraforming-Technologie, erhöht verfügbaren Platz aller Planeten um 30 pro Stufe', terraformingTechnology: 'Forschung zur Planeten-Terraforming-Technologie, erhöht verfügbaren Platz aller Planeten um 30 pro Stufe',
planetDestructionTech: 'Schreckliche Technologie zur Zerstörung ganzer Planeten', planetDestructionTech: 'Schreckliche Technologie zur Zerstörung ganzer Planeten',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: 'Verbindet mehrere Forschungslabore über Planeten hinweg. Jede Stufe verbindet 1 zusätzliches Labor',
mineralResearch: 'Erforscht effizientere Metallextraktionstechniken. +2% Metallproduktion pro Stufe',
crystalResearch: 'Erforscht effizientere Kristallveredelungstechniken. +2% Kristallproduktion pro Stufe',
fuelResearch: 'Erforscht effizientere Deuterium-Synthesetechniken. +2% Deuteriumproduktion pro Stufe'
}, },
officers: { officers: {
commander: 'Kommandant', commander: 'Kommandant',
@@ -392,8 +413,10 @@ export default {
tabConsumption: 'Verbrauchsdetails' tabConsumption: 'Verbrauchsdetails'
}, },
buildingsView: { buildingsView: {
title: 'Gebäude',
spaceUsage: 'Platzbedarf', spaceUsage: 'Platzbedarf',
upgradeCost: 'Ausbaukosten', upgradeCost: 'Ausbaukosten',
build: 'Bauen',
upgrade: 'Ausbauen', upgrade: 'Ausbauen',
maxLevelReached: 'Maximale Stufe erreicht', maxLevelReached: 'Maximale Stufe erreicht',
requirementsNotMet: 'Anforderungen nicht erfüllt', requirementsNotMet: 'Anforderungen nicht erfüllt',
@@ -415,10 +438,20 @@ export default {
'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben, die Voraussetzungen erfüllt sind oder keine anderen Forschungsaufträge vorhanden sind.' 'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben, die Voraussetzungen erfüllt sind oder keine anderen Forschungsaufträge vorhanden sind.'
}, },
shipyard: { shipyard: {
attack: 'Angriff',
shield: 'Schild',
armor: 'Panzerung',
missileAttack: 'Raketenangriff', missileAttack: 'Raketenangriff',
speed: 'Geschwindigkeit', speed: 'Geschwindigkeit',
cargoCapacity: 'Ladekapazität', cargoCapacity: 'Ladekapazität',
fuelConsumption: 'Treibstoffverbrauch' fuelConsumption: 'Treibstoffverbrauch',
buildCost: 'Baukosten',
buildTime: 'Bauzeit',
perUnit: 'Pro Einheit',
batchCalculator: 'Stapelrechner',
quantity: 'Menge',
totalCost: 'Gesamtkosten',
totalTime: 'Gesamtzeit'
}, },
shipyardView: { shipyardView: {
title: 'Raumschiffwerft', title: 'Raumschiffwerft',
@@ -530,6 +563,8 @@ export default {
} }
}, },
recycle: 'Recyceln', recycle: 'Recyceln',
harvestDarkMatter: 'Dunkle Materie ernten',
station: 'Stationieren',
transportResources: 'Ressourcen transportieren', transportResources: 'Ressourcen transportieren',
totalCargoCapacity: 'Gesamtladekapazität', totalCargoCapacity: 'Gesamtladekapazität',
used: 'Verwendet', used: 'Verwendet',
@@ -911,7 +946,10 @@ export default {
hideRoundDetails: 'Rundendetails ausblenden', hideRoundDetails: 'Rundendetails ausblenden',
round: 'Runde {round}', round: 'Runde {round}',
attackerRemainingPower: 'Verbleibende Angreiferkraft', attackerRemainingPower: 'Verbleibende Angreiferkraft',
defenderRemainingPower: 'Verbleibende Verteidigerkraft' defenderRemainingPower: 'Verbleibende Verteidigerkraft',
importFromSpyReport: 'Aus Spionagebericht importieren',
selectSpyReport: 'Spionagebericht auswählen',
noSpyReports: 'Keine Spionageberichte verfügbar'
}, },
settings: { settings: {
dataManagement: 'Datenverwaltung', dataManagement: 'Datenverwaltung',
@@ -922,6 +960,8 @@ export default {
exporting: 'Exportieren...', exporting: 'Exportieren...',
exportSuccess: 'Export erfolgreich', exportSuccess: 'Export erfolgreich',
exportSuccessWithPath: 'Export erfolgreich, Datei gespeichert unter: {path}', exportSuccessWithPath: 'Export erfolgreich, Datei gespeichert unter: {path}',
exportCopiedToClipboard: 'Export erfolgreich, Daten in die Zwischenablage kopiert',
storagePermissionDenied: 'Speicherberechtigung verweigert, Datei kann nicht exportiert werden',
exportFailed: 'Export fehlgeschlagen, bitte erneut versuchen', exportFailed: 'Export fehlgeschlagen, bitte erneut versuchen',
importData: 'Daten importieren', importData: 'Daten importieren',
importDataDesc: 'Spielfortschritt aus JSON-Datei wiederherstellen', importDataDesc: 'Spielfortschritt aus JSON-Datei wiederherstellen',
@@ -941,6 +981,8 @@ export default {
gameSettingsDesc: 'Spielparameter und Einstellungen anpassen', gameSettingsDesc: 'Spielparameter und Einstellungen anpassen',
gamePause: 'Spielpause', gamePause: 'Spielpause',
gamePauseDesc: 'Spielzeit und Ressourcenproduktion pausieren oder fortsetzen', gamePauseDesc: 'Spielzeit und Ressourcenproduktion pausieren oder fortsetzen',
battleMode: 'Bis zum Ende kämpfen',
battleModeDesc: 'Wenn aktiviert, dauern Kämpfe bis zu 100 Runden bis ein Sieger feststeht. Wenn deaktiviert, wird der klassische 6-Runden-Modus verwendet',
pause: 'Pausieren', pause: 'Pausieren',
resume: 'Fortsetzen', resume: 'Fortsetzen',
gamePaused: 'Spiel pausiert', gamePaused: 'Spiel pausiert',
@@ -1379,19 +1421,23 @@ export default {
skip: 'Anleitung überspringen', skip: 'Anleitung überspringen',
welcome: { welcome: {
title: 'Willkommen bei OGame', title: 'Willkommen bei OGame',
content: 'Willkommen, Kommandant! Diese Anleitung führt Sie durch die Grundlagen des Imperiumsaufbaus. Klicken Sie auf "Weiter", um Ihre Reise zu beginnen.' content:
'Willkommen, Kommandant! Diese Anleitung führt Sie durch die Grundlagen des Imperiumsaufbaus. Klicken Sie auf "Weiter", um Ihre Reise zu beginnen.'
}, },
resources: { resources: {
title: 'Ressourcenübersicht', title: 'Ressourcenübersicht',
content: 'Dies sind Ihre Ressourcen: Metall, Kristall und Deuterium. Sie sind für den Bau von Gebäuden und die Erforschung von Technologien unerlässlich. Energie ist ebenfalls wichtig, um Ihre Infrastruktur zu betreiben.' content:
'Dies sind Ihre Ressourcen: Metall, Kristall und Deuterium. Sie sind für den Bau von Gebäuden und die Erforschung von Technologien unerlässlich. Energie ist ebenfalls wichtig, um Ihre Infrastruktur zu betreiben.'
}, },
planet: { planet: {
title: 'Ihr Planet', title: 'Ihr Planet',
content: 'Dies ist Ihr Heimatplanet. Hier sehen Sie den Planetennamen, die Koordinaten und können zwischen Planeten wechseln, wenn Sie Ihr Imperium erweitern.' content:
'Dies ist Ihr Heimatplanet. Hier sehen Sie den Planetennamen, die Koordinaten und können zwischen Planeten wechseln, wenn Sie Ihr Imperium erweitern.'
}, },
navigation: { navigation: {
title: 'Navigationsmenü', title: 'Navigationsmenü',
content: 'Verwenden Sie dieses Menü, um zwischen verschiedenen Bereichen zu navigieren: Gebäude, Forschung, Flotte, Galaxie usw. Jeder Bereich bietet einzigartige Spielfunktionen.' content:
'Verwenden Sie dieses Menü, um zwischen verschiedenen Bereichen zu navigieren: Gebäude, Forschung, Flotte, Galaxie usw. Jeder Bereich bietet einzigartige Spielfunktionen.'
}, },
gotoBuildings: { gotoBuildings: {
title: 'Zu Gebäuden gehen', title: 'Zu Gebäuden gehen',
@@ -1399,39 +1445,48 @@ export default {
}, },
buildSolarPlant: { buildSolarPlant: {
title: 'Solarkraftwerk bauen', title: 'Solarkraftwerk bauen',
content: 'Bauen Sie zuerst ein Solarkraftwerk! Es versorgt Ihren Planeten mit Energie. Ohne Energie können andere Ressourcengebäude nicht funktionieren. Dies ist der wichtigste erste Schritt.' content:
'Bauen Sie zuerst ein Solarkraftwerk! Es versorgt Ihren Planeten mit Energie. Ohne Energie können andere Ressourcengebäude nicht funktionieren. Dies ist der wichtigste erste Schritt.'
}, },
waitBuild: { waitBuild: {
title: 'Bauauftrag', title: 'Bauauftrag',
content: 'Ihr Gebäude befindet sich jetzt im Bauauftrag. Klicken Sie auf das Warteschlangensymbol oben rechts, um alle laufenden Bau- und Forschungsaufgaben anzuzeigen. Gebäude brauchen Zeit zur Fertigstellung, aber Sie können während des Wartens weitermachen.' content:
'Ihr Gebäude befindet sich jetzt im Bauauftrag. Klicken Sie auf das Warteschlangensymbol oben rechts, um alle laufenden Bau- und Forschungsaufgaben anzuzeigen. Gebäude brauchen Zeit zur Fertigstellung, aber Sie können während des Wartens weitermachen.'
}, },
buildMetalMine: { buildMetalMine: {
title: 'Metallmine bauen', title: 'Metallmine bauen',
content: 'Jetzt, da Sie Energie haben, bauen Sie eine Metallmine. Die Metallmine ist Ihre Hauptmetallquelle, und Metall wird für fast jedes Gebäude und Schiff benötigt.' content:
'Jetzt, da Sie Energie haben, bauen Sie eine Metallmine. Die Metallmine ist Ihre Hauptmetallquelle, und Metall wird für fast jedes Gebäude und Schiff benötigt.'
}, },
buildCrystalMine: { buildCrystalMine: {
title: 'Kristallmine bauen', title: 'Kristallmine bauen',
content: 'Kristall ist seltener, aber für fortgeschrittene Technologien entscheidend. Bauen Sie eine Kristallmine, um diese wertvolle Ressource zu sammeln.' content:
'Kristall ist seltener, aber für fortgeschrittene Technologien entscheidend. Bauen Sie eine Kristallmine, um diese wertvolle Ressource zu sammeln.'
}, },
buildDeuterium: { buildDeuterium: {
title: 'Deuteriumsynthesizer bauen', title: 'Deuteriumsynthesizer bauen',
content: 'Deuterium ist für Schiffstreibstoff und fortgeschrittene Forschung unerlässlich. Bauen Sie einen Deuteriumsynthesizer, um diese wichtige Ressource zu produzieren.' content:
'Deuterium ist für Schiffstreibstoff und fortgeschrittene Forschung unerlässlich. Bauen Sie einen Deuteriumsynthesizer, um diese wichtige Ressource zu produzieren.'
}, },
upgradeMines: { upgradeMines: {
title: 'Ressourcenminen upgraden', title: 'Ressourcenminen upgraden',
content: 'Als Nächstes müssen Sie die drei Ressourcenminen (Metall, Kristall, Deuterium) auf Level 2 upgraden, um die Anforderungen für den Bau der Roboterfabrik zu erfüllen. Upgraden Sie sie weiter, wenn Sie genügend Ressourcen haben.' content:
'Als Nächstes müssen Sie die drei Ressourcenminen (Metall, Kristall, Deuterium) auf Level 2 upgraden, um die Anforderungen für den Bau der Roboterfabrik zu erfüllen. Upgraden Sie sie weiter, wenn Sie genügend Ressourcen haben.'
}, },
buildRobotics: { buildRobotics: {
title: 'Roboterfabrik bauen', title: 'Roboterfabrik bauen',
content: 'Die Roboterfabrik kann die Baugeschwindigkeit erheblich erhöhen. Sie benötigt Metallmine, Kristallmine und Deuteriumsynthesizer jeweils auf Level 2. Bauen Sie sie, um die Baueffizienz zu steigern!' content:
'Die Roboterfabrik kann die Baugeschwindigkeit erheblich erhöhen. Sie benötigt Metallmine, Kristallmine und Deuteriumsynthesizer jeweils auf Level 2. Bauen Sie sie, um die Baueffizienz zu steigern!'
}, },
upgradeMinesForLab: { upgradeMinesForLab: {
title: 'Ressourcenminen weiter upgraden', title: 'Ressourcenminen weiter upgraden',
content: 'Jetzt müssen Sie die drei Ressourcenminen auf Level 3 upgraden, um die Bauanforderungen des Forschungslabors zu erfüllen. Entwickeln Sie Ihre Ressourcenproduktion weiter.' content:
'Jetzt müssen Sie die drei Ressourcenminen auf Level 3 upgraden, um die Bauanforderungen des Forschungslabors zu erfüllen. Entwickeln Sie Ihre Ressourcenproduktion weiter.'
}, },
buildResearchLab: { buildResearchLab: {
title: 'Forschungslabor bauen', title: 'Forschungslabor bauen',
content: 'Das Forschungslabor ist die Grundlage für technologischen Fortschritt. Es benötigt alle drei Ressourcenminen auf Level 3. Bauen Sie es, um Technologieforschung freizuschalten!' content:
'Das Forschungslabor ist die Grundlage für technologischen Fortschritt. Es benötigt alle drei Ressourcenminen auf Level 3. Bauen Sie es, um Technologieforschung freizuschalten!'
}, },
gotoResearch: { gotoResearch: {
title: 'Zur Forschung gehen', title: 'Zur Forschung gehen',
@@ -1439,11 +1494,13 @@ export default {
}, },
researchEnergy: { researchEnergy: {
title: 'Energietechnik erforschen', title: 'Energietechnik erforschen',
content: 'Die Energietechnik kann Ihre Energieproduktion verbessern und fortgeschrittene Gebäude freischalten. Dies ist eine der grundlegendsten und wichtigsten Technologien.' content:
'Die Energietechnik kann Ihre Energieproduktion verbessern und fortgeschrittene Gebäude freischalten. Dies ist eine der grundlegendsten und wichtigsten Technologien.'
}, },
shipyardIntro: { shipyardIntro: {
title: 'Flotte & Werft', title: 'Flotte & Werft',
content: 'Schiffe ermöglichen es Ihnen, die Galaxie zu erkunden, Ressourcen zu transportieren und Ihr Imperium zu verteidigen. Um Schiffe zu bauen, benötigen Sie eine Raumschiffwerft (benötigt Roboterfabrik Level 2).' content:
'Schiffe ermöglichen es Ihnen, die Galaxie zu erkunden, Ressourcen zu transportieren und Ihr Imperium zu verteidigen. Um Schiffe zu bauen, benötigen Sie eine Raumschiffwerft (benötigt Roboterfabrik Level 2).'
}, },
gotoBuildingsForShipyard: { gotoBuildingsForShipyard: {
title: 'Zurück zur Gebäudeseite', title: 'Zurück zur Gebäudeseite',
@@ -1451,32 +1508,39 @@ export default {
}, },
buildShipyard: { buildShipyard: {
title: 'Raumschiffwerft bauen', title: 'Raumschiffwerft bauen',
content: 'Die Raumschiffwerft ermöglicht es Ihnen, Schiffe und Verteidigungssysteme zu bauen. Dies ist für Flottenoperationen unerlässlich.' content:
'Die Raumschiffwerft ermöglicht es Ihnen, Schiffe und Verteidigungssysteme zu bauen. Dies ist für Flottenoperationen unerlässlich.'
}, },
fleetIntro: { fleetIntro: {
title: 'Flottenoperationen', title: 'Flottenoperationen',
content: 'Sobald Sie Schiffe haben, können Sie sie auf Missionen schicken: Ressourcen transportieren, Planeten kolonisieren, Feinde angreifen oder Trümmerfelder erkunden.' content:
'Sobald Sie Schiffe haben, können Sie sie auf Missionen schicken: Ressourcen transportieren, Planeten kolonisieren, Feinde angreifen oder Trümmerfelder erkunden.'
}, },
galaxyIntro: { galaxyIntro: {
title: 'Galaxie erkunden', title: 'Galaxie erkunden',
content: 'Die Galaxieansicht zeigt andere Planeten, Trümmerfelder und Expansionsmöglichkeiten. Verwenden Sie sie, um Ziele auszukundschaften und Ihre Strategie zu planen.' content:
'Die Galaxieansicht zeigt andere Planeten, Trümmerfelder und Expansionsmöglichkeiten. Verwenden Sie sie, um Ziele auszukundschaften und Ihre Strategie zu planen.'
}, },
complete: { complete: {
title: 'Anleitung abgeschlossen!', title: 'Anleitung abgeschlossen!',
content: 'Glückwunsch, Kommandant! Sie kennen jetzt die Grundlagen. Bauen Sie Ihr Imperium weiter auf, erforschen Sie Technologien und erkunden Sie die Galaxie. Denken Sie daran: Zuerst Energie entwickeln, dann Ressourcen, dann Fabriken und Forschung! Viel Glück!' content:
'Glückwunsch, Kommandant! Sie kennen jetzt die Grundlagen. Bauen Sie Ihr Imperium weiter auf, erforschen Sie Technologien und erkunden Sie die Galaxie. Denken Sie daran: Zuerst Energie entwickeln, dann Ressourcen, dann Fabriken und Forschung! Viel Glück!'
}, },
mobile: { mobile: {
welcome: { welcome: {
title: 'Willkommen bei OGame (Mobil)', title: 'Willkommen bei OGame (Mobil)',
content: 'Willkommen, Kommandant! Dies ist eine vereinfachte Anleitung für Touchscreens. Wir werden schnell die Kernfunktionen durchgehen, damit Sie mit dem Aufbau Ihres Imperiums beginnen können.' content:
'Willkommen, Kommandant! Dies ist eine vereinfachte Anleitung für Touchscreens. Wir werden schnell die Kernfunktionen durchgehen, damit Sie mit dem Aufbau Ihres Imperiums beginnen können.'
}, },
resources: { resources: {
title: 'Obere Ressourcenleiste', title: 'Obere Ressourcenleiste',
content: 'Oben werden Ihre Ressourcen angezeigt: Metall, Kristall und Deuterium. Tippen Sie, um detaillierte Produktionsinformationen anzuzeigen.' content:
'Oben werden Ihre Ressourcen angezeigt: Metall, Kristall und Deuterium. Tippen Sie, um detaillierte Produktionsinformationen anzuzeigen.'
}, },
menu: { menu: {
title: 'Navigationsmenü öffnen', title: 'Navigationsmenü öffnen',
content: 'Tippen Sie auf dieses Menüsymbol, um die Navigationsleiste zu öffnen. Sie können auf alle Funktionen zugreifen: Gebäude, Forschung, Flotte usw.' content:
'Tippen Sie auf dieses Menüsymbol, um die Navigationsleiste zu öffnen. Sie können auf alle Funktionen zugreifen: Gebäude, Forschung, Flotte usw.'
}, },
gotoBuildings: { gotoBuildings: {
title: 'Zur Gebäudeseite gehen', title: 'Zur Gebäudeseite gehen',
@@ -1484,19 +1548,23 @@ export default {
}, },
buildSolarPlant: { buildSolarPlant: {
title: 'Solarkraftwerk bauen', title: 'Solarkraftwerk bauen',
content: 'Bauen Sie zuerst ein Solarkraftwerk! Scrollen Sie nach unten, um es zu finden, und tippen Sie auf die Karte zum Bauen. Energie ist die Grundlage für alles.' content:
'Bauen Sie zuerst ein Solarkraftwerk! Scrollen Sie nach unten, um es zu finden, und tippen Sie auf die Karte zum Bauen. Energie ist die Grundlage für alles.'
}, },
waitBuild: { waitBuild: {
title: 'Bauauftrag', title: 'Bauauftrag',
content: 'Tippen Sie auf das Warteschlangensymbol oben rechts, um den Baufortschritt anzuzeigen. Sie können weiter andere Seiten durchsuchen - der Bau läuft im Hintergrund.' content:
'Tippen Sie auf das Warteschlangensymbol oben rechts, um den Baufortschritt anzuzeigen. Sie können weiter andere Seiten durchsuchen - der Bau läuft im Hintergrund.'
}, },
buildMetalMine: { buildMetalMine: {
title: 'Metallmine bauen', title: 'Metallmine bauen',
content: 'Nachdem Sie Energie haben, bauen Sie eine Metallmine. Scrollen Sie nach unten, um die Metallmine zu finden, und tippen Sie zum Bauen.' content:
'Nachdem Sie Energie haben, bauen Sie eine Metallmine. Scrollen Sie nach unten, um die Metallmine zu finden, und tippen Sie zum Bauen.'
}, },
complete: { complete: {
title: 'Schnellanleitung abgeschlossen!', title: 'Schnellanleitung abgeschlossen!',
content: 'Sehr gut! Sie haben die Grundlagen gemeistert. Bauen Sie weiter die Kristallmine und den Deuteriumsynthesizer und erkunden Sie andere Funktionen. Denken Sie daran: Zuerst Energie, dann Ressourcen!' content:
'Sehr gut! Sie haben die Grundlagen gemeistert. Bauen Sie weiter die Kristallmine und den Deuteriumsynthesizer und erkunden Sie andere Funktionen. Denken Sie daran: Zuerst Energie, dann Ressourcen!'
} }
} }
}, },
@@ -1565,7 +1633,8 @@ export default {
}, },
campaign: { campaign: {
title: 'Kampagnenmodus', title: 'Kampagnenmodus',
message: 'Erkunden Sie die Galaxie-Story-Kampagne! Schließen Sie Missionen ab, um Ressourcenbelohnungen zu erhalten und neue Herausforderungen freizuschalten. Jeder Knoten hat einzigartige Ziele und Feinde.' message:
'Erkunden Sie die Galaxie-Story-Kampagne! Schließen Sie Missionen ab, um Ressourcenbelohnungen zu erhalten und neue Herausforderungen freizuschalten. Jeder Knoten hat einzigartige Ziele und Feinde.'
}, },
achievements: { achievements: {
title: 'Erfolgssystem', title: 'Erfolgssystem',
@@ -1804,6 +1873,27 @@ export default {
reputationBonusDesc: 'Dein Verbündeter {npcName} spricht gut von dir zu {targetNpc}' reputationBonusDesc: 'Dein Verbündeter {npcName} spricht gut von dir zu {targetNpc}'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV-Verbindung erfolgreich',
connectionSuccessDirectoryCreated: 'WebDAV-Verbindung erfolgreich, Speicherverzeichnis erstellt',
authFailed: 'Authentifizierung fehlgeschlagen, bitte Benutzername und Passwort überprüfen',
directoryNotExist: 'Verzeichnis existiert nicht und konnte nicht erstellt werden',
networkError: 'Netzwerkfehler, bitte Serveradresse und Netzwerk überprüfen',
unknownError: 'Unbekannter Fehler',
uploadSuccess: 'Spielstand erfolgreich hochgeladen',
uploadFailed: 'Hochladen fehlgeschlagen',
downloadSuccess: 'Spielstand erfolgreich heruntergeladen',
downloadFailed: 'Herunterladen fehlgeschlagen',
noSaveFiles: 'Keine Spielstände auf dem Server',
fileListSuccess: 'Spielstandliste erfolgreich abgerufen',
fileListFailed: 'Abrufen der Spielstandliste fehlgeschlagen',
deleteSuccess: 'Spielstand erfolgreich gelöscht',
deleteFailed: 'Löschen fehlgeschlagen',
serverError: 'Serverfehler',
notConfigured: 'WebDAV nicht konfiguriert',
invalidUrl: 'Ungültige WebDAV-URL',
timeout: 'Verbindungszeitüberschreitung'
},
campaign: { campaign: {
name: 'Kampagne', name: 'Kampagne',
description: 'Erkunde die mysteriöse Galaxie und entdecke antike Geheimnisse', description: 'Erkunde die mysteriöse Galaxie und entdecke antike Geheimnisse',
@@ -1830,6 +1920,8 @@ export default {
branchUnlocked: 'Neuer Storyzweig freigeschaltet!' branchUnlocked: 'Neuer Storyzweig freigeschaltet!'
}, },
dialogue: { dialogue: {
title: 'Story-Dialog',
description: 'Kampagnen-Story-Dialoginhalt',
skip: 'Überspringen', skip: 'Überspringen',
continue: 'Weiter', continue: 'Weiter',
finish: 'Beenden', finish: 'Beenden',
@@ -1920,13 +2012,19 @@ export default {
questNotActive: 'Quest nicht aktiv', questNotActive: 'Quest nicht aktiv',
questNotCompleted: 'Quest nicht abgeschlossen', questNotCompleted: 'Quest nicht abgeschlossen',
rewardsAlreadyClaimed: 'Belohnungen bereits erhalten', rewardsAlreadyClaimed: 'Belohnungen bereits erhalten',
prerequisiteNotMet: 'Voraussetzungs-Quest nicht abgeschlossen' prerequisiteNotMet: 'Voraussetzungs-Quest nicht abgeschlossen',
questLocked: 'Quest ist gesperrt',
notInitialized: 'Kampagne nicht initialisiert',
questAlreadyCompleted: 'Quest bereits abgeschlossen'
}, },
speakers: { speakers: {
ancientVoice: 'Antike Stimme', ancientVoice: 'Antike Stimme',
neighborNPC: 'Nachbar-Fraktion', neighborNPC: 'Nachbar-Fraktion',
mysteriousSignal: 'Mysteriöses Signal', mysteriousSignal: 'Mysteriöses Signal',
enemyCommander: 'Feindlicher Kommandant' enemyCommander: 'Feindlicher Kommandant',
shadowVoice: 'Schattenstimme',
allyNPC: 'Verbündete Fraktion',
ancientGuardian: 'Antiker Wächter'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: 'Baue Metallmine auf Stufe 2', buildMetalMine: 'Baue Metallmine auf Stufe 2',
@@ -1942,25 +2040,42 @@ export default {
spyAnyNPC: 'Spioniere einen NPC-Planeten aus', spyAnyNPC: 'Spioniere einen NPC-Planeten aus',
sendGiftToNPC: 'Sende ein Geschenk an einen NPC', sendGiftToNPC: 'Sende ein Geschenk an einen NPC',
researchAstrophysics: 'Erforsche Astrophysik auf Stufe 1', researchAstrophysics: 'Erforsche Astrophysik auf Stufe 1',
researchAstrophysicsHigher: 'Erforsche Astrophysik auf Stufe 3',
buildColonyShip: 'Baue ein Kolonieschiff', buildColonyShip: 'Baue ein Kolonieschiff',
colonizeNewPlanet: 'Kolonisiere einen neuen Planeten', colonizeNewPlanet: 'Kolonisiere einen neuen Planeten',
completeExpedition: 'Schließe eine Expeditionsmission ab', colonizeMultiple: 'Kolonisiere 5 Planeten',
completeExpedition: 'Schließe 3 Expeditionsmissionen ab',
expeditionDeepSpace: 'Schließe 2 Tiefenraum-Expeditionen ab',
expeditionUncharted: 'Erkunde 1 unbekannte Region',
expeditionDangerous: 'Schließe 3 gefährliche Nebel-Expeditionen ab',
discoverRuins: 'Entdecke antike Ruinen', discoverRuins: 'Entdecke antike Ruinen',
researchComputer: 'Erforsche Computertechnik auf Stufe 4', researchComputer: 'Erforsche Computertechnik auf Stufe 4',
researchImpulse: 'Erforsche Impulsantrieb auf Stufe 3',
researchLaser: 'Erforsche Lasertechnik auf Stufe 5',
researchIntergalactic: 'Erforsche Computertechnik auf Stufe 10',
researchGraviton: 'Erforsche Gravitontechnik auf Stufe 1',
improveRelation: 'Verbessere Beziehungen zu einem NPC', improveRelation: 'Verbessere Beziehungen zu einem NPC',
reachFriendly: 'Erreiche freundlichen Status mit einem NPC', reachFriendly: 'Erreiche freundlichen Status mit einem NPC',
spyHostileNPC: 'Spioniere einen feindlichen NPC aus', reachFriendlyRelation: 'Erreiche freundlichen Status mit einem beliebigen NPC',
sendMultipleGifts: 'Sende 3 Geschenke an NPCs',
spyHostileNPC: 'Spioniere 2 feindliche NPCs aus',
formAlliance: 'Schließe Allianz mit einem freundlichen NPC', formAlliance: 'Schließe Allianz mit einem freundlichen NPC',
buildDefenses: 'Baue Verteidigungsanlagen', buildDefenses: 'Baue Verteidigungsanlagen',
buildMissileSilo: 'Baue Raketensilo auf Stufe 2',
buildCruisers: 'Baue 10 Kreuzer',
winDefenseBattle: 'Gewinne eine Verteidigungsschlacht', winDefenseBattle: 'Gewinne eine Verteidigungsschlacht',
defendAgainstAttack: 'Verteidige erfolgreich gegen 1 Angriff',
spyEnemyPlanet: 'Spioniere Feindplaneten aus', spyEnemyPlanet: 'Spioniere Feindplaneten aus',
spyEnemyPlanets: 'Spioniere 5 Feindplaneten aus',
winAttackBattles: 'Gewinne 3 Angriffsschlachten',
attackEnemy: 'Greife den Feind an', attackEnemy: 'Greife den Feind an',
recycleDebris: 'Recycel Trümmer', recycleDebris: 'Recycel 5 Mal Trümmer',
buildBattleships: 'Baue 10 Schlachtschiffe', buildBattleships: 'Baue 20 Schlachtschiffe',
exploreDeepRuins: 'Erkunde tiefe Ruinen', exploreDeepRuins: 'Erkunde tiefe Ruinen',
researchHyperspace: 'Erforsche Hyperraumtechnologie', researchHyperspace: 'Erforsche Hyperraumantrieb auf Stufe 3',
defeatBoss: 'Besiege den mysteriösen Feind', defeatBoss: 'Besiege den Antiken Wächter',
colonizeSpecial: 'Kolonisiere besonderen Standort', colonizeSpecial: 'Kolonisiere besonderen Standort',
accumulateWealth: 'Sammle 1 Million Gesamtressourcen',
continueDevelopment: 'Setze Entwicklung fort' continueDevelopment: 'Setze Entwicklung fort'
}, },
dialogues: { dialogues: {
@@ -1999,13 +2114,19 @@ export default {
'2_3': { '2_3': {
prologue_1: prologue_1:
'Deine Expedition entdeckte anomale Signale. Diese Signale scheinen von einer antiken Zivilisation zu stammen... Untersuche ihre Quelle.', 'Deine Expedition entdeckte anomale Signale. Diese Signale scheinen von einer antiken Zivilisation zu stammen... Untersuche ihre Quelle.',
prologue_2:
'Diese Signale... Sie tragen Echos einer längst verlorenen Zivilisation. Ihre Geheimnisse warten darauf, entdeckt zu werden...',
epilogue_1: 'Diese Symbole... Sie sind Ruinen einer antiken Zivilisation! Untersuche weiter, um ihre Geheimnisse zu enthüllen.' epilogue_1: 'Diese Symbole... Sie sind Ruinen einer antiken Zivilisation! Untersuche weiter, um ihre Geheimnisse zu enthüllen.'
}, },
'2_4': { '2_4': {
prologue_1: 'Du hast den Standort antiker Ruinen gefunden. Sende deine Flotte zur Erkundung und sieh, was du entdecken kannst.' prologue_1: 'Du hast den Standort antiker Ruinen gefunden. Sende deine Flotte zur Erkundung und sieh, was du entdecken kannst.',
prologue_2: 'Die Ruinen bergen viele Geheimnisse... Wähle deinen Weg weise...',
choice_1: 'Vorsichtig erkunden - Sicherheit priorisieren',
choice_2: 'Aggressiv erkunden - Entdeckung priorisieren'
}, },
'2_5': { '2_5': {
prologue_1: 'Datenarchive wurden in den Ruinen gefunden. Studiere diese Daten, vielleicht kannst du neue Technologie freischalten.' prologue_1: 'Datenarchive wurden in den Ruinen gefunden. Studiere diese Daten, vielleicht kannst du neue Technologie freischalten.',
epilogue_1: 'Die antiken Daten wurden entschlüsselt! Neue technologische Erkenntnisse wurden gewonnen.'
}, },
'3_1': { prologue_1: 'Beim Erkunden vergiss nicht die Diplomatie. Gute Beziehungen zu umliegenden Fraktionen zu pflegen nützt dir.' }, '3_1': { prologue_1: 'Beim Erkunden vergiss nicht die Diplomatie. Gute Beziehungen zu umliegenden Fraktionen zu pflegen nützt dir.' },
'3_2': { '3_2': {
@@ -2014,14 +2135,19 @@ export default {
}, },
'3_3': { '3_3': {
prologue_1: prologue_1:
'Geheimdienstberichte deuten darauf hin, dass feindliche Kräfte dich aus den Schatten beobachten. Bleibe wachsam und erkunde ihre Bewegungen.' 'Geheimdienstberichte deuten darauf hin, dass feindliche Kräfte dich aus den Schatten beobachten. Bleibe wachsam und erkunde ihre Bewegungen.',
prologue_2: 'Dunkle Mächte regen sich in der Leere... Sie haben deine wachsende Macht bemerkt...'
}, },
'3_4': { '3_4': {
prologue_1: 'Schließe eine formelle Allianz mit freundlichen Fraktionen, um euch gegenseitig gegen Bedrohungen zu unterstützen.' prologue_1: 'Schließe eine formelle Allianz mit freundlichen Fraktionen, um euch gegenseitig gegen Bedrohungen zu unterstützen.'
}, },
'3_5': { prologue_1: 'Bedrohungen nähern sich. Baue Verteidigungsanlagen und bereite dich auf mögliche Konflikte vor.' }, '3_5': {
prologue_1: 'Bedrohungen nähern sich. Baue Verteidigungsanlagen und bereite dich auf mögliche Konflikte vor.',
epilogue_1: 'Deine Verteidigung ist bereit. Der Sturm naht, aber du bist vorbereitet.'
},
'4_1': { '4_1': {
prologue_1: 'Der Feind hat einen Angriff gestartet! Verteidige deinen Planeten!', prologue_1: 'Der Feind hat einen Angriff gestartet! Verteidige deinen Planeten!',
prologue_2: 'Die Schattenflotte nähert sich... Deine Stunde der Prüfung ist gekommen...',
epilogue_1: 'Du hast die erste Welle des Feindes erfolgreich abgewehrt. Aber das ist erst der Anfang...' epilogue_1: 'Du hast die erste Welle des Feindes erfolgreich abgewehrt. Aber das ist erst der Anfang...'
}, },
'4_2': { '4_2': {
@@ -2031,20 +2157,28 @@ export default {
'4_4': { '4_4': {
prologue_1: 'Viele Trümmer bleiben auf dem Schlachtfeld. Recycel diese Ressourcen, um dich auf die nächste Schlacht vorzubereiten.' prologue_1: 'Viele Trümmer bleiben auf dem Schlachtfeld. Recycel diese Ressourcen, um dich auf die nächste Schlacht vorzubereiten.'
}, },
'4_5': { prologue_1: 'Die letzte Schlacht naht. Baue eine mächtige Flotte und bereite dich auf die ultimative Herausforderung vor.' }, '4_5': {
prologue_1: 'Die letzte Schlacht naht. Baue eine mächtige Flotte und bereite dich auf die ultimative Herausforderung vor.',
epilogue_1: 'Deine Flotte ist versammelt. Der entscheidende Moment naht...'
},
'5_1': { '5_1': {
prologue_1: 'Alle Hinweise deuten auf den tiefsten Teil der Ruinen. Die Kerngeheimnisse der antiken Zivilisation liegen dort.', prologue_1: 'Alle Hinweise deuten auf den tiefsten Teil der Ruinen. Die Kerngeheimnisse der antiken Zivilisation liegen dort.',
prologue_2: 'Du bist endlich angekommen... Die Wahrheit wird bald enthüllt...' prologue_2: 'Du bist endlich angekommen... Die Wahrheit wird bald enthüllt...'
}, },
'5_2': { prologue_1: 'In den Tiefen der Ruinen entdecktest du verlorene antike Technologie. Erforsche und schalte ihre Macht frei.' }, '5_2': {
prologue_1: 'In den Tiefen der Ruinen entdecktest du verlorene antike Technologie. Erforsche und schalte ihre Macht frei.',
prologue_2: 'Diese Technologie... Sie ist älter als alle bekannten Zivilisationen. Gehe vorsichtig damit um...'
},
'5_3': { '5_3': {
prologue_1: 'Ein mysteriöser Feind ist aufgetaucht. Dies ist die letzte Herausforderung. Besiege ihn!', prologue_1: 'Ein mysteriöser Feind ist aufgetaucht. Dies ist die letzte Herausforderung. Besiege ihn!',
prologue_2: 'Ich bin der Wächter dieser Geheimnisse. Beweise deinen Wert oder werde vernichtet!',
epilogue_1: 'Du hast es geschafft! Der antike Wächter wurde besiegt. Die Geheimnisse der Galaxie stehen dir nun offen.' epilogue_1: 'Du hast es geschafft! Der antike Wächter wurde besiegt. Die Geheimnisse der Galaxie stehen dir nun offen.'
}, },
'5_4': { prologue_1: 'Frieden ist endlich eingekehrt. In dieser neuen Ära gründe neue Kolonien und erweitere dein Imperium.' }, '5_4': { prologue_1: 'Frieden ist endlich eingekehrt. In dieser neuen Ära gründe neue Kolonien und erweitere dein Imperium.' },
'5_5': { '5_5': {
prologue_1: 'Deine Legende hat gerade erst begonnen. Erkunde weiter und erobere mehr Sternensysteme!', prologue_1: 'Deine Legende hat gerade erst begonnen. Erkunde weiter und erobere mehr Sternensysteme!',
epilogue_1: 'Die Galaxie ist weit und grenzenlos, mit zahllosen Geheimnissen, die auf dich warten...' epilogue_1: 'Die Galaxie ist weit und grenzenlos, mit zahllosen Geheimnissen, die auf dich warten...',
epilogue_2: 'Deine Reise geht weiter... Neue Abenteuer warten jenseits der Sterne...'
} }
} }
} }

View File

@@ -46,6 +46,8 @@ export default {
cannotAttackOwnPlanet: 'Cannot attack your own planet', cannotAttackOwnPlanet: 'Cannot attack your own planet',
fleetMissionsFull: 'Fleet mission slots full', fleetMissionsFull: 'Fleet mission slots full',
insufficientFleet: 'Insufficient fleet', insufficientFleet: 'Insufficient fleet',
insufficientShips: 'Insufficient ships',
invalidQuantity: 'Invalid quantity',
insufficientFuel: 'Insufficient fuel', insufficientFuel: 'Insufficient fuel',
planetOnly: 'This building can only be built on planets', planetOnly: 'This building can only be built on planets',
moonOnly: 'This building can only be built on moons', moonOnly: 'This building can only be built on moons',
@@ -55,7 +57,12 @@ export default {
researchQueueFull: 'Research queue full', researchQueueFull: 'Research queue full',
moonExists: 'Moon already exists', moonExists: 'Moon already exists',
insufficientDebris: 'Insufficient debris field', insufficientDebris: 'Insufficient debris field',
launchFailed: 'Launch failed' launchFailed: 'Launch failed',
planetNotFound: 'Planet not found',
cannotAbandonHomePlanet: 'Cannot abandon home planet',
hasBuildQueue: 'Please wait for build queue to complete',
hasFleetOnPlanet: 'Please transfer or scrap all fleet first',
hasDefenseOnPlanet: 'Please demolish all defense first'
}, },
nav: { nav: {
overview: 'Overview', overview: 'Overview',
@@ -123,7 +130,12 @@ export default {
renamePlanet: 'Rename Planet', renamePlanet: 'Rename Planet',
renamePlanetTitle: 'Rename Planet', renamePlanetTitle: 'Rename Planet',
planetNamePlaceholder: 'Enter new planet name', planetNamePlaceholder: 'Enter new planet name',
rename: 'Rename' rename: 'Rename',
abandonColony: 'Abandon Colony',
confirmAbandon: 'Confirm Abandon Colony',
abandonWarning: 'Are you sure you want to abandon "{name}"?\n\nThis action cannot be undone!\nAll buildings, resources and the moon (if any) will be lost.',
confirmAbandonButton: 'Abandon',
abandonFailed: 'Abandon Failed'
}, },
player: { player: {
points: 'Total Points' points: 'Total Points'
@@ -152,9 +164,14 @@ export default {
planetDestroyerFactory: 'Planet Destroyer Factory', planetDestroyerFactory: 'Planet Destroyer Factory',
geoResearchStation: 'Geological Research Station', geoResearchStation: 'Geological Research Station',
deepDrillingFacility: 'Deep Drilling Facility', deepDrillingFacility: 'Deep Drilling Facility',
university: 'University',
buildTime: 'Build Time', buildTime: 'Build Time',
production: 'Production', production: 'Production',
consumption: 'Consumption', consumption: 'Consumption',
levelRange: 'Level Range',
totalCost: 'Total Cost',
totalPoints: 'Total Points',
researchSpeedBonus: 'Research Speed Bonus',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -195,8 +212,9 @@ export default {
sensorPhalanx: 'Detects fleet activities in surrounding systems', sensorPhalanx: 'Detects fleet activities in surrounding systems',
jumpGate: 'Instantly transfers fleets to other moons', jumpGate: 'Instantly transfers fleets to other moons',
planetDestroyerFactory: 'Constructs ultimate weapons capable of destroying planets', planetDestroyerFactory: 'Constructs ultimate weapons capable of destroying planets',
geoResearchStation: 'Researches geological structures and increases ore deposit regeneration rate. +50% regeneration per level', geoResearchStation: 'Researches geological structures and increases ore deposit regeneration rate. +10% regeneration per level',
deepDrillingFacility: 'Drills deep into the crust to access deeper ore veins. +20% ore deposit capacity per level' deepDrillingFacility: 'Drills deep into the crust to access deeper ore veins. +20% ore deposit capacity per level',
university: 'Trains researchers to accelerate research speed. -8% research time per level (stacks with Intergalactic Research Network)'
}, },
ships: { ships: {
lightFighter: 'Light Fighter', lightFighter: 'Light Fighter',
@@ -260,6 +278,9 @@ export default {
}, },
research: { research: {
researchTime: 'Research Time', researchTime: 'Research Time',
levelRange: 'Level Range',
totalCost: 'Total Cost',
totalPoints: 'Total Points',
attackBonus: 'Attack Bonus', attackBonus: 'Attack Bonus',
shieldBonus: 'Shield Bonus', shieldBonus: 'Shield Bonus',
@@ -268,7 +289,11 @@ export default {
researchQueueBonus: 'Research Queue', researchQueueBonus: 'Research Queue',
colonySlots: 'Colony Slots', colonySlots: 'Colony Slots',
forAllPlanets: '(Global)', forAllPlanets: '(Global)',
speedBonus: 'Speed Bonus' speedBonus: 'Speed Bonus',
// 资源产量加成显示
mineralResearch: 'Mineral Research',
crystalResearch: 'Crystal Research',
fuelResearch: 'Fuel Research'
}, },
technologies: { technologies: {
energyTechnology: 'Energy Technology', energyTechnology: 'Energy Technology',
@@ -289,7 +314,11 @@ export default {
darkMatterTechnology: 'Dark Matter Technology', darkMatterTechnology: 'Dark Matter Technology',
terraformingTechnology: 'Terraforming Technology', terraformingTechnology: 'Terraforming Technology',
planetDestructionTech: 'Planet Destruction Technology', planetDestructionTech: 'Planet Destruction Technology',
miningTechnology: 'Mining Technology' miningTechnology: 'Mining Technology',
intergalacticResearchNetwork: 'Intergalactic Research Network',
mineralResearch: 'Mineral Research',
crystalResearch: 'Crystal Research',
fuelResearch: 'Fuel Research'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: 'Improves energy efficiency', energyTechnology: 'Improves energy efficiency',
@@ -311,7 +340,11 @@ export default {
darkMatterTechnology: 'Research into dark matter properties and applications', darkMatterTechnology: 'Research into dark matter properties and applications',
terraformingTechnology: 'Research planet terraforming technology, adds 30 available space to all planets per level', terraformingTechnology: 'Research planet terraforming technology, adds 30 available space to all planets per level',
planetDestructionTech: 'Terrifying technology for destroying entire planets', planetDestructionTech: 'Terrifying technology for destroying entire planets',
miningTechnology: 'Improves mining methods and equipment, increases ore deposit capacity on all planets. +15% capacity per level' miningTechnology: 'Improves mining methods and equipment, increases ore deposit capacity on all planets. +15% capacity per level',
intergalacticResearchNetwork: 'Links multiple research labs across planets. Each level connects 1 additional lab (highest level labs are used)',
mineralResearch: 'Research more efficient metal extraction techniques. +2% metal production per level',
crystalResearch: 'Research more efficient crystal refinement techniques. +2% crystal production per level',
fuelResearch: 'Research more efficient deuterium synthesis techniques. +2% deuterium production per level'
}, },
officers: { officers: {
commander: 'Commander', commander: 'Commander',
@@ -382,6 +415,7 @@ export default {
title: 'Buildings', title: 'Buildings',
spaceUsage: 'Space Usage', spaceUsage: 'Space Usage',
upgradeCost: 'Upgrade Cost', upgradeCost: 'Upgrade Cost',
build: 'Build',
upgrade: 'Upgrade', upgrade: 'Upgrade',
maxLevelReached: 'Max Level Reached', maxLevelReached: 'Max Level Reached',
requirementsNotMet: 'Requirements Not Met', requirementsNotMet: 'Requirements Not Met',
@@ -402,10 +436,20 @@ export default {
researchFailedMessage: 'Please check if you have enough resources, prerequisites are met, or if there are other research tasks.' researchFailedMessage: 'Please check if you have enough resources, prerequisites are met, or if there are other research tasks.'
}, },
shipyard: { shipyard: {
attack: 'Attack',
shield: 'Shield',
armor: 'Armor',
missileAttack: 'Missile Attack', missileAttack: 'Missile Attack',
speed: 'Speed', speed: 'Speed',
cargoCapacity: 'Cargo Capacity', cargoCapacity: 'Cargo Capacity',
fuelConsumption: 'Fuel Consumption' fuelConsumption: 'Fuel Consumption',
buildCost: 'Build Cost',
buildTime: 'Build Time',
perUnit: 'Per Unit',
batchCalculator: 'Batch Calculator',
quantity: 'Quantity',
totalCost: 'Total Cost',
totalTime: 'Total Time'
}, },
shipyardView: { shipyardView: {
title: 'Shipyard', title: 'Shipyard',
@@ -422,10 +466,25 @@ export default {
inputError: 'Input Error', inputError: 'Input Error',
inputErrorMessage: 'Please enter build quantity!', inputErrorMessage: 'Please enter build quantity!',
buildFailed: 'Build Failed', buildFailed: 'Build Failed',
buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met.' buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met.',
scrapQuantity: 'Scrap Quantity',
scrapRefund: 'Scrap Refund (50%)',
scrap: 'Scrap',
scrapFailed: 'Scrap Failed',
scrapFailedMessage: 'Please check if you have enough ships.'
}, },
defense: { defense: {
missileAttack: 'Missile Attack' attack: 'Attack',
shield: 'Shield',
armor: 'Armor',
missileAttack: 'Missile Attack',
buildCost: 'Build Cost',
buildTime: 'Build Time',
perUnit: 'Per Unit',
batchCalculator: 'Batch Calculator',
quantity: 'Quantity',
totalCost: 'Total Cost',
totalTime: 'Total Time'
}, },
defenseView: { defenseView: {
title: 'Defense', title: 'Defense',
@@ -504,6 +563,8 @@ export default {
}, },
recycle: 'Recycle', recycle: 'Recycle',
destroy: 'Planet Destruction', destroy: 'Planet Destruction',
harvestDarkMatter: 'Harvest Dark Matter',
station: 'Station',
transportResources: 'Transport Resources', transportResources: 'Transport Resources',
totalCargoCapacity: 'Total Cargo Capacity', totalCargoCapacity: 'Total Cargo Capacity',
used: 'Used', used: 'Used',
@@ -887,6 +948,9 @@ export default {
round: 'Round {round}', round: 'Round {round}',
attackerRemainingPower: 'Attacker remaining power', attackerRemainingPower: 'Attacker remaining power',
defenderRemainingPower: 'Defender remaining power', defenderRemainingPower: 'Defender remaining power',
importFromSpyReport: 'Import from Spy Report',
selectSpyReport: 'Select Spy Report',
noSpyReports: 'No spy reports available',
// Battle animation // Battle animation
playAnimation: 'Play Animation', playAnimation: 'Play Animation',
showDetails: 'Show Details', showDetails: 'Show Details',
@@ -914,6 +978,8 @@ export default {
exporting: 'Exporting...', exporting: 'Exporting...',
exportSuccess: 'Export successful', exportSuccess: 'Export successful',
exportSuccessWithPath: 'Export successful, file saved to: {path}', exportSuccessWithPath: 'Export successful, file saved to: {path}',
exportCopiedToClipboard: 'Export successful, data copied to clipboard',
storagePermissionDenied: 'Storage permission denied, cannot export file',
exportFailed: 'Export failed, please try again', exportFailed: 'Export failed, please try again',
importData: 'Import Data', importData: 'Import Data',
importDataDesc: 'Restore game progress from JSON file', importDataDesc: 'Restore game progress from JSON file',
@@ -931,6 +997,8 @@ export default {
gameSettingsDesc: 'Adjust game parameters and preferences', gameSettingsDesc: 'Adjust game parameters and preferences',
gamePause: 'Game Pause', gamePause: 'Game Pause',
gamePauseDesc: 'Pause or resume game time and resource production', gamePauseDesc: 'Pause or resume game time and resource production',
battleMode: 'Fight to Finish Mode',
battleModeDesc: 'When enabled, battles last up to 100 rounds until a winner is decided. When disabled, uses classic 6-round mode',
pause: 'Pause', pause: 'Pause',
resume: 'Resume', resume: 'Resume',
gamePaused: 'Game paused', gamePaused: 'Game paused',
@@ -1753,6 +1821,8 @@ export default {
branchUnlocked: 'New story branch unlocked!' branchUnlocked: 'New story branch unlocked!'
}, },
dialogue: { dialogue: {
title: 'Story Dialogue',
description: 'Campaign story dialogue content',
skip: 'Skip', skip: 'Skip',
continue: 'Continue', continue: 'Continue',
finish: 'Finish', finish: 'Finish',
@@ -1918,13 +1988,19 @@ export default {
questNotActive: 'Quest not active', questNotActive: 'Quest not active',
questNotCompleted: 'Quest not completed', questNotCompleted: 'Quest not completed',
rewardsAlreadyClaimed: 'Rewards already claimed', rewardsAlreadyClaimed: 'Rewards already claimed',
prerequisiteNotMet: 'Prerequisite quest not completed' prerequisiteNotMet: 'Prerequisite quest not completed',
questLocked: 'Quest is locked',
notInitialized: 'Campaign not initialized',
questAlreadyCompleted: 'Quest already completed'
}, },
speakers: { speakers: {
ancientVoice: 'Ancient Voice', ancientVoice: 'Ancient Voice',
neighborNPC: 'Neighbor Faction', neighborNPC: 'Neighbor Faction',
mysteriousSignal: 'Mysterious Signal', mysteriousSignal: 'Mysterious Signal',
enemyCommander: 'Enemy Commander' enemyCommander: 'Enemy Commander',
shadowVoice: 'Shadow Voice',
allyNPC: 'Allied Faction',
ancientGuardian: 'Ancient Guardian'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: 'Build Metal Mine to level 2', buildMetalMine: 'Build Metal Mine to level 2',
@@ -1940,25 +2016,42 @@ export default {
spyAnyNPC: 'Spy on any NPC planet', spyAnyNPC: 'Spy on any NPC planet',
sendGiftToNPC: 'Send a gift to any NPC', sendGiftToNPC: 'Send a gift to any NPC',
researchAstrophysics: 'Research Astrophysics to level 1', researchAstrophysics: 'Research Astrophysics to level 1',
researchAstrophysicsHigher: 'Research Astrophysics to level 3',
buildColonyShip: 'Build a Colony Ship', buildColonyShip: 'Build a Colony Ship',
colonizeNewPlanet: 'Colonize a new planet', colonizeNewPlanet: 'Colonize a new planet',
completeExpedition: 'Complete an expedition mission', colonizeMultiple: 'Colonize 5 planets',
completeExpedition: 'Complete 3 expedition missions',
expeditionDeepSpace: 'Complete 2 deep space expeditions',
expeditionUncharted: 'Explore 1 uncharted region',
expeditionDangerous: 'Complete 3 dangerous nebula expeditions',
discoverRuins: 'Discover ancient ruins', discoverRuins: 'Discover ancient ruins',
researchComputer: 'Research Computer Technology to level 4', researchComputer: 'Research Computer Technology to level 4',
researchImpulse: 'Research Impulse Drive to level 3',
researchLaser: 'Research Laser Technology to level 5',
researchIntergalactic: 'Research Computer Technology to level 10',
researchGraviton: 'Research Graviton Technology to level 1',
improveRelation: 'Improve relations with an NPC', improveRelation: 'Improve relations with an NPC',
reachFriendly: 'Reach friendly status with an NPC', reachFriendly: 'Reach friendly status with an NPC',
spyHostileNPC: 'Spy on a hostile NPC', reachFriendlyRelation: 'Reach friendly status with any NPC',
sendMultipleGifts: 'Send 3 gifts to NPCs',
spyHostileNPC: 'Spy on 2 hostile NPCs',
formAlliance: 'Form alliance with a friendly NPC', formAlliance: 'Form alliance with a friendly NPC',
buildDefenses: 'Build defense facilities', buildDefenses: 'Build defense facilities',
buildMissileSilo: 'Build Missile Silo to level 2',
buildCruisers: 'Build 10 Cruisers',
winDefenseBattle: 'Win a defensive battle', winDefenseBattle: 'Win a defensive battle',
defendAgainstAttack: 'Successfully defend against 1 attack',
spyEnemyPlanet: 'Spy on enemy planet', spyEnemyPlanet: 'Spy on enemy planet',
spyEnemyPlanets: 'Spy on 5 enemy planets',
winAttackBattles: 'Win 3 attack battles',
attackEnemy: 'Attack the enemy', attackEnemy: 'Attack the enemy',
recycleDebris: 'Recycle debris', recycleDebris: 'Recycle debris 5 times',
buildBattleships: 'Build 10 Battleships', buildBattleships: 'Build 20 Battleships',
exploreDeepRuins: 'Explore deep ruins', exploreDeepRuins: 'Explore deep ruins',
researchHyperspace: 'Research Hyperspace Technology', researchHyperspace: 'Research Hyperspace Drive to level 3',
defeatBoss: 'Defeat the mysterious enemy', defeatBoss: 'Defeat the Ancient Guardian',
colonizeSpecial: 'Colonize special location', colonizeSpecial: 'Colonize special location',
accumulateWealth: 'Accumulate 1 million total resources',
continueDevelopment: 'Continue development' continueDevelopment: 'Continue development'
}, },
dialogues: { dialogues: {
@@ -1995,13 +2088,18 @@ export default {
'2_3': { '2_3': {
prologue_1: prologue_1:
'Your expedition discovered anomalous signals. These signals seem to come from an ancient civilization... Investigate their source.', 'Your expedition discovered anomalous signals. These signals seem to come from an ancient civilization... Investigate their source.',
prologue_2: 'These signals... they carry echoes of a long-lost civilization. Their secrets await discovery...',
epilogue_1: 'These symbols... They are ruins of an ancient civilization! Continue investigating to uncover their secrets.' epilogue_1: 'These symbols... They are ruins of an ancient civilization! Continue investigating to uncover their secrets.'
}, },
'2_4': { '2_4': {
prologue_1: 'You have found the location of ancient ruins. Send your fleet to explore and see what you can discover.' prologue_1: 'You have found the location of ancient ruins. Send your fleet to explore and see what you can discover.',
prologue_2: 'The ruins hold many secrets... Choose your path wisely...',
choice_1: 'Explore cautiously - prioritize safety',
choice_2: 'Explore aggressively - prioritize discovery'
}, },
'2_5': { '2_5': {
prologue_1: 'Data archives were found in the ruins. Study this data, perhaps you can unlock new technology.' prologue_1: 'Data archives were found in the ruins. Study this data, perhaps you can unlock new technology.',
epilogue_1: 'The ancient data has been decrypted! New technological insights have been gained.'
}, },
'3_1': { '3_1': {
prologue_1: 'While exploring, do not forget about diplomacy. Maintaining good relations with surrounding factions benefits you.' prologue_1: 'While exploring, do not forget about diplomacy. Maintaining good relations with surrounding factions benefits you.'
@@ -2010,16 +2108,19 @@ export default {
prologue_1: 'Some factions have shown friendliness. Continue deepening relations, perhaps you can gain more support.' prologue_1: 'Some factions have shown friendliness. Continue deepening relations, perhaps you can gain more support.'
}, },
'3_3': { '3_3': {
prologue_1: 'Intelligence indicates hostile forces are watching you from the shadows. Stay vigilant and scout their movements.' prologue_1: 'Intelligence indicates hostile forces are watching you from the shadows. Stay vigilant and scout their movements.',
prologue_2: 'Dark forces stir in the void... They have noticed your growing power...'
}, },
'3_4': { '3_4': {
prologue_1: 'Establish a formal alliance with friendly factions to support each other against threats.' prologue_1: 'Establish a formal alliance with friendly factions to support each other against threats.'
}, },
'3_5': { '3_5': {
prologue_1: 'Threats are approaching. Build defense facilities and prepare for possible conflict.' prologue_1: 'Threats are approaching. Build defense facilities and prepare for possible conflict.',
epilogue_1: 'Your defenses are ready. The storm is coming, but you are prepared.'
}, },
'4_1': { '4_1': {
prologue_1: 'The enemy has launched an attack! Defend your planet!', prologue_1: 'The enemy has launched an attack! Defend your planet!',
prologue_2: 'The shadow fleet approaches... Your moment of trial has come...',
epilogue_1: "You successfully repelled the enemy's first wave. But this is just the beginning..." epilogue_1: "You successfully repelled the enemy's first wave. But this is just the beginning..."
}, },
'4_2': { '4_2': {
@@ -2032,17 +2133,20 @@ export default {
prologue_1: 'Much debris remains on the battlefield. Recycle these resources to prepare for the next battle.' prologue_1: 'Much debris remains on the battlefield. Recycle these resources to prepare for the next battle.'
}, },
'4_5': { '4_5': {
prologue_1: 'The final battle approaches. Build a powerful fleet and prepare for the ultimate challenge.' prologue_1: 'The final battle approaches. Build a powerful fleet and prepare for the ultimate challenge.',
epilogue_1: 'Your fleet is assembled. The decisive moment draws near...'
}, },
'5_1': { '5_1': {
prologue_1: 'All clues point to the deepest part of the ruins. The core secrets of the ancient civilization lie there.', prologue_1: 'All clues point to the deepest part of the ruins. The core secrets of the ancient civilization lie there.',
prologue_2: 'You have finally arrived... The truth will soon be revealed...' prologue_2: 'You have finally arrived... The truth will soon be revealed...'
}, },
'5_2': { '5_2': {
prologue_1: 'In the depths of the ruins, you discovered lost ancient technology. Research and unlock their power.' prologue_1: 'In the depths of the ruins, you discovered lost ancient technology. Research and unlock their power.',
prologue_2: 'This technology... it predates all known civilizations. Handle it with care...'
}, },
'5_3': { '5_3': {
prologue_1: 'A mysterious enemy has appeared. This is the final challenge. Defeat it!', prologue_1: 'A mysterious enemy has appeared. This is the final challenge. Defeat it!',
prologue_2: 'I am the guardian of these secrets. Prove your worth, or be destroyed!',
epilogue_1: 'You did it! The ancient guardian has been defeated. The secrets of the galaxy are now open to you.' epilogue_1: 'You did it! The ancient guardian has been defeated. The secrets of the galaxy are now open to you.'
}, },
'5_4': { '5_4': {
@@ -2050,7 +2154,8 @@ export default {
}, },
'5_5': { '5_5': {
prologue_1: 'Your legend has just begun. Continue exploring and conquering more star systems!', prologue_1: 'Your legend has just begun. Continue exploring and conquering more star systems!',
epilogue_1: 'The galaxy is vast and boundless, with countless secrets waiting for you to discover...' epilogue_1: 'The galaxy is vast and boundless, with countless secrets waiting for you to discover...',
epilogue_2: 'Your journey continues... New adventures await beyond the stars...'
} }
} }
}, },
@@ -2162,5 +2267,31 @@ export default {
reputationBonus: 'Reputation Bonus', reputationBonus: 'Reputation Bonus',
reputationBonusDesc: 'Your ally {npcName} speaks well of you to {targetNpc}' reputationBonusDesc: 'Your ally {npcName} speaks well of you to {targetNpc}'
} }
},
webdav: {
// Connection
connectionSuccess: 'WebDAV connection successful',
connectionSuccessDirectoryCreated: 'WebDAV connection successful, save directory created',
authFailed: 'Authentication failed, please check username and password',
directoryNotExist: 'Directory does not exist and cannot be created',
connectionFailedHttp: 'Connection failed: HTTP {status}',
networkError: 'Network error, possibly CORS restriction. Try using a CORS-enabled WebDAV service or proxy.',
connectionError: 'Connection error: {error}',
// Upload
uploadSuccess: 'Upload successful',
noWritePermission: 'No write permission',
insufficientStorage: 'Insufficient storage space',
uploadFailedHttp: 'Upload failed: HTTP {status}',
uploadError: 'Upload error: {error}',
// Download
fileNotExist: 'File does not exist',
downloadFailedHttp: 'Download failed: HTTP {status}',
downloadError: 'Download error: {error}',
// List
listFailedHttp: 'Failed to get file list: HTTP {status}',
listError: 'Error getting file list: {error}',
// Delete
deleteFailedHttp: 'Delete failed: HTTP {status}',
deleteError: 'Delete error: {error}'
} }
} }

View File

@@ -152,9 +152,14 @@ export default {
planetDestroyerFactory: 'Fábrica de Destructores de Planetas', planetDestroyerFactory: 'Fábrica de Destructores de Planetas',
geoResearchStation: 'Estación de Investigación Geológica', geoResearchStation: 'Estación de Investigación Geológica',
deepDrillingFacility: 'Instalación de Perforación Profunda', deepDrillingFacility: 'Instalación de Perforación Profunda',
university: 'Universidad',
buildTime: 'Tiempo de Construcción', buildTime: 'Tiempo de Construcción',
production: 'Producción', production: 'Producción',
consumption: 'Consumo', consumption: 'Consumo',
levelRange: 'Rango de Nivel',
totalCost: 'Costo Total',
totalPoints: 'Puntos Totales',
researchSpeedBonus: 'Bono de Velocidad de Investigación',
storageCapacity: 'Capacidad', storageCapacity: 'Capacidad',
energyProduction: 'Producción de Energía', energyProduction: 'Producción de Energía',
@@ -196,9 +201,10 @@ export default {
jumpGate: 'Transfiere flotas instantáneamente a otras lunas', jumpGate: 'Transfiere flotas instantáneamente a otras lunas',
planetDestroyerFactory: 'Construye armas definitivas capaces de destruir planetas', planetDestroyerFactory: 'Construye armas definitivas capaces de destruir planetas',
geoResearchStation: geoResearchStation:
'Investiga estructuras geológicas y aumenta la tasa de regeneración de depósitos de mineral. +50% de regeneración por nivel', 'Investiga estructuras geológicas y aumenta la tasa de regeneración de depósitos de mineral. +10% de regeneración por nivel',
deepDrillingFacility: deepDrillingFacility:
'Perfora profundamente en la corteza para acceder a vetas de mineral más profundas. +20% de capacidad de depósito de mineral por nivel' 'Perfora profundamente en la corteza para acceder a vetas de mineral más profundas. +20% de capacidad de depósito de mineral por nivel',
university: 'Entrena investigadores para acelerar la velocidad de investigación. -8% de tiempo de investigación por nivel'
}, },
ships: { ships: {
lightFighter: 'Caza Ligero', lightFighter: 'Caza Ligero',
@@ -274,7 +280,11 @@ export default {
colonySlots: 'Espacios de Colonia', colonySlots: 'Espacios de Colonia',
forAllPlanets: '(Global)', forAllPlanets: '(Global)',
speedBonus: 'Bono de Velocidad', speedBonus: 'Bono de Velocidad',
researchSpeedBonus: 'Bono de Velocidad de Investigación' researchSpeedBonus: 'Bono de Velocidad de Investigación',
// Visualización de bono de producción de recursos
mineralResearch: 'Investigación Mineral',
crystalResearch: 'Investigación de Cristal',
fuelResearch: 'Investigación de Combustible'
}, },
technologies: { technologies: {
energyTechnology: 'Tecnología de Energía', energyTechnology: 'Tecnología de Energía',
@@ -295,7 +305,11 @@ export default {
darkMatterTechnology: 'Tecnología de Materia Oscura', darkMatterTechnology: 'Tecnología de Materia Oscura',
terraformingTechnology: 'Tecnología de Terraformación', terraformingTechnology: 'Tecnología de Terraformación',
planetDestructionTech: 'Tecnología de Destrucción Planetaria', planetDestructionTech: 'Tecnología de Destrucción Planetaria',
miningTechnology: 'Tecnología de Minería' miningTechnology: 'Tecnología de Minería',
intergalacticResearchNetwork: 'Red de Investigación Intergaláctica',
mineralResearch: 'Investigación Mineral',
crystalResearch: 'Investigación de Cristal',
fuelResearch: 'Investigación de Combustible'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: 'Mejora la eficiencia energética', energyTechnology: 'Mejora la eficiencia energética',
@@ -319,7 +333,11 @@ export default {
'Investigación de tecnología de terraformación planetaria, añade 30 espacios disponibles a todos los planetas por nivel', 'Investigación de tecnología de terraformación planetaria, añade 30 espacios disponibles a todos los planetas por nivel',
planetDestructionTech: 'Tecnología aterradora para destruir planetas enteros', planetDestructionTech: 'Tecnología aterradora para destruir planetas enteros',
miningTechnology: miningTechnology:
'Mejora los métodos y equipos de minería, aumenta la capacidad de depósito de mineral en todos los planetas. +15% de capacidad por nivel' 'Mejora los métodos y equipos de minería, aumenta la capacidad de depósito de mineral en todos los planetas. +15% de capacidad por nivel',
intergalacticResearchNetwork: 'Conecta múltiples laboratorios de investigación entre planetas. Cada nivel conecta 1 laboratorio adicional',
mineralResearch: 'Investiga técnicas más eficientes de extracción de metal. +2% de producción de metal por nivel',
crystalResearch: 'Investiga técnicas más eficientes de refinamiento de cristal. +2% de producción de cristal por nivel',
fuelResearch: 'Investiga técnicas más eficientes de síntesis de deuterio. +2% de producción de deuterio por nivel'
}, },
officers: { officers: {
commander: 'Comandante', commander: 'Comandante',
@@ -411,10 +429,20 @@ export default {
'Por favor verifica si tienes suficientes recursos, si se cumplen los prerrequisitos, o si hay otras tareas de investigación.' 'Por favor verifica si tienes suficientes recursos, si se cumplen los prerrequisitos, o si hay otras tareas de investigación.'
}, },
shipyard: { shipyard: {
attack: 'Ataque',
shield: 'Escudo',
armor: 'Blindaje',
missileAttack: 'Ataque de Misil', missileAttack: 'Ataque de Misil',
speed: 'Velocidad', speed: 'Velocidad',
cargoCapacity: 'Capacidad de Carga', cargoCapacity: 'Capacidad de Carga',
fuelConsumption: 'Consumo de Combustible' fuelConsumption: 'Consumo de Combustible',
buildCost: 'Costo de Construcción',
buildTime: 'Tiempo de Construcción',
perUnit: 'Por Unidad',
batchCalculator: 'Calculadora de Lotes',
quantity: 'Cantidad',
totalCost: 'Costo Total',
totalTime: 'Tiempo Total'
}, },
shipyardView: { shipyardView: {
title: 'Astillero', title: 'Astillero',
@@ -524,6 +552,8 @@ export default {
}, },
recycle: 'Reciclar', recycle: 'Reciclar',
destroy: 'Destrucción Planetaria', destroy: 'Destrucción Planetaria',
harvestDarkMatter: 'Recolectar Materia Oscura',
station: 'Estacionar',
transportResources: 'Transportar Recursos', transportResources: 'Transportar Recursos',
totalCargoCapacity: 'Capacidad de Carga Total', totalCargoCapacity: 'Capacidad de Carga Total',
used: 'Usado', used: 'Usado',
@@ -909,6 +939,9 @@ export default {
round: 'Ronda {round}', round: 'Ronda {round}',
attackerRemainingPower: 'Poder restante del atacante', attackerRemainingPower: 'Poder restante del atacante',
defenderRemainingPower: 'Poder restante del defensor', defenderRemainingPower: 'Poder restante del defensor',
importFromSpyReport: 'Importar desde informe de espionaje',
selectSpyReport: 'Seleccionar informe de espionaje',
noSpyReports: 'No hay informes de espionaje disponibles',
// Battle animation // Battle animation
playAnimation: 'Reproducir Animación', playAnimation: 'Reproducir Animación',
showDetails: 'Mostrar Detalles', showDetails: 'Mostrar Detalles',
@@ -936,6 +969,8 @@ export default {
exporting: 'Exportando...', exporting: 'Exportando...',
exportSuccess: 'Exportación exitosa', exportSuccess: 'Exportación exitosa',
exportSuccessWithPath: 'Exportación exitosa, archivo guardado en: {path}', exportSuccessWithPath: 'Exportación exitosa, archivo guardado en: {path}',
exportCopiedToClipboard: 'Exportación exitosa, datos copiados al portapapeles',
storagePermissionDenied: 'Permiso de almacenamiento denegado, no se puede exportar el archivo',
exportFailed: 'Exportación fallida, por favor intenta de nuevo', exportFailed: 'Exportación fallida, por favor intenta de nuevo',
importData: 'Importar Datos', importData: 'Importar Datos',
importDataDesc: 'Restaurar progreso del juego desde archivo JSON', importDataDesc: 'Restaurar progreso del juego desde archivo JSON',
@@ -953,6 +988,8 @@ export default {
gameSettingsDesc: 'Ajustar parámetros y preferencias del juego', gameSettingsDesc: 'Ajustar parámetros y preferencias del juego',
gamePause: 'Pausa del Juego', gamePause: 'Pausa del Juego',
gamePauseDesc: 'Pausar o reanudar el tiempo del juego y la producción de recursos', gamePauseDesc: 'Pausar o reanudar el tiempo del juego y la producción de recursos',
battleMode: 'Modo de combate hasta el final',
battleModeDesc: 'Cuando está activado, las batallas duran hasta 100 rondas hasta que se decide un ganador. Cuando está desactivado, se usa el modo clásico de 6 rondas',
pause: 'Pausar', pause: 'Pausar',
resume: 'Reanudar', resume: 'Reanudar',
gamePaused: 'Juego pausado', gamePaused: 'Juego pausado',
@@ -1771,6 +1808,8 @@ export default {
branchUnlocked: '¡Nueva rama de historia desbloqueada!' branchUnlocked: '¡Nueva rama de historia desbloqueada!'
}, },
dialogue: { dialogue: {
title: 'Diálogo de historia',
description: 'Contenido del diálogo de la campaña',
skip: 'Saltar', skip: 'Saltar',
continue: 'Continuar', continue: 'Continuar',
finish: 'Finalizar', finish: 'Finalizar',
@@ -1936,13 +1975,19 @@ export default {
questNotActive: 'Misión no activa', questNotActive: 'Misión no activa',
questNotCompleted: 'Misión no completada', questNotCompleted: 'Misión no completada',
rewardsAlreadyClaimed: 'Recompensas ya reclamadas', rewardsAlreadyClaimed: 'Recompensas ya reclamadas',
prerequisiteNotMet: 'Misión prerequisito no completada' prerequisiteNotMet: 'Misión prerequisito no completada',
questLocked: 'Misión bloqueada',
notInitialized: 'Campaña no inicializada',
questAlreadyCompleted: 'Misión ya completada'
}, },
speakers: { speakers: {
ancientVoice: 'Voz Antigua', ancientVoice: 'Voz Antigua',
neighborNPC: 'Facción Vecina', neighborNPC: 'Facción Vecina',
mysteriousSignal: 'Señal Misteriosa', mysteriousSignal: 'Señal Misteriosa',
enemyCommander: 'Comandante Enemigo' enemyCommander: 'Comandante Enemigo',
shadowVoice: 'Voz de la Sombra',
allyNPC: 'Facción Aliada',
ancientGuardian: 'Guardián Antiguo'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: 'Construir Mina de Metal al nivel 2', buildMetalMine: 'Construir Mina de Metal al nivel 2',
@@ -1958,25 +2003,42 @@ export default {
spyAnyNPC: 'Espiar cualquier planeta NPC', spyAnyNPC: 'Espiar cualquier planeta NPC',
sendGiftToNPC: 'Enviar un regalo a cualquier NPC', sendGiftToNPC: 'Enviar un regalo a cualquier NPC',
researchAstrophysics: 'Investigar Astrofísica al nivel 1', researchAstrophysics: 'Investigar Astrofísica al nivel 1',
researchAstrophysicsHigher: 'Investigar Astrofísica al nivel 3',
buildColonyShip: 'Construir una Nave Colonial', buildColonyShip: 'Construir una Nave Colonial',
colonizeNewPlanet: 'Colonizar un nuevo planeta', colonizeNewPlanet: 'Colonizar un nuevo planeta',
completeExpedition: 'Completar una misión de expedición', colonizeMultiple: 'Colonizar 5 planetas',
completeExpedition: 'Completar 3 misiones de expedición',
expeditionDeepSpace: 'Completar 2 expediciones al espacio profundo',
expeditionUncharted: 'Explorar 1 región desconocida',
expeditionDangerous: 'Completar 3 expediciones a nebulosas peligrosas',
discoverRuins: 'Descubrir ruinas antiguas', discoverRuins: 'Descubrir ruinas antiguas',
researchComputer: 'Investigar Tecnología de Computación al nivel 4', researchComputer: 'Investigar Tecnología de Computación al nivel 4',
researchImpulse: 'Investigar Motor de Impulso al nivel 3',
researchLaser: 'Investigar Tecnología Láser al nivel 5',
researchIntergalactic: 'Investigar Tecnología de Computación al nivel 10',
researchGraviton: 'Investigar Tecnología Gravitón al nivel 1',
improveRelation: 'Mejorar relaciones con un NPC', improveRelation: 'Mejorar relaciones con un NPC',
reachFriendly: 'Alcanzar estatus amigable con un NPC', reachFriendly: 'Alcanzar estatus amigable con un NPC',
spyHostileNPC: 'Espiar un NPC hostil', reachFriendlyRelation: 'Alcanzar estatus amigable con cualquier NPC',
sendMultipleGifts: 'Enviar 3 regalos a NPCs',
spyHostileNPC: 'Espiar 2 NPCs hostiles',
formAlliance: 'Formar alianza con un NPC amigable', formAlliance: 'Formar alianza con un NPC amigable',
buildDefenses: 'Construir instalaciones de defensa', buildDefenses: 'Construir instalaciones de defensa',
buildMissileSilo: 'Construir Silo de Misiles al nivel 2',
buildCruisers: 'Construir 10 Cruceros',
winDefenseBattle: 'Ganar una batalla defensiva', winDefenseBattle: 'Ganar una batalla defensiva',
defendAgainstAttack: 'Defender exitosamente contra 1 ataque',
spyEnemyPlanet: 'Espiar planeta enemigo', spyEnemyPlanet: 'Espiar planeta enemigo',
spyEnemyPlanets: 'Espiar 5 planetas enemigos',
winAttackBattles: 'Ganar 3 batallas de ataque',
attackEnemy: 'Atacar al enemigo', attackEnemy: 'Atacar al enemigo',
recycleDebris: 'Reciclar escombros', recycleDebris: 'Reciclar escombros 5 veces',
buildBattleships: 'Construir 10 Naves de Batalla', buildBattleships: 'Construir 20 Naves de Batalla',
exploreDeepRuins: 'Explorar ruinas profundas', exploreDeepRuins: 'Explorar ruinas profundas',
researchHyperspace: 'Investigar Tecnología de Hiperespacio', researchHyperspace: 'Investigar Motor de Hiperespacio al nivel 3',
defeatBoss: 'Derrotar al enemigo misterioso', defeatBoss: 'Derrotar al Guardián Antiguo',
colonizeSpecial: 'Colonizar ubicación especial', colonizeSpecial: 'Colonizar ubicación especial',
accumulateWealth: 'Acumular 1 millón de recursos totales',
continueDevelopment: 'Continuar desarrollo' continueDevelopment: 'Continuar desarrollo'
}, },
dialogues: { dialogues: {
@@ -2015,13 +2077,18 @@ export default {
'2_3': { '2_3': {
prologue_1: prologue_1:
'Tu expedición descubrió señales anómalas. Estas señales parecen venir de una civilización antigua... Investiga su origen.', 'Tu expedición descubrió señales anómalas. Estas señales parecen venir de una civilización antigua... Investiga su origen.',
prologue_2: 'Estas señales... llevan ecos de una civilización perdida hace tiempo. Sus secretos esperan ser descubiertos...',
epilogue_1: 'Estos símbolos... ¡Son ruinas de una civilización antigua! Continúa investigando para descubrir sus secretos.' epilogue_1: 'Estos símbolos... ¡Son ruinas de una civilización antigua! Continúa investigando para descubrir sus secretos.'
}, },
'2_4': { '2_4': {
prologue_1: 'Has encontrado la ubicación de ruinas antiguas. Envía tu flota a explorar y ver qué puedes descubrir.' prologue_1: 'Has encontrado la ubicación de ruinas antiguas. Envía tu flota a explorar y ver qué puedes descubrir.',
prologue_2: 'Las ruinas guardan muchos secretos... Elige tu camino sabiamente...',
choice_1: 'Explorar con cautela - priorizar seguridad',
choice_2: 'Explorar agresivamente - priorizar descubrimiento'
}, },
'2_5': { '2_5': {
prologue_1: 'Se encontraron archivos de datos en las ruinas. Estudia estos datos, quizás puedas desbloquear nueva tecnología.' prologue_1: 'Se encontraron archivos de datos en las ruinas. Estudia estos datos, quizás puedas desbloquear nueva tecnología.',
epilogue_1: '¡Los datos antiguos han sido descifrados! Se han obtenido nuevos conocimientos tecnológicos.'
}, },
'3_1': { '3_1': {
prologue_1: 'Mientras exploras, no olvides la diplomacia. Mantener buenas relaciones con las facciones circundantes te beneficia.' prologue_1: 'Mientras exploras, no olvides la diplomacia. Mantener buenas relaciones con las facciones circundantes te beneficia.'
@@ -2031,16 +2098,19 @@ export default {
}, },
'3_3': { '3_3': {
prologue_1: prologue_1:
'La inteligencia indica que fuerzas hostiles te están vigilando desde las sombras. Mantente alerta y explora sus movimientos.' 'La inteligencia indica que fuerzas hostiles te están vigilando desde las sombras. Mantente alerta y explora sus movimientos.',
prologue_2: 'Fuerzas oscuras se agitan en el vacío... Han notado tu poder creciente...'
}, },
'3_4': { '3_4': {
prologue_1: 'Establece una alianza formal con facciones amigables para apoyarse mutuamente contra las amenazas.' prologue_1: 'Establece una alianza formal con facciones amigables para apoyarse mutuamente contra las amenazas.'
}, },
'3_5': { '3_5': {
prologue_1: 'Las amenazas se acercan. Construye instalaciones de defensa y prepárate para posibles conflictos.' prologue_1: 'Las amenazas se acercan. Construye instalaciones de defensa y prepárate para posibles conflictos.',
epilogue_1: 'Tus defensas están listas. La tormenta se acerca, pero estás preparado.'
}, },
'4_1': { '4_1': {
prologue_1: '¡El enemigo ha lanzado un ataque! ¡Defiende tu planeta!', prologue_1: '¡El enemigo ha lanzado un ataque! ¡Defiende tu planeta!',
prologue_2: 'La flota de las sombras se acerca... Ha llegado tu momento de prueba...',
epilogue_1: 'Repeliste exitosamente la primera ola del enemigo. Pero esto es solo el comienzo...' epilogue_1: 'Repeliste exitosamente la primera ola del enemigo. Pero esto es solo el comienzo...'
}, },
'4_2': { '4_2': {
@@ -2053,7 +2123,8 @@ export default {
prologue_1: 'Muchos escombros permanecen en el campo de batalla. Recicla estos recursos para prepararte para la próxima batalla.' prologue_1: 'Muchos escombros permanecen en el campo de batalla. Recicla estos recursos para prepararte para la próxima batalla.'
}, },
'4_5': { '4_5': {
prologue_1: 'La batalla final se acerca. Construye una flota poderosa y prepárate para el desafío definitivo.' prologue_1: 'La batalla final se acerca. Construye una flota poderosa y prepárate para el desafío definitivo.',
epilogue_1: 'Tu flota está reunida. El momento decisivo se acerca...'
}, },
'5_1': { '5_1': {
prologue_1: prologue_1:
@@ -2061,10 +2132,12 @@ export default {
prologue_2: 'Finalmente has llegado... La verdad pronto será revelada...' prologue_2: 'Finalmente has llegado... La verdad pronto será revelada...'
}, },
'5_2': { '5_2': {
prologue_1: 'En las profundidades de las ruinas, descubriste tecnología antigua perdida. Investiga y desbloquea su poder.' prologue_1: 'En las profundidades de las ruinas, descubriste tecnología antigua perdida. Investiga y desbloquea su poder.',
prologue_2: 'Esta tecnología... es más antigua que todas las civilizaciones conocidas. Manéjala con cuidado...'
}, },
'5_3': { '5_3': {
prologue_1: 'Ha aparecido un enemigo misterioso. Este es el desafío final. ¡Derrótalo!', prologue_1: 'Ha aparecido un enemigo misterioso. Este es el desafío final. ¡Derrótalo!',
prologue_2: '¡Soy el guardián de estos secretos. Demuestra tu valor o serás destruido!',
epilogue_1: '¡Lo lograste! El guardián antiguo ha sido derrotado. Los secretos de la galaxia ahora están abiertos para ti.' epilogue_1: '¡Lo lograste! El guardián antiguo ha sido derrotado. Los secretos de la galaxia ahora están abiertos para ti.'
}, },
'5_4': { '5_4': {
@@ -2072,7 +2145,8 @@ export default {
}, },
'5_5': { '5_5': {
prologue_1: 'Tu leyenda acaba de comenzar. ¡Continúa explorando y conquistando más sistemas estelares!', prologue_1: 'Tu leyenda acaba de comenzar. ¡Continúa explorando y conquistando más sistemas estelares!',
epilogue_1: 'La galaxia es vasta e infinita, con innumerables secretos esperando que los descubras...' epilogue_1: 'La galaxia es vasta e infinita, con innumerables secretos esperando que los descubras...',
epilogue_2: 'Tu viaje continúa... Nuevas aventuras esperan más allá de las estrellas...'
} }
} }
}, },
@@ -2184,5 +2258,26 @@ export default {
reputationBonus: 'Bonificación de Reputación', reputationBonus: 'Bonificación de Reputación',
reputationBonusDesc: 'Tu aliado {npcName} habla bien de ti ante {targetNpc}' reputationBonusDesc: 'Tu aliado {npcName} habla bien de ti ante {targetNpc}'
} }
},
webdav: {
connectionSuccess: 'Conexión WebDAV exitosa',
connectionSuccessDirectoryCreated: 'Conexión WebDAV exitosa, directorio de guardado creado',
authFailed: 'Autenticación fallida, verifica el nombre de usuario y contraseña',
directoryNotExist: 'El directorio no existe y no se puede crear',
networkError: 'Error de red, verifica la dirección del servidor y la red',
unknownError: 'Error desconocido',
uploadSuccess: 'Guardado subido exitosamente',
uploadFailed: 'Error al subir',
downloadSuccess: 'Guardado descargado exitosamente',
downloadFailed: 'Error al descargar',
noSaveFiles: 'No hay archivos de guardado en el servidor',
fileListSuccess: 'Lista de guardados obtenida exitosamente',
fileListFailed: 'Error al obtener la lista de guardados',
deleteSuccess: 'Guardado eliminado exitosamente',
deleteFailed: 'Error al eliminar',
serverError: 'Error del servidor',
notConfigured: 'WebDAV no configurado',
invalidUrl: 'URL de WebDAV inválida',
timeout: 'Tiempo de conexión agotado'
} }
} }

View File

@@ -165,6 +165,7 @@ export default {
planetDestroyerFactory: '惑星破壊工場', planetDestroyerFactory: '惑星破壊工場',
geoResearchStation: '地質研究所', geoResearchStation: '地質研究所',
deepDrillingFacility: '深部掘削施設', deepDrillingFacility: '深部掘削施設',
university: '大学',
buildTime: '建設時間', buildTime: '建設時間',
build: '建設', build: '建設',
production: '生産量', production: '生産量',
@@ -215,8 +216,9 @@ export default {
sensorPhalanx: '周辺星系の艦隊活動を探知', sensorPhalanx: '周辺星系の艦隊活動を探知',
jumpGate: '他の月へ艦隊を瞬間移動', jumpGate: '他の月へ艦隊を瞬間移動',
planetDestroyerFactory: '惑星を破壊できる究極兵器を建造', planetDestroyerFactory: '惑星を破壊できる究極兵器を建造',
geoResearchStation: '地質構造を研究し、鉱脈の自然回復速度を向上。レベル毎に回復速度50%増加', geoResearchStation: '地質構造を研究し、鉱脈の自然回復速度を向上。レベル毎に回復速度10%増加',
deepDrillingFacility: '' deepDrillingFacility: '',
university: '研究者を育成し、研究速度を加速。レベル毎に研究時間-8%'
}, },
ships: { ships: {
lightFighter: '軽戦闘機', lightFighter: '軽戦闘機',
@@ -299,7 +301,11 @@ export default {
colonySlots: '植民地スロット', colonySlots: '植民地スロット',
forAllPlanets: '(全惑星)', forAllPlanets: '(全惑星)',
speedBonus: '速度ボーナス', speedBonus: '速度ボーナス',
researchSpeedBonus: '研究速度ボーナス' researchSpeedBonus: '研究速度ボーナス',
// 資源生産ボーナス表示
mineralResearch: '鉱物研究',
crystalResearch: 'クリスタル研究',
fuelResearch: '燃料研究'
}, },
technologies: { technologies: {
energyTechnology: 'エネルギー技術', energyTechnology: 'エネルギー技術',
@@ -320,7 +326,11 @@ export default {
darkMatterTechnology: 'ダークマター技術', darkMatterTechnology: 'ダークマター技術',
terraformingTechnology: 'テラフォーミング技術', terraformingTechnology: 'テラフォーミング技術',
planetDestructionTech: '惑星破壊技術', planetDestructionTech: '惑星破壊技術',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: '銀河間研究ネットワーク',
mineralResearch: '鉱物研究',
crystalResearch: 'クリスタル研究',
fuelResearch: '燃料研究'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: 'エネルギー利用効率を向上', energyTechnology: 'エネルギー利用効率を向上',
@@ -342,7 +352,11 @@ export default {
darkMatterTechnology: 'ダークマターの性質と応用を研究', darkMatterTechnology: 'ダークマターの性質と応用を研究',
terraformingTechnology: '惑星地形改造技術を研究、レベル毎に全惑星の利用可能スペース30増加', terraformingTechnology: '惑星地形改造技術を研究、レベル毎に全惑星の利用可能スペース30増加',
planetDestructionTech: '惑星全体を破壊する恐怖の技術を研究', planetDestructionTech: '惑星全体を破壊する恐怖の技術を研究',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: '複数の惑星の研究所を連結。レベル毎に追加1研究所を接続',
mineralResearch: 'より効率的な金属抽出技術を研究。レベル毎に金属生産+2%',
crystalResearch: 'より効率的なクリスタル精製技術を研究。レベル毎にクリスタル生産+2%',
fuelResearch: 'より効率的な重水素合成技術を研究。レベル毎に重水素生産+2%'
}, },
officers: { officers: {
commander: '司令官', commander: '司令官',
@@ -641,7 +655,9 @@ export default {
jumpGateSuccessMessage: '艦隊は{target}へ瞬時に転送されました', jumpGateSuccessMessage: '艦隊は{target}へ瞬時に転送されました',
jumpGateFailed: 'ジャンプゲート転送失敗', jumpGateFailed: 'ジャンプゲート転送失敗',
jumpGateFailedMessage: 'ジャンプゲートの状態と艦隊構成を確認してください', jumpGateFailedMessage: 'ジャンプゲートの状態と艦隊構成を確認してください',
destroy: '破壊' destroy: '破壊',
harvestDarkMatter: 'ダークマター採取',
station: '駐留'
}, },
officersView: { officersView: {
title: '士官', title: '士官',
@@ -953,7 +969,10 @@ export default {
hideRoundDetails: 'ラウンド詳細非表示', hideRoundDetails: 'ラウンド詳細非表示',
round: '第{round}ラウンド', round: '第{round}ラウンド',
attackerRemainingPower: '攻撃側残存火力', attackerRemainingPower: '攻撃側残存火力',
defenderRemainingPower: '防御側残存火力' defenderRemainingPower: '防御側残存火力',
importFromSpyReport: 'スパイレポートからインポート',
selectSpyReport: 'スパイレポートを選択',
noSpyReports: 'スパイレポートがありません'
}, },
settings: { settings: {
dataManagement: 'データ管理', dataManagement: 'データ管理',
@@ -964,6 +983,8 @@ export default {
exporting: 'エクスポート中...', exporting: 'エクスポート中...',
exportSuccess: 'エクスポート成功', exportSuccess: 'エクスポート成功',
exportSuccessWithPath: 'エクスポート成功、ファイルの保存先:{path}', exportSuccessWithPath: 'エクスポート成功、ファイルの保存先:{path}',
exportCopiedToClipboard: 'エクスポート成功、データがクリップボードにコピーされました',
storagePermissionDenied: 'ストレージ権限が拒否されました。ファイルをエクスポートできません',
exportFailed: 'エクスポートに失敗しました。もう一度お試しください', exportFailed: 'エクスポートに失敗しました。もう一度お試しください',
importData: 'データインポート', importData: 'データインポート',
importDataDesc: 'JSONファイルからゲームの進行状況を復元', importDataDesc: 'JSONファイルからゲームの進行状況を復元',
@@ -981,6 +1002,8 @@ export default {
gameSettingsDesc: 'ゲームパラメータと設定を調整', gameSettingsDesc: 'ゲームパラメータと設定を調整',
gamePause: 'ゲーム一時停止', gamePause: 'ゲーム一時停止',
gamePauseDesc: 'ゲーム時間と資源生産を一時停止または再開', gamePauseDesc: 'ゲーム時間と資源生産を一時停止または再開',
battleMode: '最後まで戦うモード',
battleModeDesc: '有効にすると、勝者が決まるまで最大100ラウンドの戦闘が行われます。無効の場合は、クラシックな6ラウンドモードが使用されます',
pause: '一時停止', pause: '一時停止',
resume: '再開', resume: '再開',
gamePaused: 'ゲームを一時停止しました', gamePaused: 'ゲームを一時停止しました',
@@ -1854,6 +1877,27 @@ export default {
reputationBonusDesc: '同盟の{npcName}が{targetNpc}にあなたのことを良く言っています' reputationBonusDesc: '同盟の{npcName}が{targetNpc}にあなたのことを良く言っています'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV接続成功',
connectionSuccessDirectoryCreated: 'WebDAV接続成功、保存ディレクトリを作成しました',
authFailed: '認証失敗、ユーザー名とパスワードを確認してください',
directoryNotExist: 'ディレクトリが存在せず、作成できませんでした',
networkError: 'ネットワークエラー、サーバーアドレスとネットワークを確認してください',
unknownError: '不明なエラー',
uploadSuccess: 'セーブデータのアップロード成功',
uploadFailed: 'アップロード失敗',
downloadSuccess: 'セーブデータのダウンロード成功',
downloadFailed: 'ダウンロード失敗',
noSaveFiles: 'サーバーにセーブデータがありません',
fileListSuccess: 'セーブデータリストの取得成功',
fileListFailed: 'セーブデータリストの取得失敗',
deleteSuccess: 'セーブデータの削除成功',
deleteFailed: '削除失敗',
serverError: 'サーバーエラー',
notConfigured: 'WebDAVが設定されていません',
invalidUrl: '無効なWebDAV URL',
timeout: '接続タイムアウト'
},
campaign: { campaign: {
name: 'キャンペーン', name: 'キャンペーン',
description: '神秘的な銀河を探索し、古代の秘密を解き明かす', description: '神秘的な銀河を探索し、古代の秘密を解き明かす',
@@ -1881,6 +1925,8 @@ export default {
branchUnlocked: '新しいストーリー分岐が解放されました!' branchUnlocked: '新しいストーリー分岐が解放されました!'
}, },
dialogue: { dialogue: {
title: 'ストーリー対話',
description: 'キャンペーンストーリーの対話内容',
skip: 'スキップ', skip: 'スキップ',
continue: '続ける', continue: '続ける',
finish: '完了', finish: '完了',
@@ -1971,13 +2017,19 @@ export default {
questNotActive: 'クエストはアクティブではありません', questNotActive: 'クエストはアクティブではありません',
questNotCompleted: 'クエスト未完了', questNotCompleted: 'クエスト未完了',
rewardsAlreadyClaimed: '報酬は既に受け取り済み', rewardsAlreadyClaimed: '報酬は既に受け取り済み',
prerequisiteNotMet: '前提クエスト未完了' prerequisiteNotMet: '前提クエスト未完了',
questLocked: 'クエストはロックされています',
notInitialized: 'キャンペーンが初期化されていません',
questAlreadyCompleted: 'クエストは既に完了しています'
}, },
speakers: { speakers: {
ancientVoice: '古代の声', ancientVoice: '古代の声',
neighborNPC: '隣接勢力', neighborNPC: '隣接勢力',
mysteriousSignal: '謎の信号', mysteriousSignal: '謎の信号',
enemyCommander: '敵司令官' enemyCommander: '敵司令官',
shadowVoice: '影の声',
allyNPC: '同盟勢力',
ancientGuardian: '古代の守護者'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: '金属鉱山をレベル2に建設', buildMetalMine: '金属鉱山をレベル2に建設',
@@ -1993,25 +2045,42 @@ export default {
spyAnyNPC: 'NPC惑星をスパイ', spyAnyNPC: 'NPC惑星をスパイ',
sendGiftToNPC: 'NPCに贈り物を送る', sendGiftToNPC: 'NPCに贈り物を送る',
researchAstrophysics: '宇宙物理学をレベル1まで研究', researchAstrophysics: '宇宙物理学をレベル1まで研究',
researchAstrophysicsHigher: '宇宙物理学をレベル3まで研究',
buildColonyShip: 'コロニーシップを建造', buildColonyShip: 'コロニーシップを建造',
colonizeNewPlanet: '新惑星を植民地化', colonizeNewPlanet: '新惑星を植民地化',
completeExpedition: '遠征ミッションを完了', colonizeMultiple: '5つの惑星を植民地化',
completeExpedition: '3回の遠征ミッションを完了',
expeditionDeepSpace: '2回の深宇宙遠征を完了',
expeditionUncharted: '1回の未知領域探索',
expeditionDangerous: '3回の危険な星雲遠征を完了',
discoverRuins: '古代遺跡を発見', discoverRuins: '古代遺跡を発見',
researchComputer: 'コンピュータ技術をレベル4まで研究', researchComputer: 'コンピュータ技術をレベル4まで研究',
researchImpulse: 'インパルスドライブをレベル3まで研究',
researchLaser: 'レーザー技術をレベル5まで研究',
researchIntergalactic: 'コンピュータ技術をレベル10まで研究',
researchGraviton: 'グラビトン技術をレベル1まで研究',
improveRelation: 'NPCとの関係を改善', improveRelation: 'NPCとの関係を改善',
reachFriendly: 'NPCと友好ステータスに到達', reachFriendly: 'NPCと友好ステータスに到達',
spyHostileNPC: '敵対NPCをスパイ', reachFriendlyRelation: '任意のNPCと友好ステータスに到達',
sendMultipleGifts: 'NPCに3回贈り物を送る',
spyHostileNPC: '2つの敵対NPCをスパイ',
formAlliance: '友好NPCと同盟を結ぶ', formAlliance: '友好NPCと同盟を結ぶ',
buildDefenses: '防衛施設を建設', buildDefenses: '防衛施設を建設',
buildMissileSilo: 'ミサイルサイロをレベル2に建設',
buildCruisers: 'クルーザー10隻を建造',
winDefenseBattle: '防衛戦に勝利', winDefenseBattle: '防衛戦に勝利',
defendAgainstAttack: '1回の攻撃を防御成功',
spyEnemyPlanet: '敵惑星をスパイ', spyEnemyPlanet: '敵惑星をスパイ',
spyEnemyPlanets: '5つの敵惑星をスパイ',
winAttackBattles: '3回の攻撃戦に勝利',
attackEnemy: '敵を攻撃', attackEnemy: '敵を攻撃',
recycleDebris: 'デブリをリサイクル', recycleDebris: '5回デブリをリサイクル',
buildBattleships: 'バトルシップ10隻を建造', buildBattleships: 'バトルシップ20隻を建造',
exploreDeepRuins: '深部遺跡を探索', exploreDeepRuins: '深部遺跡を探索',
researchHyperspace: 'ハイパースペース技術を研究', researchHyperspace: 'ハイパースペースドライブをレベル3まで研究',
defeatBoss: '謎の敵を撃破', defeatBoss: '古代の守護者を撃破',
colonizeSpecial: '特別な場所を植民地化', colonizeSpecial: '特別な場所を植民地化',
accumulateWealth: '総資源100万を蓄積',
continueDevelopment: '発展を継続' continueDevelopment: '発展を継続'
}, },
dialogues: { dialogues: {
@@ -2040,36 +2109,60 @@ export default {
}, },
'2_3': { '2_3': {
prologue_1: '遠征で異常な信号を発見しました。これらの信号は古代文明からのようです...発信源を調査してください。', prologue_1: '遠征で異常な信号を発見しました。これらの信号は古代文明からのようです...発信源を調査してください。',
prologue_2: 'これらの信号...失われた文明の残響を運んでいる。その秘密が発見を待っている...',
epilogue_1: 'これらの記号は...古代文明の遺跡だ!さらに調査して秘密を解き明かそう。' epilogue_1: 'これらの記号は...古代文明の遺跡だ!さらに調査して秘密を解き明かそう。'
}, },
'2_4': { prologue_1: '古代遺跡の場所を発見しました。艦隊を送り、何が見つかるか探索してください。' }, '2_4': {
'2_5': { prologue_1: '遺跡でデータアーカイブが見つかりました。このデータを研究すれば、新しい技術が解放できるかもしれません。' }, prologue_1: '古代遺跡の場所を発見しました。艦隊を送り、何が見つかるか探索してください。',
prologue_2: '遺跡には多くの秘密がある...賢く道を選べ...',
choice_1: '慎重に探索する - 安全を優先',
choice_2: '積極的に探索する - 発見を優先'
},
'2_5': {
prologue_1: '遺跡でデータアーカイブが見つかりました。このデータを研究すれば、新しい技術が解放できるかもしれません。',
epilogue_1: '古代のデータが解読されました!新しい技術的洞察を得ました。'
},
'3_1': { prologue_1: '探索中も外交を忘れないでください。周囲の勢力と良好な関係を維持することは有益です。' }, '3_1': { prologue_1: '探索中も外交を忘れないでください。周囲の勢力と良好な関係を維持することは有益です。' },
'3_2': { prologue_1: 'いくつかの勢力が友好を示しています。関係を深め続ければ、より多くのサポートを得られるかもしれません。' }, '3_2': { prologue_1: 'いくつかの勢力が友好を示しています。関係を深め続ければ、より多くのサポートを得られるかもしれません。' },
'3_3': { prologue_1: '情報によると敵対勢力が影からあなたを監視しています。警戒を怠らず、彼らの動きを偵察してください。' }, '3_3': {
prologue_1: '情報によると敵対勢力が影からあなたを監視しています。警戒を怠らず、彼らの動きを偵察してください。',
prologue_2: '闇の力が虚空で蠢いている...彼らはあなたの成長する力に気づいた...'
},
'3_4': { prologue_1: '友好勢力と正式な同盟を結び、脅威に対してお互いをサポートしましょう。' }, '3_4': { prologue_1: '友好勢力と正式な同盟を結び、脅威に対してお互いをサポートしましょう。' },
'3_5': { prologue_1: '脅威が迫っています。防衛施設を建設し、可能な紛争に備えてください。' }, '3_5': {
prologue_1: '脅威が迫っています。防衛施設を建設し、可能な紛争に備えてください。',
epilogue_1: '防衛準備が整いました。嵐が来るが、あなたは準備ができている。'
},
'4_1': { '4_1': {
prologue_1: '敵が攻撃を開始しました!惑星を守ってください!', prologue_1: '敵が攻撃を開始しました!惑星を守ってください!',
prologue_2: '影の艦隊が迫っている...あなたの試練の時が来た...',
epilogue_1: '敵の第一波を撃退しました。しかしこれは始まりに過ぎません...' epilogue_1: '敵の第一波を撃退しました。しかしこれは始まりに過ぎません...'
}, },
'4_2': { prologue_1: '敵は撤退しましたが、戻ってくるでしょう。彼らの惑星を偵察して戦力を把握してください。' }, '4_2': { prologue_1: '敵は撤退しましたが、戻ってくるでしょう。彼らの惑星を偵察して戦力を把握してください。' },
'4_3': { prologue_1: '反撃の時です。敵の惑星を攻撃し、彼らの戦力を弱めてください。' }, '4_3': { prologue_1: '反撃の時です。敵の惑星を攻撃し、彼らの戦力を弱めてください。' },
'4_4': { prologue_1: '戦場に多くのデブリが残っています。これらの資源をリサイクルして次の戦闘に備えてください。' }, '4_4': { prologue_1: '戦場に多くのデブリが残っています。これらの資源をリサイクルして次の戦闘に備えてください。' },
'4_5': { prologue_1: '最終決戦が近づいています。強力な艦隊を建造し、究極の挑戦に備えてください。' }, '4_5': {
prologue_1: '最終決戦が近づいています。強力な艦隊を建造し、究極の挑戦に備えてください。',
epilogue_1: '艦隊が集結しました。決戦の時が近づいている...'
},
'5_1': { '5_1': {
prologue_1: 'すべての手がかりは遺跡の最深部を指しています。古代文明の核心的な秘密がそこにあります。', prologue_1: 'すべての手がかりは遺跡の最深部を指しています。古代文明の核心的な秘密がそこにあります。',
prologue_2: 'ついに到着した...真実がまもなく明かされる...' prologue_2: 'ついに到着した...真実がまもなく明かされる...'
}, },
'5_2': { prologue_1: '遺跡の深部で失われた古代技術を発見しました。研究してその力を解放してください。' }, '5_2': {
prologue_1: '遺跡の深部で失われた古代技術を発見しました。研究してその力を解放してください。',
prologue_2: 'この技術は...既知のすべての文明より古い。慎重に扱え...'
},
'5_3': { '5_3': {
prologue_1: '謎の敵が現れました。これが最後の挑戦です。撃破してください!', prologue_1: '謎の敵が現れました。これが最後の挑戦です。撃破してください!',
prologue_2: '私はこれらの秘密の守護者だ。お前の価値を証明せよ、さもなくば滅びよ!',
epilogue_1: 'やりました!古代の守護者は倒されました。銀河の秘密は今やあなたに開かれています。' epilogue_1: 'やりました!古代の守護者は倒されました。銀河の秘密は今やあなたに開かれています。'
}, },
'5_4': { prologue_1: 'ついに平和が訪れました。この新時代に新しい植民地を築き、帝国を拡大してください。' }, '5_4': { prologue_1: 'ついに平和が訪れました。この新時代に新しい植民地を築き、帝国を拡大してください。' },
'5_5': { '5_5': {
prologue_1: 'あなたの伝説は始まったばかりです。さらに探索を続け、より多くの星系を征服してください!', prologue_1: 'あなたの伝説は始まったばかりです。さらに探索を続け、より多くの星系を征服してください!',
epilogue_1: '銀河は広大で果てしなく、数え切れない秘密があなたを待っています...' epilogue_1: '銀河は広大で果てしなく、数え切れない秘密があなたを待っています...',
epilogue_2: 'あなたの旅は続く...新たな冒険が星の彼方で待っている...'
} }
} }
} }

View File

@@ -153,6 +153,7 @@ export default {
planetDestroyerFactory: '행성 파괴 공장', planetDestroyerFactory: '행성 파괴 공장',
geoResearchStation: '지질 연구소', geoResearchStation: '지질 연구소',
deepDrillingFacility: '심층 시추 시설', deepDrillingFacility: '심층 시추 시설',
university: '대학',
buildTime: '건설 시간', buildTime: '건설 시간',
build: '건설', build: '건설',
production: '생산량', production: '생산량',
@@ -199,8 +200,9 @@ export default {
sensorPhalanx: '주변 행성계의 함대 활동 감지', sensorPhalanx: '주변 행성계의 함대 활동 감지',
jumpGate: '다른 위성으로 함대 순간 이동', jumpGate: '다른 위성으로 함대 순간 이동',
planetDestroyerFactory: '행성을 파괴할 수 있는 궁극 병기 건조', planetDestroyerFactory: '행성을 파괴할 수 있는 궁극 병기 건조',
geoResearchStation: '지질 구조를 연구하여 광맥 자연 회복 속도를 높입니다. 레벨당 회복 속도 50% 증가', geoResearchStation: '지질 구조를 연구하여 광맥 자연 회복 속도를 높입니다. 레벨당 회복 속도 10% 증가',
deepDrillingFacility: '' deepDrillingFacility: '',
university: '연구원을 양성하여 연구 속도를 가속합니다. 레벨당 연구 시간 -8%'
}, },
ships: { ships: {
lightFighter: '경전투기', lightFighter: '경전투기',
@@ -276,7 +278,11 @@ export default {
colonySlots: '식민지 슬롯', colonySlots: '식민지 슬롯',
forAllPlanets: '(전역)', forAllPlanets: '(전역)',
speedBonus: '속도 보너스', speedBonus: '속도 보너스',
researchSpeedBonus: '연구 속도 보너스' researchSpeedBonus: '연구 속도 보너스',
// 자원 생산 보너스 표시
mineralResearch: '광물 연구',
crystalResearch: '크리스탈 연구',
fuelResearch: '연료 연구'
}, },
technologies: { technologies: {
energyTechnology: '에너지 기술', energyTechnology: '에너지 기술',
@@ -297,7 +303,11 @@ export default {
darkMatterTechnology: '암흑 물질 기술', darkMatterTechnology: '암흑 물질 기술',
terraformingTechnology: '지형 변환 기술', terraformingTechnology: '지형 변환 기술',
planetDestructionTech: '행성 파괴 기술', planetDestructionTech: '행성 파괴 기술',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: '은하간 연구 네트워크',
mineralResearch: '광물 연구',
crystalResearch: '크리스탈 연구',
fuelResearch: '연료 연구'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: '에너지 이용 효율 향상', energyTechnology: '에너지 이용 효율 향상',
@@ -319,7 +329,11 @@ export default {
darkMatterTechnology: '암흑 물질의 성질과 응용 연구', darkMatterTechnology: '암흑 물질의 성질과 응용 연구',
terraformingTechnology: '행성 지형 개조 기술 연구, 레벨당 모든 행성의 가용 공간 30 증가', terraformingTechnology: '행성 지형 개조 기술 연구, 레벨당 모든 행성의 가용 공간 30 증가',
planetDestructionTech: '행성 전체를 파괴하는 공포의 기술 연구', planetDestructionTech: '행성 전체를 파괴하는 공포의 기술 연구',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: '여러 행성의 연구소를 연결합니다. 레벨당 1개 추가 연구소 연결',
mineralResearch: '더 효율적인 금속 추출 기술 연구. 레벨당 금속 생산 +2%',
crystalResearch: '더 효율적인 크리스탈 정제 기술 연구. 레벨당 크리스탈 생산 +2%',
fuelResearch: '더 효율적인 중수소 합성 기술 연구. 레벨당 중수소 생산 +2%'
}, },
officers: { officers: {
commander: '사령관', commander: '사령관',
@@ -388,6 +402,7 @@ export default {
title: '건물', title: '건물',
spaceUsage: '공간 사용', spaceUsage: '공간 사용',
upgradeCost: '업그레이드 비용', upgradeCost: '업그레이드 비용',
build: '건설',
upgrade: '업그레이드', upgrade: '업그레이드',
maxLevelReached: '최대 레벨 도달', maxLevelReached: '최대 레벨 도달',
requirementsNotMet: '요구 사항 미충족', requirementsNotMet: '요구 사항 미충족',
@@ -408,9 +423,19 @@ export default {
researchFailedMessage: '자원이 충분한지, 전제 조건이 충족되었는지, 또는 다른 연구 작업이 있는지 확인하세요.' researchFailedMessage: '자원이 충분한지, 전제 조건이 충족되었는지, 또는 다른 연구 작업이 있는지 확인하세요.'
}, },
shipyard: { shipyard: {
attack: '공격력',
shield: '실드',
armor: '장갑',
speed: '속도', speed: '속도',
cargoCapacity: '화물 용량', cargoCapacity: '화물 용량',
fuelConsumption: '연료 소비' fuelConsumption: '연료 소비',
buildCost: '건조 비용',
buildTime: '건조 시간',
perUnit: '단위당',
batchCalculator: '일괄 계산기',
quantity: '수량',
totalCost: '총 비용',
totalTime: '총 시간'
}, },
shipyardView: { shipyardView: {
title: '조선소', title: '조선소',
@@ -521,6 +546,8 @@ export default {
}, },
recycle: '회수', recycle: '회수',
destroy: '행성 파괴', destroy: '행성 파괴',
harvestDarkMatter: '암흑 물질 수확',
station: '주둔',
transportResources: '자원 수송', transportResources: '자원 수송',
totalCargoCapacity: '총 적재량', totalCargoCapacity: '총 적재량',
used: '사용됨', used: '사용됨',
@@ -897,7 +924,10 @@ export default {
hideRoundDetails: '라운드 상세 숨기기', hideRoundDetails: '라운드 상세 숨기기',
round: '제 {round} 라운드', round: '제 {round} 라운드',
attackerRemainingPower: '공격자 잔여 화력', attackerRemainingPower: '공격자 잔여 화력',
defenderRemainingPower: '방어자 잔여 화력' defenderRemainingPower: '방어자 잔여 화력',
importFromSpyReport: '정찰 보고서에서 가져오기',
selectSpyReport: '정찰 보고서 선택',
noSpyReports: '정찰 보고서가 없습니다'
}, },
settings: { settings: {
dataManagement: '데이터 관리', dataManagement: '데이터 관리',
@@ -908,6 +938,8 @@ export default {
exporting: '내보내는 중...', exporting: '내보내는 중...',
exportSuccess: '내보내기 성공', exportSuccess: '내보내기 성공',
exportSuccessWithPath: '내보내기 성공, 파일 저장 위치: {path}', exportSuccessWithPath: '내보내기 성공, 파일 저장 위치: {path}',
exportCopiedToClipboard: '내보내기 성공, 데이터가 클립보드에 복사됨',
storagePermissionDenied: '저장소 권한이 거부되어 파일을 내보낼 수 없습니다',
exportFailed: '내보내기 실패, 다시 시도해주세요', exportFailed: '내보내기 실패, 다시 시도해주세요',
importData: '데이터 가져오기', importData: '데이터 가져오기',
importDataDesc: 'JSON 파일에서 게임 진행 상황 복원', importDataDesc: 'JSON 파일에서 게임 진행 상황 복원',
@@ -925,6 +957,8 @@ export default {
gameSettingsDesc: '게임 매개변수 및 설정 조정', gameSettingsDesc: '게임 매개변수 및 설정 조정',
gamePause: '게임 일시정지', gamePause: '게임 일시정지',
gamePauseDesc: '게임 시간 및 자원 생산 일시정지 또는 재개', gamePauseDesc: '게임 시간 및 자원 생산 일시정지 또는 재개',
battleMode: '끝까지 싸우기 모드',
battleModeDesc: '활성화하면 승자가 결정될 때까지 최대 100라운드까지 전투가 진행됩니다. 비활성화하면 클래식 6라운드 모드가 사용됩니다',
pause: '일시정지', pause: '일시정지',
resume: '재개', resume: '재개',
gamePaused: '게임이 일시정지되었습니다', gamePaused: '게임이 일시정지되었습니다',
@@ -1784,6 +1818,27 @@ export default {
reputationBonusDesc: '동맹 {npcName}이(가) {targetNpc}에게 당신을 좋게 말하고 있습니다' reputationBonusDesc: '동맹 {npcName}이(가) {targetNpc}에게 당신을 좋게 말하고 있습니다'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV 연결 성공',
connectionSuccessDirectoryCreated: 'WebDAV 연결 성공, 저장 디렉토리 생성됨',
authFailed: '인증 실패, 사용자 이름과 비밀번호를 확인하세요',
directoryNotExist: '디렉토리가 존재하지 않으며 생성할 수 없습니다',
networkError: '네트워크 오류, 서버 주소와 네트워크를 확인하세요',
unknownError: '알 수 없는 오류',
uploadSuccess: '저장 파일 업로드 성공',
uploadFailed: '업로드 실패',
downloadSuccess: '저장 파일 다운로드 성공',
downloadFailed: '다운로드 실패',
noSaveFiles: '서버에 저장 파일이 없습니다',
fileListSuccess: '저장 파일 목록 가져오기 성공',
fileListFailed: '저장 파일 목록 가져오기 실패',
deleteSuccess: '저장 파일 삭제 성공',
deleteFailed: '삭제 실패',
serverError: '서버 오류',
notConfigured: 'WebDAV가 구성되지 않았습니다',
invalidUrl: '잘못된 WebDAV URL',
timeout: '연결 시간 초과'
},
campaign: { campaign: {
name: '캠페인', name: '캠페인',
description: '신비로운 은하를 탐험하고 고대의 비밀을 밝혀내세요', description: '신비로운 은하를 탐험하고 고대의 비밀을 밝혀내세요',
@@ -1810,6 +1865,8 @@ export default {
branchUnlocked: '새로운 스토리 분기가 해금되었습니다!' branchUnlocked: '새로운 스토리 분기가 해금되었습니다!'
}, },
dialogue: { dialogue: {
title: '스토리 대화',
description: '캠페인 스토리 대화 내용',
skip: '건너뛰기', skip: '건너뛰기',
continue: '계속', continue: '계속',
finish: '완료', finish: '완료',
@@ -1900,13 +1957,19 @@ export default {
questNotActive: '퀘스트가 활성화되지 않았습니다', questNotActive: '퀘스트가 활성화되지 않았습니다',
questNotCompleted: '퀘스트 미완료', questNotCompleted: '퀘스트 미완료',
rewardsAlreadyClaimed: '이미 보상을 받았습니다', rewardsAlreadyClaimed: '이미 보상을 받았습니다',
prerequisiteNotMet: '선행 퀘스트 미완료' prerequisiteNotMet: '선행 퀘스트 미완료',
questLocked: '퀘스트가 잠겨 있습니다',
notInitialized: '캠페인이 초기화되지 않았습니다',
questAlreadyCompleted: '퀘스트가 이미 완료되었습니다'
}, },
speakers: { speakers: {
ancientVoice: '고대의 목소리', ancientVoice: '고대의 목소리',
neighborNPC: '인접 세력', neighborNPC: '인접 세력',
mysteriousSignal: '신비한 신호', mysteriousSignal: '신비한 신호',
enemyCommander: '적 사령관' enemyCommander: '적 사령관',
shadowVoice: '그림자의 목소리',
allyNPC: '동맹 세력',
ancientGuardian: '고대의 수호자'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: '금속 광산을 레벨 2로 건설', buildMetalMine: '금속 광산을 레벨 2로 건설',
@@ -1922,25 +1985,42 @@ export default {
spyAnyNPC: 'NPC 행성 정찰', spyAnyNPC: 'NPC 행성 정찰',
sendGiftToNPC: 'NPC에게 선물 보내기', sendGiftToNPC: 'NPC에게 선물 보내기',
researchAstrophysics: '천체물리학을 레벨 1까지 연구', researchAstrophysics: '천체물리학을 레벨 1까지 연구',
researchAstrophysicsHigher: '천체물리학을 레벨 3까지 연구',
buildColonyShip: '식민선 건조', buildColonyShip: '식민선 건조',
colonizeNewPlanet: '새 행성 식민지화', colonizeNewPlanet: '새 행성 식민지화',
completeExpedition: '탐험 임무 완료', colonizeMultiple: '5개 행성 식민지화',
completeExpedition: '3회 탐험 임무 완료',
expeditionDeepSpace: '2회 심우주 탐험 완료',
expeditionUncharted: '1회 미지 영역 탐험',
expeditionDangerous: '3회 위험 성운 탐험 완료',
discoverRuins: '고대 유적 발견', discoverRuins: '고대 유적 발견',
researchComputer: '컴퓨터 기술을 레벨 4까지 연구', researchComputer: '컴퓨터 기술을 레벨 4까지 연구',
researchImpulse: '임펄스 드라이브를 레벨 3까지 연구',
researchLaser: '레이저 기술을 레벨 5까지 연구',
researchIntergalactic: '컴퓨터 기술을 레벨 10까지 연구',
researchGraviton: '그래비톤 기술을 레벨 1까지 연구',
improveRelation: 'NPC와의 관계 개선', improveRelation: 'NPC와의 관계 개선',
reachFriendly: 'NPC와 우호 상태 도달', reachFriendly: 'NPC와 우호 상태 도달',
spyHostileNPC: '적대 NPC 정찰', reachFriendlyRelation: '임의의 NPC와 우호 상태 도달',
sendMultipleGifts: 'NPC에게 3회 선물 보내기',
spyHostileNPC: '적대 NPC 2곳 정찰',
formAlliance: '우호 NPC와 동맹 체결', formAlliance: '우호 NPC와 동맹 체결',
buildDefenses: '방어 시설 건설', buildDefenses: '방어 시설 건설',
buildMissileSilo: '미사일 사일로를 레벨 2로 건설',
buildCruisers: '순양함 10대 건조',
winDefenseBattle: '방어전 승리', winDefenseBattle: '방어전 승리',
defendAgainstAttack: '1회 공격 방어 성공',
spyEnemyPlanet: '적 행성 정찰', spyEnemyPlanet: '적 행성 정찰',
spyEnemyPlanets: '적 행성 5곳 정찰',
winAttackBattles: '3회 공격전 승리',
attackEnemy: '적 공격', attackEnemy: '적 공격',
recycleDebris: '잔해 재활용', recycleDebris: '5회 잔해 재활용',
buildBattleships: '전함 10대 건조', buildBattleships: '전함 20대 건조',
exploreDeepRuins: '깊은 유적 탐험', exploreDeepRuins: '깊은 유적 탐험',
researchHyperspace: '하이퍼스페이스 기술 연구', researchHyperspace: '하이퍼스페이스 드라이브를 레벨 3까지 연구',
defeatBoss: '신비한 적 격파', defeatBoss: '고대 수호자 격파',
colonizeSpecial: '특별한 장소 식민지화', colonizeSpecial: '특별한 장소 식민지화',
accumulateWealth: '총 자원 100만 축적',
continueDevelopment: '개발 계속' continueDevelopment: '개발 계속'
}, },
dialogues: { dialogues: {
@@ -1970,38 +2050,60 @@ export default {
}, },
'2_3': { '2_3': {
prologue_1: '탐험에서 이상한 신호를 발견했습니다. 이 신호들은 고대 문명에서 온 것 같습니다... 출처를 조사하세요.', prologue_1: '탐험에서 이상한 신호를 발견했습니다. 이 신호들은 고대 문명에서 온 것 같습니다... 출처를 조사하세요.',
prologue_2: '이 신호들... 잃어버린 문명의 메아리를 담고 있다. 그들의 비밀이 발견을 기다리고 있다...',
epilogue_1: '이 기호들은... 고대 문명의 유적이다! 계속 조사하여 비밀을 밝히세요.' epilogue_1: '이 기호들은... 고대 문명의 유적이다! 계속 조사하여 비밀을 밝히세요.'
}, },
'2_4': { prologue_1: '고대 유적의 위치를 발견했습니다. 함대를 보내 무엇을 발견할 수 있는지 탐험하세요.' }, '2_4': {
'2_5': { prologue_1: '유적에서 데이터 아카이브가 발견되었습니다. 이 데이터를 연구하면 새 기술을 해금할 수 있을지도 모릅니다.' }, prologue_1: '고대 유적의 위치를 발견습니다. 함대를 보내 무엇을 발견할 수 있는지 탐험하세요.',
prologue_2: '유적에는 많은 비밀이 있다... 현명하게 길을 선택하라...',
choice_1: '신중하게 탐험 - 안전 우선',
choice_2: '적극적으로 탐험 - 발견 우선'
},
'2_5': {
prologue_1: '유적에서 데이터 아카이브가 발견되었습니다. 이 데이터를 연구하면 새 기술을 해금할 수 있을지도 모릅니다.',
epilogue_1: '고대 데이터가 해독되었습니다! 새로운 기술적 통찰을 얻었습니다.'
},
'3_1': { prologue_1: '탐험 중에도 외교를 잊지 마세요. 주변 세력과 좋은 관계를 유지하는 것이 유익합니다.' }, '3_1': { prologue_1: '탐험 중에도 외교를 잊지 마세요. 주변 세력과 좋은 관계를 유지하는 것이 유익합니다.' },
'3_2': { prologue_1: '일부 세력이 우호를 보이고 있습니다. 관계를 계속 깊게 하면 더 많은 지원을 받을 수 있을지도 모릅니다.' }, '3_2': { prologue_1: '일부 세력이 우호를 보이고 있습니다. 관계를 계속 깊게 하면 더 많은 지원을 받을 수 있을지도 모릅니다.' },
'3_3': { '3_3': {
prologue_1: '정보에 따르면 적대 세력이 그림자에서 당신을 감시하고 있습니다. 경계를 늦추지 말고 그들의 움직임을 정찰하세요.' prologue_1: '정보에 따르면 적대 세력이 그림자에서 당신을 감시하고 있습니다. 경계를 늦추지 말고 그들의 움직임을 정찰하세요.',
prologue_2: '어둠의 힘이 허공에서 움직이고 있다... 그들은 당신의 성장하는 힘을 알아챘다...'
}, },
'3_4': { prologue_1: '우호 세력과 공식 동맹을 맺어 위협에 대해 서로를 지원하세요.' }, '3_4': { prologue_1: '우호 세력과 공식 동맹을 맺어 위협에 대해 서로를 지원하세요.' },
'3_5': { prologue_1: '위협이 다가오고 있습니다. 방어 시설을 건설하고 가능한 충돌에 대비하세요.' }, '3_5': {
prologue_1: '위협이 다가오고 있습니다. 방어 시설을 건설하고 가능한 충돌에 대비하세요.',
epilogue_1: '방어 준비가 완료되었습니다. 폭풍이 오지만, 당신은 준비가 되어 있습니다.'
},
'4_1': { '4_1': {
prologue_1: '적이 공격을 시작했습니다! 행성을 방어하세요!', prologue_1: '적이 공격을 시작했습니다! 행성을 방어하세요!',
prologue_2: '그림자 함대가 다가온다... 당신의 시련의 시간이 왔다...',
epilogue_1: '적의 첫 번째 파도를 성공적으로 격퇴했습니다. 하지만 이것은 시작에 불과합니다...' epilogue_1: '적의 첫 번째 파도를 성공적으로 격퇴했습니다. 하지만 이것은 시작에 불과합니다...'
}, },
'4_2': { prologue_1: '적이 후퇴했지만 돌아올 것입니다. 그들의 행성을 정찰하여 전력을 파악하세요.' }, '4_2': { prologue_1: '적이 후퇴했지만 돌아올 것입니다. 그들의 행성을 정찰하여 전력을 파악하세요.' },
'4_3': { prologue_1: '반격할 시간입니다. 적 행성을 공격하여 그들의 전력을 약화시키세요.' }, '4_3': { prologue_1: '반격할 시간입니다. 적 행성을 공격하여 그들의 전력을 약화시키세요.' },
'4_4': { prologue_1: '전장에 많은 잔해가 남아 있습니다. 이 자원을 재활용하여 다음 전투에 대비하세요.' }, '4_4': { prologue_1: '전장에 많은 잔해가 남아 있습니다. 이 자원을 재활용하여 다음 전투에 대비하세요.' },
'4_5': { prologue_1: '최종 전투가 다가오고 있습니다. 강력한 함대를 건조하고 궁극의 도전에 대비하세요.' }, '4_5': {
prologue_1: '최종 전투가 다가오고 있습니다. 강력한 함대를 건조하고 궁극의 도전에 대비하세요.',
epilogue_1: '함대가 집결했습니다. 결전의 시간이 다가온다...'
},
'5_1': { '5_1': {
prologue_1: '모든 단서가 유적의 가장 깊은 곳을 가리킵니다. 고대 문명의 핵심 비밀이 그곳에 있습니다.', prologue_1: '모든 단서가 유적의 가장 깊은 곳을 가리킵니다. 고대 문명의 핵심 비밀이 그곳에 있습니다.',
prologue_2: '드디어 도착했다... 진실이 곧 밝혀질 것이다...' prologue_2: '드디어 도착했다... 진실이 곧 밝혀질 것이다...'
}, },
'5_2': { prologue_1: '유적 깊은 곳에서 잃어버린 고대 기술을 발견했습니다. 연구하여 그 힘을 해방하세요.' }, '5_2': {
prologue_1: '유적 깊은 곳에서 잃어버린 고대 기술을 발견했습니다. 연구하여 그 힘을 해방하세요.',
prologue_2: '이 기술은... 알려진 모든 문명보다 오래되었다. 신중하게 다루어라...'
},
'5_3': { '5_3': {
prologue_1: '신비한 적이 나타났습니다. 이것이 마지막 도전입니다. 격파하세요!', prologue_1: '신비한 적이 나타났습니다. 이것이 마지막 도전입니다. 격파하세요!',
prologue_2: '나는 이 비밀들의 수호자다. 네 가치를 증명하라, 그렇지 않으면 멸망하라!',
epilogue_1: '해냈습니다! 고대의 수호자가 쓰러졌습니다. 은하의 비밀이 이제 당신에게 열려 있습니다.' epilogue_1: '해냈습니다! 고대의 수호자가 쓰러졌습니다. 은하의 비밀이 이제 당신에게 열려 있습니다.'
}, },
'5_4': { prologue_1: '마침내 평화가 찾아왔습니다. 이 새 시대에 새 식민지를 세우고 제국을 확장하세요.' }, '5_4': { prologue_1: '마침내 평화가 찾아왔습니다. 이 새 시대에 새 식민지를 세우고 제국을 확장하세요.' },
'5_5': { '5_5': {
prologue_1: '당신의 전설은 이제 막 시작되었습니다. 계속 탐험하고 더 많은 성계를 정복하세요!', prologue_1: '당신의 전설은 이제 막 시작되었습니다. 계속 탐험하고 더 많은 성계를 정복하세요!',
epilogue_1: '은하는 광대하고 끝이 없으며, 셀 수 없는 비밀이 당신을 기다리고 있습니다...' epilogue_1: '은하는 광대하고 끝이 없으며, 셀 수 없는 비밀이 당신을 기다리고 있습니다...',
epilogue_2: '당신의 여정은 계속된다... 새로운 모험이 별 너머에서 기다리고 있다...'
} }
} }
} }

View File

@@ -154,6 +154,7 @@ export default {
planetDestroyerFactory: 'Фабрика разрушителей планет', planetDestroyerFactory: 'Фабрика разрушителей планет',
geoResearchStation: 'Геологическая станция', geoResearchStation: 'Геологическая станция',
deepDrillingFacility: 'Глубинная буровая установка', deepDrillingFacility: 'Глубинная буровая установка',
university: 'Университет',
buildTime: 'Время строительства', buildTime: 'Время строительства',
build: 'Построить', build: 'Построить',
production: 'Производство', production: 'Производство',
@@ -161,6 +162,7 @@ export default {
totalCost: 'Общая стоимость', totalCost: 'Общая стоимость',
totalPoints: 'Общие очки', totalPoints: 'Общие очки',
levelRange: 'Диапазон уровней', levelRange: 'Диапазон уровней',
researchSpeedBonus: 'Бонус скорости исследований',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -204,8 +206,9 @@ export default {
jumpGate: 'Мгновенно переносит флоты на другие луны', jumpGate: 'Мгновенно переносит флоты на другие луны',
planetDestroyerFactory: 'Производит абсолютное оружие, способное уничтожать планеты', planetDestroyerFactory: 'Производит абсолютное оружие, способное уничтожать планеты',
geoResearchStation: geoResearchStation:
'Исследует геологическую структуру и повышает скорость восстановления рудных месторождений. +50% скорости восстановления за уровень', 'Исследует геологическую структуру и повышает скорость восстановления рудных месторождений. +10% скорости восстановления за уровень',
deepDrillingFacility: '' deepDrillingFacility: '',
university: 'Обучает исследователей для ускорения исследований. -8% времени исследования за уровень'
}, },
ships: { ships: {
lightFighter: 'Лёгкий истребитель', lightFighter: 'Лёгкий истребитель',
@@ -269,6 +272,9 @@ export default {
}, },
research: { research: {
researchTime: 'Время исследования', researchTime: 'Время исследования',
levelRange: 'Диапазон уровней',
totalCost: 'Общая стоимость',
totalPoints: 'Всего очков',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -284,7 +290,11 @@ export default {
researchQueueBonus: 'Очередь исследований', researchQueueBonus: 'Очередь исследований',
colonySlots: 'Слоты колоний', colonySlots: 'Слоты колоний',
forAllPlanets: '(Глобально)', forAllPlanets: '(Глобально)',
speedBonus: 'Бонус скорости' speedBonus: 'Бонус скорости',
// Отображение бонуса производства ресурсов
mineralResearch: 'Минеральные исследования',
crystalResearch: 'Кристаллические исследования',
fuelResearch: 'Топливные исследования'
}, },
technologies: { technologies: {
energyTechnology: 'Энергетическая технология', energyTechnology: 'Энергетическая технология',
@@ -305,7 +315,11 @@ export default {
darkMatterTechnology: 'Технология тёмной материи', darkMatterTechnology: 'Технология тёмной материи',
terraformingTechnology: 'Технология терраформирования', terraformingTechnology: 'Технология терраформирования',
planetDestructionTech: 'Технология уничтожения планет', planetDestructionTech: 'Технология уничтожения планет',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: 'Межгалактическая исследовательская сеть',
mineralResearch: 'Исследование минералов',
crystalResearch: 'Исследование кристаллов',
fuelResearch: 'Исследование топлива'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: 'Улучшает энергоэффективность', energyTechnology: 'Улучшает энергоэффективность',
@@ -328,7 +342,11 @@ export default {
terraformingTechnology: terraformingTechnology:
'Исследование технологии терраформирования планет, увеличивает доступное пространство всех планет на 30 за уровень', 'Исследование технологии терраформирования планет, увеличивает доступное пространство всех планет на 30 за уровень',
planetDestructionTech: 'Исследование ужасающей технологии уничтожения целых планет', planetDestructionTech: 'Исследование ужасающей технологии уничтожения целых планет',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: 'Связывает несколько исследовательских лабораторий на разных планетах. Каждый уровень подключает 1 дополнительную лабораторию',
mineralResearch: 'Исследование более эффективных методов добычи металла. +2% производства металла за уровень',
crystalResearch: 'Исследование более эффективных методов обработки кристаллов. +2% производства кристаллов за уровень',
fuelResearch: 'Исследование более эффективных методов синтеза дейтерия. +2% производства дейтерия за уровень'
}, },
officers: { officers: {
commander: 'Командир', commander: 'Командир',
@@ -402,6 +420,7 @@ export default {
spaceUsage: 'Использование полей', spaceUsage: 'Использование полей',
level: 'Уровень', level: 'Уровень',
upgradeCost: 'Стоимость улучшения', upgradeCost: 'Стоимость улучшения',
build: 'Построить',
upgrade: 'Улучшить', upgrade: 'Улучшить',
maxLevelReached: 'Достигнут максимальный уровень', maxLevelReached: 'Достигнут максимальный уровень',
requirementsNotMet: 'Требования не выполнены', requirementsNotMet: 'Требования не выполнены',
@@ -423,9 +442,19 @@ export default {
'Пожалуйста, проверьте, достаточно ли у вас ресурсов, выполнены ли предварительные условия или нет других исследовательских задач.' 'Пожалуйста, проверьте, достаточно ли у вас ресурсов, выполнены ли предварительные условия или нет других исследовательских задач.'
}, },
shipyard: { shipyard: {
attack: 'Атака',
shield: 'Щит',
armor: 'Броня',
speed: 'Скорость', speed: 'Скорость',
cargoCapacity: 'Грузоподъёмность', cargoCapacity: 'Грузоподъёмность',
fuelConsumption: 'Расход топлива' fuelConsumption: 'Расход топлива',
buildCost: 'Стоимость постройки',
buildTime: 'Время постройки',
perUnit: 'За единицу',
batchCalculator: 'Калькулятор партии',
quantity: 'Количество',
totalCost: 'Общая стоимость',
totalTime: 'Общее время'
}, },
shipyardView: { shipyardView: {
title: 'Верфь', title: 'Верфь',
@@ -607,7 +636,9 @@ export default {
jumpGateSuccessMessage: 'Флот мгновенно переброшен к {target}', jumpGateSuccessMessage: 'Флот мгновенно переброшен к {target}',
jumpGateFailed: 'Переброска через ворота не удалась', jumpGateFailed: 'Переброска через ворота не удалась',
jumpGateFailedMessage: 'Проверьте состояние ворот и конфигурацию флота', jumpGateFailedMessage: 'Проверьте состояние ворот и конфигурацию флота',
destroy: 'Уничтожение' destroy: 'Уничтожение',
harvestDarkMatter: 'Сбор тёмной материи',
station: 'Расположение'
}, },
officersView: { officersView: {
title: 'Офицеры', title: 'Офицеры',
@@ -916,7 +947,10 @@ export default {
hideRoundDetails: 'Скрыть детали раундов', hideRoundDetails: 'Скрыть детали раундов',
round: 'Раунд {round}', round: 'Раунд {round}',
attackerRemainingPower: 'Оставшаяся мощь нападающего', attackerRemainingPower: 'Оставшаяся мощь нападающего',
defenderRemainingPower: 'Оставшаяся мощь защитника' defenderRemainingPower: 'Оставшаяся мощь защитника',
importFromSpyReport: 'Импорт из шпионского отчета',
selectSpyReport: 'Выбрать шпионский отчет',
noSpyReports: 'Нет шпионских отчетов'
}, },
settings: { settings: {
dataManagement: 'Управление данными', dataManagement: 'Управление данными',
@@ -927,6 +961,8 @@ export default {
exporting: 'Экспорт...', exporting: 'Экспорт...',
exportSuccess: 'Экспорт успешен', exportSuccess: 'Экспорт успешен',
exportSuccessWithPath: 'Экспорт успешен, файл сохранен в: {path}', exportSuccessWithPath: 'Экспорт успешен, файл сохранен в: {path}',
exportCopiedToClipboard: 'Экспорт успешен, данные скопированы в буфер обмена',
storagePermissionDenied: 'Разрешение на хранение отклонено, невозможно экспортировать файл',
exportFailed: 'Экспорт не удался, попробуйте еще раз', exportFailed: 'Экспорт не удался, попробуйте еще раз',
importData: 'Импорт данных', importData: 'Импорт данных',
importDataDesc: 'Восстановить прогресс игры из JSON файла', importDataDesc: 'Восстановить прогресс игры из JSON файла',
@@ -944,6 +980,8 @@ export default {
gameSettingsDesc: 'Настроить параметры и предпочтения игры', gameSettingsDesc: 'Настроить параметры и предпочтения игры',
gamePause: 'Пауза игры', gamePause: 'Пауза игры',
gamePauseDesc: 'Приостановить или возобновить игровое время и производство ресурсов', gamePauseDesc: 'Приостановить или возобновить игровое время и производство ресурсов',
battleMode: 'Режим боя до конца',
battleModeDesc: 'При включении бои длятся до 100 раундов до определения победителя. При выключении используется классический режим 6 раундов',
pause: 'Пауза', pause: 'Пауза',
resume: 'Возобновить', resume: 'Возобновить',
gamePaused: 'Игра приостановлена', gamePaused: 'Игра приостановлена',
@@ -1836,6 +1874,27 @@ export default {
reputationBonusDesc: 'Ваш союзник {npcName} хорошо отзывается о вас {targetNpc}' reputationBonusDesc: 'Ваш союзник {npcName} хорошо отзывается о вас {targetNpc}'
} }
}, },
webdav: {
connectionSuccess: 'Подключение WebDAV успешно',
connectionSuccessDirectoryCreated: 'Подключение WebDAV успешно, каталог сохранений создан',
authFailed: 'Ошибка аутентификации, проверьте имя пользователя и пароль',
directoryNotExist: 'Каталог не существует и не может быть создан',
networkError: 'Ошибка сети, проверьте адрес сервера и сеть',
unknownError: 'Неизвестная ошибка',
uploadSuccess: 'Сохранение успешно загружено',
uploadFailed: 'Ошибка загрузки',
downloadSuccess: 'Сохранение успешно скачано',
downloadFailed: 'Ошибка скачивания',
noSaveFiles: 'На сервере нет сохранений',
fileListSuccess: 'Список сохранений получен',
fileListFailed: 'Ошибка получения списка сохранений',
deleteSuccess: 'Сохранение успешно удалено',
deleteFailed: 'Ошибка удаления',
serverError: 'Ошибка сервера',
notConfigured: 'WebDAV не настроен',
invalidUrl: 'Недействительный URL WebDAV',
timeout: 'Превышено время ожидания'
},
campaign: { campaign: {
name: 'Кампания', name: 'Кампания',
description: 'Исследуйте загадочную галактику и раскройте древние тайны', description: 'Исследуйте загадочную галактику и раскройте древние тайны',
@@ -1863,6 +1922,8 @@ export default {
branchUnlocked: 'Новая сюжетная ветка разблокирована!' branchUnlocked: 'Новая сюжетная ветка разблокирована!'
}, },
dialogue: { dialogue: {
title: 'Сюжетный диалог',
description: 'Содержание сюжетного диалога кампании',
skip: 'Пропустить', skip: 'Пропустить',
continue: 'Продолжить', continue: 'Продолжить',
finish: 'Завершить', finish: 'Завершить',
@@ -1953,13 +2014,19 @@ export default {
questNotActive: 'Квест не активен', questNotActive: 'Квест не активен',
questNotCompleted: 'Квест не завершён', questNotCompleted: 'Квест не завершён',
rewardsAlreadyClaimed: 'Награды уже получены', rewardsAlreadyClaimed: 'Награды уже получены',
prerequisiteNotMet: 'Предварительный квест не завершён' prerequisiteNotMet: 'Предварительный квест не завершён',
questLocked: 'Квест заблокирован',
notInitialized: 'Кампания не инициализирована',
questAlreadyCompleted: 'Квест уже завершён'
}, },
speakers: { speakers: {
ancientVoice: 'Древний голос', ancientVoice: 'Древний голос',
neighborNPC: 'Соседняя фракция', neighborNPC: 'Соседняя фракция',
mysteriousSignal: 'Загадочный сигнал', mysteriousSignal: 'Загадочный сигнал',
enemyCommander: 'Вражеский командир' enemyCommander: 'Вражеский командир',
shadowVoice: 'Голос тени',
allyNPC: 'Союзная фракция',
ancientGuardian: 'Древний страж'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: 'Построить Металлургический завод до уровня 2', buildMetalMine: 'Построить Металлургический завод до уровня 2',
@@ -1975,25 +2042,42 @@ export default {
spyAnyNPC: 'Разведать планету NPC', spyAnyNPC: 'Разведать планету NPC',
sendGiftToNPC: 'Отправить подарок NPC', sendGiftToNPC: 'Отправить подарок NPC',
researchAstrophysics: 'Исследовать Астрофизику до уровня 1', researchAstrophysics: 'Исследовать Астрофизику до уровня 1',
researchAstrophysicsHigher: 'Исследовать Астрофизику до уровня 3',
buildColonyShip: 'Построить Колониальный корабль', buildColonyShip: 'Построить Колониальный корабль',
colonizeNewPlanet: 'Колонизировать новую планету', colonizeNewPlanet: 'Колонизировать новую планету',
completeExpedition: 'Завершить экспедиционную миссию', colonizeMultiple: 'Колонизировать 5 планет',
completeExpedition: 'Завершить 3 экспедиционные миссии',
expeditionDeepSpace: 'Завершить 2 экспедиции в глубокий космос',
expeditionUncharted: 'Исследовать 1 неизвестный регион',
expeditionDangerous: 'Завершить 3 экспедиции в опасные туманности',
discoverRuins: 'Обнаружить древние руины', discoverRuins: 'Обнаружить древние руины',
researchComputer: 'Исследовать Компьютерную технологию до уровня 4', researchComputer: 'Исследовать Компьютерную технологию до уровня 4',
researchImpulse: 'Исследовать Импульсный двигатель до уровня 3',
researchLaser: 'Исследовать Лазерную технологию до уровня 5',
researchIntergalactic: 'Исследовать Компьютерную технологию до уровня 10',
researchGraviton: 'Исследовать Гравитонную технологию до уровня 1',
improveRelation: 'Улучшить отношения с NPC', improveRelation: 'Улучшить отношения с NPC',
reachFriendly: 'Достичь дружеского статуса с NPC', reachFriendly: 'Достичь дружеского статуса с NPC',
spyHostileNPC: 'Разведать враждебного NPC', reachFriendlyRelation: 'Достичь дружеского статуса с любым NPC',
sendMultipleGifts: 'Отправить 3 подарка NPC',
spyHostileNPC: 'Разведать 2 враждебных NPC',
formAlliance: 'Заключить альянс с дружественным NPC', formAlliance: 'Заключить альянс с дружественным NPC',
buildDefenses: 'Построить оборонительные сооружения', buildDefenses: 'Построить оборонительные сооружения',
buildMissileSilo: 'Построить Ракетную шахту до уровня 2',
buildCruisers: 'Построить 10 Крейсеров',
winDefenseBattle: 'Выиграть оборонительное сражение', winDefenseBattle: 'Выиграть оборонительное сражение',
defendAgainstAttack: 'Успешно отразить 1 атаку',
spyEnemyPlanet: 'Разведать вражескую планету', spyEnemyPlanet: 'Разведать вражескую планету',
spyEnemyPlanets: 'Разведать 5 вражеских планет',
winAttackBattles: 'Выиграть 3 атакующих сражения',
attackEnemy: 'Атаковать врага', attackEnemy: 'Атаковать врага',
recycleDebris: 'Переработать обломки', recycleDebris: 'Переработать обломки 5 раз',
buildBattleships: 'Построить 10 Линкоров', buildBattleships: 'Построить 20 Линкоров',
exploreDeepRuins: 'Исследовать глубокие руины', exploreDeepRuins: 'Исследовать глубокие руины',
researchHyperspace: 'Исследовать Гиперпространственную технологию', researchHyperspace: 'Исследовать Гиперпространственный двигатель до уровня 3',
defeatBoss: 'Победить загадочного врага', defeatBoss: 'Победить Древнего Стража',
colonizeSpecial: 'Колонизировать особое место', colonizeSpecial: 'Колонизировать особое место',
accumulateWealth: 'Накопить 1 миллион ресурсов',
continueDevelopment: 'Продолжить развитие' continueDevelopment: 'Продолжить развитие'
}, },
dialogues: { dialogues: {
@@ -2032,12 +2116,19 @@ export default {
'2_3': { '2_3': {
prologue_1: prologue_1:
'Ваша экспедиция обнаружила аномальные сигналы. Эти сигналы, похоже, исходят от древней цивилизации... Исследуйте их источник.', 'Ваша экспедиция обнаружила аномальные сигналы. Эти сигналы, похоже, исходят от древней цивилизации... Исследуйте их источник.',
prologue_2: 'Эти сигналы... Они несут отголоски исчезнувшей цивилизации. Их тайны ждут открытия...',
epilogue_1: 'Эти символы... Это руины древней цивилизации! Продолжайте исследование, чтобы раскрыть их тайны.' epilogue_1: 'Эти символы... Это руины древней цивилизации! Продолжайте исследование, чтобы раскрыть их тайны.'
}, },
'2_4': { '2_4': {
prologue_1: 'Вы нашли местоположение древних руин. Отправьте свой флот на исследование и посмотрите, что вы можете обнаружить.' prologue_1: 'Вы нашли местоположение древних руин. Отправьте свой флот на исследование и посмотрите, что вы можете обнаружить.',
prologue_2: 'В руинах скрыто много тайн... Выбирайте путь мудро...',
choice_1: 'Исследовать осторожно - приоритет безопасности',
choice_2: 'Исследовать агрессивно - приоритет открытий'
},
'2_5': {
prologue_1: 'В руинах найдены архивы данных. Изучите эти данные, возможно, вы сможете разблокировать новую технологию.',
epilogue_1: 'Древние данные расшифрованы! Получены новые технологические знания.'
}, },
'2_5': { prologue_1: 'В руинах найдены архивы данных. Изучите эти данные, возможно, вы сможете разблокировать новую технологию.' },
'3_1': { '3_1': {
prologue_1: 'Во время исследований не забывайте о дипломатии. Поддержание хороших отношений с окружающими фракциями выгодно.' prologue_1: 'Во время исследований не забывайте о дипломатии. Поддержание хороших отношений с окружающими фракциями выгодно.'
}, },
@@ -2046,31 +2137,44 @@ export default {
}, },
'3_3': { '3_3': {
prologue_1: prologue_1:
'Разведка показывает, что враждебные силы наблюдают за вами из тени. Оставайтесь бдительными и разведайте их перемещения.' 'Разведка показывает, что враждебные силы наблюдают за вами из тени. Оставайтесь бдительными и разведайте их перемещения.',
prologue_2: 'Тёмные силы шевелятся в пустоте... Они заметили вашу растущую мощь...'
}, },
'3_4': { prologue_1: 'Заключите официальный альянс с дружественными фракциями для взаимной поддержки против угроз.' }, '3_4': { prologue_1: 'Заключите официальный альянс с дружественными фракциями для взаимной поддержки против угроз.' },
'3_5': { prologue_1: 'Угрозы приближаются. Постройте оборонительные сооружения и подготовьтесь к возможному конфликту.' }, '3_5': {
prologue_1: 'Угрозы приближаются. Постройте оборонительные сооружения и подготовьтесь к возможному конфликту.',
epilogue_1: 'Ваша оборона готова. Буря надвигается, но вы подготовлены.'
},
'4_1': { '4_1': {
prologue_1: 'Враг начал атаку! Защитите свою планету!', prologue_1: 'Враг начал атаку! Защитите свою планету!',
prologue_2: 'Теневой флот приближается... Час твоего испытания настал...',
epilogue_1: 'Вы успешно отбили первую волну врага. Но это только начало...' epilogue_1: 'Вы успешно отбили первую волну врага. Но это только начало...'
}, },
'4_2': { prologue_1: 'Враг отступил, но он вернётся. Разведайте их планеты, чтобы понять их силу.' }, '4_2': { prologue_1: 'Враг отступил, но он вернётся. Разведайте их планеты, чтобы понять их силу.' },
'4_3': { prologue_1: 'Пора контратаковать. Атакуйте вражеские планеты и ослабьте их силы.' }, '4_3': { prologue_1: 'Пора контратаковать. Атакуйте вражеские планеты и ослабьте их силы.' },
'4_4': { prologue_1: 'На поле боя осталось много обломков. Переработайте эти ресурсы для подготовки к следующей битве.' }, '4_4': { prologue_1: 'На поле боя осталось много обломков. Переработайте эти ресурсы для подготовки к следующей битве.' },
'4_5': { prologue_1: 'Финальная битва приближается. Постройте мощный флот и приготовьтесь к конечному испытанию.' }, '4_5': {
prologue_1: 'Финальная битва приближается. Постройте мощный флот и приготовьтесь к конечному испытанию.',
epilogue_1: 'Ваш флот собран. Решающий момент приближается...'
},
'5_1': { '5_1': {
prologue_1: 'Все улики указывают на самую глубокую часть руин. Ключевые тайны древней цивилизации находятся там.', prologue_1: 'Все улики указывают на самую глубокую часть руин. Ключевые тайны древней цивилизации находятся там.',
prologue_2: 'Ты наконец прибыл... Истина скоро откроется...' prologue_2: 'Ты наконец прибыл... Истина скоро откроется...'
}, },
'5_2': { prologue_1: 'В глубинах руин вы обнаружили утерянные древние технологии. Исследуйте и разблокируйте их мощь.' }, '5_2': {
prologue_1: 'В глубинах руин вы обнаружили утерянные древние технологии. Исследуйте и разблокируйте их мощь.',
prologue_2: 'Эта технология... Она древнее всех известных цивилизаций. Обращайтесь с ней осторожно...'
},
'5_3': { '5_3': {
prologue_1: 'Появился загадочный враг. Это финальное испытание. Победите его!', prologue_1: 'Появился загадочный враг. Это финальное испытание. Победите его!',
prologue_2: 'Я — страж этих тайн. Докажи свою ценность или будешь уничтожен!',
epilogue_1: 'Вы справились! Древний страж повержен. Тайны галактики теперь открыты для вас.' epilogue_1: 'Вы справились! Древний страж повержен. Тайны галактики теперь открыты для вас.'
}, },
'5_4': { prologue_1: 'Мир наконец наступил. В эту новую эру основывайте новые колонии и расширяйте свою империю.' }, '5_4': { prologue_1: 'Мир наконец наступил. В эту новую эру основывайте новые колонии и расширяйте свою империю.' },
'5_5': { '5_5': {
prologue_1: 'Ваша легенда только началась. Продолжайте исследовать и завоёвывать больше звёздных систем!', prologue_1: 'Ваша легенда только началась. Продолжайте исследовать и завоёвывать больше звёздных систем!',
epilogue_1: 'Галактика необъятна и бесконечна, бесчисленные тайны ждут вас...' epilogue_1: 'Галактика необъятна и бесконечна, бесчисленные тайны ждут вас...',
epilogue_2: 'Ваше путешествие продолжается... Новые приключения ждут за звёздами...'
} }
} }
} }

View File

@@ -32,7 +32,8 @@ export default {
exitConfirmTitle: '退出游戏', exitConfirmTitle: '退出游戏',
exitConfirmMessage: '确定要退出游戏吗?游戏进度会自动保存。', exitConfirmMessage: '确定要退出游戏吗?游戏进度会自动保存。',
points: '积分', points: '积分',
retry: '重试' retry: '重试',
playerName: '玩家名称'
}, },
errors: { errors: {
requirementsNotMet: '不满足前置条件', requirementsNotMet: '不满足前置条件',
@@ -47,6 +48,8 @@ export default {
launchFailed: '发射失败', launchFailed: '发射失败',
fleetMissionsFull: '舰队任务槽位已满', fleetMissionsFull: '舰队任务槽位已满',
insufficientFleet: '舰队数量不足', insufficientFleet: '舰队数量不足',
insufficientShips: '舰船数量不足',
invalidQuantity: '无效数量',
insufficientFuel: '燃料不足', insufficientFuel: '燃料不足',
planetOnly: '该建筑只能在行星上建造', planetOnly: '该建筑只能在行星上建造',
moonOnly: '该建筑只能在月球上建造', moonOnly: '该建筑只能在月球上建造',
@@ -55,7 +58,12 @@ export default {
buildingLevelZero: '建筑等级为0无法拆除', buildingLevelZero: '建筑等级为0无法拆除',
researchQueueFull: '研究队列已满', researchQueueFull: '研究队列已满',
moonExists: '已存在月球', moonExists: '已存在月球',
insufficientDebris: '残骸场不足' insufficientDebris: '残骸场不足',
planetNotFound: '找不到星球',
cannotAbandonHomePlanet: '不能放弃母星',
hasBuildQueue: '请等待建造队列完成',
hasFleetOnPlanet: '请先转移或拆除所有舰船',
hasDefenseOnPlanet: '请先拆除所有防御设施'
}, },
nav: { nav: {
overview: '总览', overview: '总览',
@@ -123,7 +131,12 @@ export default {
renamePlanet: '重命名星球', renamePlanet: '重命名星球',
renamePlanetTitle: '重命名星球', renamePlanetTitle: '重命名星球',
planetNamePlaceholder: '输入新的星球名称', planetNamePlaceholder: '输入新的星球名称',
rename: '重命名' rename: '重命名',
abandonColony: '放弃殖民地',
confirmAbandon: '确认放弃殖民地',
abandonWarning: '确定要放弃 "{name}" 吗?\n\n此操作无法撤销\n所有建筑、资源和月球如有都将丢失。',
confirmAbandonButton: '放弃',
abandonFailed: '放弃失败'
}, },
player: { player: {
points: '总积分' points: '总积分'
@@ -152,6 +165,7 @@ export default {
planetDestroyerFactory: '行星毁灭者工厂', planetDestroyerFactory: '行星毁灭者工厂',
geoResearchStation: '地质研究站', geoResearchStation: '地质研究站',
deepDrillingFacility: '深层钻探设施', deepDrillingFacility: '深层钻探设施',
university: '大学',
buildTime: '建造时间', buildTime: '建造时间',
production: '产量', production: '产量',
consumption: '消耗', consumption: '消耗',
@@ -199,8 +213,9 @@ export default {
sensorPhalanx: '侦测周围星系的舰队活动', sensorPhalanx: '侦测周围星系的舰队活动',
jumpGate: '瞬间传送舰队到其他月球', jumpGate: '瞬间传送舰队到其他月球',
planetDestroyerFactory: '建造能够摧毁行星的终极武器', planetDestroyerFactory: '建造能够摧毁行星的终极武器',
geoResearchStation: '研究地质结构,提高矿脉自然恢复速度。每级增加50%恢复速率', geoResearchStation: '研究地质结构,提高矿脉自然恢复速度。每级增加10%恢复速率',
deepDrillingFacility: '深入地壳钻探解锁更深层的矿脉储量。每级增加20%矿脉上限' deepDrillingFacility: '深入地壳钻探解锁更深层的矿脉储量。每级增加20%矿脉上限',
university: '培养科研人员加快研究速度。每级减少研究时间8%(与星际研究网络叠加)'
}, },
ships: { ships: {
lightFighter: '轻型战斗机', lightFighter: '轻型战斗机',
@@ -264,6 +279,9 @@ export default {
}, },
research: { research: {
researchTime: '研究时间', researchTime: '研究时间',
levelRange: '等级范围',
totalCost: '总成本',
totalPoints: '总积分',
attackBonus: '攻击加成', attackBonus: '攻击加成',
shieldBonus: '护盾加成', shieldBonus: '护盾加成',
@@ -272,7 +290,11 @@ export default {
researchQueueBonus: '研究队列', researchQueueBonus: '研究队列',
colonySlots: '殖民地槽位', colonySlots: '殖民地槽位',
forAllPlanets: '(全局)', forAllPlanets: '(全局)',
speedBonus: '速度加成' speedBonus: '速度加成',
// 资源产量加成显示
mineralResearch: '矿物研究',
crystalResearch: '晶体研究',
fuelResearch: '燃料研究'
}, },
technologies: { technologies: {
energyTechnology: '能源技术', energyTechnology: '能源技术',
@@ -293,7 +315,11 @@ export default {
darkMatterTechnology: '暗物质技术', darkMatterTechnology: '暗物质技术',
terraformingTechnology: '地形改造技术', terraformingTechnology: '地形改造技术',
planetDestructionTech: '行星毁灭技术', planetDestructionTech: '行星毁灭技术',
miningTechnology: '采矿技术' miningTechnology: '采矿技术',
intergalacticResearchNetwork: '星际研究网络',
mineralResearch: '矿物研究',
crystalResearch: '晶体研究',
fuelResearch: '燃料研究'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: '提高能源利用效率', energyTechnology: '提高能源利用效率',
@@ -315,7 +341,11 @@ export default {
darkMatterTechnology: '研究暗物质的性质和应用', darkMatterTechnology: '研究暗物质的性质和应用',
terraformingTechnology: '研究行星地形改造技术每级为所有行星增加30个可用空间', terraformingTechnology: '研究行星地形改造技术每级为所有行星增加30个可用空间',
planetDestructionTech: '研究如何摧毁整个行星的恐怖技术', planetDestructionTech: '研究如何摧毁整个行星的恐怖技术',
miningTechnology: '改进采矿方法和设备提升所有星球矿脉储量上限。每级增加15%矿脉上限' miningTechnology: '改进采矿方法和设备提升所有星球矿脉储量上限。每级增加15%矿脉上限',
intergalacticResearchNetwork: '连接多个星球的研究实验室共享研究资源。每级可连接1个额外的研究实验室取等级最高的N个实验室',
mineralResearch: '研究更高效的金属提取技术提升金属矿产量。每级增加金属产量2%',
crystalResearch: '研究更高效的晶体提炼技术提升晶体矿产量。每级增加晶体产量2%',
fuelResearch: '研究更高效的重氢合成技术提升重氢产量。每级增加重氢产量2%'
}, },
officers: { officers: {
commander: '指挥官', commander: '指挥官',
@@ -384,6 +414,7 @@ export default {
title: '建筑', title: '建筑',
spaceUsage: '占用空间', spaceUsage: '占用空间',
upgradeCost: '升级消耗', upgradeCost: '升级消耗',
build: '建造',
upgrade: '升级', upgrade: '升级',
maxLevelReached: '等级已满', maxLevelReached: '等级已满',
requirementsNotMet: '条件不足', requirementsNotMet: '条件不足',
@@ -404,9 +435,19 @@ export default {
researchFailedMessage: '请检查资源是否足够、前置条件是否满足,或是否有其他研究任务。' researchFailedMessage: '请检查资源是否足够、前置条件是否满足,或是否有其他研究任务。'
}, },
shipyard: { shipyard: {
attack: '攻击力',
shield: '护盾',
armor: '装甲',
speed: '速度', speed: '速度',
cargoCapacity: '载货量', cargoCapacity: '载货量',
fuelConsumption: '燃料消耗' fuelConsumption: '燃料消耗',
buildCost: '建造成本',
buildTime: '建造时间',
perUnit: '单位',
batchCalculator: '批量计算器',
quantity: '数量',
totalCost: '总成本',
totalTime: '总时间'
}, },
shipyardView: { shipyardView: {
title: '船坞', title: '船坞',
@@ -422,7 +463,12 @@ export default {
inputError: '输入错误', inputError: '输入错误',
inputErrorMessage: '请输入建造数量!', inputErrorMessage: '请输入建造数量!',
buildFailed: '建造失败', buildFailed: '建造失败',
buildFailedMessage: '请检查资源是否足够或前置条件是否满足。' buildFailedMessage: '请检查资源是否足够或前置条件是否满足。',
scrapQuantity: '拆除数量',
scrapRefund: '拆除返还 (50%)',
scrap: '拆除',
scrapFailed: '拆除失败',
scrapFailedMessage: '请检查舰船数量是否足够。'
}, },
defense: { defense: {
attack: '攻击力', attack: '攻击力',
@@ -511,6 +557,8 @@ export default {
}, },
recycle: '回收', recycle: '回收',
destroy: '行星毁灭', destroy: '行星毁灭',
harvestDarkMatter: '暗物质采集',
station: '驻守协防',
transportResources: '运输资源', transportResources: '运输资源',
totalCargoCapacity: '总载货量', totalCargoCapacity: '总载货量',
used: '已用', used: '已用',
@@ -842,14 +890,14 @@ export default {
missileAttackFailed: '导弹攻击失败,目标星球不存在', missileAttackFailed: '导弹攻击失败,目标星球不存在',
missileAttackIntercepted: '所有导弹被拦截', missileAttackIntercepted: '所有导弹被拦截',
hits: '枚命中', hits: '枚命中',
expeditionResources: '远征队发现了资源!', expeditionResources: '探险队发现了资源!',
expeditionDarkMatter: '远征队发现了暗物质!', expeditionDarkMatter: '探险队发现了暗物质!',
expeditionFleet: '远征队发现了废弃的舰船!', expeditionFleet: '探险队发现了废弃的舰船!',
expeditionPiratesAttack: '远征队遭遇海盗袭击,损失了部分舰船', expeditionPiratesAttack: '探险队遭遇海盗袭击,损失了部分舰船',
expeditionPiratesEscaped: '远征队遭遇海盗,但成功逃脱', expeditionPiratesEscaped: '探险队遭遇海盗,但成功逃脱',
expeditionAliensAttack: '远征队遭遇外星人袭击,损失了部分舰船', expeditionAliensAttack: '探险队遭遇外星人袭击,损失了部分舰船',
expeditionAliensEscaped: '远征队遭遇外星人,但成功逃脱', expeditionAliensEscaped: '探险队遭遇外星人,但成功逃脱',
expeditionNothing: '远征队什么也没有发现' expeditionNothing: '探险队什么也没有发现'
}, },
simulatorView: { simulatorView: {
title: '战斗模拟器', title: '战斗模拟器',
@@ -886,7 +934,10 @@ export default {
hideRoundDetails: '隐藏回合详情', hideRoundDetails: '隐藏回合详情',
round: '第 {round} 回合', round: '第 {round} 回合',
attackerRemainingPower: '攻击方剩余火力', attackerRemainingPower: '攻击方剩余火力',
defenderRemainingPower: '防守方剩余火力' defenderRemainingPower: '防守方剩余火力',
importFromSpyReport: '从侦查报告导入',
selectSpyReport: '选择侦查报告',
noSpyReports: '没有侦查报告'
}, },
settings: { settings: {
dataManagement: '数据管理', dataManagement: '数据管理',
@@ -897,6 +948,8 @@ export default {
exporting: '导出中...', exporting: '导出中...',
exportSuccess: '导出成功', exportSuccess: '导出成功',
exportSuccessWithPath: '导出成功,文件已保存到:{path}', exportSuccessWithPath: '导出成功,文件已保存到:{path}',
exportCopiedToClipboard: '导出成功,数据已复制到剪贴板',
storagePermissionDenied: '存储权限被拒绝,无法导出文件',
exportFailed: '导出失败,请重试', exportFailed: '导出失败,请重试',
importData: '导入数据', importData: '导入数据',
importDataDesc: '从JSON文件恢复游戏进度', importDataDesc: '从JSON文件恢复游戏进度',
@@ -914,6 +967,8 @@ export default {
gameSettingsDesc: '调整游戏参数和偏好设置', gameSettingsDesc: '调整游戏参数和偏好设置',
gamePause: '游戏暂停', gamePause: '游戏暂停',
gamePauseDesc: '暂停或恢复游戏时间和资源生产', gamePauseDesc: '暂停或恢复游戏时间和资源生产',
battleMode: '战斗到底模式',
battleModeDesc: '启用后战斗最多进行100回合直到分出胜负关闭则使用经典6回合模式',
pause: '暂停', pause: '暂停',
resume: '恢复', resume: '恢复',
gamePaused: '游戏已暂停', gamePaused: '游戏已暂停',
@@ -1721,8 +1776,8 @@ export default {
colonizer: '成功殖民星球次数', colonizer: '成功殖民星球次数',
spy: '执行侦查任务次数', spy: '执行侦查任务次数',
deployer: '执行部署任务次数', deployer: '执行部署任务次数',
explorer: '远征总次数', explorer: '探险总次数',
luckyExplorer: '远征成功次数', luckyExplorer: '探险成功次数',
recycler: '回收任务次数', recycler: '回收任务次数',
scavenger: '回收资源总量', scavenger: '回收资源总量',
destroyer: '摧毁星球次数', destroyer: '摧毁星球次数',
@@ -1782,6 +1837,8 @@ export default {
branchUnlocked: '新的故事分支已解锁!' branchUnlocked: '新的故事分支已解锁!'
}, },
dialogue: { dialogue: {
title: '剧情对话',
description: '战役剧情对话内容',
skip: '跳过', skip: '跳过',
continue: '继续', continue: '继续',
finish: '完成', finish: '完成',
@@ -1848,7 +1905,7 @@ export default {
}, },
'2_2': { '2_2': {
title: '深空探险', title: '深空探险',
description: '派遣舰队进行远征探险' description: '派遣舰队进行探险探险'
}, },
'2_3': { '2_3': {
title: '神秘信号', title: '神秘信号',
@@ -1944,13 +2001,19 @@ export default {
questNotActive: '任务未激活', questNotActive: '任务未激活',
questNotCompleted: '任务未完成', questNotCompleted: '任务未完成',
rewardsAlreadyClaimed: '奖励已领取', rewardsAlreadyClaimed: '奖励已领取',
prerequisiteNotMet: '前置任务未完成' prerequisiteNotMet: '前置任务未完成',
questLocked: '任务已锁定',
notInitialized: '战役未初始化',
questAlreadyCompleted: '任务已完成'
}, },
speakers: { speakers: {
ancientVoice: '古代之声', ancientVoice: '古代之声',
neighborNPC: '邻近势力', neighborNPC: '邻近势力',
mysteriousSignal: '神秘信号', mysteriousSignal: '神秘信号',
enemyCommander: '敌方指挥官' enemyCommander: '敌方指挥官',
shadowVoice: '暗影之声',
allyNPC: '盟友势力',
ancientGuardian: '远古守护者'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: '建造金属矿到2级', buildMetalMine: '建造金属矿到2级',
@@ -1966,25 +2029,42 @@ export default {
spyAnyNPC: '侦查任意NPC星球', spyAnyNPC: '侦查任意NPC星球',
sendGiftToNPC: '向任意NPC送礼', sendGiftToNPC: '向任意NPC送礼',
researchAstrophysics: '研究天体物理学到1级', researchAstrophysics: '研究天体物理学到1级',
researchAstrophysicsHigher: '研究天体物理学到3级',
buildColonyShip: '建造殖民船', buildColonyShip: '建造殖民船',
colonizeNewPlanet: '殖民新星球', colonizeNewPlanet: '殖民新星球',
completeExpedition: '完成远征任务', colonizeMultiple: '殖民5个星球',
completeExpedition: '完成3次探险任务',
expeditionDeepSpace: '完成2次深空探险',
expeditionUncharted: '探索1次未知区域',
expeditionDangerous: '完成3次危险星云探险',
discoverRuins: '发现古代遗迹', discoverRuins: '发现古代遗迹',
researchComputer: '研究电脑技术到4级', researchComputer: '研究电脑技术到4级',
researchImpulse: '研究脉冲驱动到3级',
researchLaser: '研究激光技术到5级',
researchIntergalactic: '研究电脑技术到10级',
researchGraviton: '研究引力子技术到1级',
improveRelation: '提升与NPC的关系', improveRelation: '提升与NPC的关系',
reachFriendly: '与NPC达到友好关系', reachFriendly: '与NPC达到友好关系',
spyHostileNPC: '侦查敌对NPC', reachFriendlyRelation: '与任意NPC达到友好关系',
sendMultipleGifts: '向NPC发送3次礼物',
spyHostileNPC: '侦查2个敌对NPC',
formAlliance: '与友好NPC结盟', formAlliance: '与友好NPC结盟',
buildDefenses: '建造防御设施', buildDefenses: '建造防御设施',
buildMissileSilo: '建造导弹发射井到2级',
buildCruisers: '建造10艘巡洋舰',
winDefenseBattle: '赢得防御战斗', winDefenseBattle: '赢得防御战斗',
defendAgainstAttack: '成功防御1次攻击',
spyEnemyPlanet: '侦查敌方星球', spyEnemyPlanet: '侦查敌方星球',
spyEnemyPlanets: '侦查5个敌方星球',
winAttackBattles: '赢得3次进攻战斗',
attackEnemy: '攻击敌方', attackEnemy: '攻击敌方',
recycleDebris: '回收残骸', recycleDebris: '回收5次残骸',
buildBattleships: '建造10艘战列舰', buildBattleships: '建造20艘战列舰',
exploreDeepRuins: '探索遗迹深处', exploreDeepRuins: '探索遗迹深处',
researchHyperspace: '研究超空间技术', researchHyperspace: '研究超空间驱动到3级',
defeatBoss: '击败神秘敌人', defeatBoss: '击败古代守护者',
colonizeSpecial: '殖民特殊位置', colonizeSpecial: '殖民特殊位置',
accumulateWealth: '积累总资源达到100万',
continueDevelopment: '继续发展' continueDevelopment: '继续发展'
}, },
dialogues: { dialogues: {
@@ -2012,18 +2092,23 @@ export default {
prologue_2: '宇宙是无限的...更多的星球意味着更多的可能性...' prologue_2: '宇宙是无限的...更多的星球意味着更多的可能性...'
}, },
'2_2': { '2_2': {
prologue_1: '殖民成功!但宇宙深处还有更多秘密等待发现。派遣舰队进行远征探险吧。', prologue_1: '殖民成功!但宇宙深处还有更多秘密等待发现。派遣舰队进行探险探险吧。',
prologue_2: '远方传来微弱的信号...那里有什么在等待着你...' prologue_2: '远方传来微弱的信号...那里有什么在等待着你...'
}, },
'2_3': { '2_3': {
prologue_1: '你的探险队发现了异常信号。这些信号似乎来自一个古老的文明...调查它们的来源。', prologue_1: '你的探险队发现了异常信号。这些信号似乎来自一个古老的文明...调查它们的来源。',
prologue_2: '这些信号...承载着一个早已消逝的文明的回响。它们的秘密等待被发现...',
epilogue_1: '这些符号...是古代文明的遗迹!继续调查,揭开它们的秘密。' epilogue_1: '这些符号...是古代文明的遗迹!继续调查,揭开它们的秘密。'
}, },
'2_4': { '2_4': {
prologue_1: '你发现了古代遗迹的位置。派遣舰队前去探索,看看能发现什么。' prologue_1: '你发现了古代遗迹的位置。派遣舰队前去探索,看看能发现什么。',
prologue_2: '遗迹中隐藏着许多秘密...明智地选择你的道路...',
choice_1: '谨慎探索 - 优先安全',
choice_2: '激进探索 - 优先发现'
}, },
'2_5': { '2_5': {
prologue_1: '遗迹中发现了大量数据档案。研究这些数据,也许能解锁新的科技。' prologue_1: '遗迹中发现了大量数据档案。研究这些数据,也许能解锁新的科技。',
epilogue_1: '古代数据已被解密!获得了新的科技洞见。'
}, },
'3_1': { '3_1': {
prologue_1: '在探索的同时,也不要忘记外交。与周围的势力保持良好关系对你有益。' prologue_1: '在探索的同时,也不要忘记外交。与周围的势力保持良好关系对你有益。'
@@ -2032,16 +2117,19 @@ export default {
prologue_1: '有些势力对你表示了友好。继续加深关系,也许能获得更多支持。' prologue_1: '有些势力对你表示了友好。继续加深关系,也许能获得更多支持。'
}, },
'3_3': { '3_3': {
prologue_1: '情报显示,有敌对势力正在暗中观察你。保持警惕,侦查他们的动向。' prologue_1: '情报显示,有敌对势力正在暗中观察你。保持警惕,侦查他们的动向。',
prologue_2: '黑暗势力在虚空中蠢蠢欲动...他们已经注意到你日益增长的力量...'
}, },
'3_4': { '3_4': {
prologue_1: '与友好势力建立正式同盟,在面对威胁时互相支持。' prologue_1: '与友好势力建立正式同盟,在面对威胁时互相支持。'
}, },
'3_5': { '3_5': {
prologue_1: '威胁正在逼近。建设防御设施,准备迎接可能的冲突。' prologue_1: '威胁正在逼近。建设防御设施,准备迎接可能的冲突。',
epilogue_1: '你的防线已经准备就绪。风暴即将来临,但你已经做好了准备。'
}, },
'4_1': { '4_1': {
prologue_1: '敌人发动了进攻!保卫你的星球!', prologue_1: '敌人发动了进攻!保卫你的星球!',
prologue_2: '暗影舰队正在逼近...你的考验时刻已经到来...',
epilogue_1: '你成功击退了敌人的第一波进攻。但这只是开始...' epilogue_1: '你成功击退了敌人的第一波进攻。但这只是开始...'
}, },
'4_2': { '4_2': {
@@ -2054,17 +2142,20 @@ export default {
prologue_1: '战场上留下了大量残骸。回收这些资源,为下一场战斗做准备。' prologue_1: '战场上留下了大量残骸。回收这些资源,为下一场战斗做准备。'
}, },
'4_5': { '4_5': {
prologue_1: '最终决战即将来临。建造强大的舰队,准备迎接最后的挑战。' prologue_1: '最终决战即将来临。建造强大的舰队,准备迎接最后的挑战。',
epilogue_1: '你的舰队已经集结完毕。决定性的时刻即将到来...'
}, },
'5_1': { '5_1': {
prologue_1: '所有线索都指向遗迹的最深处。那里隐藏着古代文明最核心的秘密。', prologue_1: '所有线索都指向遗迹的最深处。那里隐藏着古代文明最核心的秘密。',
prologue_2: '你终于来到了这里...真相即将揭晓...' prologue_2: '你终于来到了这里...真相即将揭晓...'
}, },
'5_2': { '5_2': {
prologue_1: '在遗迹深处,你发现了失落的古代科技。研究并解锁它们的力量。' prologue_1: '在遗迹深处,你发现了失落的古代科技。研究并解锁它们的力量。',
prologue_2: '这项科技...比所有已知文明都要古老。小心处理...'
}, },
'5_3': { '5_3': {
prologue_1: '一个神秘的敌人出现了。这是最后的挑战,击败它!', prologue_1: '一个神秘的敌人出现了。这是最后的挑战,击败它!',
prologue_2: '我是这些秘密的守护者。证明你的价值,否则将被毁灭!',
epilogue_1: '你做到了!古代的守护者已被击败。银河系的秘密向你敞开。' epilogue_1: '你做到了!古代的守护者已被击败。银河系的秘密向你敞开。'
}, },
'5_4': { '5_4': {
@@ -2072,8 +2163,35 @@ export default {
}, },
'5_5': { '5_5': {
prologue_1: '你的传奇才刚刚开始。继续探索,征服更多的星系!', prologue_1: '你的传奇才刚刚开始。继续探索,征服更多的星系!',
epilogue_1: '银河系广阔无垠,还有无数秘密等待你去发现...' epilogue_1: '银河系广阔无垠,还有无数秘密等待你去发现...',
epilogue_2: '你的旅程仍在继续...新的冒险在群星之外等待着你...'
} }
} }
},
webdav: {
// 连接相关
connectionSuccess: 'WebDAV 连接成功',
connectionSuccessDirectoryCreated: 'WebDAV 连接成功,已创建存档目录',
authFailed: '认证失败,请检查用户名和密码',
directoryNotExist: '目录不存在且无法创建',
connectionFailedHttp: '连接失败: HTTP {status}',
networkError: '网络错误,可能是 CORS 限制。建议使用支持 CORS 的 WebDAV 服务或通过代理访问。',
connectionError: '连接错误: {error}',
// 上传相关
uploadSuccess: '上传成功',
noWritePermission: '没有写入权限',
insufficientStorage: '存储空间不足',
uploadFailedHttp: '上传失败: HTTP {status}',
uploadError: '上传错误: {error}',
// 下载相关
fileNotExist: '文件不存在',
downloadFailedHttp: '下载失败: HTTP {status}',
downloadError: '下载错误: {error}',
// 列表相关
listFailedHttp: '获取文件列表失败: HTTP {status}',
listError: '获取文件列表错误: {error}',
// 删除相关
deleteFailedHttp: '删除失败: HTTP {status}',
deleteError: '删除错误: {error}'
} }
} }

View File

@@ -155,6 +155,7 @@ export default {
planetDestroyerFactory: '行星毀滅者工廠', planetDestroyerFactory: '行星毀滅者工廠',
geoResearchStation: '地質研究站', geoResearchStation: '地質研究站',
deepDrillingFacility: '深層鑽探設施', deepDrillingFacility: '深層鑽探設施',
university: '大學',
buildTime: '建造時間', buildTime: '建造時間',
build: '建造', build: '建造',
production: '產量', production: '產量',
@@ -206,7 +207,8 @@ export default {
jumpGate: '瞬間傳送艦隊到其他月球', jumpGate: '瞬間傳送艦隊到其他月球',
planetDestroyerFactory: '建造能夠摧毀行星的終極武器', planetDestroyerFactory: '建造能夠摧毀行星的終極武器',
geoResearchStation: '研究地質結構提高礦脈自然恢復速度。每級增加50%恢復速率', geoResearchStation: '研究地質結構提高礦脈自然恢復速度。每級增加50%恢復速率',
deepDrillingFacility: '' deepDrillingFacility: '',
university: '培養科研人員加快研究速度。每級減少研究時間8%'
}, },
ships: { ships: {
lightFighter: '輕型戰鬥機', lightFighter: '輕型戰鬥機',
@@ -270,6 +272,9 @@ export default {
}, },
research: { research: {
researchTime: '研究時間', researchTime: '研究時間',
levelRange: '等級範圍',
totalCost: '總成本',
totalPoints: '總積分',
capacity: '容量/效果', capacity: '容量/效果',
attackBonus: '攻擊加成', attackBonus: '攻擊加成',
shieldBonus: '護盾加成', shieldBonus: '護盾加成',
@@ -278,7 +283,11 @@ export default {
researchQueueBonus: '研究隊列', researchQueueBonus: '研究隊列',
colonySlots: '殖民地槽位', colonySlots: '殖民地槽位',
forAllPlanets: '(全局)', forAllPlanets: '(全局)',
speedBonus: '速度加成' speedBonus: '速度加成',
// 資源產量加成顯示
mineralResearch: '礦物研究',
crystalResearch: '晶體研究',
fuelResearch: '燃料研究'
}, },
technologies: { technologies: {
energyTechnology: '能源技術', energyTechnology: '能源技術',
@@ -299,7 +308,11 @@ export default {
darkMatterTechnology: '暗物質技術', darkMatterTechnology: '暗物質技術',
terraformingTechnology: '地形改造技術', terraformingTechnology: '地形改造技術',
planetDestructionTech: '行星毀滅技術', planetDestructionTech: '行星毀滅技術',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: '星際研究網絡',
mineralResearch: '礦物研究',
crystalResearch: '晶體研究',
fuelResearch: '燃料研究'
}, },
technologyDescriptions: { technologyDescriptions: {
energyTechnology: '提高能源利用效率', energyTechnology: '提高能源利用效率',
@@ -321,7 +334,11 @@ export default {
darkMatterTechnology: '研究暗物質的性質和應用', darkMatterTechnology: '研究暗物質的性質和應用',
terraformingTechnology: '研究行星地形改造技術每級為所有行星增加30個可用空間', terraformingTechnology: '研究行星地形改造技術每級為所有行星增加30個可用空間',
planetDestructionTech: '研究如何摧毀整個行星的恐怖技術', planetDestructionTech: '研究如何摧毀整個行星的恐怖技術',
miningTechnology: '' miningTechnology: '',
intergalacticResearchNetwork: '連接多個星球的研究實驗室共享研究資源。每級可連接1個額外的研究實驗室',
mineralResearch: '研究更高效的金屬提取技術提升金屬礦產量。每級增加金屬產量2%',
crystalResearch: '研究更高效的晶體提煉技術提升晶體礦產量。每級增加晶體產量2%',
fuelResearch: '研究更高效的重氫合成技術提升重氫產量。每級增加重氫產量2%'
}, },
officers: { officers: {
commander: '指揮官', commander: '指揮官',
@@ -414,11 +431,20 @@ export default {
researchFailedMessage: '請檢查資源是否足夠、前置條件是否滿足,或是否有其他研究任務。' researchFailedMessage: '請檢查資源是否足夠、前置條件是否滿足,或是否有其他研究任務。'
}, },
shipyard: { shipyard: {
missileAttack: '導彈攻擊', attack: '攻擊',
shield: '護盾',
armor: '裝甲', armor: '裝甲',
missileAttack: '導彈攻擊',
speed: '速度', speed: '速度',
cargoCapacity: '載貨量', cargoCapacity: '載貨量',
fuelConsumption: '燃料消耗', fuelConsumption: '燃料消耗',
buildCost: '建造成本',
buildTime: '建造時間',
perUnit: '單位',
batchCalculator: '批量計算器',
quantity: '數量',
totalCost: '總成本',
totalTime: '總時間',
build: '建造' build: '建造'
}, },
shipyardView: { shipyardView: {
@@ -530,6 +556,8 @@ export default {
}, },
recycle: '回收', recycle: '回收',
destroy: '行星毀滅', destroy: '行星毀滅',
harvestDarkMatter: '暗物質採集',
station: '駐守協防',
transportResources: '運輸資源', transportResources: '運輸資源',
totalCargoCapacity: '總載貨量', totalCargoCapacity: '總載貨量',
used: '已用', used: '已用',
@@ -910,7 +938,10 @@ export default {
hideRoundDetails: '隱藏回合詳情', hideRoundDetails: '隱藏回合詳情',
round: '第 {round} 回合', round: '第 {round} 回合',
attackerRemainingPower: '攻擊方剩餘火力', attackerRemainingPower: '攻擊方剩餘火力',
defenderRemainingPower: '防守方剩餘火力' defenderRemainingPower: '防守方剩餘火力',
importFromSpyReport: '從偵查報告匯入',
selectSpyReport: '選擇偵查報告',
noSpyReports: '沒有偵查報告'
}, },
settings: { settings: {
dataManagement: '資料管理', dataManagement: '資料管理',
@@ -921,6 +952,8 @@ export default {
exporting: '匯出中...', exporting: '匯出中...',
exportSuccess: '匯出成功', exportSuccess: '匯出成功',
exportSuccessWithPath: '匯出成功,檔案已儲存到:{path}', exportSuccessWithPath: '匯出成功,檔案已儲存到:{path}',
exportCopiedToClipboard: '匯出成功,資料已複製到剪貼簿',
storagePermissionDenied: '儲存權限被拒絕,無法匯出檔案',
exportFailed: '匯出失敗,請重試', exportFailed: '匯出失敗,請重試',
importData: '匯入資料', importData: '匯入資料',
importDataDesc: '從JSON檔案恢復遊戲進度', importDataDesc: '從JSON檔案恢復遊戲進度',
@@ -938,6 +971,8 @@ export default {
gameSettingsDesc: '調整遊戲參數和偏好設定', gameSettingsDesc: '調整遊戲參數和偏好設定',
gamePause: '遊戲暫停', gamePause: '遊戲暫停',
gamePauseDesc: '暫停或恢復遊戲時間和資源生產', gamePauseDesc: '暫停或恢復遊戲時間和資源生產',
battleMode: '戰鬥到底模式',
battleModeDesc: '啟用後戰鬥最多進行100回合直到分出勝負關閉則使用經典6回合模式',
pause: '暫停', pause: '暫停',
resume: '恢復', resume: '恢復',
gamePaused: '遊戲已暫停', gamePaused: '遊戲已暫停',
@@ -1933,6 +1968,27 @@ export default {
reputationBonusDesc: '你的盟友{npcName}向{targetNpc}說了你的好話' reputationBonusDesc: '你的盟友{npcName}向{targetNpc}說了你的好話'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV 連線成功',
connectionSuccessDirectoryCreated: 'WebDAV 連線成功,已建立存檔目錄',
authFailed: '認證失敗,請檢查使用者名稱和密碼',
directoryNotExist: '目錄不存在且無法建立',
networkError: '網路錯誤,請檢查伺服器位址和網路連線',
unknownError: '未知錯誤',
uploadSuccess: '存檔上傳成功',
uploadFailed: '上傳失敗',
downloadSuccess: '存檔下載成功',
downloadFailed: '下載失敗',
noSaveFiles: '伺服器上沒有存檔',
fileListSuccess: '取得存檔列表成功',
fileListFailed: '取得存檔列表失敗',
deleteSuccess: '存檔刪除成功',
deleteFailed: '刪除失敗',
serverError: '伺服器錯誤',
notConfigured: 'WebDAV 未設定',
invalidUrl: '無效的 WebDAV URL',
timeout: '連線逾時'
},
campaign: { campaign: {
name: '戰役', name: '戰役',
description: '探索神秘的銀河系,揭開古代文明的秘密', description: '探索神秘的銀河系,揭開古代文明的秘密',
@@ -1960,6 +2016,8 @@ export default {
branchUnlocked: '新的故事分支已解鎖!' branchUnlocked: '新的故事分支已解鎖!'
}, },
dialogue: { dialogue: {
title: '劇情對話',
description: '戰役劇情對話內容',
skip: '跳過', skip: '跳過',
continue: '繼續', continue: '繼續',
finish: '完成', finish: '完成',
@@ -2122,13 +2180,19 @@ export default {
questNotActive: '任務未激活', questNotActive: '任務未激活',
questNotCompleted: '任務未完成', questNotCompleted: '任務未完成',
rewardsAlreadyClaimed: '獎勵已領取', rewardsAlreadyClaimed: '獎勵已領取',
prerequisiteNotMet: '前置任務未完成' prerequisiteNotMet: '前置任務未完成',
questLocked: '任務已鎖定',
notInitialized: '戰役未初始化',
questAlreadyCompleted: '任務已完成'
}, },
speakers: { speakers: {
ancientVoice: '古代之聲', ancientVoice: '古代之聲',
neighborNPC: '鄰近勢力', neighborNPC: '鄰近勢力',
mysteriousSignal: '神秘信號', mysteriousSignal: '神秘信號',
enemyCommander: '敵方指揮官' enemyCommander: '敵方指揮官',
shadowVoice: '暗影之聲',
allyNPC: '盟友勢力',
ancientGuardian: '遠古守護者'
}, },
objectiveDescriptions: { objectiveDescriptions: {
buildMetalMine: '建造金屬礦到2級', buildMetalMine: '建造金屬礦到2級',
@@ -2144,25 +2208,42 @@ export default {
spyAnyNPC: '偵查任意NPC星球', spyAnyNPC: '偵查任意NPC星球',
sendGiftToNPC: '向任意NPC送禮', sendGiftToNPC: '向任意NPC送禮',
researchAstrophysics: '研究天體物理學到1級', researchAstrophysics: '研究天體物理學到1級',
researchAstrophysicsHigher: '研究天體物理學到3級',
buildColonyShip: '建造殖民船', buildColonyShip: '建造殖民船',
colonizeNewPlanet: '殖民新星球', colonizeNewPlanet: '殖民新星球',
completeExpedition: '完成遠征任務', colonizeMultiple: '殖民5個星球',
completeExpedition: '完成3次遠征任務',
expeditionDeepSpace: '完成2次深空遠征',
expeditionUncharted: '探索1次未知區域',
expeditionDangerous: '完成3次危險星雲遠征',
discoverRuins: '發現古代遺跡', discoverRuins: '發現古代遺跡',
researchComputer: '研究電腦技術到4級', researchComputer: '研究電腦技術到4級',
researchImpulse: '研究脈衝驅動到3級',
researchLaser: '研究雷射技術到5級',
researchIntergalactic: '研究電腦技術到10級',
researchGraviton: '研究引力子技術到1級',
improveRelation: '提升與NPC的關係', improveRelation: '提升與NPC的關係',
reachFriendly: '與NPC達到友好關係', reachFriendly: '與NPC達到友好關係',
spyHostileNPC: '偵查敵對NPC', reachFriendlyRelation: '與任意NPC達到友好關係',
sendMultipleGifts: '向NPC發送3次禮物',
spyHostileNPC: '偵查2個敵對NPC',
formAlliance: '與友好NPC結盟', formAlliance: '與友好NPC結盟',
buildDefenses: '建造防禦設施', buildDefenses: '建造防禦設施',
buildMissileSilo: '建造飛彈發射井到2級',
buildCruisers: '建造10艘巡洋艦',
winDefenseBattle: '贏得防禦戰鬥', winDefenseBattle: '贏得防禦戰鬥',
defendAgainstAttack: '成功防禦1次攻擊',
spyEnemyPlanet: '偵查敵方星球', spyEnemyPlanet: '偵查敵方星球',
spyEnemyPlanets: '偵查5個敵方星球',
winAttackBattles: '贏得3次進攻戰鬥',
attackEnemy: '攻擊敵方', attackEnemy: '攻擊敵方',
recycleDebris: '回收殘骸', recycleDebris: '回收5次殘骸',
buildBattleships: '建造10艘戰列艦', buildBattleships: '建造20艘戰列艦',
exploreDeepRuins: '探索遺跡深處', exploreDeepRuins: '探索遺跡深處',
researchHyperspace: '研究超空間技術', researchHyperspace: '研究超空間驅動到3級',
defeatBoss: '擊敗神秘敵人', defeatBoss: '擊敗古代守護者',
colonizeSpecial: '殖民特殊位置', colonizeSpecial: '殖民特殊位置',
accumulateWealth: '積累總資源達到100萬',
continueDevelopment: '繼續發展' continueDevelopment: '繼續發展'
}, },
dialogues: { dialogues: {
@@ -2195,13 +2276,18 @@ export default {
}, },
'2_3': { '2_3': {
prologue_1: '你的探險隊發現了異常信號。這些信號似乎來自一個古老的文明...調查它們的來源。', prologue_1: '你的探險隊發現了異常信號。這些信號似乎來自一個古老的文明...調查它們的來源。',
prologue_2: '這些信號...承載著失落文明的回響。它們的秘密等待被發現...',
epilogue_1: '這些符號...是古代文明的遺跡!繼續調查,揭開它們的秘密。' epilogue_1: '這些符號...是古代文明的遺跡!繼續調查,揭開它們的秘密。'
}, },
'2_4': { '2_4': {
prologue_1: '你發現了古代遺跡的位置。派遣艦隊前去探索,看看能發現什麼。' prologue_1: '你發現了古代遺跡的位置。派遣艦隊前去探索,看看能發現什麼。',
prologue_2: '遺跡中藏有許多秘密...明智地選擇你的道路...',
choice_1: '謹慎探索 - 優先安全',
choice_2: '積極探索 - 優先發現'
}, },
'2_5': { '2_5': {
prologue_1: '遺跡中發現了大量數據檔案。研究這些數據,也許能解鎖新的科技。' prologue_1: '遺跡中發現了大量數據檔案。研究這些數據,也許能解鎖新的科技。',
epilogue_1: '古代數據已被解密!獲得了新的技術見解。'
}, },
'3_1': { '3_1': {
prologue_1: '在探索的同時,也不要忘記外交。與周圍的勢力保持良好關係對你有益。' prologue_1: '在探索的同時,也不要忘記外交。與周圍的勢力保持良好關係對你有益。'
@@ -2210,16 +2296,19 @@ export default {
prologue_1: '有些勢力對你表示了友好。繼續加深關係,也許能獲得更多支持。' prologue_1: '有些勢力對你表示了友好。繼續加深關係,也許能獲得更多支持。'
}, },
'3_3': { '3_3': {
prologue_1: '情報顯示,有敵對勢力正在暗中觀察你。保持警惕,偵查他們的動向。' prologue_1: '情報顯示,有敵對勢力正在暗中觀察你。保持警惕,偵查他們的動向。',
prologue_2: '黑暗勢力在虛空中蠢蠢欲動...他們已經注意到你日益增長的力量...'
}, },
'3_4': { '3_4': {
prologue_1: '與友好勢力建立正式同盟,在面對威脅時互相支持。' prologue_1: '與友好勢力建立正式同盟,在面對威脅時互相支持。'
}, },
'3_5': { '3_5': {
prologue_1: '威脅正在逼近。建設防禦設施,準備迎接可能的衝突。' prologue_1: '威脅正在逼近。建設防禦設施,準備迎接可能的衝突。',
epilogue_1: '你的防禦已經就緒。風暴即將來臨,但你已準備好了。'
}, },
'4_1': { '4_1': {
prologue_1: '敵人發動了進攻!保衛你的星球!', prologue_1: '敵人發動了進攻!保衛你的星球!',
prologue_2: '暗影艦隊正在逼近...你的考驗時刻已經到來...',
epilogue_1: '你成功擊退了敵人的第一波進攻。但這只是開始...' epilogue_1: '你成功擊退了敵人的第一波進攻。但這只是開始...'
}, },
'4_2': { '4_2': {
@@ -2232,17 +2321,20 @@ export default {
prologue_1: '戰場上留下了大量殘骸。回收這些資源,為下一場戰鬥做準備。' prologue_1: '戰場上留下了大量殘骸。回收這些資源,為下一場戰鬥做準備。'
}, },
'4_5': { '4_5': {
prologue_1: '最終決戰即將來臨。建造強大的艦隊,準備迎接最後的挑戰。' prologue_1: '最終決戰即將來臨。建造強大的艦隊,準備迎接最後的挑戰。',
epilogue_1: '你的艦隊已經集結完畢。決戰時刻即將來臨...'
}, },
'5_1': { '5_1': {
prologue_1: '所有線索都指向遺跡的最深處。那裡隱藏著古代文明最核心的秘密。', prologue_1: '所有線索都指向遺跡的最深處。那裡隱藏著古代文明最核心的秘密。',
prologue_2: '你終於來到了這裡...真相即將揭曉...' prologue_2: '你終於來到了這裡...真相即將揭曉...'
}, },
'5_2': { '5_2': {
prologue_1: '在遺跡深處,你發現了失落的古代科技。研究並解鎖它們的力量。' prologue_1: '在遺跡深處,你發現了失落的古代科技。研究並解鎖它們的力量。',
prologue_2: '這項科技...比所有已知文明都要古老。小心處理它...'
}, },
'5_3': { '5_3': {
prologue_1: '一個神秘的敵人出現了。這是最後的挑戰,擊敗它!', prologue_1: '一個神秘的敵人出現了。這是最後的挑戰,擊敗它!',
prologue_2: '我是這些秘密的守護者。證明你的價值,否則將被毀滅!',
epilogue_1: '你做到了!古代的守護者已被擊敗。銀河系的秘密向你敞開。' epilogue_1: '你做到了!古代的守護者已被擊敗。銀河系的秘密向你敞開。'
}, },
'5_4': { '5_4': {
@@ -2250,7 +2342,8 @@ export default {
}, },
'5_5': { '5_5': {
prologue_1: '你的傳奇才剛剛開始。繼續探索,征服更多的星系!', prologue_1: '你的傳奇才剛剛開始。繼續探索,征服更多的星系!',
epilogue_1: '銀河系廣闘無垠,還有無數秘密等待你去發現...' epilogue_1: '銀河系廣闘無垠,還有無數秘密等待你去發現...',
epilogue_2: '你的旅程還在繼續...新的冒險在星海彼岸等待著你...'
} }
} }
} }

View File

@@ -15,25 +15,37 @@ import { ACHIEVEMENTS, ACHIEVEMENT_MAP, getNextTier, getTierIndex } from '@/conf
// 初始化空的成就统计数据 // 初始化空的成就统计数据
export const initializeAchievementStats = (): AchievementStats => { export const initializeAchievementStats = (): AchievementStats => {
const emptyShipRecord = Object.values(ShipType).reduce((acc, type) => { const emptyShipRecord = Object.values(ShipType).reduce(
acc[type] = 0 (acc, type) => {
return acc acc[type] = 0
}, {} as Record<ShipType, number>) return acc
},
{} as Record<ShipType, number>
)
const emptyDefenseRecord = Object.values(DefenseType).reduce((acc, type) => { const emptyDefenseRecord = Object.values(DefenseType).reduce(
acc[type] = 0 (acc, type) => {
return acc acc[type] = 0
}, {} as Record<DefenseType, number>) return acc
},
{} as Record<DefenseType, number>
)
const emptyBuildingRecord = Object.values(BuildingType).reduce((acc, type) => { const emptyBuildingRecord = Object.values(BuildingType).reduce(
acc[type] = 0 (acc, type) => {
return acc acc[type] = 0
}, {} as Record<BuildingType, number>) return acc
},
{} as Record<BuildingType, number>
)
const emptyTechRecord = Object.values(TechnologyType).reduce((acc, type) => { const emptyTechRecord = Object.values(TechnologyType).reduce(
acc[type] = 0 (acc, type) => {
return acc acc[type] = 0
}, {} as Record<TechnologyType, number>) return acc
},
{} as Record<TechnologyType, number>
)
return { return {
// 资源统计 // 资源统计
@@ -221,7 +233,8 @@ export const applyAchievementReward = (player: Player, reward: AchievementReward
} }
if (reward.points) { if (reward.points) {
player.points += reward.points // 奖励积分存入 bonusPoints不会被 calculatePlayerPoints 覆盖
player.bonusPoints = (player.bonusPoints || 0) + reward.points
} }
} }

View File

@@ -15,7 +15,8 @@ export const simulateBattle = async (
_attackerOfficers: Record<OfficerType, Officer>, _attackerOfficers: Record<OfficerType, Officer>,
_defenderOfficers: Record<OfficerType, Officer>, _defenderOfficers: Record<OfficerType, Officer>,
attackerTechnologies: Record<TechnologyType, number>, attackerTechnologies: Record<TechnologyType, number>,
defenderTechnologies: Record<TechnologyType, number> defenderTechnologies: Record<TechnologyType, number>,
battleToFinish: boolean = false // 战斗到底模式
): Promise<BattleResult> => { ): Promise<BattleResult> => {
// 从科技系统读取实际科技等级 // 从科技系统读取实际科技等级
const attackerWeaponTech = attackerTechnologies['weaponsTechnology'] || 0 const attackerWeaponTech = attackerTechnologies['weaponsTechnology'] || 0
@@ -41,7 +42,7 @@ export const simulateBattle = async (
shieldTech: defenderShieldTech, shieldTech: defenderShieldTech,
armorTech: defenderArmorTech armorTech: defenderArmorTech
}, },
maxRounds: 6 // 最多6回合 maxRounds: battleToFinish ? 100 : 6 // 战斗到底模式最多100回合经典模式6回合
}) })
// 计算掠夺(仅攻击方胜利时) // 计算掠夺(仅攻击方胜利时)

View File

@@ -64,29 +64,80 @@ export const calculateUsedSpace = (planet: Planet): number => {
} }
/** /**
* 检查建筑升级条件 * 检查建筑升级条件(包括基础要求和等级门槛要求)
*/ */
export const checkBuildingRequirements = ( export const checkBuildingRequirements = (
buildingType: BuildingType, buildingType: BuildingType,
planet: Planet, planet: Planet,
technologies: Partial<Record<TechnologyType, number>> technologies: Partial<Record<TechnologyType, number>>,
targetLevel?: number
): boolean => { ): boolean => {
const config = BUILDINGS[buildingType] const config = BUILDINGS[buildingType]
const requirements = (config as any).requirements const currentLevel = planet.buildings[buildingType] || 0
if (!requirements) return true const level = targetLevel ?? currentLevel + 1
for (const [key, level] of Object.entries(requirements)) { // 检查基础 requirements
const requiredLevel = level as number const requirements = (config as any).requirements
if (Object.values(BuildingType).includes(key as BuildingType)) { if (requirements) {
if ((planet.buildings[key as BuildingType] || 0) < requiredLevel) { for (const [key, reqLevel] of Object.entries(requirements)) {
return false const requiredLevel = reqLevel as number
} if (Object.values(BuildingType).includes(key as BuildingType)) {
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) { const requiredBuildingType = key as BuildingType
if ((technologies[key as TechnologyType] || 0) < requiredLevel) { const requiredBuildingConfig = BUILDINGS[requiredBuildingType]
return false
// 如果当前是月球且所需建筑是星球专属建筑planetOnly则跳过此前置条件
// 这允许在月球上建造机器人工厂等建筑,即使它们的前置条件是无法在月球建造的矿场
if (planet.isMoon && requiredBuildingConfig?.planetOnly) {
continue
}
if ((planet.buildings[requiredBuildingType] || 0) < requiredLevel) {
return false
}
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
if ((technologies[key as TechnologyType] || 0) < requiredLevel) {
return false
}
} }
} }
} }
// 检查等级门槛 levelRequirements
const levelRequirements = (config as any).levelRequirements
if (levelRequirements) {
// 找出所有小于等于目标等级的门槛
const applicableLevels = Object.keys(levelRequirements)
.map(Number)
.filter(l => l <= level)
.sort((a, b) => a - b)
for (const threshold of applicableLevels) {
const reqs = levelRequirements[threshold]
if (!reqs) continue
for (const [key, reqLevel] of Object.entries(reqs)) {
const requiredLevel = reqLevel as number
if (Object.values(BuildingType).includes(key as BuildingType)) {
const requiredBuildingType = key as BuildingType
const requiredBuildingConfig = BUILDINGS[requiredBuildingType]
// 如果当前是月球且所需建筑是星球专属建筑planetOnly则跳过此前置条件
if (planet.isMoon && requiredBuildingConfig?.planetOnly) {
continue
}
if ((planet.buildings[requiredBuildingType] || 0) < requiredLevel) {
return false
}
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
if ((technologies[key as TechnologyType] || 0) < requiredLevel) {
return false
}
}
}
}
}
return true return true
} }
@@ -168,6 +219,12 @@ export const completeBuildQueue = (
const buildingType = item.itemType as BuildingType const buildingType = item.itemType as BuildingType
const currentLevel = planet.buildings[buildingType] || 0 const currentLevel = planet.buildings[buildingType] || 0
planet.buildings[buildingType] = Math.max(0, currentLevel - 1) planet.buildings[buildingType] = Math.max(0, currentLevel - 1)
} else if (item.type === 'scrap_ship') {
// 舰船拆除完成,减少舰船数量(舰船已在开始拆除时扣除)
// 资源返还也在开始拆除时完成,这里不需要额外操作
if (onCompleted) {
onCompleted('ship', item.itemType, undefined, item.quantity)
}
} }
return false return false
} }

View File

@@ -364,7 +364,8 @@ export const claimQuestRewards = (
} }
if (rewards.points) { if (rewards.points) {
player.points += rewards.points // 奖励积分存入 bonusPoints不会被 calculatePlayerPoints 覆盖
player.bonusPoints = (player.bonusPoints || 0) + rewards.points
} }
if (rewards.ships && currentPlanet) { if (rewards.ships && currentPlanet) {

View File

@@ -2,6 +2,7 @@ import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, P
import type { Locale } from '@/locales' import type { Locale } from '@/locales'
import { ShipType, DefenseType, MissionType, BuildingType, OfficerType, TechnologyType, ExpeditionZone } from '@/types/game' import { ShipType, DefenseType, MissionType, BuildingType, OfficerType, TechnologyType, ExpeditionZone } from '@/types/game'
import { FLEET_STORAGE_CONFIG, EXPEDITION_ZONES } from '@/config/gameConfig' import { FLEET_STORAGE_CONFIG, EXPEDITION_ZONES } from '@/config/gameConfig'
import { useGameStore } from '@/stores/gameStore'
import * as battleLogic from './battleLogic' import * as battleLogic from './battleLogic'
import * as moonLogic from './moonLogic' import * as moonLogic from './moonLogic'
import * as moonValidation from './moonValidation' import * as moonValidation from './moonValidation'
@@ -156,6 +157,7 @@ export const processAttackArrival = async (
} }
// 执行战斗(使用 Worker 进行异步计算) // 执行战斗(使用 Worker 进行异步计算)
const gameStore = useGameStore()
const battleResult = await battleLogic.simulateBattle( const battleResult = await battleLogic.simulateBattle(
mission.fleet, mission.fleet,
targetPlanet.fleet, targetPlanet.fleet,
@@ -164,7 +166,8 @@ export const processAttackArrival = async (
attacker.officers, attacker.officers,
defender?.officers || ({} as Record<OfficerType, Officer>), defender?.officers || ({} as Record<OfficerType, Officer>),
attacker.technologies, attacker.technologies,
defender?.technologies || ({} as Record<TechnologyType, number>) defender?.technologies || ({} as Record<TechnologyType, number>),
gameStore.battleToFinish
) )
// 更新战斗报告ID // 更新战斗报告ID
@@ -250,6 +253,7 @@ export const processNPCAttackArrival = async (
allPlanets: Planet[] allPlanets: Planet[]
): Promise<{ battleResult: BattleResult; moon: Planet | null; debrisField: DebrisField | null } | null> => { ): Promise<{ battleResult: BattleResult; moon: Planet | null; debrisField: DebrisField | null } | null> => {
// 执行战斗(使用 Worker 进行异步计算) // 执行战斗(使用 Worker 进行异步计算)
const gameStore = useGameStore()
const battleResult = await battleLogic.simulateBattle( const battleResult = await battleLogic.simulateBattle(
mission.fleet, // NPC舰队 mission.fleet, // NPC舰队
targetPlanet.fleet, // 玩家舰队 targetPlanet.fleet, // 玩家舰队
@@ -258,7 +262,8 @@ export const processNPCAttackArrival = async (
{} as Record<OfficerType, Officer>, // NPC没有军官系统 {} as Record<OfficerType, Officer>, // NPC没有军官系统
defender.officers || ({} as Record<OfficerType, Officer>), // 玩家军官 defender.officers || ({} as Record<OfficerType, Officer>), // 玩家军官
npc.technologies, // NPC科技等级 npc.technologies, // NPC科技等级
defender.technologies // 玩家科技等级 defender.technologies, // 玩家科技等级
gameStore.battleToFinish
) )
// 更新战斗报告ID和参与者信息 // 更新战斗报告ID和参与者信息
@@ -721,12 +726,12 @@ export const processRecycleArrival = (
} }
/** /**
* 远征事件类型 * 探险事件类型
*/ */
export type ExpeditionEventType = 'resources' | 'darkMatter' | 'fleet' | 'nothing' | 'pirates' | 'aliens' export type ExpeditionEventType = 'resources' | 'darkMatter' | 'fleet' | 'nothing' | 'pirates' | 'aliens'
/** /**
* 远征结果 * 探险结果
*/ */
export interface ExpeditionResult { export interface ExpeditionResult {
eventType: ExpeditionEventType eventType: ExpeditionEventType
@@ -737,8 +742,8 @@ export interface ExpeditionResult {
} }
/** /**
* 处理远征任务到达 * 处理探险任务到达
* 远征任务会随机触发各种事件,基于探险区域配置 * 探险任务会随机触发各种事件,基于探险区域配置
*/ */
export const processExpeditionArrival = (mission: FleetMission): ExpeditionResult => { export const processExpeditionArrival = (mission: FleetMission): ExpeditionResult => {
// 获取探险区域配置,默认为近空区域 // 获取探险区域配置,默认为近空区域
@@ -968,9 +973,18 @@ export interface DestroyResult {
deathstarDestructionChance: number // 死星反向销毁概率 deathstarDestructionChance: number // 死星反向销毁概率
isMoon: boolean // 目标是否为月球 isMoon: boolean // 目标是否为月球
failReason?: DestroyFailReason // 失败原因 failReason?: DestroyFailReason // 失败原因
battleResult?: BattleResult // 战斗结果(如果发生了战斗)
debrisField?: DebrisField // 战斗产生的残骸场
moon?: Planet // 战斗产生的月球
} }
export const processDestroyArrival = (mission: FleetMission, targetPlanet: Planet | undefined, attacker: Player): DestroyResult => { export const processDestroyArrival = async (
mission: FleetMission,
targetPlanet: Planet | undefined,
attacker: Player,
defender: Player | null,
allPlanets: Planet[]
): Promise<DestroyResult> => {
if (!targetPlanet) { if (!targetPlanet) {
mission.status = 'returning' mission.status = 'returning'
return { return {
@@ -995,8 +1009,8 @@ export const processDestroyArrival = (mission: FleetMission, targetPlanet: Plane
} }
// 检查是否有死星 // 检查是否有死星
const deathstarCount = mission.fleet[ShipType.Deathstar] || 0 const initialDeathstarCount = mission.fleet[ShipType.Deathstar] || 0
if (deathstarCount === 0) { if (initialDeathstarCount === 0) {
mission.status = 'returning' mission.status = 'returning'
return { return {
success: false, success: false,
@@ -1008,10 +1022,113 @@ export const processDestroyArrival = (mission: FleetMission, targetPlanet: Plane
} }
} }
// 检查目标是否有防御力量(舰队或防御设施)
const hasDefenderFleet = Object.values(targetPlanet.fleet || {}).some(count => count > 0)
const hasDefense = Object.values(targetPlanet.defense || {}).some(count => count > 0)
const needsBattle = hasDefenderFleet || hasDefense
let battleResult: BattleResult | undefined
let debrisField: DebrisField | undefined
let newMoon: Planet | undefined
let survivingDeathstars = initialDeathstarCount
// 如果目标有防御力量,先进行战斗
if (needsBattle) {
const gameStore = useGameStore()
// 执行战斗
battleResult = await battleLogic.simulateBattle(
mission.fleet,
targetPlanet.fleet,
targetPlanet.defense,
targetPlanet.resources,
attacker.officers,
defender?.officers || ({} as Record<OfficerType, Officer>),
attacker.technologies,
defender?.technologies || ({} as Record<TechnologyType, number>),
gameStore.battleToFinish
)
// 更新战斗报告
battleResult.id = `battle_${Date.now()}`
battleResult.attackerId = attacker.id
battleResult.defenderId = targetPlanet.ownerId || 'unknown'
battleResult.attackerPlanetId = mission.originPlanetId
battleResult.defenderPlanetId = targetPlanet.id
// 更新舰队 - 计算幸存舰船
const survivingFleet: Partial<Fleet> = {}
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
// 计算存活的死星数量
survivingDeathstars = survivingFleet[ShipType.Deathstar] || 0
// 更新目标星球舰队和防御
Object.entries(battleResult.defenderLosses.fleet).forEach(([shipType, lost]) => {
const current = targetPlanet.fleet[shipType as ShipType] || 0
targetPlanet.fleet[shipType as ShipType] = Math.max(0, current - lost)
})
Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => {
const current = targetPlanet.defense[defenseType as DefenseType] || 0
targetPlanet.defense[defenseType as DefenseType] = Math.max(0, current - lost)
})
// 计算残骸场
const debrisResources = battleResult.debrisField
if (debrisResources.metal > 0 || debrisResources.crystal > 0) {
debrisField = {
id: `debris_${targetPlanet.position.galaxy}_${targetPlanet.position.system}_${targetPlanet.position.position}`,
position: { ...targetPlanet.position },
resources: {
metal: debrisResources.metal,
crystal: debrisResources.crystal
},
createdAt: Date.now()
}
}
// 检查是否生成月球(只有非月球位置才能生成月球)
if (!targetPlanet.isMoon && debrisField) {
const moonExists = moonLogic.hasMoonAtPosition(allPlanets, targetPlanet.position)
if (!moonExists) {
newMoon =
moonLogic.tryGenerateMoon(
{ ...debrisResources, deuterium: 0, darkMatter: 0, energy: 0 },
targetPlanet.position,
targetPlanet.id,
targetPlanet.ownerId || attacker.id
) || undefined
}
}
// 如果攻击方失败或没有存活的死星,直接返回
if (battleResult.winner === 'defender' || survivingDeathstars === 0) {
mission.status = 'returning'
return {
success: false,
destructionChance: 0,
deathstarsLost: initialDeathstarCount > 0 && survivingDeathstars === 0,
deathstarDestructionChance: 0,
isMoon: targetPlanet.isMoon || false,
failReason: survivingDeathstars === 0 ? 'noDeathstar' : 'chanceFailed',
battleResult,
debrisField,
moon: newMoon
}
}
}
// 根据目标类型使用不同的销毁逻辑 // 根据目标类型使用不同的销毁逻辑
if (targetPlanet.isMoon) { if (targetPlanet.isMoon) {
// 月球销毁使用 OGame 公式 // 月球销毁使用 OGame 公式
const result = moonLogic.tryDestroyMoon(targetPlanet, deathstarCount) const result = moonLogic.tryDestroyMoon(targetPlanet, survivingDeathstars)
// 如果死星被反向销毁,从任务舰队中移除 // 如果死星被反向销毁,从任务舰队中移除
if (result.deathstarsDestroyed) { if (result.deathstarsDestroyed) {
@@ -1027,13 +1144,16 @@ export const processDestroyArrival = (mission: FleetMission, targetPlanet: Plane
deathstarsLost: result.deathstarsDestroyed, deathstarsLost: result.deathstarsDestroyed,
deathstarDestructionChance: result.deathstarDestructionChance, deathstarDestructionChance: result.deathstarDestructionChance,
isMoon: true, isMoon: true,
failReason: result.moonDestroyed ? undefined : 'chanceFailed' failReason: result.moonDestroyed ? undefined : 'chanceFailed',
battleResult,
debrisField,
moon: newMoon
} }
} else { } else {
// 行星销毁使用原有逻辑 // 行星销毁使用原有逻辑(基于存活的死星数量)
const planetaryShieldCount = targetPlanet.defense[DefenseType.PlanetaryShield] || 0 const planetaryShieldCount = targetPlanet.defense[DefenseType.PlanetaryShield] || 0
const defensePower = calculatePlanetDefensePower(targetPlanet.fleet, targetPlanet.defense) const defensePower = calculatePlanetDefensePower(targetPlanet.fleet, targetPlanet.defense)
const destructionChance = calculateDestructionChance(deathstarCount, planetaryShieldCount, defensePower) const destructionChance = calculateDestructionChance(survivingDeathstars, planetaryShieldCount, defensePower)
const randomValue = Math.random() * 100 const randomValue = Math.random() * 100
const success = randomValue < destructionChance const success = randomValue < destructionChance
@@ -1047,7 +1167,10 @@ export const processDestroyArrival = (mission: FleetMission, targetPlanet: Plane
deathstarsLost: false, deathstarsLost: false,
deathstarDestructionChance: 0, deathstarDestructionChance: 0,
isMoon: false, isMoon: false,
failReason: success ? undefined : 'chanceFailed' failReason: success ? undefined : 'chanceFailed',
battleResult,
debrisField,
moon: newMoon
} }
} }
} }
@@ -1200,8 +1323,40 @@ export const updateFleetMissions = async (
} }
break break
case MissionType.Destroy: case MissionType.Destroy: {
const destroyResult = processDestroyArrival(mission, targetPlanet, attacker) const destroyResult = await processDestroyArrival(mission, targetPlanet, attacker, defender, allPlanets)
// 处理战斗报告
if (destroyResult.battleResult) {
battleReports.push(destroyResult.battleResult)
}
// 处理新生成的月球
if (destroyResult.moon) {
newMoons.push(destroyResult.moon)
const moonKey = `${destroyResult.moon.position.galaxy}:${destroyResult.moon.position.system}:${destroyResult.moon.position.position}`
planets.set(moonKey, destroyResult.moon)
}
// 处理残骸场
if (destroyResult.debrisField) {
const existingDebris = debrisFields.get(destroyResult.debrisField.id)
if (existingDebris) {
const updatedDebris: DebrisField = {
...existingDebris,
resources: {
metal: existingDebris.resources.metal + destroyResult.debrisField.resources.metal,
crystal: existingDebris.resources.crystal + destroyResult.debrisField.resources.crystal
}
}
debrisFields.set(destroyResult.debrisField.id, updatedDebris)
updatedDebrisFields.push(updatedDebris)
} else {
debrisFields.set(destroyResult.debrisField.id, destroyResult.debrisField)
newDebrisFields.push(destroyResult.debrisField)
}
}
if (destroyResult.success && destroyResult.planetId) { if (destroyResult.success && destroyResult.planetId) {
// 星球被摧毁 // 星球被摧毁
destroyedPlanetIds.push(destroyResult.planetId) destroyedPlanetIds.push(destroyResult.planetId)
@@ -1217,6 +1372,7 @@ export const updateFleetMissions = async (
planets.delete(targetKey) planets.delete(targetKey)
} }
break break
}
} }
} }

View File

@@ -149,35 +149,10 @@ export const processGameUpdate = (
} }
} }
// 更新所有星球资源(直接同步计算,避免 Worker 通信开销)
player.planets.forEach(planet => {
// 计算更新前的资源(用于计算生产量)
const resourcesBefore = { ...planet.resources }
resourceLogic.updatePlanetResources(planet, now, bonuses, gameSpeed)
// 追踪资源生产统计
if (player.achievementStats) {
const metalProduced = Math.max(0, planet.resources.metal - resourcesBefore.metal)
const crystalProduced = Math.max(0, planet.resources.crystal - resourcesBefore.crystal)
const deuteriumProduced = Math.max(0, planet.resources.deuterium - resourcesBefore.deuterium)
const darkMatterProduced = Math.max(0, planet.resources.darkMatter - resourcesBefore.darkMatter)
if (metalProduced > 0 || crystalProduced > 0 || deuteriumProduced > 0 || darkMatterProduced > 0) {
achievementLogic.updateResourceProductionStats(player.achievementStats, {
metal: metalProduced,
crystal: crystalProduced,
deuterium: deuteriumProduced,
darkMatter: darkMatterProduced
})
}
}
})
// 收集所有新解锁的内容 // 收集所有新解锁的内容
const allUnlockedItems: UnlockedItem[] = [] const allUnlockedItems: UnlockedItem[] = []
// 更新所有星球其他状态 // 先完成所有星球的建造队列(升级建筑),确保资源更新时使用新的建筑等级
player.planets.forEach(planet => { player.planets.forEach(planet => {
// 保存完成前的建筑状态 // 保存完成前的建筑状态
const previousBuildings = { ...planet.buildings } const previousBuildings = { ...planet.buildings }
@@ -200,6 +175,40 @@ export const processGameUpdate = (
} }
}) })
// 然后更新所有星球资源(使用新的建筑等级计算生产)
// 获取采矿技术等级(用于矿脉恢复上限计算)
const miningTechLevel = player.technologies[TechnologyType.MiningTechnology] || 0
// 获取资源研究科技等级
const techBonuses = {
mineralResearchLevel: player.technologies[TechnologyType.MineralResearch] || 0,
crystalResearchLevel: player.technologies[TechnologyType.CrystalResearch] || 0,
fuelResearchLevel: player.technologies[TechnologyType.FuelResearch] || 0
}
player.planets.forEach(planet => {
// 计算更新前的资源(用于计算生产量)
const resourcesBefore = { ...planet.resources }
resourceLogic.updatePlanetResources(planet, now, bonuses, gameSpeed, miningTechLevel, techBonuses)
// 追踪资源生产统计
if (player.achievementStats) {
const metalProduced = Math.max(0, planet.resources.metal - resourcesBefore.metal)
const crystalProduced = Math.max(0, planet.resources.crystal - resourcesBefore.crystal)
const deuteriumProduced = Math.max(0, planet.resources.deuterium - resourcesBefore.deuterium)
const darkMatterProduced = Math.max(0, planet.resources.darkMatter - resourcesBefore.darkMatter)
if (metalProduced > 0 || crystalProduced > 0 || deuteriumProduced > 0 || darkMatterProduced > 0) {
achievementLogic.updateResourceProductionStats(player.achievementStats, {
metal: metalProduced,
crystal: crystalProduced,
deuterium: deuteriumProduced,
darkMatter: darkMatterProduced
})
}
}
})
// 检查研究队列 // 检查研究队列
const updatedResearchQueue = researchLogic.completeResearchQueue( const updatedResearchQueue = researchLogic.completeResearchQueue(
player.researchQueue, player.researchQueue,
@@ -209,6 +218,10 @@ export const processGameUpdate = (
onCompleted onCompleted
) )
// 重要:先更新 player.researchQueue再处理等待队列
// 这样等待队列处理时可以正确 push 新项目到队列中
player.researchQueue = updatedResearchQueue
// 处理等待队列自动执行 // 处理等待队列自动执行
const waitingResult = waitingQueueLogic.processAllWaitingQueues(player, now) const waitingResult = waitingQueueLogic.processAllWaitingQueues(player, now)
// 如果有等待队列项被执行,可以在这里添加通知(可选) // 如果有等待队列项被执行,可以在这里添加通知(可选)
@@ -244,8 +257,9 @@ export const processGameUpdate = (
onUnlock(allUnlockedItems) onUnlock(allUnlockedItems)
} }
// 返回当前的研究队列(包含等待队列处理后添加的新项)
return { return {
updatedResearchQueue updatedResearchQueue: player.researchQueue
} }
} }

View File

@@ -17,14 +17,14 @@ import type {
AllyDefenseNotification, AllyDefenseNotification,
AttitudeChangeNotification AttitudeChangeNotification
} from '@/types/game' } from '@/types/game'
import { MissionType, ShipType, TechnologyType, RelationStatus, NPCAIType } from '@/types/game' import { MissionType, ShipType, DefenseType, TechnologyType, RelationStatus, NPCAIType } from '@/types/game'
// 重新导出类型供外部使用 // 重新导出类型供外部使用
export type { TradeOffer, IntelReport, JointAttackInvite, IntelType, AidNotification, AllyDefenseNotification, AttitudeChangeNotification } export type { TradeOffer, IntelReport, JointAttackInvite, IntelType, AidNotification, AllyDefenseNotification, AttitudeChangeNotification }
import * as fleetLogic from './fleetLogic' import * as fleetLogic from './fleetLogic'
import * as diplomaticLogic from './diplomaticLogic' import * as diplomaticLogic from './diplomaticLogic'
import * as resourceLogic from './resourceLogic' import * as resourceLogic from './resourceLogic'
import { DIPLOMATIC_CONFIG, SHIPS } from '@/config/gameConfig' import { DIPLOMATIC_CONFIG, SHIPS, DEFENSES } from '@/config/gameConfig'
// ========== 敌对NPC增强行为类型定义 ========== // ========== 敌对NPC增强行为类型定义 ==========
@@ -908,11 +908,69 @@ export const processNPCSpyArrival = (
} }
/** /**
* 决定NPC攻击舰队组成 * 计算舰队的战斗力
* 基于侦查报告和NPC实 * 使用 attack + shield + armor 作为单位战斗
*/ */
const decideAttackFleet = (_npc: NPC, npcPlanet: Planet, _spyReport: SpyReport, config: DynamicBehaviorConfig): Partial<Fleet> | null => { const calculateFleetCombatPower = (fleet: Partial<Fleet> | undefined): number => {
// 简单策略:派出一定比例的可用舰队 if (!fleet) return 0
let power = 0
for (const [shipType, count] of Object.entries(fleet)) {
if (count && count > 0) {
const config = SHIPS[shipType as ShipType]
if (config) {
// 战斗力 = (攻击力 + 护盾 + 装甲) * 数量
power += (config.attack + config.shield + config.armor) * count
}
}
}
return power
}
/**
* 计算防御设施的战斗力
*/
const calculateDefenseCombatPower = (defense: Partial<Record<DefenseType, number>> | undefined): number => {
if (!defense) return 0
let power = 0
for (const [defenseType, count] of Object.entries(defense)) {
if (count && count > 0) {
const config = DEFENSES[defenseType as DefenseType]
if (config) {
// 战斗力 = (攻击力 + 护盾 + 装甲) * 数量
power += (config.attack + config.shield + config.armor) * count
}
}
}
return power
}
/**
* 评估NPC是否能赢得战斗
* 返回NPC战斗力与敌方战斗力的比值
*/
export const evaluateWinProbability = (npcFleet: Partial<Fleet>, spyReport: SpyReport): number => {
const npcPower = calculateFleetCombatPower(npcFleet)
const enemyFleetPower = calculateFleetCombatPower(spyReport.fleet)
const enemyDefensePower = calculateDefenseCombatPower(spyReport.defense)
const totalEnemyPower = enemyFleetPower + enemyDefensePower
// 如果敌方没有防御力,返回无限大(可以攻击)
if (totalEnemyPower === 0) return Infinity
return npcPower / totalEnemyPower
}
/**
* 决定NPC攻击舰队组成
* 基于侦查报告和NPC实力只有当有合理胜算时才攻击
*/
const decideAttackFleet = (_npc: NPC, npcPlanet: Planet, spyReport: SpyReport, config: DynamicBehaviorConfig): Partial<Fleet> | null => {
// 计算敌方总战斗力
const enemyFleetPower = calculateFleetCombatPower(spyReport.fleet)
const enemyDefensePower = calculateDefenseCombatPower(spyReport.defense)
const totalEnemyPower = enemyFleetPower + enemyDefensePower
// 先计算可用的攻击舰队
const attackFleet: Partial<Fleet> = {} const attackFleet: Partial<Fleet> = {}
let hasShips = false let hasShips = false
@@ -939,7 +997,22 @@ const decideAttackFleet = (_npc: NPC, npcPlanet: Planet, _spyReport: SpyReport,
} }
} }
return hasShips ? attackFleet : null if (!hasShips) {
return null
}
// 计算NPC舰队战斗力
const npcPower = calculateFleetCombatPower(attackFleet)
// 如果敌方有防御力,检查是否有足够的胜算
// 要求NPC战斗力至少是敌方的1.2倍才会发动攻击(确保有较大胜算)
const MIN_WIN_RATIO = 1.2
if (totalEnemyPower > 0 && npcPower < totalEnemyPower * MIN_WIN_RATIO) {
// NPC战斗力不足不发动攻击
return null
}
return attackFleet
} }
/** /**
@@ -1520,6 +1593,25 @@ export const createNPCRecycleMission = (npc: NPC, debris: DebrisField, player: P
return null return null
} }
// 检查残骸位置是否有玩家星球
const playerPlanetAtDebris = allPlanets.find(
p =>
p.ownerId === player.id &&
p.position.galaxy === debris.position.galaxy &&
p.position.system === debris.position.system &&
p.position.position === debris.position.position
)
// 如果残骸在玩家星球位置,检查玩家是否有防御
// 如果玩家有防御NPC不应该只派回收船去送死会被击毁产生更多残骸形成恶性循环
if (playerPlanetAtDebris) {
// 计算玩家星球的总防御数量
const totalDefense = Object.values(playerPlanetAtDebris.defense || {}).reduce((sum, count) => sum + (count || 0), 0)
if (totalDefense > 0) {
return null // 玩家有防御,不派纯回收船去送死
}
}
// 检查NPC是否有回收船 // 检查NPC是否有回收船
const recyclers = closestPlanet.fleet[ShipType.Recycler] || 0 const recyclers = closestPlanet.fleet[ShipType.Recycler] || 0
if (recyclers === 0) { if (recyclers === 0) {
@@ -1566,15 +1658,7 @@ export const createNPCRecycleMission = (npc: NPC, debris: DebrisField, player: P
} }
npc.fleetMissions.push(mission) npc.fleetMissions.push(mission)
// 检查残骸位置是否有玩家星球,如果有则发送警告 // 如果残骸在玩家星球位置发送警告非敌对NPC才会执行到这里
const playerPlanetAtDebris = allPlanets.find(
p =>
p.ownerId === player.id &&
p.position.galaxy === debris.position.galaxy &&
p.position.system === debris.position.system &&
p.position.position === debris.position.position
)
if (playerPlanetAtDebris) { if (playerPlanetAtDebris) {
// 创建即将到来的舰队警告(非敌对) // 创建即将到来的舰队警告(非敌对)
const alert = createIncomingFleetAlert(mission, npc, playerPlanetAtDebris) const alert = createIncomingFleetAlert(mission, npc, playerPlanetAtDebris)

View File

@@ -266,12 +266,12 @@ export const formatDepletionTime = (hours: number): string => {
/** /**
* 计算地质研究站带来的恢复速率加成 * 计算地质研究站带来的恢复速率加成
* @param geoStationLevel 地质研究站等级 * @param geoStationLevel 地质研究站等级
* @returns 恢复速率倍数1 + 等级 * 0.5 * @returns 恢复速率倍数1 + 等级 * 0.1
*/ */
export const calculateGeoStationBonus = (geoStationLevel: number): number => { export const calculateGeoStationBonus = (geoStationLevel: number): number => {
// 每级地质研究站增加50%恢复速度 // 每级地质研究站增加50%恢复速度
// 0级 = 1倍, 1级 = 1.5倍, 2级 = 2倍, ..., 10级 = 6倍 // 0级 = 1倍, 1级 = 1.5倍, 2级 = 2倍, ..., 10级 = 6倍
return 1 + geoStationLevel * 0.5 return 1 + geoStationLevel * 0.1
} }
/** /**
@@ -280,8 +280,16 @@ export const calculateGeoStationBonus = (geoStationLevel: number): number => {
* @param deposits 矿脉储量对象 * @param deposits 矿脉储量对象
* @param hoursElapsed 经过的小时数 * @param hoursElapsed 经过的小时数
* @param geoStationLevel 地质研究站等级(可选,影响恢复速度) * @param geoStationLevel 地质研究站等级(可选,影响恢复速度)
* @param deepDrillingLevel 深层钻探设施等级(可选,影响恢复上限)
* @param miningTechLevel 采矿技术等级(可选,影响恢复上限)
*/ */
export const regenerateDeposits = (deposits: OreDeposits, hoursElapsed: number, geoStationLevel: number = 0): void => { export const regenerateDeposits = (
deposits: OreDeposits,
hoursElapsed: number,
geoStationLevel: number = 0,
deepDrillingLevel: number = 0,
miningTechLevel: number = 0
): void => {
const { REGENERATION } = ORE_DEPOSIT_CONFIG const { REGENERATION } = ORE_DEPOSIT_CONFIG
if (!REGENERATION.ENABLED || hoursElapsed <= 0) return if (!REGENERATION.ENABLED || hoursElapsed <= 0) return
@@ -289,32 +297,36 @@ export const regenerateDeposits = (deposits: OreDeposits, hoursElapsed: number,
// 计算地质研究站加成 // 计算地质研究站加成
const geoBonus = calculateGeoStationBonus(geoStationLevel) const geoBonus = calculateGeoStationBonus(geoStationLevel)
// 动态计算初始储量上限 // 计算带有建筑和科技加成的储量上限
const initialDeposits = calculateInitialDeposits(deposits.position) const enhancedDeposits = calculateEnhancedDeposits(deposits.position, deepDrillingLevel, miningTechLevel)
// 计算恢复量(基于初始储量的百分比,乘以地质研究站加成) // 计算恢复量(基于增强后储量的百分比,乘以地质研究站加成)
const regenRate = REGENERATION.RATE_PER_HOUR * hoursElapsed * geoBonus const regenRate = REGENERATION.RATE_PER_HOUR * hoursElapsed * geoBonus
const maxPercentage = REGENERATION.MAX_PERCENTAGE const maxPercentage = REGENERATION.MAX_PERCENTAGE
// 恢复每种资源 // 恢复每种资源
const metalRegen = initialDeposits.metal * regenRate const metalRegen = enhancedDeposits.metal * regenRate
const crystalRegen = initialDeposits.crystal * regenRate const crystalRegen = enhancedDeposits.crystal * regenRate
const deuteriumRegen = initialDeposits.deuterium * regenRate const deuteriumRegen = enhancedDeposits.deuterium * regenRate
// 添加恢复量,但不超过初始储量的最大百分比 // 添加恢复量,但不超过增强后储量的最大百分比
deposits.metal = Math.min(initialDeposits.metal * maxPercentage, deposits.metal + metalRegen) deposits.metal = Math.min(enhancedDeposits.metal * maxPercentage, deposits.metal + metalRegen)
deposits.crystal = Math.min(initialDeposits.crystal * maxPercentage, deposits.crystal + crystalRegen) deposits.crystal = Math.min(enhancedDeposits.crystal * maxPercentage, deposits.crystal + crystalRegen)
deposits.deuterium = Math.min(initialDeposits.deuterium * maxPercentage, deposits.deuterium + deuteriumRegen) deposits.deuterium = Math.min(enhancedDeposits.deuterium * maxPercentage, deposits.deuterium + deuteriumRegen)
} }
/** /**
* 获取矿脉恢复状态信息 * 获取矿脉恢复状态信息
* @param deposits 矿脉储量 * @param deposits 矿脉储量
* @param geoStationLevel 地质研究站等级(可选,影响恢复时间计算) * @param geoStationLevel 地质研究站等级(可选,影响恢复时间计算)
* @param deepDrillingLevel 深层钻探设施等级(可选,影响恢复上限)
* @param miningTechLevel 采矿技术等级(可选,影响恢复上限)
*/ */
export const getRegenerationInfo = ( export const getRegenerationInfo = (
deposits: OreDeposits | undefined, deposits: OreDeposits | undefined,
geoStationLevel: number = 0 geoStationLevel: number = 0,
deepDrillingLevel: number = 0,
miningTechLevel: number = 0
): { ): {
metalRecovering: boolean metalRecovering: boolean
crystalRecovering: boolean crystalRecovering: boolean
@@ -338,26 +350,26 @@ export const getRegenerationInfo = (
} }
} }
// 动态计算初始储量上限 // 计算带有建筑和科技加成的储量上限
const initialDeposits = calculateInitialDeposits(deposits.position) const enhancedDeposits = calculateEnhancedDeposits(deposits.position, deepDrillingLevel, miningTechLevel)
const { REGENERATION } = ORE_DEPOSIT_CONFIG const { REGENERATION } = ORE_DEPOSIT_CONFIG
const maxPercentage = REGENERATION.MAX_PERCENTAGE const maxPercentage = REGENERATION.MAX_PERCENTAGE
// 实际恢复速率 = 基础速率 * 地质研究站加成 // 实际恢复速率 = 基础速率 * 地质研究站加成
const ratePerHour = REGENERATION.RATE_PER_HOUR * geoBonus const ratePerHour = REGENERATION.RATE_PER_HOUR * geoBonus
const metalMax = initialDeposits.metal * maxPercentage const metalMax = enhancedDeposits.metal * maxPercentage
const crystalMax = initialDeposits.crystal * maxPercentage const crystalMax = enhancedDeposits.crystal * maxPercentage
const deuteriumMax = initialDeposits.deuterium * maxPercentage const deuteriumMax = enhancedDeposits.deuterium * maxPercentage
const metalRecovering = deposits.metal < metalMax const metalRecovering = deposits.metal < metalMax
const crystalRecovering = deposits.crystal < crystalMax const crystalRecovering = deposits.crystal < crystalMax
const deuteriumRecovering = deposits.deuterium < deuteriumMax const deuteriumRecovering = deposits.deuterium < deuteriumMax
// 计算恢复到满需要的小时数(考虑地质研究站加成) // 计算恢复到满需要的小时数(考虑地质研究站加成)
const hoursToFullMetal = metalRecovering ? (metalMax - deposits.metal) / (initialDeposits.metal * ratePerHour) : 0 const hoursToFullMetal = metalRecovering ? (metalMax - deposits.metal) / (enhancedDeposits.metal * ratePerHour) : 0
const hoursToFullCrystal = crystalRecovering ? (crystalMax - deposits.crystal) / (initialDeposits.crystal * ratePerHour) : 0 const hoursToFullCrystal = crystalRecovering ? (crystalMax - deposits.crystal) / (enhancedDeposits.crystal * ratePerHour) : 0
const hoursToFullDeuterium = deuteriumRecovering ? (deuteriumMax - deposits.deuterium) / (initialDeposits.deuterium * ratePerHour) : 0 const hoursToFullDeuterium = deuteriumRecovering ? (deuteriumMax - deposits.deuterium) / (enhancedDeposits.deuterium * ratePerHour) : 0
return { return {
metalRecovering, metalRecovering,

View File

@@ -237,7 +237,7 @@ export const createMoon = (
/** /**
* 计算月球空间上限 * 计算月球空间上限
* OGame规则月球初始1格月球基地每级+3格但月球基地本身占用1格净增2格 * OGame规则月球初始1格月球基地每级+30
*/ */
export const calculateMoonMaxSpace = (moon: Planet): number => { export const calculateMoonMaxSpace = (moon: Planet): number => {
if (!moon.isMoon) return 0 if (!moon.isMoon) return 0
@@ -320,3 +320,63 @@ export const calculateDeuteriumTemperatureBonus = (maxTemperature: number): numb
// 返回乘数,例如:-40°C时返回1.52+100°C时返回0.96 // 返回乘数,例如:-40°C时返回1.52+100°C时返回0.96
return 1.36 - 0.004 * maxTemperature return 1.36 - 0.004 * maxTemperature
} }
/**
* 检查是否可以放弃殖民地
* @param planets 玩家所有星球
* @param planetId 要放弃的星球ID
* @returns 是否可以放弃及原因
*/
export const canAbandonColony = (
planets: Planet[],
planetId: string
): { canAbandon: boolean; reason?: string } => {
const planet = planets.find(p => p.id === planetId)
if (!planet) {
return { canAbandon: false, reason: 'errors.planetNotFound' }
}
// 主星(第一个非月球星球)不能放弃
const mainPlanet = planets.find(p => !p.isMoon)
if (mainPlanet && mainPlanet.id === planetId) {
return { canAbandon: false, reason: 'errors.cannotAbandonHomePlanet' }
}
// 检查是否有正在进行的建造队列
if (planet.buildQueue.length > 0) {
return { canAbandon: false, reason: 'errors.hasBuildQueue' }
}
// 检查是否有舰队在该星球
const hasFleet = Object.values(planet.fleet).some(count => count > 0)
if (hasFleet) {
return { canAbandon: false, reason: 'errors.hasFleetOnPlanet' }
}
// 检查是否有防御在该星球
const hasDefense = Object.values(planet.defense).some(count => count > 0)
if (hasDefense) {
return { canAbandon: false, reason: 'errors.hasDefenseOnPlanet' }
}
return { canAbandon: true }
}
/**
* 放弃殖民地
* @param planets 玩家所有星球
* @param planetId 要放弃的星球ID
* @returns 放弃后的星球列表
*/
export const abandonColony = (planets: Planet[], planetId: string): Planet[] => {
const planet = planets.find(p => p.id === planetId)
if (!planet) return planets
// 如果放弃的是行星,同时删除其月球
if (!planet.isMoon) {
return planets.filter(p => p.id !== planetId && p.parentPlanetId !== planetId)
}
// 如果放弃的是月球,只删除月球
return planets.filter(p => p.id !== planetId)
}

View File

@@ -72,7 +72,16 @@ export const checkRequirements = (
for (const [key, requiredLevel] of Object.entries(requirements)) { for (const [key, requiredLevel] of Object.entries(requirements)) {
// 检查是否为建筑类型 // 检查是否为建筑类型
if (Object.values(BuildingType).includes(key as BuildingType)) { if (Object.values(BuildingType).includes(key as BuildingType)) {
const currentLevel = planet.buildings[key as BuildingType] || 0 const requiredBuildingType = key as BuildingType
const requiredBuildingConfig = BUILDINGS[requiredBuildingType]
// 如果当前是月球且所需建筑是星球专属建筑planetOnly则跳过此前置条件
// 这允许在月球上建造机器人工厂等建筑,即使它们的前置条件是无法在月球建造的矿场
if (planet.isMoon && requiredBuildingConfig?.planetOnly) {
continue
}
const currentLevel = planet.buildings[requiredBuildingType] || 0
if (currentLevel < requiredLevel) { if (currentLevel < requiredLevel) {
return false return false
} }
@@ -90,16 +99,27 @@ export const checkRequirements = (
} }
/** /**
* 计算星球的资源产量(包含军官加成) * 计算星球的资源产量(包含军官加成和科技加成
* @param planet 星球对象 * @param planet 星球对象
* @param officers 玩家的军官对象 * @param officers 玩家的军官对象
* @param resourceSpeed 游戏速度
* @param techBonuses 科技加成(可选,矿物研究/晶体研究/燃料研究)
* @returns 每小时各类资源的产量 * @returns 每小时各类资源的产量
*/ */
export const getResourceProduction = (planet: Planet, officers: Record<OfficerType, Officer>, resourceSpeed: number = 1): Resources => { export const getResourceProduction = (
planet: Planet,
officers: Record<OfficerType, Officer>,
resourceSpeed: number = 1,
techBonuses?: {
mineralResearchLevel?: number
crystalResearchLevel?: number
fuelResearchLevel?: number
}
): Resources => {
// 计算当前激活的军官加成 // 计算当前激活的军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 根据建筑等级和军官加成计算资源产量 // 根据建筑等级和军官加成计算资源产量
const base = resourceLogic.calculateResourceProduction(planet, bonuses) const base = resourceLogic.calculateResourceProduction(planet, bonuses, techBonuses)
return scaleResources(base, resourceSpeed) return scaleResources(base, resourceSpeed)
} }
@@ -153,7 +173,7 @@ export const getMaxFleetMissions = (additionalFleetSlots: number = 0, computerTe
* @param level 目标等级 * @param level 目标等级
* @returns 总资源成本(金属+水晶+重氢) * @returns 总资源成本(金属+水晶+重氢)
*/ */
const calculateBuildingTotalCost = (buildingType: BuildingType, level: number): number => { export const calculateBuildingTotalCost = (buildingType: BuildingType, level: number): number => {
if (level <= 0) return 0 if (level <= 0) return 0
const config = BUILDINGS[buildingType] const config = BUILDINGS[buildingType]
@@ -181,7 +201,7 @@ const calculateBuildingTotalCost = (buildingType: BuildingType, level: number):
* @param level 目标等级 * @param level 目标等级
* @returns 总资源成本(金属+水晶+重氢) * @returns 总资源成本(金属+水晶+重氢)
*/ */
const calculateTechnologyTotalCost = (techType: TechnologyType, level: number): number => { export const calculateTechnologyTotalCost = (techType: TechnologyType, level: number): number => {
if (level <= 0) return 0 if (level <= 0) return 0
const config = TECHNOLOGIES[techType] const config = TECHNOLOGIES[techType]
@@ -208,7 +228,7 @@ const calculateTechnologyTotalCost = (techType: TechnologyType, level: number):
* @param shipType 舰船类型 * @param shipType 舰船类型
* @returns 单个舰船的资源成本(金属+水晶+重氢) * @returns 单个舰船的资源成本(金属+水晶+重氢)
*/ */
const calculateShipUnitCost = (shipType: ShipType): number => { export const calculateShipUnitCost = (shipType: ShipType): number => {
const config = SHIPS[shipType] const config = SHIPS[shipType]
if (!config) return 0 if (!config) return 0
@@ -220,7 +240,7 @@ const calculateShipUnitCost = (shipType: ShipType): number => {
* @param defenseType 防御类型 * @param defenseType 防御类型
* @returns 单个防御的资源成本(金属+水晶+重氢) * @returns 单个防御的资源成本(金属+水晶+重氢)
*/ */
const calculateDefenseUnitCost = (defenseType: DefenseType): number => { export const calculateDefenseUnitCost = (defenseType: DefenseType): number => {
const config = DEFENSES[defenseType] const config = DEFENSES[defenseType]
if (!config) return 0 if (!config) return 0
@@ -262,6 +282,11 @@ export const calculatePlayerPoints = (player: Player): number => {
}) })
}) })
// 每1000资源 = 1积分 // 每1000资源 = 1积分(基础积分)
return Math.floor(totalCost / 1000) const basePoints = Math.floor(totalCost / 1000)
// 加上奖励积分(战役、成就等奖励的积分)
const bonusPoints = player.bonusPoints || 0
return basePoints + bonusPoints
} }

View File

@@ -1,8 +1,57 @@
import type { Resources, BuildQueueItem } from '@/types/game' import type { Resources, BuildQueueItem, Planet } from '@/types/game'
import { TechnologyType, BuildingType } from '@/types/game' import { TechnologyType, BuildingType } from '@/types/game'
import { TECHNOLOGIES } from '@/config/gameConfig' import { TECHNOLOGIES } from '@/config/gameConfig'
import * as pointsLogic from './pointsLogic' import * as pointsLogic from './pointsLogic'
/**
* 计算有效研究实验室等级(考虑星际研究网络)
* 星际研究网络允许连接多个星球的研究实验室取等级最高的N个实验室等级之和
* @param planets 玩家的所有星球
* @param currentPlanetId 当前星球ID研究所在的星球
* @param intergalacticResearchNetworkLevel 星际研究网络等级
* @returns 有效的研究实验室等级总和
*/
export const calculateEffectiveLabLevel = (
planets: Planet[],
currentPlanetId: string,
intergalacticResearchNetworkLevel: number
): number => {
// 收集所有星球的研究实验室等级
const labLevels: { planetId: string; level: number }[] = []
for (const planet of planets) {
const labLevel = planet.buildings[BuildingType.ResearchLab] || 0
if (labLevel > 0) {
labLevels.push({ planetId: planet.id, level: labLevel })
}
}
// 如果没有星际研究网络,只返回当前星球的实验室等级
if (intergalacticResearchNetworkLevel === 0) {
const currentPlanet = planets.find(p => p.id === currentPlanetId)
return currentPlanet?.buildings[BuildingType.ResearchLab] || 0
}
// 按等级降序排序
labLevels.sort((a, b) => b.level - a.level)
// 可连接的实验室数量 = 1 + 星际研究网络等级
// 等级1可连接2个实验室等级2可连接3个以此类推
const maxLabs = 1 + intergalacticResearchNetworkLevel
// 取前N个实验室的等级之和
let totalLevel = 0
const count = Math.min(maxLabs, labLevels.length)
for (let i = 0; i < count; i++) {
const lab = labLevels[i]
if (lab) {
totalLevel += lab.level
}
}
return totalLevel
}
// 用于生成唯一ID的计数器 // 用于生成唯一ID的计数器
let researchQueueIdCounter = 0 let researchQueueIdCounter = 0
@@ -23,31 +72,56 @@ export const calculateTechnologyCost = (techType: TechnologyType, targetLevel: n
/** /**
* 计算科技研究时间 * 计算科技研究时间
* 使用 2moons 公式(调整版):
* 1. 成本系数 = Σ (资源^0.3 / 0.003)
* 2. 时间(秒) = 成本系数 / ((1 + 研究实验室等级) × 能源加成 × 游戏速度)
* @param techType 科技类型 * @param techType 科技类型
* @param currentLevel 当前等级 * @param currentLevel 当前等级(要研究的目标等级 = currentLevel + 1
* @param researchSpeedBonus 军官等提供的研究速度加成百分比 * @param researchSpeedBonus 军官等提供的研究速度加成百分比
* @param researchLabLevel 研究实验室等级 * @param researchLabLevel 研究实验室等级
* @param energyTechLevel 能源技术等级 * @param energyTechLevel 能源技术等级(提供额外加速)
* @param gameSpeed 游戏速度默认1
* @param universityLevel 大学等级每级减少研究时间8%
*/ */
export const calculateTechnologyTime = ( export const calculateTechnologyTime = (
techType: TechnologyType, techType: TechnologyType,
currentLevel: number, currentLevel: number,
researchSpeedBonus: number = 0, researchSpeedBonus: number = 0,
researchLabLevel: number = 1, researchLabLevel: number = 1,
energyTechLevel: number = 0 energyTechLevel: number = 0,
gameSpeed: number = 1,
universityLevel: number = 0
): number => { ): number => {
const config = TECHNOLOGIES[techType] // 目标等级 = 当前等级 + 1
const baseTime = config.baseTime * Math.pow(config.costMultiplier, currentLevel) const targetLevel = currentLevel + 1
// 计算该等级的成本
const cost = calculateTechnologyCost(techType, targetLevel)
// 2moons 公式:成本系数 = Σ (资源^0.3 / 0.003)
let elementCost = 0
if (cost.metal > 0) elementCost += Math.pow(cost.metal, 0.3) / 0.003
if (cost.crystal > 0) elementCost += Math.pow(cost.crystal, 0.3) / 0.003
if (cost.deuterium > 0) elementCost += Math.pow(cost.deuterium, 0.3) / 0.003
// 研究实验室和能源技术的加速:研究时间 / (研究实验室等级 × (1 + 能源技术等级))
// 研究实验室等级至少为1防止除以0 // 研究实验室等级至少为1防止除以0
const labLevel = Math.max(1, researchLabLevel) const labLevel = Math.max(1, researchLabLevel)
const techSpeedDivisor = labLevel * (1 + energyTechLevel)
// 能源技术提供额外加速每级5%
const energyBonus = 1 + energyTechLevel * 0.05
// 简化公式:时间(秒) = 成本系数 / (实验室加成 × 能源加成 × 游戏速度)
const timeInSeconds = elementCost / ((1 + labLevel) * energyBonus * gameSpeed)
// 军官等的百分比加成 // 军官等的百分比加成
const speedMultiplier = 1 - researchSpeedBonus / 100 const speedMultiplier = 1 - researchSpeedBonus / 100
return Math.floor((baseTime / techSpeedDivisor) * speedMultiplier) // 大学加成每级减少研究时间8%最高10级=57%减少,因为是乘法叠加)
// 使用乘法叠加:(1 - 0.08)^level
const universityMultiplier = Math.pow(0.92, universityLevel)
// 确保最小时间为5秒
return Math.max(5, Math.floor(timeInSeconds * speedMultiplier * universityMultiplier))
} }
/** /**

View File

@@ -54,7 +54,8 @@ export const executeTechnologyResearch = (
techType: TechnologyType, techType: TechnologyType,
currentLevel: number, currentLevel: number,
officers: Record<OfficerType, Officer>, officers: Record<OfficerType, Officer>,
technologies: Partial<Record<TechnologyType, number>> technologies: Partial<Record<TechnologyType, number>>,
allPlanets?: Planet[]
): { queueItem: BuildQueueItem } => { ): { queueItem: BuildQueueItem } => {
const targetLevel = currentLevel + 1 const targetLevel = currentLevel + 1
const cost = researchLogic.calculateTechnologyCost(techType, targetLevel) const cost = researchLogic.calculateTechnologyCost(techType, targetLevel)
@@ -62,11 +63,30 @@ export const executeTechnologyResearch = (
// 计算军官加成 // 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now()) const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 获取研究实验室等级和能源技术等级 // 获取星际研究网络等级
const researchLabLevel = planet.buildings[BuildingType.ResearchLab] || 1 const intergalacticResearchNetworkLevel = technologies[TechnologyType.IntergalacticResearchNetwork] || 0
const energyTechLevel = technologies[TechnologyType.EnergyTechnology] || 0
const time = researchLogic.calculateTechnologyTime(techType, currentLevel, bonuses.researchSpeedBonus, researchLabLevel, energyTechLevel) // 计算有效研究实验室等级(考虑星际研究网络)
let researchLabLevel: number
if (allPlanets && intergalacticResearchNetworkLevel > 0) {
researchLabLevel = researchLogic.calculateEffectiveLabLevel(allPlanets, planet.id, intergalacticResearchNetworkLevel)
} else {
researchLabLevel = planet.buildings[BuildingType.ResearchLab] || 1
}
const energyTechLevel = technologies[TechnologyType.EnergyTechnology] || 0
// 获取大学等级(加速研究)
const universityLevel = planet.buildings[BuildingType.University] || 0
const time = researchLogic.calculateTechnologyTime(
techType,
currentLevel,
bonuses.researchSpeedBonus,
researchLabLevel,
energyTechLevel,
1,
universityLevel
)
// 扣除资源 // 扣除资源
resourceLogic.deductResources(planet.resources, cost) resourceLogic.deductResources(planet.resources, cost)

View File

@@ -5,6 +5,24 @@ import { OFFICERS } from '@/config/gameConfig'
import * as oreDepositLogic from './oreDepositLogic' import * as oreDepositLogic from './oreDepositLogic'
import * as planetLogic from './planetLogic' import * as planetLogic from './planetLogic'
/**
* 计算资源研究科技加成
* @param mineralResearchLevel 矿物研究等级(每级+2%金属产量)
* @param crystalResearchLevel 晶体研究等级(每级+2%晶体产量)
* @param fuelResearchLevel 燃料研究等级(每级+2%重氢产量)
*/
export const calculateResearchProductionBonus = (
mineralResearchLevel: number = 0,
crystalResearchLevel: number = 0,
fuelResearchLevel: number = 0
): { metalBonus: number; crystalBonus: number; deuteriumBonus: number } => {
return {
metalBonus: mineralResearchLevel * 2, // 每级2%
crystalBonus: crystalResearchLevel * 2,
deuteriumBonus: fuelResearchLevel * 2
}
}
/** /**
* 计算电量产出 * 计算电量产出
*/ */
@@ -93,6 +111,9 @@ export const calculateEnergyConsumption = (planet: Planet): number => {
/** /**
* 计算资源产量(每小时) * 计算资源产量(每小时)
* @param planet 星球
* @param bonuses 军官等加成
* @param techBonuses 科技加成(可选,矿物研究/晶体研究/燃料研究)
*/ */
export const calculateResourceProduction = ( export const calculateResourceProduction = (
planet: Planet, planet: Planet,
@@ -100,6 +121,11 @@ export const calculateResourceProduction = (
resourceProductionBonus: number resourceProductionBonus: number
darkMatterProductionBonus: number darkMatterProductionBonus: number
energyProductionBonus: number energyProductionBonus: number
},
techBonuses?: {
mineralResearchLevel?: number
crystalResearchLevel?: number
fuelResearchLevel?: number
} }
): Resources => { ): Resources => {
const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0 const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0
@@ -110,6 +136,16 @@ export const calculateResourceProduction = (
const resourceBonus = 1 + (bonuses.resourceProductionBonus || 0) / 100 const resourceBonus = 1 + (bonuses.resourceProductionBonus || 0) / 100
const darkMatterBonus = 1 + (bonuses.darkMatterProductionBonus || 0) / 100 const darkMatterBonus = 1 + (bonuses.darkMatterProductionBonus || 0) / 100
// 计算科技加成
const researchBonus = calculateResearchProductionBonus(
techBonuses?.mineralResearchLevel || 0,
techBonuses?.crystalResearchLevel || 0,
techBonuses?.fuelResearchLevel || 0
)
const metalTechBonus = 1 + researchBonus.metalBonus / 100
const crystalTechBonus = 1 + researchBonus.crystalBonus / 100
const deuteriumTechBonus = 1 + researchBonus.deuteriumBonus / 100
// 计算能量产出(每小时) // 计算能量产出(每小时)
const energyProduction = calculateEnergyProduction(planet, { energyProductionBonus: bonuses.energyProductionBonus }) const energyProduction = calculateEnergyProduction(planet, { energyProductionBonus: bonuses.energyProductionBonus })
@@ -131,13 +167,28 @@ export const calculateResourceProduction = (
const deuteriumTempBonus = planetLogic.calculateDeuteriumTemperatureBonus(planet.temperature?.max ?? 0) const deuteriumTempBonus = planetLogic.calculateDeuteriumTemperatureBonus(planet.temperature?.max ?? 0)
return { return {
metal: metalMineLevel * 1500 * Math.pow(1.5, metalMineLevel) * resourceBonus * productionEfficiency * metalDepositEfficiency, metal:
crystal: crystalMineLevel * 1000 * Math.pow(1.5, crystalMineLevel) * resourceBonus * productionEfficiency * crystalDepositEfficiency, metalMineLevel *
1500 *
Math.pow(1.5, metalMineLevel) *
resourceBonus *
metalTechBonus *
productionEfficiency *
metalDepositEfficiency,
crystal:
crystalMineLevel *
1000 *
Math.pow(1.5, crystalMineLevel) *
resourceBonus *
crystalTechBonus *
productionEfficiency *
crystalDepositEfficiency,
deuterium: deuterium:
deuteriumSynthesizerLevel * deuteriumSynthesizerLevel *
500 * 500 *
Math.pow(1.5, deuteriumSynthesizerLevel) * Math.pow(1.5, deuteriumSynthesizerLevel) *
resourceBonus * resourceBonus *
deuteriumTechBonus *
productionEfficiency * productionEfficiency *
deuteriumDepositEfficiency * deuteriumDepositEfficiency *
deuteriumTempBonus, deuteriumTempBonus,
@@ -183,7 +234,13 @@ export const updatePlanetResources = (
energyProductionBonus: number energyProductionBonus: number
storageCapacityBonus: number storageCapacityBonus: number
}, },
gameSpeed: number = 1 gameSpeed: number = 1,
miningTechLevel: number = 0,
techBonuses?: {
mineralResearchLevel?: number
crystalResearchLevel?: number
fuelResearchLevel?: number
}
): void => { ): void => {
const timeDiff = (now - planet.lastUpdate) / 1000 // 转换为秒 const timeDiff = (now - planet.lastUpdate) / 1000 // 转换为秒
@@ -214,11 +271,15 @@ export const updatePlanetResources = (
planet.resources.energy = Math.max(0, planet.resources.energy) planet.resources.energy = Math.max(0, planet.resources.energy)
// 计算资源产量(会检查能量是否充足,以及矿脉储量效率) // 计算资源产量(会检查能量是否充足,以及矿脉储量效率)
const production = calculateResourceProduction(planet, { const production = calculateResourceProduction(
resourceProductionBonus: bonuses.resourceProductionBonus, planet,
darkMatterProductionBonus: bonuses.darkMatterProductionBonus, {
energyProductionBonus: bonuses.energyProductionBonus resourceProductionBonus: bonuses.resourceProductionBonus,
}) darkMatterProductionBonus: bonuses.darkMatterProductionBonus,
energyProductionBonus: bonuses.energyProductionBonus
},
techBonuses
)
// 计算实际产出量(用于消耗矿脉储量) // 计算实际产出量(用于消耗矿脉储量)
const metalProduced = (production.metal * effectiveTimeDiff) / 3600 const metalProduced = (production.metal * effectiveTimeDiff) / 3600
@@ -232,10 +293,11 @@ export const updatePlanetResources = (
oreDepositLogic.consumeDeposit(planet.oreDeposits, 'deuterium', deuteriumProduced) oreDepositLogic.consumeDeposit(planet.oreDeposits, 'deuterium', deuteriumProduced)
// 矿脉缓慢恢复(每次更新时恢复一小部分) // 矿脉缓慢恢复(每次更新时恢复一小部分)
// 地质研究站等级影响恢复速度 // 地质研究站等级影响恢复速度,深层钻探设施和采矿技术影响恢复上限
const hoursElapsed = effectiveTimeDiff / 3600 const hoursElapsed = effectiveTimeDiff / 3600
const geoStationLevel = planet.buildings[BuildingType.GeoResearchStation] || 0 const geoStationLevel = planet.buildings[BuildingType.GeoResearchStation] || 0
oreDepositLogic.regenerateDeposits(planet.oreDeposits, hoursElapsed, geoStationLevel) const deepDrillingLevel = planet.buildings[BuildingType.DeepDrillingFacility] || 0
oreDepositLogic.regenerateDeposits(planet.oreDeposits, hoursElapsed, geoStationLevel, deepDrillingLevel, miningTechLevel)
} }
// 更新资源(转换为每秒产量,应用游戏速度) // 更新资源(转换为每秒产量,应用游戏速度)
@@ -386,12 +448,22 @@ export interface ConsumptionDetail {
/** /**
* 计算资源产量详细breakdown * 计算资源产量详细breakdown
* @param planet 星球
* @param officers 军官
* @param currentTime 当前时间
* @param resourceSpeed 游戏速度
* @param techBonuses 科技加成(可选,矿物研究/晶体研究/燃料研究)
*/ */
export const calculateProductionBreakdown = ( export const calculateProductionBreakdown = (
planet: Planet, planet: Planet,
officers: Record<OfficerType, Officer>, officers: Record<OfficerType, Officer>,
currentTime: number, currentTime: number,
resourceSpeed: number = 1 resourceSpeed: number = 1,
techBonuses?: {
mineralResearchLevel?: number
crystalResearchLevel?: number
fuelResearchLevel?: number
}
): ProductionBreakdown => { ): ProductionBreakdown => {
const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0 const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0
const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0 const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0
@@ -432,6 +504,13 @@ export const calculateProductionBreakdown = (
const totalDarkMatterBonus = activeOfficerBonuses.reduce((sum, officer) => sum + officer.darkMatterBonus, 0) const totalDarkMatterBonus = activeOfficerBonuses.reduce((sum, officer) => sum + officer.darkMatterBonus, 0)
const totalEnergyBonus = activeOfficerBonuses.reduce((sum, officer) => sum + officer.energyBonus, 0) const totalEnergyBonus = activeOfficerBonuses.reduce((sum, officer) => sum + officer.energyBonus, 0)
// 计算科技加成
const researchBonus = calculateResearchProductionBonus(
techBonuses?.mineralResearchLevel || 0,
techBonuses?.crystalResearchLevel || 0,
techBonuses?.fuelResearchLevel || 0
)
// 金属矿产量 // 金属矿产量
const metalBase = metalMineLevel * 1500 * Math.pow(1.5, metalMineLevel) const metalBase = metalMineLevel * 1500 * Math.pow(1.5, metalMineLevel)
const metalBonuses: ProductionBonus[] = [] const metalBonuses: ProductionBonus[] = []
@@ -449,16 +528,27 @@ export const calculateProductionBreakdown = (
} }
}) })
// 添加矿物研究科技加成
if (researchBonus.metalBonus > 0) {
const techBonusValue = metalBase * (1 + totalResourceBonus / 100) * (researchBonus.metalBonus / 100)
metalBonuses.push({
name: 'research.mineralResearch',
percentage: researchBonus.metalBonus,
value: techBonusValue,
source: 'technology'
})
}
if (!hasPositiveEnergyBalance) { if (!hasPositiveEnergyBalance) {
metalBonuses.push({ metalBonuses.push({
name: 'resources.noEnergy', name: 'resources.noEnergy',
percentage: -100, percentage: -100,
value: -metalBase * (1 + totalResourceBonus / 100), value: -metalBase * (1 + totalResourceBonus / 100) * (1 + researchBonus.metalBonus / 100),
source: 'other' source: 'other'
}) })
} }
const metalFinal = metalBase * (1 + totalResourceBonus / 100) * productionEfficiency const metalFinal = metalBase * (1 + totalResourceBonus / 100) * (1 + researchBonus.metalBonus / 100) * productionEfficiency
// 晶体矿产量 // 晶体矿产量
const crystalBase = crystalMineLevel * 1000 * Math.pow(1.5, crystalMineLevel) const crystalBase = crystalMineLevel * 1000 * Math.pow(1.5, crystalMineLevel)
@@ -476,16 +566,27 @@ export const calculateProductionBreakdown = (
} }
}) })
// 添加晶体研究科技加成
if (researchBonus.crystalBonus > 0) {
const techBonusValue = crystalBase * (1 + totalResourceBonus / 100) * (researchBonus.crystalBonus / 100)
crystalBonuses.push({
name: 'research.crystalResearch',
percentage: researchBonus.crystalBonus,
value: techBonusValue,
source: 'technology'
})
}
if (!hasPositiveEnergyBalance) { if (!hasPositiveEnergyBalance) {
crystalBonuses.push({ crystalBonuses.push({
name: 'resources.noEnergy', name: 'resources.noEnergy',
percentage: -100, percentage: -100,
value: -crystalBase * (1 + totalResourceBonus / 100), value: -crystalBase * (1 + totalResourceBonus / 100) * (1 + researchBonus.crystalBonus / 100),
source: 'other' source: 'other'
}) })
} }
const crystalFinal = crystalBase * (1 + totalResourceBonus / 100) * productionEfficiency const crystalFinal = crystalBase * (1 + totalResourceBonus / 100) * (1 + researchBonus.crystalBonus / 100) * productionEfficiency
// 重氢合成器产量(受温度影响) // 重氢合成器产量(受温度影响)
// OGame 原版规则:温度越低,重氢产量越高 // OGame 原版规则:温度越低,重氢产量越高
@@ -518,16 +619,27 @@ export const calculateProductionBreakdown = (
} }
}) })
// 添加燃料研究科技加成
if (researchBonus.deuteriumBonus > 0) {
const techBonusValue = deuteriumBase * (1 + totalResourceBonus / 100) * (researchBonus.deuteriumBonus / 100)
deuteriumBonuses.push({
name: 'research.fuelResearch',
percentage: researchBonus.deuteriumBonus,
value: techBonusValue,
source: 'technology'
})
}
if (!hasPositiveEnergyBalance) { if (!hasPositiveEnergyBalance) {
deuteriumBonuses.push({ deuteriumBonuses.push({
name: 'resources.noEnergy', name: 'resources.noEnergy',
percentage: -100, percentage: -100,
value: -deuteriumBase * (1 + totalResourceBonus / 100), value: -deuteriumBase * (1 + totalResourceBonus / 100) * (1 + researchBonus.deuteriumBonus / 100),
source: 'other' source: 'other'
}) })
} }
const deuteriumFinal = deuteriumBase * (1 + totalResourceBonus / 100) * productionEfficiency const deuteriumFinal = deuteriumBase * (1 + totalResourceBonus / 100) * (1 + researchBonus.deuteriumBonus / 100) * productionEfficiency
// 暗物质收集器产量 // 暗物质收集器产量
const darkMatterBase = darkMatterCollectorLevel * 25 * Math.pow(1.5, darkMatterCollectorLevel) const darkMatterBase = darkMatterCollectorLevel * 25 * Math.pow(1.5, darkMatterCollectorLevel)

View File

@@ -322,3 +322,49 @@ export const addFleet = (currentFleet: Fleet, fleet: Partial<Fleet>): void => {
} }
} }
} }
/**
* 计算舰船拆除返还资源
* 返还50%的建造成本
*/
export const calculateShipScrapRefund = (shipType: ShipType, quantity: number): Resources => {
const cost = calculateShipCost(shipType, quantity)
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
}
}
/**
* 计算舰船拆除时间
* 拆除时间为建造时间的50%
*/
export const calculateShipScrapTime = (
shipType: ShipType,
quantity: number,
buildingSpeedBonus: number = 0,
roboticsFactoryLevel: number = 0,
naniteFactoryLevel: number = 0
): number => {
const buildTime = calculateShipBuildTime(shipType, quantity, buildingSpeedBonus, roboticsFactoryLevel, naniteFactoryLevel)
return Math.floor(buildTime * 0.5)
}
/**
* 创建舰船拆除队列项
*/
export const createShipScrapQueueItem = (shipType: ShipType, quantity: number, scrapTime: number): BuildQueueItem => {
const now = Date.now()
shipQueueIdCounter++
return {
id: `scrap_ship_${now}_${shipQueueIdCounter}`,
type: 'scrap_ship',
itemType: shipType,
quantity,
startTime: now,
endTime: now + scrapTime * 1000
}
}

View File

@@ -201,3 +201,66 @@ export const executeFleetDispatch = (
resourceLogic.deductResources(planet.resources, cargo) resourceLogic.deductResources(planet.resources, cargo)
} }
} }
/**
* 验证舰船拆除的所有条件
*/
export const validateShipScrap = (
planet: Planet,
shipType: ShipType,
quantity: number
): {
valid: boolean
reason?: string
} => {
// 检查数量是否有效
if (quantity <= 0) {
return { valid: false, reason: 'errors.invalidQuantity' }
}
// 检查舰船数量是否足够
const available = planet.fleet[shipType] || 0
if (available < quantity) {
return { valid: false, reason: 'errors.insufficientShips' }
}
return { valid: true }
}
/**
* 执行舰船拆除
*/
export const executeShipScrap = (
planet: Planet,
shipType: ShipType,
quantity: number,
officers: Record<OfficerType, Officer>
): BuildQueueItem => {
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 获取机器人工厂和纳米工厂等级
const roboticsFactoryLevel = planet.buildings[BuildingType.RoboticsFactory] || 0
const naniteFactoryLevel = planet.buildings[BuildingType.NaniteFactory] || 0
// 计算拆除时间
const scrapTime = shipLogic.calculateShipScrapTime(
shipType,
quantity,
bonuses.buildingSpeedBonus,
roboticsFactoryLevel,
naniteFactoryLevel
)
// 计算返还资源
const refund = shipLogic.calculateShipScrapRefund(shipType, quantity)
// 扣除舰船(立即扣除,避免拆除过程中派遣)
planet.fleet[shipType] = (planet.fleet[shipType] || 0) - quantity
// 返还资源
resourceLogic.addResources(planet.resources, refund)
// 创建队列项
return shipLogic.createShipScrapQueueItem(shipType, quantity, scrapTime)
}

View File

@@ -1,14 +1,13 @@
/** /**
* WebDAV 同步服务 * WebDAV 同步服务
* 支持将存档上传到 WebDAV 服务器如坚果云、Nextcloud、NAS等 * 支持将存档上传到 WebDAV 服务器如坚果云、Nextcloud、NAS等
* 注意WebDAV 配置存储在 gameStore.webdavConfig 中,与用户数据一起持久化
*/ */
export interface WebDAVConfig { import type { WebDAVConfig } from '@/types/game'
serverUrl: string // WebDAV 服务器地址,如 https://dav.jianguoyun.com/dav/
username: string // 用户名 // 重新导出类型以保持向后兼容
password: string // 密码或应用专用密码 export type { WebDAVConfig }
basePath: string // 存档存放路径,如 /ogame-saves/
}
export interface WebDAVFile { export interface WebDAVFile {
name: string name: string
@@ -18,29 +17,47 @@ export interface WebDAVFile {
isDirectory: boolean isDirectory: boolean
} }
const STORAGE_KEY = 'ogame-webdav-config' // WebDAV 消息 key用于 i18n
export const WebDAVMessageKey = {
// 连接相关
connectionSuccess: 'webdav.connectionSuccess',
connectionSuccessDirectoryCreated: 'webdav.connectionSuccessDirectoryCreated',
authFailed: 'webdav.authFailed',
directoryNotExist: 'webdav.directoryNotExist',
connectionFailedHttp: 'webdav.connectionFailedHttp',
networkError: 'webdav.networkError',
connectionError: 'webdav.connectionError',
// 获取保存的 WebDAV 配置 // 上传相关
export const getWebDAVConfig = (): WebDAVConfig | null => { uploadSuccess: 'webdav.uploadSuccess',
try { noWritePermission: 'webdav.noWritePermission',
const saved = localStorage.getItem(STORAGE_KEY) insufficientStorage: 'webdav.insufficientStorage',
if (saved) { uploadFailedHttp: 'webdav.uploadFailedHttp',
return JSON.parse(saved) uploadError: 'webdav.uploadError',
}
} catch (e) {
console.error('Failed to load WebDAV config:', e)
}
return null
}
// 保存 WebDAV 配置 // 下载相关
export const saveWebDAVConfig = (config: WebDAVConfig): void => { fileNotExist: 'webdav.fileNotExist',
localStorage.setItem(STORAGE_KEY, JSON.stringify(config)) downloadFailedHttp: 'webdav.downloadFailedHttp',
} downloadError: 'webdav.downloadError',
// 清除 WebDAV 配置 // 列表相关
export const clearWebDAVConfig = (): void => { listFailedHttp: 'webdav.listFailedHttp',
localStorage.removeItem(STORAGE_KEY) listError: 'webdav.listError',
// 删除相关
deleteFailedHttp: 'webdav.deleteFailedHttp',
deleteError: 'webdav.deleteError'
} as const
export type WebDAVMessageKeyType = (typeof WebDAVMessageKey)[keyof typeof WebDAVMessageKey]
export interface WebDAVResult {
success: boolean
messageKey: WebDAVMessageKeyType
messageParams?: Record<string, string | number>
fileName?: string
data?: string
files?: WebDAVFile[]
} }
// 构建 Authorization header // 构建 Authorization header
@@ -66,7 +83,7 @@ const normalizePath = (serverUrl: string, basePath: string, fileName?: string):
} }
// 测试 WebDAV 连接 // 测试 WebDAV 连接
export const testWebDAVConnection = async (config: WebDAVConfig): Promise<{ success: boolean; message: string }> => { export const testWebDAVConnection = async (config: WebDAVConfig): Promise<WebDAVResult> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath) const url = normalizePath(config.serverUrl, config.basePath)
@@ -80,33 +97,30 @@ export const testWebDAVConnection = async (config: WebDAVConfig): Promise<{ succ
}) })
if (response.ok || response.status === 207) { if (response.ok || response.status === 207) {
return { success: true, message: 'WebDAV 连接成功' } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess }
} }
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败,请检查用户名和密码' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
// 尝试创建目录 // 尝试创建目录
const createResult = await createDirectory(config, config.basePath) const createResult = await createDirectory(config, config.basePath)
if (createResult) { if (createResult) {
return { success: true, message: 'WebDAV 连接成功,已创建存档目录' } return { success: true, messageKey: WebDAVMessageKey.connectionSuccessDirectoryCreated }
} }
return { success: false, message: '目录不存在且无法创建' } return { success: false, messageKey: WebDAVMessageKey.directoryNotExist }
} }
return { success: false, message: `连接失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.connectionFailedHttp, messageParams: { status: response.status } }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
// CORS 错误的处理 // CORS 错误的处理
if (message.includes('Failed to fetch') || message.includes('NetworkError')) { if (errorMessage.includes('Failed to fetch') || errorMessage.includes('NetworkError')) {
return { return { success: false, messageKey: WebDAVMessageKey.networkError }
success: false,
message: '网络错误,可能是 CORS 限制。建议使用支持 CORS 的 WebDAV 服务或通过代理访问。'
}
} }
return { success: false, message: `连接错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.connectionError, messageParams: { error: errorMessage } }
} }
} }
@@ -130,11 +144,7 @@ const createDirectory = async (config: WebDAVConfig, path: string): Promise<bool
} }
// 上传存档到 WebDAV // 上传存档到 WebDAV
export const uploadToWebDAV = async ( export const uploadToWebDAV = async (config: WebDAVConfig, data: string, fileName?: string): Promise<WebDAVResult> => {
config: WebDAVConfig,
data: string,
fileName?: string
): Promise<{ success: boolean; message: string; fileName?: string }> => {
try { try {
// 生成带时间戳的文件名 // 生成带时间戳的文件名
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19) const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
@@ -151,25 +161,25 @@ export const uploadToWebDAV = async (
}) })
if (response.ok || response.status === 201 || response.status === 204) { if (response.ok || response.status === 201 || response.status === 204) {
return { success: true, message: '上传成功', fileName: actualFileName } return { success: true, messageKey: WebDAVMessageKey.uploadSuccess, fileName: actualFileName }
} }
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 403) { if (response.status === 403) {
return { success: false, message: '没有写入权限' } return { success: false, messageKey: WebDAVMessageKey.noWritePermission }
} }
if (response.status === 507) { if (response.status === 507) {
return { success: false, message: '存储空间不足' } return { success: false, messageKey: WebDAVMessageKey.insufficientStorage }
} }
return { success: false, message: `上传失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.uploadFailedHttp, messageParams: { status: response.status } }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `上传错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.uploadError, messageParams: { error: errorMessage } }
} }
} }
@@ -212,7 +222,7 @@ const parsePropfindResponse = (xml: string, _basePath: string): WebDAVFile[] =>
} }
// 列出 WebDAV 目录中的存档文件 // 列出 WebDAV 目录中的存档文件
export const listWebDAVFiles = async (config: WebDAVConfig): Promise<{ success: boolean; files?: WebDAVFile[]; message?: string }> => { export const listWebDAVFiles = async (config: WebDAVConfig): Promise<WebDAVResult> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath) const url = normalizePath(config.serverUrl, config.basePath)
@@ -227,29 +237,26 @@ export const listWebDAVFiles = async (config: WebDAVConfig): Promise<{ success:
if (!response.ok && response.status !== 207) { if (!response.ok && response.status !== 207) {
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
return { success: false, message: '目录不存在' } return { success: false, messageKey: WebDAVMessageKey.directoryNotExist }
} }
return { success: false, message: `获取文件列表失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.listFailedHttp, messageParams: { status: response.status } }
} }
const xml = await response.text() const xml = await response.text()
const files = parsePropfindResponse(xml, config.basePath) const files = parsePropfindResponse(xml, config.basePath)
return { success: true, files } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess, files }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `获取文件列表错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.listError, messageParams: { error: errorMessage } }
} }
} }
// 从 WebDAV 下载存档 // 从 WebDAV 下载存档
export const downloadFromWebDAV = async ( export const downloadFromWebDAV = async (config: WebDAVConfig, fileName: string): Promise<WebDAVResult> => {
config: WebDAVConfig,
fileName: string
): Promise<{ success: boolean; data?: string; message?: string }> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath, fileName) const url = normalizePath(config.serverUrl, config.basePath, fileName)
@@ -262,27 +269,24 @@ export const downloadFromWebDAV = async (
if (!response.ok) { if (!response.ok) {
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
return { success: false, message: '文件不存在' } return { success: false, messageKey: WebDAVMessageKey.fileNotExist }
} }
return { success: false, message: `下载失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.downloadFailedHttp, messageParams: { status: response.status } }
} }
const data = await response.text() const data = await response.text()
return { success: true, data } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess, data }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `下载错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.downloadError, messageParams: { error: errorMessage } }
} }
} }
// 删除 WebDAV 文件 // 删除 WebDAV 文件
export const deleteFromWebDAV = async ( export const deleteFromWebDAV = async (config: WebDAVConfig, fileName: string): Promise<WebDAVResult> => {
config: WebDAVConfig,
fileName: string
): Promise<{ success: boolean; message?: string }> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath, fileName) const url = normalizePath(config.serverUrl, config.basePath, fileName)
@@ -294,20 +298,20 @@ export const deleteFromWebDAV = async (
}) })
if (response.ok || response.status === 204) { if (response.ok || response.status === 204) {
return { success: true } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess }
} }
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
return { success: true } // 文件不存在也视为删除成功 return { success: true, messageKey: WebDAVMessageKey.connectionSuccess } // 文件不存在也视为删除成功
} }
return { success: false, message: `删除失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.deleteFailedHttp, messageParams: { status: response.status } }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `删除错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.deleteError, messageParams: { error: errorMessage } }
} }
} }

View File

@@ -12,7 +12,8 @@ import type {
IncomingFleetAlert, IncomingFleetAlert,
MissileAttack, MissileAttack,
AchievementStats, AchievementStats,
AchievementProgress AchievementProgress,
WebDAVConfig
} from '@/types/game' } from '@/types/game'
import { TechnologyType, OfficerType } from '@/types/game' import { TechnologyType, OfficerType } from '@/types/game'
import { initializeAchievementStats, initializeAchievements } from '@/logic/achievementLogic' import { initializeAchievementStats, initializeAchievements } from '@/logic/achievementLogic'
@@ -25,6 +26,7 @@ export const useGameStore = defineStore('game', {
gameTime: Date.now(), gameTime: Date.now(),
isPaused: false, isPaused: false,
gameSpeed: 1, gameSpeed: 1,
battleToFinish: true, // 战斗到底模式false=经典模式(6回合平局)true=战斗到底(最多100回合)
player: { player: {
id: 'player1', id: 'player1',
name: '', name: '',
@@ -44,6 +46,7 @@ export const useGameStore = defineStore('game', {
giftNotifications: [], giftNotifications: [],
giftRejectedNotifications: [], giftRejectedNotifications: [],
points: 0, points: 0,
bonusPoints: 0,
isGMEnabled: false, // 明确设置 GM 模式默认为 false isGMEnabled: false, // 明确设置 GM 模式默认为 false
lastVersionCheckTime: 0, // 最后一次检查版本的时间戳默认为0 lastVersionCheckTime: 0, // 最后一次检查版本的时间戳默认为0
achievementStats: initializeAchievementStats() as AchievementStats, achievementStats: initializeAchievementStats() as AchievementStats,
@@ -61,7 +64,8 @@ export const useGameStore = defineStore('game', {
research: true, research: true,
unlock: true unlock: true
} }
} },
webdavConfig: null as WebDAVConfig | null
}), }),
actions: { actions: {
async requestBrowserPermission(): Promise<boolean> { async requestBrowserPermission(): Promise<boolean> {

View File

@@ -120,6 +120,15 @@
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
html {
/* 平滑过渡 */
transition: background-color 0.3s ease, color 0.3s ease;
/* 禁用文本大小调整,防止移动端自动放大文本 */
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
@@ -128,11 +137,6 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
/* 平滑过渡 */
html {
transition: background-color 0.3s ease, color 0.3s ease;
}
/* 滚动条样式 */ /* 滚动条样式 */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
@@ -156,12 +160,12 @@
@keyframes fade-in { @keyframes fade-in {
from { from {
opacity: 0; opacity: 0;
transform: translateY(10px); transform: translate3d(0, 10px, 0);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translate3d(0, 0, 0);
} }
} }
@@ -175,7 +179,7 @@ aside nav a {
} }
aside nav a:hover button { aside nav a:hover button {
transform: translateX(4px); transform: translate3d(4px, 0, 0);
} }
/* 资源数字更新动画 */ /* 资源数字更新动画 */
@@ -217,4 +221,22 @@ aside nav a:hover button {
:root:not(.dark) .tooltip-content-custom { :root:not(.dark) .tooltip-content-custom {
background-color: oklch(0.3 0.02 85) !important; background-color: oklch(0.3 0.02 85) !important;
color: oklch(0.95 0.008 85) !important; color: oklch(0.95 0.008 85) !important;
}
/* 修复 Edge-to-Edge 模式下 Portal 容器的定位问题 */
[data-reka-portal],
[data-radix-portal] {
position: fixed !important;
left: 0 !important;
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
pointer-events: none;
padding: 0 !important;
margin: 0 !important;
}
[data-reka-portal] > *,
[data-radix-portal] > * {
pointer-events: auto;
} }

View File

@@ -50,7 +50,9 @@ export const BuildingType = {
// 特殊建筑 // 特殊建筑
PlanetDestroyerFactory: 'planetDestroyerFactory', // 行星毁灭者工厂 PlanetDestroyerFactory: 'planetDestroyerFactory', // 行星毁灭者工厂
GeoResearchStation: 'geoResearchStation', // 地质研究站(影响矿脉恢复速度) GeoResearchStation: 'geoResearchStation', // 地质研究站(影响矿脉恢复速度)
DeepDrillingFacility: 'deepDrillingFacility' // 深层钻探设施(提升矿脉上限) DeepDrillingFacility: 'deepDrillingFacility', // 深层钻探设施(提升矿脉上限)
// 2moons新增建筑
University: 'university' // 大学(加速研究)
} as const } as const
export type BuildingType = (typeof BuildingType)[keyof typeof BuildingType] export type BuildingType = (typeof BuildingType)[keyof typeof BuildingType]
@@ -98,7 +100,12 @@ export const TechnologyType = {
DarkMatterTechnology: 'darkMatterTechnology', // 暗物质技术 DarkMatterTechnology: 'darkMatterTechnology', // 暗物质技术
TerraformingTechnology: 'terraformingTechnology', // 地形改造技术 TerraformingTechnology: 'terraformingTechnology', // 地形改造技术
PlanetDestructionTech: 'planetDestructionTech', // 行星毁灭技术 PlanetDestructionTech: 'planetDestructionTech', // 行星毁灭技术
MiningTechnology: 'miningTechnology' // 采矿技术(提升矿脉上限) MiningTechnology: 'miningTechnology', // 采矿技术(提升矿脉上限)
// 2moons新增科技
IntergalacticResearchNetwork: 'intergalacticResearchNetwork', // 星际研究网络(连接多个研究实验室)
MineralResearch: 'mineralResearch', // 矿物研究(提升金属产量)
CrystalResearch: 'crystalResearch', // 晶体研究(提升晶体产量)
FuelResearch: 'fuelResearch' // 燃料研究(提升重氢产量)
} as const } as const
export type TechnologyType = (typeof TechnologyType)[keyof typeof TechnologyType] export type TechnologyType = (typeof TechnologyType)[keyof typeof TechnologyType]
@@ -496,6 +503,8 @@ export interface MissionReport {
// 毁灭任务:概率和死星损失 // 毁灭任务:概率和死星损失
destructionChance?: number destructionChance?: number
deathstarsLost?: boolean deathstarsLost?: boolean
// 毁灭任务:是否发生了战斗
hadBattle?: boolean
// 部署任务:部署的舰队 // 部署任务:部署的舰队
deployedFleet?: Partial<Fleet> deployedFleet?: Partial<Fleet>
// 导弹攻击任务:导弹信息 // 导弹攻击任务:导弹信息
@@ -656,7 +665,7 @@ export interface DebrisField {
// 建造队列项 // 建造队列项
export interface BuildQueueItem { export interface BuildQueueItem {
id: string id: string
type: 'building' | 'technology' | 'ship' | 'defense' | 'demolish' type: 'building' | 'technology' | 'ship' | 'defense' | 'demolish' | 'scrap_ship'
itemType: BuildingType | TechnologyType | ShipType | DefenseType itemType: BuildingType | TechnologyType | ShipType | DefenseType
targetLevel?: number // 用于建筑和科技 targetLevel?: number // 用于建筑和科技
quantity?: number // 用于舰船和防御 quantity?: number // 用于舰船和防御
@@ -667,7 +676,7 @@ export interface BuildQueueItem {
// 等待队列项(尚未开始执行,不需要 startTime/endTime // 等待队列项(尚未开始执行,不需要 startTime/endTime
export interface WaitingQueueItem { export interface WaitingQueueItem {
id: string id: string
type: 'building' | 'technology' | 'ship' | 'defense' | 'demolish' type: 'building' | 'technology' | 'ship' | 'defense' | 'demolish' | 'scrap_ship'
itemType: BuildingType | TechnologyType | ShipType | DefenseType itemType: BuildingType | TechnologyType | ShipType | DefenseType
targetLevel?: number // 用于建筑和科技 targetLevel?: number // 用于建筑和科技
quantity?: number // 用于舰船和防御 quantity?: number // 用于舰船和防御
@@ -790,6 +799,7 @@ export interface Player {
allyDefenseNotifications?: AllyDefenseNotification[] // 友好NPC协防通知 allyDefenseNotifications?: AllyDefenseNotification[] // 友好NPC协防通知
attitudeChangeNotifications?: AttitudeChangeNotification[] // NPC态度变化通知 attitudeChangeNotifications?: AttitudeChangeNotification[] // NPC态度变化通知
points: number // 总积分每1000资源=1分 points: number // 总积分每1000资源=1分
bonusPoints?: number // 奖励积分(战役、成就等奖励的积分,不会被重新计算覆盖)
isGMEnabled?: boolean // GM模式开关默认false通过秘籍激活 isGMEnabled?: boolean // GM模式开关默认false通过秘籍激活
lastVersionCheckTime?: number // 最后一次自动检查版本的时间戳(被动检测) lastVersionCheckTime?: number // 最后一次自动检查版本的时间戳(被动检测)
lastManualUpdateCheck?: number // 最后一次手动检查更新的时间戳(主动检测) lastManualUpdateCheck?: number // 最后一次手动检查更新的时间戳(主动检测)
@@ -1226,3 +1236,11 @@ export interface QuestNotification {
rewards?: QuestReward rewards?: QuestReward
read?: boolean read?: boolean
} }
// WebDAV 配置
export interface WebDAVConfig {
serverUrl: string // WebDAV 服务器地址
username: string // 用户名
password: string // 密码或应用专用密码
basePath: string // 存档存放路径
}

View File

@@ -109,7 +109,10 @@ const COMBAT_EFFECTS = {
* 计算一个单位对另一个单位造成的伤害 * 计算一个单位对另一个单位造成的伤害
* 增强版:包含护盾弹回、重型武器穿透、装甲损坏累积 * 增强版:包含护盾弹回、重型武器穿透、装甲损坏累积
*/ */
const calculateDamage = (attacker: CombatUnit, defender: CombatUnit): { destroyed: number; damagedShield: number; armorDamageDealt: number } => { const calculateDamage = (
attacker: CombatUnit,
defender: CombatUnit
): { destroyed: number; damagedShield: number; armorDamageDealt: number } => {
const attackPower = attacker.attack const attackPower = attacker.attack
// 使用当前护盾值(如果有),否则使用最大护盾 // 使用当前护盾值(如果有),否则使用最大护盾
const defenderCurrentShield = defender.currentShield ?? defender.shield const defenderCurrentShield = defender.currentShield ?? defender.shield
@@ -157,14 +160,8 @@ const calculateDamage = (attacker: CombatUnit, defender: CombatUnit): { destroye
// 穿透护盾后对装甲造成损坏累积 // 穿透护盾后对装甲造成损坏累积
if (remainingDamage > 0) { if (remainingDamage > 0) {
armorDamageDealt = Math.min( armorDamageDealt = Math.min(COMBAT_EFFECTS.ARMOR_DAMAGE_RATE, COMBAT_EFFECTS.MAX_ARMOR_DAMAGE - (defender.armorDamage ?? 0))
COMBAT_EFFECTS.ARMOR_DAMAGE_RATE, defender.armorDamage = Math.min(COMBAT_EFFECTS.MAX_ARMOR_DAMAGE, (defender.armorDamage ?? 0) + armorDamageDealt)
COMBAT_EFFECTS.MAX_ARMOR_DAMAGE - (defender.armorDamage ?? 0)
)
defender.armorDamage = Math.min(
COMBAT_EFFECTS.MAX_ARMOR_DAMAGE,
(defender.armorDamage ?? 0) + armorDamageDealt
)
} }
// 再消耗装甲 // 再消耗装甲
@@ -453,8 +450,24 @@ export const simulateBattle = (
} else if (defenderUnits.length === 0) { } else if (defenderUnits.length === 0) {
winner = 'attacker' winner = 'attacker'
} else { } else {
// OGame原版规则6回合后双方都有剩余单位时判定为平局 // 达到最大回合后双方都有剩余单位
winner = 'draw' // 如果是战斗到底模式(maxRounds > 6),根据剩余战力判定胜负
if (maxRounds > 6) {
// 计算剩余战力
const attackerPower = attackerUnits.reduce((sum, u) => sum + u.count * u.armor, 0)
const defenderPower = defenderUnits.reduce((sum, u) => sum + u.count * u.armor, 0)
// 战力差距超过20%判定胜负,否则平局
if (attackerPower > defenderPower * 1.2) {
winner = 'attacker'
} else if (defenderPower > attackerPower * 1.2) {
winner = 'defender'
} else {
winner = 'draw'
}
} else {
// OGame原版规则6回合后双方都有剩余单位时判定为平局
winner = 'draw'
}
} }
return { return {

View File

@@ -57,9 +57,15 @@
<!-- 防守方配置 --> <!-- 防守方配置 -->
<TabsContent value="defender" class="mt-4"> <TabsContent value="defender" class="mt-4">
<Card> <Card>
<CardHeader> <CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle>{{ t('simulatorView.defenderConfig') }}</CardTitle> <div class="space-y-1">
<CardDescription>{{ t('simulatorView.defenderConfigDesc') }}</CardDescription> <CardTitle>{{ t('simulatorView.defenderConfig') }}</CardTitle>
<CardDescription>{{ t('simulatorView.defenderConfigDesc') }}</CardDescription>
</div>
<Button variant="outline" size="sm" @click="showSpyReportSelector = true" :disabled="!gameStore.player?.spyReports?.length">
<FileDown class="h-4 w-4 mr-2" />
{{ t('simulatorView.importFromSpyReport') }}
</Button>
</CardHeader> </CardHeader>
<CardContent class="space-y-4"> <CardContent class="space-y-4">
<!-- 舰队配置 --> <!-- 舰队配置 -->
@@ -147,28 +153,80 @@
<!-- 战斗结果对话框 --> <!-- 战斗结果对话框 -->
<BattleReportDialog v-model:open="showResultDialog" :report="simulationResult" /> <BattleReportDialog v-model:open="showResultDialog" :report="simulationResult" />
<!-- 侦查报告选择对话框 -->
<Dialog v-model:open="showSpyReportSelector">
<DialogContent class="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{{ t('simulatorView.selectSpyReport') }}</DialogTitle>
</DialogHeader>
<div class="space-y-2">
<div v-if="!sortedSpyReports.length" class="text-center py-8 text-muted-foreground">
{{ t('simulatorView.noSpyReports') }}
</div>
<div
v-for="report in sortedSpyReports"
:key="report.id"
@click="importFromSpyReport(report)"
class="p-3 border rounded-lg cursor-pointer hover:bg-accent transition-colors"
>
<div class="flex justify-between items-start">
<div>
<div class="font-medium">{{ report.targetPlanetName }}</div>
<div class="text-sm text-muted-foreground">
[{{ report.targetPosition.galaxy }}:{{ report.targetPosition.system }}:{{ report.targetPosition.position }}]
</div>
</div>
<div class="text-sm text-muted-foreground">
{{ formatTime(report.timestamp) }}
</div>
</div>
<div class="mt-2 flex gap-4 text-xs">
<span class="flex items-center gap-1">
<ResourceIcon type="metal" size="sm" />
{{ formatNumber(report.resources.metal) }}
</span>
<span class="flex items-center gap-1">
<ResourceIcon type="crystal" size="sm" />
{{ formatNumber(report.resources.crystal) }}
</span>
<span class="flex items-center gap-1">
<ResourceIcon type="deuterium" size="sm" />
{{ formatNumber(report.resources.deuterium) }}
</span>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, toRaw } from 'vue' import { ref, toRaw, computed } from 'vue'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { useGameConfig } from '@/composables/useGameConfig' import { useGameConfig } from '@/composables/useGameConfig'
import { useGameStore } from '@/stores/gameStore'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { ShipType, DefenseType } from '@/types/game' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import type { Fleet, BattleResult } from '@/types/game' import { ShipType, DefenseType, TechnologyType } from '@/types/game'
import type { Fleet, BattleResult, SpyReport } from '@/types/game'
import { workerManager } from '@/workers/workerManager' import { workerManager } from '@/workers/workerManager'
import ResourceIcon from '@/components/common/ResourceIcon.vue' import ResourceIcon from '@/components/common/ResourceIcon.vue'
import BattleReportDialog from '@/components/dialogs/BattleReportDialog.vue' import BattleReportDialog from '@/components/dialogs/BattleReportDialog.vue'
import { Sword, Shield, Zap, RotateCcw } from 'lucide-vue-next' import { Sword, Shield, Zap, RotateCcw, FileDown } from 'lucide-vue-next'
import * as planetLogic from '@/logic/planetLogic' import * as planetLogic from '@/logic/planetLogic'
const { t } = useI18n() const { t } = useI18n()
const { SHIPS, DEFENSES } = useGameConfig() const { SHIPS, DEFENSES } = useGameConfig()
const gameStore = useGameStore()
// 侦查报告选择对话框状态
const showSpyReportSelector = ref(false)
// 科技类型配置 // 科技类型配置
const techTypes = ['weapon', 'shield', 'armor'] as const const techTypes = ['weapon', 'shield', 'armor'] as const
@@ -249,7 +307,8 @@
// 使用 Worker 执行战斗模拟 // 使用 Worker 执行战斗模拟
const result = await workerManager.simulateBattle({ const result = await workerManager.simulateBattle({
attacker: attackerSide, attacker: attackerSide,
defender: defenderSide defender: defenderSide,
maxRounds: gameStore.battleToFinish ? 100 : 6
}) })
// 计算掠夺和残骸场 // 计算掠夺和残骸场
@@ -302,4 +361,65 @@
simulationResult.value = null simulationResult.value = null
showResultDialog.value = false showResultDialog.value = false
} }
// 按时间排序的侦查报告
const sortedSpyReports = computed(() => {
return [...(gameStore.player?.spyReports || [])].sort((a, b) => b.timestamp - a.timestamp)
})
// 格式化时间
const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleString()
}
// 格式化数字
const formatNumber = (num: number) => {
if (num >= 1e9) return (num / 1e9).toFixed(1) + 'B'
if (num >= 1e6) return (num / 1e6).toFixed(1) + 'M'
if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K'
return num.toString()
}
// 从侦查报告导入数据
const importFromSpyReport = (report: SpyReport) => {
// 先重置防守方数据
defenderFleet.value = initializeFleet()
defenderDefense.value = initializeDefense()
// 填入资源
if (report.resources) {
defenderResources.value = {
metal: report.resources.metal || 0,
crystal: report.resources.crystal || 0,
deuterium: report.resources.deuterium || 0,
darkMatter: report.resources.darkMatter || 0,
energy: 0
}
}
// 填入舰队
if (report.fleet) {
Object.entries(report.fleet).forEach(([key, value]) => {
defenderFleet.value[key as keyof Fleet] = value || 0
})
}
// 填入防御
if (report.defense) {
Object.entries(report.defense).forEach(([key, value]) => {
defenderDefense.value[key as DefenseType] = value || 0
})
}
// 填入科技
if (report.technologies) {
defenderTech.value.weapon = report.technologies[TechnologyType.WeaponsTechnology] || 0
defenderTech.value.shield = report.technologies[TechnologyType.ShieldingTechnology] || 0
defenderTech.value.armor = report.technologies[TechnologyType.ArmourTechnology] || 0
}
// 关闭对话框并切换到防守方标签
showSpyReportSelector.value = false
activeTab.value = 'defender'
}
</script> </script>

View File

@@ -181,133 +181,6 @@
</ScrollableDialogContent> </ScrollableDialogContent>
</Dialog> </Dialog>
<!-- NPC互动面板 - 贸易提议情报联合攻击邀请 -->
<div v-if="hasNpcInteractions" class="space-y-4">
<Collapsible v-model:open="interactionPanelOpen" class="border rounded-lg">
<CollapsibleTrigger class="flex items-center justify-between w-full p-4 hover:bg-accent/50 transition-colors">
<div class="flex items-center gap-2">
<Handshake class="h-5 w-5 text-primary" />
<span class="font-semibold">{{ t('npcBehavior.trade.title') }} & {{ t('npcBehavior.intel.title') }}</span>
<Badge variant="destructive" v-if="totalInteractionCount > 0">{{ totalInteractionCount }}</Badge>
</div>
<ChevronDown class="h-4 w-4 transition-transform" :class="{ 'rotate-180': interactionPanelOpen }" />
</CollapsibleTrigger>
<CollapsibleContent class="px-4 pb-4 space-y-4">
<!-- 贸易提议 -->
<div v-if="activeTradeOffers.length > 0">
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
<ArrowLeftRight class="h-4 w-4" />
{{ t('npcBehavior.trade.title') }} ({{ activeTradeOffers.length }})
</h3>
<div class="grid gap-2">
<Card v-for="offer in activeTradeOffers" :key="offer.id" class="p-3">
<div class="flex items-start justify-between gap-4">
<div class="flex-1 space-y-1">
<div class="font-medium">{{ getNpcName(offer.npcId) }}</div>
<div class="text-sm text-muted-foreground">
<span class="text-green-600 dark:text-green-400">{{ t('npcBehavior.trade.offers') }}:</span>
{{ formatResources(offer.offeredResources) }}
</div>
<div class="text-sm text-muted-foreground">
<span class="text-red-600 dark:text-red-400">{{ t('npcBehavior.trade.requests') }}:</span>
{{ formatResources(offer.requestedResources) }}
</div>
<div class="text-xs text-muted-foreground">
{{ t('npcBehavior.trade.expiresIn') }}: {{ formatTimeRemaining(offer.expiresAt) }}
</div>
</div>
<div class="flex gap-2">
<Button size="sm" variant="default" @click="acceptTradeOffer(offer)" :disabled="!canAcceptTrade(offer)">
{{ t('npcBehavior.trade.accept') }}
</Button>
<Button size="sm" variant="outline" @click="declineTradeOffer(offer)">
{{ t('npcBehavior.trade.decline') }}
</Button>
</div>
</div>
</Card>
</div>
</div>
<!-- 情报报告 -->
<div v-if="unreadIntelReports.length > 0">
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
<Eye class="h-4 w-4" />
{{ t('npcBehavior.intel.title') }} ({{ unreadIntelReports.length }})
</h3>
<div class="grid gap-2">
<Card v-for="intel in unreadIntelReports" :key="intel.id" class="p-3">
<div class="flex items-start justify-between gap-4">
<div class="flex-1 space-y-1">
<div class="font-medium">{{ t('npcBehavior.intel.from') }}: {{ getNpcName(intel.fromNpcId) }}</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.intel.target') }}: {{ getNpcName(intel.targetNpcId) }}
</div>
<div class="text-sm">
<Badge variant="outline">{{ t(`npcBehavior.intel.types.${intel.intelType}`) }}</Badge>
</div>
<div v-if="intel.data?.fleet" class="text-sm text-muted-foreground">
{{ t('npcBehavior.intel.fleetInfo') }}: {{ formatFleetInfo(intel.data.fleet) }}
</div>
<div v-if="intel.data?.resources" class="text-sm text-muted-foreground">
{{ t('npcBehavior.intel.resourceInfo') }}: {{ formatResources(intel.data.resources as Resources) }}
</div>
</div>
<Button size="sm" variant="ghost" @click="markIntelAsRead(intel)">
{{ t('npcBehavior.intel.markAsRead') }}
</Button>
</div>
</Card>
</div>
</div>
<!-- 联合攻击邀请 -->
<div v-if="activeJointAttackInvites.length > 0">
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
<Swords class="h-4 w-4" />
{{ t('npcBehavior.jointAttack.title') }} ({{ activeJointAttackInvites.length }})
</h3>
<div class="grid gap-2">
<Card v-for="invite in activeJointAttackInvites" :key="invite.id" class="p-3">
<div class="flex items-start justify-between gap-4">
<div class="flex-1 space-y-1">
<div class="font-medium">{{ t('npcBehavior.jointAttack.from') }}: {{ getNpcName(invite.fromNpcId) }}</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.jointAttack.target') }}: {{ getNpcName(invite.targetNpcId) }}
</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.jointAttack.targetPlanet') }}: [{{ invite.targetPosition.galaxy }}:{{
invite.targetPosition.system
}}:{{ invite.targetPosition.position }}]
</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.jointAttack.lootShare') }}: {{ (invite.expectedLootRatio * 100).toFixed(0) }}%
</div>
<div class="text-xs text-muted-foreground">
{{ t('npcBehavior.jointAttack.expiresIn') }}: {{ formatTimeRemaining(invite.expiresAt) }}
</div>
</div>
<div class="flex gap-2">
<Button size="sm" variant="default" @click="acceptJointAttack(invite)">
{{ t('npcBehavior.jointAttack.accept') }}
</Button>
<Button size="sm" variant="outline" @click="declineJointAttack(invite)">
{{ t('npcBehavior.jointAttack.decline') }}
</Button>
</div>
</div>
</Card>
</div>
</div>
<!-- 无互动内容提示 -->
<div v-if="!hasActiveInteractions" class="text-center py-4 text-muted-foreground">
{{ t('npcBehavior.trade.noOffers') }}
</div>
</CollapsibleContent>
</Collapsible>
</div>
<!-- 搜索框 --> <!-- 搜索框 -->
<div class="relative"> <div class="relative">
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Search class="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
@@ -503,14 +376,11 @@
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useNPCStore } from '@/stores/npcStore' import { useNPCStore } from '@/stores/npcStore'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { toast } from 'vue-sonner'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Dialog, DialogDescription, DialogTitle } from '@/components/ui/dialog' import { Dialog, DialogDescription, DialogTitle } from '@/components/ui/dialog'
import ScrollableDialogContent from '@/components/ui/dialog/ScrollableDialogContent.vue' import ScrollableDialogContent from '@/components/ui/dialog/ScrollableDialogContent.vue'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import { import {
FixedPagination, FixedPagination,
Pagination, Pagination,
@@ -523,7 +393,7 @@
import NpcRelationCard from '@/components/npc/NpcRelationCard.vue' import NpcRelationCard from '@/components/npc/NpcRelationCard.vue'
import NpcRelationRow from '@/components/npc/NpcRelationRow.vue' import NpcRelationRow from '@/components/npc/NpcRelationRow.vue'
import { RelationStatus } from '@/types/game' import { RelationStatus } from '@/types/game'
import type { DiplomaticRelation, TradeOffer, IntelReport, JointAttackInvite, Resources } from '@/types/game' import type { DiplomaticRelation } from '@/types/game'
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic' import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
import { import {
Search, Search,
@@ -533,11 +403,7 @@
Swords, Swords,
Activity, Activity,
LayoutGrid, LayoutGrid,
List, List
Handshake,
ChevronDown,
ArrowLeftRight,
Eye
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty' import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty'
@@ -548,9 +414,6 @@
const activeTab = ref('all') const activeTab = ref('all')
// NPC互动面板状态
const interactionPanelOpen = ref(true)
// 视图模式: 'card' | 'list' // 视图模式: 'card' | 'list'
const viewMode = ref<'card' | 'list'>('list') const viewMode = ref<'card' | 'list'>('list')
@@ -864,188 +727,6 @@
currentPage.value[activeTab.value] = val currentPage.value[activeTab.value] = val
} }
}) })
// ========== NPC互动面板相关 ==========
// 获取当前时间戳
const now = computed(() => Date.now())
// 有效的贸易提议(未过期)
const activeTradeOffers = computed(() => {
return (gameStore.player.tradeOffers || []).filter(offer => offer.expiresAt > now.value)
})
// 未读的情报报告
const unreadIntelReports = computed(() => {
return (gameStore.player.intelReports || []).filter(report => !report.read)
})
// 有效的联合攻击邀请(未过期)
const activeJointAttackInvites = computed(() => {
return (gameStore.player.jointAttackInvites || []).filter(invite => invite.expiresAt > now.value)
})
// 是否有NPC互动数据
const hasNpcInteractions = computed(() => {
return (
(gameStore.player.tradeOffers?.length || 0) > 0 ||
(gameStore.player.intelReports?.length || 0) > 0 ||
(gameStore.player.jointAttackInvites?.length || 0) > 0
)
})
// 是否有有效的互动内容
const hasActiveInteractions = computed(() => {
return activeTradeOffers.value.length > 0 || unreadIntelReports.value.length > 0 || activeJointAttackInvites.value.length > 0
})
// 总互动数量(用于显示徽章)
const totalInteractionCount = computed(() => {
return activeTradeOffers.value.length + unreadIntelReports.value.length + activeJointAttackInvites.value.length
})
// 获取NPC名称
const getNpcName = (npcId: string): string => {
const npc = npcStore.npcs.find(n => n.id === npcId)
return npc?.name || npcId
}
// 格式化资源显示
// 格式化资源(兼容新旧格式)
const formatResources = (resources: Resources | { type: string; amount: number }): string => {
// 新格式:{ type: 'metal', amount: 1000 }
if ('type' in resources && 'amount' in resources) {
const typeLabels: Record<string, string> = {
metal: 'M',
crystal: 'C',
deuterium: 'D'
}
return `${Math.floor(resources.amount).toLocaleString()} ${typeLabels[resources.type] || resources.type}`
}
// 旧格式:{ metal: 1000, crystal: 0, deuterium: 0 }
const parts: string[] = []
if ((resources as Resources).metal > 0) parts.push(`${Math.floor((resources as Resources).metal).toLocaleString()} M`)
if ((resources as Resources).crystal > 0) parts.push(`${Math.floor((resources as Resources).crystal).toLocaleString()} C`)
if ((resources as Resources).deuterium > 0) parts.push(`${Math.floor((resources as Resources).deuterium).toLocaleString()} D`)
return parts.join(' / ') || '-'
}
// 格式化舰队信息
const formatFleetInfo = (fleetInfo: Record<string, number>): string => {
const parts: string[] = []
for (const [shipType, count] of Object.entries(fleetInfo)) {
if (count > 0) {
parts.push(`${shipType}: ${count}`)
}
}
return parts.join(', ') || '-'
}
// 格式化剩余时间
const formatTimeRemaining = (expiresAt: number): string => {
const remaining = expiresAt - now.value
if (remaining <= 0) return t('npcBehavior.trade.expired')
const minutes = Math.floor(remaining / 60000)
const hours = Math.floor(minutes / 60)
const mins = minutes % 60
if (hours > 0) {
return `${hours}h ${mins}m`
}
return `${mins}m`
}
// 检查是否可以接受贸易(兼容新格式 { type, amount }
const canAcceptTrade = (offer: TradeOffer): boolean => {
const planet = gameStore.player.planets[0]
if (!planet) return false
// 新格式:{ type: 'metal', amount: 1000 }
const requestedType = offer.requestedResources.type
const requestedAmount = offer.requestedResources.amount
return planet.resources[requestedType] >= requestedAmount
}
// 接受贸易提议
const acceptTradeOffer = (offer: TradeOffer) => {
if (!canAcceptTrade(offer)) {
toast.error(t('npcBehavior.trade.acceptFailed'))
return
}
const planet = gameStore.player.planets[0]
if (!planet) return
// 新格式:{ type: 'metal', amount: 1000 }
const requestedType = offer.requestedResources.type
const requestedAmount = offer.requestedResources.amount
const offeredType = offer.offeredResources.type
const offeredAmount = offer.offeredResources.amount
// 扣除请求的资源
planet.resources[requestedType] -= requestedAmount
// 添加获得的资源
planet.resources[offeredType] += offeredAmount
// 移除贸易提议
const index = gameStore.player.tradeOffers?.indexOf(offer)
if (index !== undefined && index >= 0) {
gameStore.player.tradeOffers?.splice(index, 1)
}
// 提高与该NPC的好感度使用 npcId 而不是 fromNpcId
const npcRelation = npcStore.npcs.find(n => n.id === offer.npcId)?.relations?.[gameStore.player.id]
if (npcRelation) {
npcRelation.reputation += 10
}
toast.success(t('npcBehavior.trade.acceptSuccess'))
}
// 拒绝贸易提议
const declineTradeOffer = (offer: TradeOffer) => {
const index = gameStore.player.tradeOffers?.indexOf(offer)
if (index !== undefined && index >= 0) {
gameStore.player.tradeOffers?.splice(index, 1)
}
toast.info(t('npcBehavior.trade.declined'))
}
// 标记情报为已读
const markIntelAsRead = (intel: IntelReport) => {
intel.read = true
}
// 接受联合攻击邀请
const acceptJointAttack = (invite: JointAttackInvite) => {
// 这里可以添加联合攻击的逻辑
// 目前只是简单地移除邀请并显示提示
const index = gameStore.player.jointAttackInvites?.indexOf(invite)
if (index !== undefined && index >= 0) {
gameStore.player.jointAttackInvites?.splice(index, 1)
}
// 提高与该NPC的好感度使用 npcStore
const npcRelation = npcStore.npcs.find(n => n.id === invite.fromNpcId)?.relations?.[gameStore.player.id]
if (npcRelation) {
npcRelation.reputation += 15
}
toast.success(t('npcBehavior.jointAttack.acceptSuccess'))
}
// 拒绝联合攻击邀请
const declineJointAttack = (invite: JointAttackInvite) => {
const index = gameStore.player.jointAttackInvites?.indexOf(invite)
if (index !== undefined && index >= 0) {
gameStore.player.jointAttackInvites?.splice(index, 1)
}
toast.info(t('npcBehavior.jointAttack.declined'))
}
</script> </script>
<style> <style>

View File

@@ -10,8 +10,8 @@
<TabsList :class="['grid', 'w-full', showJumpGateTab ? 'grid-cols-3' : 'grid-cols-2']"> <TabsList :class="['grid', 'w-full', showJumpGateTab ? 'grid-cols-3' : 'grid-cols-2']">
<TabsTrigger v-for="tab in visibleTabs" :key="tab.value" :value="tab.value"> <TabsTrigger v-for="tab in visibleTabs" :key="tab.value" :value="tab.value">
{{ t(`fleetView.${tab.labelKey}`) }} {{ t(`fleetView.${tab.labelKey}`) }}
<Badge v-if="tab.value === 'missions' && gameStore.player.fleetMissions.length > 0" variant="destructive" class="ml-1"> <Badge v-if="tab.value === 'missions' && totalMissionsCount > 0" variant="destructive" class="ml-1">
{{ gameStore.player.fleetMissions.length }} {{ totalMissionsCount }}
</Badge> </Badge>
<Badge v-if="tab.value === 'jumpGate' && jumpGateReady" variant="default" class="ml-1"></Badge> <Badge v-if="tab.value === 'jumpGate' && jumpGateReady" variant="default" class="ml-1"></Badge>
</TabsTrigger> </TabsTrigger>
@@ -294,13 +294,14 @@
<!-- 飞行任务 --> <!-- 飞行任务 -->
<TabsContent value="missions" class="mt-4 space-y-4"> <TabsContent value="missions" class="mt-4 space-y-4">
<Empty v-if="gameStore.player.fleetMissions.length === 0" class="border rounded-lg"> <Empty v-if="totalMissionsCount === 0" class="border rounded-lg">
<EmptyContent> <EmptyContent>
<RocketIcon class="h-10 w-10 text-muted-foreground" /> <RocketIcon class="h-10 w-10 text-muted-foreground" />
<EmptyDescription>{{ t('fleetView.noFlightMissions') }}</EmptyDescription> <EmptyDescription>{{ t('fleetView.noFlightMissions') }}</EmptyDescription>
</EmptyContent> </EmptyContent>
</Empty> </Empty>
<!-- 舰队任务 -->
<Card v-for="mission in gameStore.player.fleetMissions" :key="mission.id"> <Card v-for="mission in gameStore.player.fleetMissions" :key="mission.id">
<CardHeader> <CardHeader>
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
@@ -378,6 +379,44 @@
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<!-- 导弹攻击任务 -->
<Card v-for="missileAttack in flyingMissileAttacks" :key="missileAttack.id">
<CardHeader>
<div class="flex justify-between items-start">
<div>
<CardTitle class="text-base sm:text-lg flex items-center gap-2">
<Crosshair class="h-4 w-4 text-destructive" />
{{ t('galaxyView.missileAttackTitle') }}
</CardTitle>
<CardDescription class="text-xs sm:text-sm">
{{ getPlanetName(missileAttack.originPlanetId) }} [{{ missileAttack.targetPosition.galaxy }}:{{ missileAttack.targetPosition.system }}:{{
missileAttack.targetPosition.position
}}]
</CardDescription>
</div>
<Badge variant="destructive">
{{ t('fleetView.outbound') }}
</Badge>
</div>
</CardHeader>
<CardContent class="space-y-3">
<!-- 导弹数量 -->
<div>
<p class="text-xs sm:text-sm font-medium mb-2">{{ t('galaxyView.missileCount') }}:</p>
<Badge variant="outline">{{ missileAttack.missileCount }}</Badge>
</div>
<!-- 进度条 -->
<div class="space-y-2">
<div class="flex justify-between text-xs sm:text-sm">
<span>{{ t('fleetView.arrivalTime') }}:</span>
<span>{{ formatTime(getMissileRemainingTime(missileAttack)) }}</span>
</div>
<Progress :model-value="getMissileProgress(missileAttack)" />
</div>
</CardContent>
</Card>
</TabsContent> </TabsContent>
<!-- 跳跃门 --> <!-- 跳跃门 -->
@@ -591,7 +630,8 @@
Clock, Clock,
Check, Check,
Globe, Globe,
Moon Moon,
Crosshair
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { formatNumber, formatTime } from '@/utils/format' import { formatNumber, formatTime } from '@/utils/format'
import * as shipValidation from '@/logic/shipValidation' import * as shipValidation from '@/logic/shipValidation'
@@ -628,6 +668,16 @@
return publicLogic.getMaxFleetMissions(bonuses.additionalFleetSlots, computerTechLevel) return publicLogic.getMaxFleetMissions(bonuses.additionalFleetSlots, computerTechLevel)
}) })
// 飞行中的导弹攻击
const flyingMissileAttacks = computed(() => {
return gameStore.player.missileAttacks?.filter(m => m.status === 'flying') || []
})
// 总任务数量(舰队任务 + 导弹攻击)
const totalMissionsCount = computed(() => {
return gameStore.player.fleetMissions.length + flyingMissileAttacks.value.length
})
const activeTab = ref<'send' | 'missions' | 'jumpGate'>('send') const activeTab = ref<'send' | 'missions' | 'jumpGate'>('send')
// Tab 配置 // Tab 配置
@@ -837,6 +887,8 @@
selectedMission.value = MissionType.Attack selectedMission.value = MissionType.Attack
} else if (mission === 'colonize') { } else if (mission === 'colonize') {
selectedMission.value = MissionType.Colonize selectedMission.value = MissionType.Colonize
} else if (mission === 'recycle') {
selectedMission.value = MissionType.Recycle
} else if (gift === '1') { } else if (gift === '1') {
// 如果有gift参数设置为运输任务并启用赠送模式 // 如果有gift参数设置为运输任务并启用赠送模式
selectedMission.value = MissionType.Transport selectedMission.value = MissionType.Transport
@@ -1413,4 +1465,18 @@
return Math.max(0, Math.min(100, (elapsed / total) * 100)) return Math.max(0, Math.min(100, (elapsed / total) * 100))
} }
} }
// 获取导弹任务剩余时间
const getMissileRemainingTime = (missileAttack: any): number => {
const now = currentTime.value
return Math.max(0, (missileAttack.arrivalTime - now) / 1000)
}
// 获取导弹任务进度
const getMissileProgress = (missileAttack: any): number => {
const now = currentTime.value
const total = missileAttack.arrivalTime - missileAttack.launchTime
const elapsed = now - missileAttack.launchTime
return Math.max(0, Math.min(100, (elapsed / total) * 100))
}
</script> </script>

View File

@@ -66,7 +66,7 @@
{{ t('galaxyView.myPlanets') }} {{ t('galaxyView.myPlanets') }}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-72 p-2" align="start"> <PopoverContent class="w-87 p-2 max-h-80 overflow-y-auto" align="start">
<div class="space-y-1"> <div class="space-y-1">
<div class="px-2 py-1.5 text-xs font-semibold text-muted-foreground"> <div class="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
{{ t('galaxyView.selectPlanetToView') }} {{ t('galaxyView.selectPlanetToView') }}
@@ -114,7 +114,7 @@
{{ highlightedNpc.name }} ({{ highlightedNpc.planets.length }}) {{ highlightedNpc.name }} ({{ highlightedNpc.planets.length }})
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-72 p-2" align="start"> <PopoverContent class="w-72 p-2 max-h-96 overflow-y-auto" align="start">
<div class="space-y-1"> <div class="space-y-1">
<div class="px-2 py-1.5 text-xs font-semibold text-muted-foreground"> <div class="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
{{ t('galaxyView.selectPlanetToView') }} {{ t('galaxyView.selectPlanetToView') }}

View File

@@ -149,11 +149,11 @@
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translate3d(0, -10px, 0);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translate3d(0, 0, 0);
} }
} }
</style> </style>

View File

@@ -1117,13 +1117,17 @@
// 获取任务类型名称 // 获取任务类型名称
const getMissionTypeName = (missionType: string): string => { const getMissionTypeName = (missionType: string): string => {
const typeMap: Record<string, string> = { const typeMap: Record<string, string> = {
[MissionType.Attack]: t('fleetView.attack'),
[MissionType.Transport]: t('fleetView.transport'), [MissionType.Transport]: t('fleetView.transport'),
[MissionType.Colonize]: t('fleetView.colonize'), [MissionType.Colonize]: t('fleetView.colonize'),
[MissionType.Spy]: t('fleetView.spy'),
[MissionType.Deploy]: t('fleetView.deploy'), [MissionType.Deploy]: t('fleetView.deploy'),
[MissionType.Expedition]: t('fleetView.expedition'), [MissionType.Expedition]: t('fleetView.expedition'),
[MissionType.Recycle]: t('fleetView.recycle'), [MissionType.Recycle]: t('fleetView.recycle'),
[MissionType.Destroy]: t('fleetView.destroy'), [MissionType.Destroy]: t('fleetView.destroy'),
[MissionType.MissileAttack]: t('galaxyView.missileAttack') [MissionType.MissileAttack]: t('galaxyView.missileAttack'),
[MissionType.HarvestDarkMatter]: t('fleetView.harvestDarkMatter'),
[MissionType.Station]: t('fleetView.station')
} }
return typeMap[missionType] || missionType return typeMap[missionType] || missionType
} }

View File

@@ -22,8 +22,47 @@
<div v-if="planet.isMoon" class="mt-2"> <div v-if="planet.isMoon" class="mt-2">
<Button @click="switchToParentPlanet" variant="outline" size="sm">{{ t('planet.backToPlanet') }}</Button> <Button @click="switchToParentPlanet" variant="outline" size="sm">{{ t('planet.backToPlanet') }}</Button>
</div> </div>
<!-- 放弃殖民地按钮 -->
<div v-if="canShowAbandonButton" class="mt-4">
<Button @click="showAbandonDialog = true" variant="destructive" size="sm">
{{ t('planet.abandonColony') }}
</Button>
</div>
</div> </div>
<!-- 放弃殖民地确认对话框 -->
<AlertDialog :open="showAbandonDialog" @update:open="showAbandonDialog = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ t('planet.confirmAbandon') }}</AlertDialogTitle>
<AlertDialogDescription class="whitespace-pre-line">
{{ t('planet.abandonWarning', { name: planet.name }) }}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{{ t('common.cancel') }}</AlertDialogCancel>
<AlertDialogAction @click="handleAbandonColony" class="bg-destructive text-destructive-foreground hover:bg-destructive/90">
{{ t('planet.confirmAbandonButton') }}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<!-- 错误提示对话框 -->
<AlertDialog :open="showErrorDialog" @update:open="showErrorDialog = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ t('planet.abandonFailed') }}</AlertDialogTitle>
<AlertDialogDescription>
{{ errorMessage }}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction>{{ t('common.confirm') }}</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<!-- 资源管理 --> <!-- 资源管理 -->
<Card> <Card>
<CardHeader> <CardHeader>
@@ -195,25 +234,45 @@
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { useGameConfig } from '@/composables/useGameConfig' import { useGameConfig } from '@/composables/useGameConfig'
import { computed } from 'vue' import { computed, ref } from 'vue'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog'
import ResourceIcon from '@/components/common/ResourceIcon.vue' import ResourceIcon from '@/components/common/ResourceIcon.vue'
import { formatNumber, getResourceColor } from '@/utils/format' import { formatNumber, getResourceColor } from '@/utils/format'
import { scaleNumber } from '@/utils/speed' import { scaleNumber } from '@/utils/speed'
import type { Planet } from '@/types/game' import type { Planet } from '@/types/game'
import { TechnologyType } from '@/types/game'
import * as publicLogic from '@/logic/publicLogic' import * as publicLogic from '@/logic/publicLogic'
import * as resourceLogic from '@/logic/resourceLogic' import * as resourceLogic from '@/logic/resourceLogic'
import * as planetLogic from '@/logic/planetLogic'
const gameStore = useGameStore() const gameStore = useGameStore()
const { t } = useI18n() const { t } = useI18n()
const { SHIPS } = useGameConfig() const { SHIPS } = useGameConfig()
const planet = computed(() => gameStore.currentPlanet) const planet = computed(() => gameStore.currentPlanet)
// 获取科技加成
const techBonuses = computed(() => ({
mineralResearchLevel: gameStore.player.technologies[TechnologyType.MineralResearch] || 0,
crystalResearchLevel: gameStore.player.technologies[TechnologyType.CrystalResearch] || 0,
fuelResearchLevel: gameStore.player.technologies[TechnologyType.FuelResearch] || 0
}))
const production = computed(() => const production = computed(() =>
planet.value ? publicLogic.getResourceProduction(planet.value, gameStore.player.officers, gameStore.gameSpeed) : null planet.value ? publicLogic.getResourceProduction(planet.value, gameStore.player.officers, gameStore.gameSpeed, techBonuses.value) : null
) )
const capacity = computed(() => (planet.value ? publicLogic.getResourceCapacity(planet.value, gameStore.player.officers) : null)) const capacity = computed(() => (planet.value ? publicLogic.getResourceCapacity(planet.value, gameStore.player.officers) : null))
@@ -226,7 +285,7 @@
// 资源产量详细breakdown // 资源产量详细breakdown
const productionBreakdown = computed(() => { const productionBreakdown = computed(() => {
if (!planet.value) return null if (!planet.value) return null
return resourceLogic.calculateProductionBreakdown(planet.value, gameStore.player.officers, Date.now(), gameStore.gameSpeed) return resourceLogic.calculateProductionBreakdown(planet.value, gameStore.player.officers, Date.now(), gameStore.gameSpeed, techBonuses.value)
}) })
// 资源消耗详细breakdown // 资源消耗详细breakdown
@@ -286,4 +345,45 @@
gameStore.currentPlanetId = planet.value.parentPlanetId gameStore.currentPlanetId = planet.value.parentPlanetId
} }
} }
// 放弃殖民地相关
const showAbandonDialog = ref(false)
const showErrorDialog = ref(false)
const errorMessage = ref('')
// 是否显示放弃按钮(非主星才显示)
const canShowAbandonButton = computed(() => {
if (!planet.value) return false
// 找到主星(第一个非月球星球)
const mainPlanet = gameStore.player.planets.find(p => !p.isMoon)
// 当前星球不是主星时才显示放弃按钮
return mainPlanet && mainPlanet.id !== planet.value.id
})
// 处理放弃殖民地
const handleAbandonColony = () => {
if (!planet.value) return
const check = planetLogic.canAbandonColony(gameStore.player.planets, planet.value.id)
if (!check.canAbandon) {
showAbandonDialog.value = false
errorMessage.value = check.reason ? t(check.reason) : t('planet.abandonFailed')
showErrorDialog.value = true
return
}
// 记录当前星球ID用于后续切换
const abandonedPlanetId = planet.value.id
// 执行放弃
gameStore.player.planets = planetLogic.abandonColony(gameStore.player.planets, abandonedPlanetId)
// 切换到主星
const mainPlanet = gameStore.player.planets.find(p => !p.isMoon)
if (mainPlanet) {
gameStore.currentPlanetId = mainPlanet.id
}
showAbandonDialog.value = false
}
</script> </script>

View File

@@ -175,7 +175,8 @@
techType, techType,
currentLevel, currentLevel,
gameStore.player.officers, gameStore.player.officers,
gameStore.player.technologies gameStore.player.technologies,
gameStore.player.planets
) )
gameStore.player.researchQueue.push(queueItem) gameStore.player.researchQueue.push(queueItem)
return true return true
@@ -333,11 +334,33 @@
const getResearchTime = (techType: TechnologyType): number => { const getResearchTime = (techType: TechnologyType): number => {
if (!planet.value) return 0 if (!planet.value) return 0
const currentLevel = getTechLevel(techType) const currentLevel = getTechLevel(techType)
const researchLabLevel = planet.value.buildings['researchLab'] || 0 const intergalacticResearchNetworkLevel = player.value.technologies[TechnologyType.IntergalacticResearchNetwork] || 0
// 计算有效研究实验室等级(考虑星际研究网络)
let researchLabLevel: number
if (intergalacticResearchNetworkLevel > 0) {
researchLabLevel = researchLogic.calculateEffectiveLabLevel(
gameStore.player.planets,
planet.value.id,
intergalacticResearchNetworkLevel
)
} else {
researchLabLevel = planet.value.buildings['researchLab'] || 0
}
const energyTechLevel = player.value.technologies['energyTechnology'] || 0 const energyTechLevel = player.value.technologies['energyTechnology'] || 0
const universityLevel = planet.value.buildings['university'] || 0
const bonuses = officerLogic.calculateActiveBonuses(player.value.officers, gameStore.gameTime) const bonuses = officerLogic.calculateActiveBonuses(player.value.officers, gameStore.gameTime)
return researchLogic.calculateTechnologyTime(techType, currentLevel, bonuses.researchSpeedBonus, researchLabLevel, energyTechLevel) return researchLogic.calculateTechnologyTime(
techType,
currentLevel,
bonuses.researchSpeedBonus,
researchLabLevel,
energyTechLevel,
1,
universityLevel
)
} }
// 检查是否可以添加到等待队列 // 检查是否可以添加到等待队列

View File

@@ -114,6 +114,15 @@
{{ gameStore.isPaused ? t('settings.resume') : t('settings.pause') }} {{ gameStore.isPaused ? t('settings.resume') : t('settings.pause') }}
</Button> </Button>
</div> </div>
<!-- 战斗模式 -->
<div class="flex items-center justify-between p-4 border rounded-lg">
<div class="space-y-1">
<h3 class="font-medium">{{ t('settings.battleMode') }}</h3>
<p class="text-sm text-muted-foreground">{{ t('settings.battleModeDesc') }}</p>
</div>
<Switch :checked="gameStore.battleToFinish" @update:checked="(val: boolean) => (gameStore.battleToFinish = val)" />
</div>
</CardContent> </CardContent>
</Card> </Card>
@@ -170,8 +179,8 @@
areMainSwitchesOff areMainSwitchesOff
? t('settings.notificationsDisabled') ? t('settings.notificationsDisabled')
: isTypesExpanded : isTypesExpanded
? t('settings.collapseTypes') ? t('settings.collapseTypes')
: t('settings.expandTypes') : t('settings.expandTypes')
}} }}
</p> </p>
</div> </div>
@@ -334,7 +343,7 @@
<PrivacyDialog v-model:open="showPrivacyDialog" /> <PrivacyDialog v-model:open="showPrivacyDialog" />
<!-- WebDAV 配置对话框 --> <!-- WebDAV 配置对话框 -->
<WebDAVConfigDialog v-model:open="showWebDAVConfig" @saved="onWebDAVConfigSaved" /> <WebDAVConfigDialog v-model:open="showWebDAVConfig" />
<!-- WebDAV 文件列表对话框 --> <!-- WebDAV 文件列表对话框 -->
<WebDAVFileListDialog v-model:open="showWebDAVFiles" :config="webdavConfig" @select="handleWebDAVDownload" /> <WebDAVFileListDialog v-model:open="showWebDAVFiles" :config="webdavConfig" @select="handleWebDAVDownload" />
@@ -380,6 +389,7 @@
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import { Capacitor } from '@capacitor/core' import { Capacitor } from '@capacitor/core'
import { decryptData, encryptData } from '@/utils/crypto'
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem' import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'
import pkg from '../../package.json' import pkg from '../../package.json'
import { checkLatestVersion, canCheckVersion } from '@/utils/versionCheck' import { checkLatestVersion, canCheckVersion } from '@/utils/versionCheck'
@@ -389,12 +399,7 @@
import WebDAVConfigDialog from '@/components/settings/WebDAVConfigDialog.vue' import WebDAVConfigDialog from '@/components/settings/WebDAVConfigDialog.vue'
import WebDAVFileListDialog from '@/components/settings/WebDAVFileListDialog.vue' import WebDAVFileListDialog from '@/components/settings/WebDAVFileListDialog.vue'
import { useHints } from '@/composables/useHints' import { useHints } from '@/composables/useHints'
import { import { uploadToWebDAV, downloadFromWebDAV } from '@/services/webdavService'
type WebDAVConfig,
getWebDAVConfig,
uploadToWebDAV,
downloadFromWebDAV
} from '@/services/webdavService'
const { t } = useI18n() const { t } = useI18n()
const { hintsEnabled, setHintsEnabled, resetHints } = useHints() const { hintsEnabled, setHintsEnabled, resetHints } = useHints()
@@ -415,7 +420,7 @@
// WebDAV 相关状态 // WebDAV 相关状态
const showWebDAVConfig = ref(false) const showWebDAVConfig = ref(false)
const showWebDAVFiles = ref(false) const showWebDAVFiles = ref(false)
const webdavConfig = ref<WebDAVConfig | null>(getWebDAVConfig()) const webdavConfig = computed(() => gameStore.webdavConfig)
const isWebDAVUploading = ref(false) const isWebDAVUploading = ref(false)
// 确保通知设置存在 // 确保通知设置存在
@@ -586,21 +591,92 @@
const fileName = `${pkg.name}-${new Date().toISOString().slice(0, 10)}-${Date.now()}.json` const fileName = `${pkg.name}-${new Date().toISOString().slice(0, 10)}-${Date.now()}.json`
const jsonString = JSON.stringify(exportData, null, 2) const jsonString = JSON.stringify(exportData, null, 2)
// Android 保存到 Documents 目录 // Android/原生平台
if (Capacitor.isNativePlatform()) { if (Capacitor.isNativePlatform()) {
const result = await Filesystem.writeFile({ // 尝试保存到公共 Downloads 目录
path: fileName, try {
data: jsonString, const permStatus = await Filesystem.checkPermissions()
directory: Directory.Documents, if (permStatus.publicStorage === 'granted') {
encoding: Encoding.UTF8 const result = await Filesystem.writeFile({
}) path: `Download/${fileName}`,
toast.success(t('settings.exportSuccessWithPath', { path: result.uri })) data: jsonString,
return directory: Directory.ExternalStorage,
encoding: Encoding.UTF8
})
toast.success(t('settings.exportSuccessWithPath', { path: result.uri }))
return
}
// 尝试请求权限
const reqResult = await Filesystem.requestPermissions()
if (reqResult.publicStorage === 'granted') {
const result = await Filesystem.writeFile({
path: `Download/${fileName}`,
data: jsonString,
directory: Directory.ExternalStorage,
encoding: Encoding.UTF8
})
toast.success(t('settings.exportSuccessWithPath', { path: result.uri }))
return
}
} catch (extError) {
console.warn('ExternalStorage failed, trying Documents:', extError)
}
// 备选方案:保存到应用文档目录 (TapPlay 等沙盒环境)
try {
const result = await Filesystem.writeFile({
path: fileName,
data: jsonString,
directory: Directory.Documents,
encoding: Encoding.UTF8
})
toast.success(t('settings.exportSuccessWithPath', { path: result.uri }))
return
} catch (docError) {
console.warn('Documents failed, trying Data:', docError)
}
// 最后备选:保存到应用数据目录
try {
const result = await Filesystem.writeFile({
path: fileName,
data: jsonString,
directory: Directory.Data,
encoding: Encoding.UTF8
})
toast.success(t('settings.exportSuccessWithPath', { path: result.uri }))
return
} catch (dataError) {
console.warn('Data directory failed:', dataError)
}
// 所有文件系统方式都失败,尝试 Web Share API
if (navigator.share && navigator.canShare) {
const file = new File([jsonString], fileName, { type: 'application/json' })
if (navigator.canShare({ files: [file] })) {
await navigator.share({
files: [file],
title: t('settings.exportData')
})
toast.success(t('settings.exportSuccess'))
return
}
}
// 最后的备选:复制到剪贴板
try {
await navigator.clipboard.writeText(jsonString)
toast.success(t('settings.exportCopiedToClipboard'))
return
} catch {
toast.error(t('settings.exportFailed'))
}
} else { } else {
// Web 使用 file-saver // Web 使用 file-saver
saveAs(new Blob([jsonString], { type: 'application/json' }), fileName) saveAs(new Blob([jsonString], { type: 'application/json' }), fileName)
toast.success(t('settings.exportSuccess'))
} }
toast.success(t('settings.exportSuccess'))
} catch (error) { } catch (error) {
console.error('Export failed:', error) console.error('Export failed:', error)
toast.error(t('settings.exportFailed')) toast.error(t('settings.exportFailed'))
@@ -635,6 +711,15 @@
if (typeof result === 'string') { if (typeof result === 'string') {
const importData = JSON.parse(result) const importData = JSON.parse(result)
if (importData.data) {
const data = decryptData(importData.data)
localStorage.setItem(pkg.name, encryptData(data.game))
localStorage.setItem(`${pkg.name}-universe`, encryptData(data.universe))
localStorage.setItem(`${pkg.name}-npcs`, encryptData(data.npcs))
setTimeout(() => window.location.reload(), 1000)
return
}
// 兼容旧版本:如果是旧格式(直接是字符串),只导入游戏数据 // 兼容旧版本:如果是旧格式(直接是字符串),只导入游戏数据
if (typeof importData === 'string' || !importData.game) { if (typeof importData === 'string' || !importData.game) {
localStorage.setItem(pkg.name, result) localStorage.setItem(pkg.name, result)
@@ -758,11 +843,6 @@
gameStore.player.backgroundEnabled = val gameStore.player.backgroundEnabled = val
} }
// WebDAV 配置保存回调
const onWebDAVConfigSaved = () => {
webdavConfig.value = getWebDAVConfig()
}
// WebDAV 上传 // WebDAV 上传
const handleWebDAVUpload = async () => { const handleWebDAVUpload = async () => {
if (!webdavConfig.value) return if (!webdavConfig.value) return
@@ -792,7 +872,7 @@
if (result.success) { if (result.success) {
toast.success(t('settings.webdav.uploadSuccess')) toast.success(t('settings.webdav.uploadSuccess'))
} else { } else {
toast.error(result.message || t('settings.webdav.uploadFailed')) toast.error(t(result.messageKey) || t('settings.webdav.uploadFailed'))
} }
} catch (error) { } catch (error) {
console.error('WebDAV upload failed:', error) console.error('WebDAV upload failed:', error)
@@ -810,7 +890,7 @@
const result = await downloadFromWebDAV(webdavConfig.value, fileName) const result = await downloadFromWebDAV(webdavConfig.value, fileName)
if (!result.success || !result.data) { if (!result.success || !result.data) {
toast.error(result.message || t('settings.webdav.downloadFailed')) toast.error(t(result.messageKey) || t('settings.webdav.downloadFailed'))
return return
} }

View File

@@ -101,6 +101,20 @@
/> />
</div> </div>
<!-- 拆除数量输入 -->
<div v-if="(planet.fleet[shipType] || 0) > 0" class="space-y-2">
<Label :for="`scrap-quantity-${shipType}`" class="text-xs sm:text-sm text-destructive">{{ t('shipyardView.scrapQuantity') }}</Label>
<Input
:id="`scrap-quantity-${shipType}`"
v-model.number="scrapQuantities[shipType]"
type="number"
min="0"
:max="planet.fleet[shipType] || 0"
placeholder="0"
class="text-sm"
/>
</div>
<div v-if="quantities[shipType] > 0" class="text-xs sm:text-sm space-y-1.5 sm:space-y-2 p-2.5 sm:p-3 bg-muted rounded-lg"> <div v-if="quantities[shipType] > 0" class="text-xs sm:text-sm space-y-1.5 sm:space-y-2 p-2.5 sm:p-3 bg-muted rounded-lg">
<p class="font-medium text-muted-foreground">{{ t('shipyardView.totalCost') }}:</p> <p class="font-medium text-muted-foreground">{{ t('shipyardView.totalCost') }}:</p>
<div class="space-y-1 sm:space-y-1.5"> <div class="space-y-1 sm:space-y-1.5">
@@ -133,6 +147,36 @@
> >
{{ t('queue.addToWaiting') }} {{ t('queue.addToWaiting') }}
</Button> </Button>
<!-- 拆除返还资源显示 -->
<div v-if="scrapQuantities[shipType] > 0" class="text-xs sm:text-sm space-y-1.5 sm:space-y-2 p-2.5 sm:p-3 bg-destructive/10 rounded-lg border border-destructive/30">
<p class="font-medium text-destructive">{{ t('shipyardView.scrapRefund') }}:</p>
<div class="space-y-1 sm:space-y-1.5">
<div
v-for="resourceType in costResourceTypes"
:key="resourceType.key"
v-show="resourceType.key !== 'darkMatter' || getScrapRefund(shipType).darkMatter > 0"
class="flex items-center gap-1.5 sm:gap-2"
>
<ResourceIcon :type="resourceType.key" size="sm" />
<span class="text-xs">{{ t(`resources.${resourceType.key}`) }}:</span>
<span class="font-medium text-xs sm:text-sm text-green-500">
+{{ formatNumber(getScrapRefund(shipType)[resourceType.key]) }}
</span>
</div>
</div>
</div>
<!-- 拆除按钮 -->
<Button
v-if="(planet.fleet[shipType] || 0) > 0"
@click="handleScrap(shipType, $event)"
:disabled="!canScrap(shipType)"
variant="destructive"
class="w-full"
>
{{ t('shipyardView.scrap') }}
</Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -243,6 +287,25 @@
[ShipType.Deathstar]: 0 [ShipType.Deathstar]: 0
}) })
// 每种舰船的拆除数量
const scrapQuantities = ref<Record<ShipType, number>>({
[ShipType.LightFighter]: 0,
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.Battlecruiser]: 0,
[ShipType.Bomber]: 0,
[ShipType.Destroyer]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.SolarSatellite]: 0,
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
})
const buildShip = (shipType: ShipType, quantity: number): { success: boolean; reason?: string } => { const buildShip = (shipType: ShipType, quantity: number): { success: boolean; reason?: string } => {
if (!gameStore.currentPlanet) return { success: false } if (!gameStore.currentPlanet) return { success: false }
const validation = shipValidation.validateShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.technologies) const validation = shipValidation.validateShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.technologies)
@@ -380,4 +443,54 @@
waitingQueueLogic.addToBuildWaitingQueue(planet.value, item) waitingQueueLogic.addToBuildWaitingQueue(planet.value, item)
quantities.value[shipType] = 0 quantities.value[shipType] = 0
} }
// 计算拆除返还资源
const getScrapRefund = (shipType: ShipType) => {
const quantity = scrapQuantities.value[shipType]
return shipLogic.calculateShipScrapRefund(shipType, quantity)
}
// 检查是否可以拆除
const canScrap = (shipType: ShipType): boolean => {
if (!planet.value) return false
const quantity = scrapQuantities.value[shipType]
if (quantity <= 0) return false
const available = planet.value.fleet[shipType] || 0
return available >= quantity
}
// 拆除舰船
const handleScrap = (shipType: ShipType, _event: MouseEvent) => {
// 防抖:防止快速点击
if (isProcessing.value) return
isProcessing.value = true
setTimeout(() => {
isProcessing.value = false
}, DEBOUNCE_DELAY)
const quantity = scrapQuantities.value[shipType]
if (quantity <= 0) {
alertDialogTitle.value = t('shipyardView.inputError')
alertDialogMessage.value = t('shipyardView.inputErrorMessage')
alertDialogOpen.value = true
return
}
if (!gameStore.currentPlanet) return
const validation = shipValidation.validateShipScrap(gameStore.currentPlanet, shipType, quantity)
if (!validation.valid) {
alertDialogTitle.value = t('shipyardView.scrapFailed')
alertDialogMessage.value = validation.reason ? t(validation.reason) : t('shipyardView.scrapFailedMessage')
alertDialogOpen.value = true
return
}
// 执行拆除
const queueItem = shipValidation.executeShipScrap(gameStore.currentPlanet, shipType, quantity, gameStore.player.officers)
gameStore.currentPlanet.buildQueue.push(queueItem)
scrapQuantities.value[shipType] = 0
}
</script> </script>

View File

@@ -304,8 +304,24 @@ const simulateBattle = (attacker: BattleSideData, defender: BattleSideData, maxR
} else if (defenderUnits.length === 0) { } else if (defenderUnits.length === 0) {
winner = 'attacker' winner = 'attacker'
} else { } else {
// OGame原版规则6回合后双方都有剩余单位时判定为平局 // 达到最大回合后双方都有剩余单位
winner = 'draw' // 如果是战斗到底模式(maxRounds > 6),根据剩余战力判定胜负
if (maxRounds > 6) {
// 计算剩余战力
const attackerPower = attackerUnits.reduce((sum, u) => sum + u.count * u.armor, 0)
const defenderPower = defenderUnits.reduce((sum, u) => sum + u.count * u.armor, 0)
// 战力差距超过20%判定胜负,否则平局
if (attackerPower > defenderPower * 1.2) {
winner = 'attacker'
} else if (defenderPower > attackerPower * 1.2) {
winner = 'defender'
} else {
winner = 'draw'
}
} else {
// OGame原版规则6回合后双方都有剩余单位时判定为平局
winner = 'draw'
}
} }
return { return {

View File

@@ -4,11 +4,11 @@ import tailwindcss from '@tailwindcss/vite'
import path from 'node:path' import path from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { VitePWA } from 'vite-plugin-pwa' import { VitePWA } from 'vite-plugin-pwa'
import autoprefixer from 'autoprefixer'
import pkg from './package.json' import pkg from './package.json'
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
// @ts-expect-error - Vite CSS transformer type
export default defineConfig(async () => { export default defineConfig(async () => {
const plugins = [ const plugins = [
vue(), vue(),
@@ -23,14 +23,7 @@ export default defineConfig(async () => {
background_color: '#000000', background_color: '#000000',
display: 'fullscreen', display: 'fullscreen',
orientation: 'any', orientation: 'any',
icons: [ icons: [{ src: 'logo.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'any' }]
{
src: 'logo.svg',
sizes: 'any',
type: 'image/svg+xml',
purpose: 'any'
}
]
}, },
workbox: { workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,mp3,wav,json}'], globPatterns: ['**/*.{js,css,html,ico,png,svg,mp3,wav,json}'],
@@ -39,13 +32,7 @@ export default defineConfig(async () => {
{ {
urlPattern: ({ request }) => request.destination === 'image' || request.destination === 'audio', urlPattern: ({ request }) => request.destination === 'image' || request.destination === 'audio',
handler: 'CacheFirst', // 优先使用缓存 handler: 'CacheFirst', // 优先使用缓存
options: { options: { cacheName: 'game-assets', expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 } }
cacheName: 'game-assets',
expiration: {
maxEntries: 100,
maxAgeSeconds: 30 * 24 * 60 * 60 // 缓存 30 天
}
}
} }
] ]
} }
@@ -110,12 +97,17 @@ export default defineConfig(async () => {
plugins, plugins,
resolve: { alias: { '@': path.resolve(__dirname, './src') } }, resolve: { alias: { '@': path.resolve(__dirname, './src') } },
css: { css: {
postcss: { // 使用 lightningcss 处理 CSS自动转换 oklch 等新语法为兼容格式
plugins: [ transformer: 'lightningcss',
autoprefixer({ lightningcss: {
overrideBrowserslist: ['Android >= 4.1', 'iOS >= 7.1', 'Chrome >= 31', 'Firefox >= 31', 'ie >= 8'] // 目标浏览器:降低到更保守的版本以支持华为等国产手机 WebView
}) targets: {
] android: (4 << 16) | (4 << 8), // Android 4.4
chrome: 49 << 16, // Chrome 49 (Android 4.4 WebView)
ios_saf: (9 << 16) | (3 << 8) // iOS Safari 9.3
},
// 禁用现代 CSS 特性,确保兼容旧版浏览器
drafts: { customMedia: false }
} }
}, },
// 优化依赖预构建 // 优化依赖预构建

View File

@@ -0,0 +1,28 @@
OGame-Vue-Ts v1.6.0 更新说明
调整基础矿脉恢复速度
调整地质研究站每级恢复速率
调整建造死星所需资源
修复研究等待队列资源扣除但等级不增加的问题
修复攻击NPC后NPC拥有资源不减少的问题
修复导弹攻击后反弹道导弹不减少的问题
修复矿脉上限加成计算错误问题
为战斗模拟器新增加从侦查报告一键填入的功能
调整建筑与研究升级时间
修复文本丢失问题
调整月球基地初始建造成本
修复无法派遣舰队到自己星球位置的问题
修复敌对NPC会无脑派遣舰队回收残骸导致产生更多残骸的恶性循环问题
修复敌对NPC反复派回收船到玩家星球的问题
修复NPC无脑进攻问题
修复贸易邮件大量生成导致淹没其他邮件问题
添加舰船拆除功能
添加放弃殖民地功能
修复从月球切换回母星会触发白屏问题
修复死星毁灭任务跳过战斗的问题
修复对战动画偶现卡顿导致弹窗无响应问题
增加战斗到底模式, 开启后支持最多战斗100回合
添加建筑: 大学
添加科技: 星际研究网络, 矿物研究, 晶体研究, 燃料研究