mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 16:05:12 +08:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80071ef0f6 | ||
|
|
d8dd4e7317 | ||
|
|
15eccd8f0d | ||
|
|
d4f55f6916 | ||
|
|
2cfa275c7a | ||
|
|
b1cf0acaae | ||
|
|
8f29a63756 | ||
|
|
a8ab2b0f1a | ||
|
|
28c3da2582 | ||
|
|
b0a7b5ce90 | ||
|
|
bd46c24824 | ||
|
|
8e49998205 | ||
|
|
703563c9b2 | ||
|
|
d44ea60ae9 | ||
|
|
1fc807915f | ||
|
|
bd6c474913 | ||
|
|
66c0ed8d0e | ||
|
|
9634dcb023 | ||
|
|
e4c4cdd63c | ||
|
|
7279bcbc89 | ||
|
|
d9c708e0ca | ||
|
|
21cf5762d2 | ||
|
|
8db70ea674 | ||
|
|
136591a3dd | ||
|
|
72f37aa435 | ||
|
|
4c54e1b773 | ||
|
|
9e8ceb0414 | ||
|
|
ec96d2541e | ||
|
|
9e7560cc4b | ||
|
|
1ad051cd6d | ||
|
|
fda15646eb | ||
|
|
6a9846c6df | ||
|
|
49753566c3 | ||
|
|
66783f896c | ||
|
|
7cc885c62a | ||
|
|
5c6404d86a | ||
|
|
010ea137ac | ||
|
|
6dbca76252 | ||
|
|
c047ffb88e | ||
|
|
6f8adfa586 | ||
|
|
94fa2ad57a | ||
|
|
7ed508945a | ||
|
|
fe2e5bfad9 | ||
|
|
7f36b6693f | ||
|
|
27d60ae71a | ||
|
|
ca1aed1e9b | ||
|
|
04ee72a33d | ||
|
|
d95dffcfcd | ||
|
|
b6bcae3294 | ||
|
|
ebed10b82f | ||
|
|
f4f5a719f5 | ||
|
|
1686622013 |
31
.dockerignore
Normal file
31
.dockerignore
Normal 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
38
.dockerignore.ci
Normal 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
15
.github/FUNDING.yml
vendored
Normal 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']
|
||||||
55
.github/workflows/build.yml
vendored
55
.github/workflows/build.yml
vendored
@@ -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
|
||||||
@@ -211,7 +206,7 @@ jobs:
|
|||||||
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:"
|
||||||
@@ -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 }}
|
||||||
|
|||||||
37
.github/workflows/github-pages.yml
vendored
37
.github/workflows/github-pages.yml
vendored
@@ -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
|
||||||
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v4
|
||||||
137
.github/workflows/ogame-vue-ts.yml
vendored
137
.github/workflows/ogame-vue-ts.yml
vendored
@@ -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
89
DOCKER.md
Normal 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 自动构建的镜像进行部署
|
||||||
38
Dockerfile
38
Dockerfile
@@ -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
35
Dockerfile.ci
Normal 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;"]
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 及以下) -->
|
||||||
|
<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>
|
</manifest>
|
||||||
@@ -1,18 +1,148 @@
|
|||||||
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.ValueCallback;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.content.Intent;
|
||||||
|
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;
|
||||||
|
private static final int FILE_CHOOSER_REQUEST_CODE = 1001;
|
||||||
|
private ValueCallback<Uri[]> filePathCallback;
|
||||||
|
|
||||||
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
|
||||||
|
FileChooserParams fileChooserParams) {
|
||||||
|
// 清理之前的回调
|
||||||
|
if (MainActivity.this.filePathCallback != null) {
|
||||||
|
MainActivity.this.filePathCallback.onReceiveValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainActivity.this.filePathCallback = filePathCallback;
|
||||||
|
|
||||||
|
// 创建文件选择器 Intent
|
||||||
|
Intent intent = fileChooserParams.createIntent();
|
||||||
|
try {
|
||||||
|
startActivityForResult(intent, FILE_CHOOSER_REQUEST_CODE);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
MainActivity.this.filePathCallback = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
|
if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
|
||||||
|
if (filePathCallback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri[] results = null;
|
||||||
|
if (resultCode == RESULT_OK && data != null) {
|
||||||
|
String dataString = data.getDataString();
|
||||||
|
if (dataString != null) {
|
||||||
|
results = new Uri[]{Uri.parse(dataString)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filePathCallback.onReceiveValue(results);
|
||||||
|
filePathCallback = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
android/app/src/main/res/drawable/splash_icon.xml
Normal file
9
android/app/src/main/res/drawable/splash_icon.xml
Normal 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>
|
||||||
@@ -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>
|
||||||
|
<color name="splash_background">#0f0f1a</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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
17
build-docker.bat
Normal 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
18
build-docker.sh
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
75
main.go
@@ -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")
|
||||||
}
|
}
|
||||||
@@ -132,3 +140,58 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 02:22:24",
|
||||||
"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
69
pnpm-lock.yaml
generated
@@ -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:
|
||||||
|
|||||||
139
src/App.vue
139
src/App.vue
@@ -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('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换侧边栏
|
// 切换侧边栏
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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: '',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 军官类型枚举值到翻译键的映射
|
// 军官类型枚举值到翻译键的映射
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -1043,6 +1085,20 @@ export default {
|
|||||||
},
|
},
|
||||||
gmView: {
|
gmView: {
|
||||||
title: 'GM-Kontrollpanel',
|
title: 'GM-Kontrollpanel',
|
||||||
|
presets: 'Vorlagen',
|
||||||
|
choosePreset: 'Vorlage wählen',
|
||||||
|
defaultPreset: 'Standardvorlage',
|
||||||
|
applyPreset: 'Vorlage anwenden',
|
||||||
|
savePreset: 'Vorlage speichern',
|
||||||
|
presetName: 'Vorlagenname',
|
||||||
|
presetNameRequired: 'Bitte geben Sie einen Namen ein',
|
||||||
|
presetSaved: 'Vorlage gespeichert',
|
||||||
|
presetApplied: 'Vorlage angewendet',
|
||||||
|
deletePreset: 'Vorlage löschen',
|
||||||
|
presetDeleted: 'Vorlage gelöscht',
|
||||||
|
confirmOverwriteTitle: 'Vorlage existiert bereits',
|
||||||
|
confirmOverwriteMessage: 'Eine Vorlage mit dem Namen "{name}" existiert bereits. Überschreiben?',
|
||||||
|
cannotDeleteDefault: 'Standardvorlage kann nicht gelöscht werden',
|
||||||
adminOnly: 'Nur Admin',
|
adminOnly: 'Nur Admin',
|
||||||
selectPlanet: 'Planet auswählen',
|
selectPlanet: 'Planet auswählen',
|
||||||
choosePlanet: 'Einen Planeten auswählen',
|
choosePlanet: 'Einen Planeten auswählen',
|
||||||
@@ -1143,6 +1199,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
diplomacy: {
|
diplomacy: {
|
||||||
|
sort: {
|
||||||
|
label: 'Sortieren',
|
||||||
|
ascending: 'Aufsteigend',
|
||||||
|
descending: 'Absteigend',
|
||||||
|
reputation: 'Ruf',
|
||||||
|
planets: 'Planeten',
|
||||||
|
difficulty: 'Schwierigkeit',
|
||||||
|
allies: 'Verbündete'
|
||||||
|
},
|
||||||
title: 'Diplomatie',
|
title: 'Diplomatie',
|
||||||
description: 'Verwalte diplomatische Beziehungen mit NPCs',
|
description: 'Verwalte diplomatische Beziehungen mit NPCs',
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -1379,19 +1444,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 +1468,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 +1517,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 +1531,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 +1571,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 +1656,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 +1896,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 +1943,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 +2035,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 +2063,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 +2137,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 +2158,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 +2180,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...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -1033,6 +1101,20 @@ export default {
|
|||||||
},
|
},
|
||||||
gmView: {
|
gmView: {
|
||||||
title: 'GM Control Panel',
|
title: 'GM Control Panel',
|
||||||
|
presets: 'Presets',
|
||||||
|
choosePreset: 'Choose Preset',
|
||||||
|
defaultPreset: 'Default Preset',
|
||||||
|
applyPreset: 'Apply Preset',
|
||||||
|
savePreset: 'Save Preset',
|
||||||
|
presetName: 'Preset Name',
|
||||||
|
presetNameRequired: 'Please enter a preset name',
|
||||||
|
presetSaved: 'Preset saved',
|
||||||
|
presetApplied: 'Preset applied',
|
||||||
|
deletePreset: 'Delete Preset',
|
||||||
|
presetDeleted: 'Preset deleted',
|
||||||
|
confirmOverwriteTitle: 'Preset Already Exists',
|
||||||
|
confirmOverwriteMessage: 'Preset with name "{name}" already exists. Overwrite?',
|
||||||
|
cannotDeleteDefault: 'Cannot delete default preset',
|
||||||
adminOnly: 'Admin Only',
|
adminOnly: 'Admin Only',
|
||||||
selectPlanet: 'Select Planet',
|
selectPlanet: 'Select Planet',
|
||||||
choosePlanet: 'Choose a planet',
|
choosePlanet: 'Choose a planet',
|
||||||
@@ -1223,6 +1305,15 @@ export default {
|
|||||||
npcEliminatedMessage: "You destroyed all of {npcName}'s planets! This faction has been completely wiped out."
|
npcEliminatedMessage: "You destroyed all of {npcName}'s planets! This faction has been completely wiped out."
|
||||||
},
|
},
|
||||||
searchPlaceholder: 'Search NPC name...',
|
searchPlaceholder: 'Search NPC name...',
|
||||||
|
sort: {
|
||||||
|
label: 'Sort',
|
||||||
|
ascending: 'Ascending',
|
||||||
|
descending: 'Descending',
|
||||||
|
reputation: 'Reputation',
|
||||||
|
planets: 'Planets',
|
||||||
|
difficulty: 'Difficulty',
|
||||||
|
allies: 'Allies'
|
||||||
|
},
|
||||||
// Notification types
|
// Notification types
|
||||||
notificationType: {
|
notificationType: {
|
||||||
tradeOffer: 'Trade Offer',
|
tradeOffer: 'Trade Offer',
|
||||||
@@ -1753,6 +1844,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 +2011,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 +2039,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 +2111,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 +2131,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 +2156,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 +2177,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 +2290,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}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -1056,6 +1093,20 @@ export default {
|
|||||||
},
|
},
|
||||||
gmView: {
|
gmView: {
|
||||||
title: 'Panel de Control GM',
|
title: 'Panel de Control GM',
|
||||||
|
presets: 'Preajustes',
|
||||||
|
choosePreset: 'Elegir preajuste',
|
||||||
|
defaultPreset: 'Preajuste predeterminado',
|
||||||
|
applyPreset: 'Aplicar preajuste',
|
||||||
|
savePreset: 'Guardar preajuste',
|
||||||
|
presetName: 'Nombre del preajuste',
|
||||||
|
presetNameRequired: 'Ingrese el nombre del preajuste',
|
||||||
|
presetSaved: 'Preajuste guardado',
|
||||||
|
presetApplied: 'Preajuste aplicado',
|
||||||
|
deletePreset: 'Eliminar preajuste',
|
||||||
|
presetDeleted: 'Preajuste eliminado',
|
||||||
|
confirmOverwriteTitle: 'El preajuste ya existe',
|
||||||
|
confirmOverwriteMessage: 'El preajuste con el nombre "{name}" ya existe. ¿Sobrescribir?',
|
||||||
|
cannotDeleteDefault: 'No se puede eliminar el preajuste predeterminado',
|
||||||
adminOnly: 'Solo Administrador',
|
adminOnly: 'Solo Administrador',
|
||||||
selectPlanet: 'Seleccionar Planeta',
|
selectPlanet: 'Seleccionar Planeta',
|
||||||
choosePlanet: 'Elige un planeta',
|
choosePlanet: 'Elige un planeta',
|
||||||
@@ -1156,6 +1207,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
diplomacy: {
|
diplomacy: {
|
||||||
|
sort: {
|
||||||
|
label: 'Ordenar',
|
||||||
|
ascending: 'Ascendente',
|
||||||
|
descending: 'Descendente',
|
||||||
|
reputation: 'Reputación',
|
||||||
|
planets: 'Planetas',
|
||||||
|
difficulty: 'Dificultad',
|
||||||
|
allies: 'Aliados'
|
||||||
|
},
|
||||||
title: 'Diplomacia',
|
title: 'Diplomacia',
|
||||||
description: 'Gestionar relaciones diplomáticas con NPCs',
|
description: 'Gestionar relaciones diplomáticas con NPCs',
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -1771,6 +1831,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 +1998,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 +2026,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 +2100,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 +2121,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 +2146,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 +2155,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 +2168,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 +2281,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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: 'ゲームを一時停止しました',
|
||||||
@@ -1088,6 +1111,20 @@ export default {
|
|||||||
},
|
},
|
||||||
gmView: {
|
gmView: {
|
||||||
title: 'GMコントロールパネル',
|
title: 'GMコントロールパネル',
|
||||||
|
presets: 'プリセット',
|
||||||
|
choosePreset: 'プリセット選択',
|
||||||
|
defaultPreset: 'デフォルト',
|
||||||
|
applyPreset: '適用',
|
||||||
|
savePreset: '保存',
|
||||||
|
presetName: 'プリセット名',
|
||||||
|
presetNameRequired: 'プリセット名を入力してください',
|
||||||
|
presetSaved: '保存しました',
|
||||||
|
presetApplied: '適用しました',
|
||||||
|
deletePreset: 'プリセット削除',
|
||||||
|
presetDeleted: 'プリセットを削除しました',
|
||||||
|
confirmOverwriteTitle: 'プリセットは既に存在します',
|
||||||
|
confirmOverwriteMessage: 'プリセット名 "{name}" は既に存在します。上書きしますか?',
|
||||||
|
cannotDeleteDefault: 'デフォルトプリセットは削除できません',
|
||||||
adminOnly: '管理者専用',
|
adminOnly: '管理者専用',
|
||||||
selectPlanet: '惑星を選択',
|
selectPlanet: '惑星を選択',
|
||||||
choosePlanet: '惑星を選択してください',
|
choosePlanet: '惑星を選択してください',
|
||||||
@@ -1187,6 +1224,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
diplomacy: {
|
diplomacy: {
|
||||||
|
sort: {
|
||||||
|
label: '並び替え',
|
||||||
|
ascending: '昇順',
|
||||||
|
descending: '降順',
|
||||||
|
reputation: '評判',
|
||||||
|
planets: '惑星',
|
||||||
|
difficulty: '難易度',
|
||||||
|
allies: '同盟'
|
||||||
|
},
|
||||||
title: '外交',
|
title: '外交',
|
||||||
description: 'NPCとの外交関係を管理',
|
description: 'NPCとの外交関係を管理',
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -1854,6 +1900,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 +1948,8 @@ export default {
|
|||||||
branchUnlocked: '新しいストーリー分岐が解放されました!'
|
branchUnlocked: '新しいストーリー分岐が解放されました!'
|
||||||
},
|
},
|
||||||
dialogue: {
|
dialogue: {
|
||||||
|
title: 'ストーリー対話',
|
||||||
|
description: 'キャンペーンストーリーの対話内容',
|
||||||
skip: 'スキップ',
|
skip: 'スキップ',
|
||||||
continue: '続ける',
|
continue: '続ける',
|
||||||
finish: '完了',
|
finish: '完了',
|
||||||
@@ -1971,13 +2040,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 +2068,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 +2132,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: 'あなたの旅は続く...新たな冒険が星の彼方で待っている...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: '게임이 일시정지되었습니다',
|
||||||
@@ -1027,6 +1061,20 @@ export default {
|
|||||||
},
|
},
|
||||||
gmView: {
|
gmView: {
|
||||||
title: 'GM 제어판',
|
title: 'GM 제어판',
|
||||||
|
presets: '프리셋',
|
||||||
|
choosePreset: '프리셋 선택',
|
||||||
|
defaultPreset: '기본 프리셋',
|
||||||
|
applyPreset: '프리셋 적용',
|
||||||
|
savePreset: '프리셋 저장',
|
||||||
|
presetName: '프리셋 이름',
|
||||||
|
presetNameRequired: '프리셋 이름을 입력하세요',
|
||||||
|
presetSaved: '프리셋 저장됨',
|
||||||
|
presetApplied: '프리셋 적용됨',
|
||||||
|
deletePreset: '프리셋 삭제',
|
||||||
|
presetDeleted: '프리셋 삭제됨',
|
||||||
|
confirmOverwriteTitle: '프리셋이 이미 존재함',
|
||||||
|
confirmOverwriteMessage: '"{name}" 이름의 프리셋이 이미 존재합니다. 덮어쓰시겠습니까?',
|
||||||
|
cannotDeleteDefault: '기본 프리셋은 삭제할 수 없습니다',
|
||||||
adminOnly: '관리자 전용',
|
adminOnly: '관리자 전용',
|
||||||
selectPlanet: '행성 선택',
|
selectPlanet: '행성 선택',
|
||||||
choosePlanet: '행성을 선택하세요',
|
choosePlanet: '행성을 선택하세요',
|
||||||
@@ -1126,6 +1174,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
diplomacy: {
|
diplomacy: {
|
||||||
|
sort: {
|
||||||
|
label: '정렬',
|
||||||
|
ascending: '오름차순',
|
||||||
|
descending: '내림차순',
|
||||||
|
reputation: '평판',
|
||||||
|
planets: '행성',
|
||||||
|
difficulty: '난이도',
|
||||||
|
allies: '동맹'
|
||||||
|
},
|
||||||
title: '외교',
|
title: '외교',
|
||||||
description: 'NPC와의 외교 관계 관리',
|
description: 'NPC와의 외교 관계 관리',
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -1784,6 +1841,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 +1888,8 @@ export default {
|
|||||||
branchUnlocked: '새로운 스토리 분기가 해금되었습니다!'
|
branchUnlocked: '새로운 스토리 분기가 해금되었습니다!'
|
||||||
},
|
},
|
||||||
dialogue: {
|
dialogue: {
|
||||||
|
title: '스토리 대화',
|
||||||
|
description: '캠페인 스토리 대화 내용',
|
||||||
skip: '건너뛰기',
|
skip: '건너뛰기',
|
||||||
continue: '계속',
|
continue: '계속',
|
||||||
finish: '완료',
|
finish: '완료',
|
||||||
@@ -1900,13 +1980,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 +2008,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 +2073,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: '당신의 여정은 계속된다... 새로운 모험이 별 너머에서 기다리고 있다...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: 'Игра приостановлена',
|
||||||
@@ -1049,6 +1087,20 @@ export default {
|
|||||||
},
|
},
|
||||||
gmView: {
|
gmView: {
|
||||||
title: 'Панель управления GM',
|
title: 'Панель управления GM',
|
||||||
|
presets: 'Предустановки',
|
||||||
|
choosePreset: 'Выбрать предустановку',
|
||||||
|
defaultPreset: 'Стандартная',
|
||||||
|
applyPreset: 'Применить',
|
||||||
|
savePreset: 'Сохранить',
|
||||||
|
presetName: 'Название',
|
||||||
|
presetNameRequired: 'Введите название',
|
||||||
|
presetSaved: 'Сохранено',
|
||||||
|
presetApplied: 'Применено',
|
||||||
|
deletePreset: 'Удалить',
|
||||||
|
presetDeleted: 'Удалено',
|
||||||
|
confirmOverwriteTitle: 'Уже существует',
|
||||||
|
confirmOverwriteMessage: 'Предустановка с именем "{name}" уже существует. Перезаписать?',
|
||||||
|
cannotDeleteDefault: 'Нельзя удалить стандартную предустановку',
|
||||||
adminOnly: 'Только для администратора',
|
adminOnly: 'Только для администратора',
|
||||||
selectPlanet: 'Выбрать планету',
|
selectPlanet: 'Выбрать планету',
|
||||||
choosePlanet: 'Выберите планету',
|
choosePlanet: 'Выберите планету',
|
||||||
@@ -1148,6 +1200,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
diplomacy: {
|
diplomacy: {
|
||||||
|
sort: {
|
||||||
|
label: 'Сортировка',
|
||||||
|
ascending: 'По возрастанию',
|
||||||
|
descending: 'По убыванию',
|
||||||
|
reputation: 'Репутация',
|
||||||
|
planets: 'Планеты',
|
||||||
|
difficulty: 'Сложность',
|
||||||
|
allies: 'Союзники'
|
||||||
|
},
|
||||||
title: 'Дипломатия',
|
title: 'Дипломатия',
|
||||||
description: 'Управление дипломатическими отношениями с NPC',
|
description: 'Управление дипломатическими отношениями с NPC',
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -1836,6 +1897,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 +1945,8 @@ export default {
|
|||||||
branchUnlocked: 'Новая сюжетная ветка разблокирована!'
|
branchUnlocked: 'Новая сюжетная ветка разблокирована!'
|
||||||
},
|
},
|
||||||
dialogue: {
|
dialogue: {
|
||||||
|
title: 'Сюжетный диалог',
|
||||||
|
description: 'Содержание сюжетного диалога кампании',
|
||||||
skip: 'Пропустить',
|
skip: 'Пропустить',
|
||||||
continue: 'Продолжить',
|
continue: 'Продолжить',
|
||||||
finish: 'Завершить',
|
finish: 'Завершить',
|
||||||
@@ -1953,13 +2037,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 +2065,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 +2139,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 +2160,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: 'Ваше путешествие продолжается... Новые приключения ждут за звёздами...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: '游戏已暂停',
|
||||||
@@ -1040,6 +1095,20 @@ export default {
|
|||||||
modifyOfficers: '修改军官',
|
modifyOfficers: '修改军官',
|
||||||
officersDesc: '快速设置军官到期时间',
|
officersDesc: '快速设置军官到期时间',
|
||||||
days: '天',
|
days: '天',
|
||||||
|
presets: '预设',
|
||||||
|
choosePreset: '选择预设',
|
||||||
|
defaultPreset: '默认预设',
|
||||||
|
applyPreset: '应用预设',
|
||||||
|
presetName: '预设名称',
|
||||||
|
savePreset: '保存预设',
|
||||||
|
presetNameRequired: '请输入预设名称',
|
||||||
|
presetSaved: '预设保存成功',
|
||||||
|
presetApplied: '预设应用成功',
|
||||||
|
deletePreset: '删除预设',
|
||||||
|
presetDeleted: '预设已删除',
|
||||||
|
confirmOverwriteTitle: '预设已存在',
|
||||||
|
confirmOverwriteMessage: '名为 "{name}" 的预设已存在,是否覆盖?',
|
||||||
|
cannotDeleteDefault: '无法删除默认预设',
|
||||||
npcTesting: 'NPC 测试',
|
npcTesting: 'NPC 测试',
|
||||||
npcTestingDesc: '测试NPC侦查和攻击行为',
|
npcTestingDesc: '测试NPC侦查和攻击行为',
|
||||||
selectNPC: '选择NPC',
|
selectNPC: '选择NPC',
|
||||||
@@ -1204,6 +1273,15 @@ export default {
|
|||||||
npcEliminatedMessage: '你消灭了{npcName}的所有星球!该势力已被彻底摧毁。'
|
npcEliminatedMessage: '你消灭了{npcName}的所有星球!该势力已被彻底摧毁。'
|
||||||
},
|
},
|
||||||
searchPlaceholder: '搜索NPC名称...',
|
searchPlaceholder: '搜索NPC名称...',
|
||||||
|
sort: {
|
||||||
|
label: '排序',
|
||||||
|
ascending: '升序',
|
||||||
|
descending: '降序',
|
||||||
|
reputation: '好感度',
|
||||||
|
planets: '星球数量',
|
||||||
|
difficulty: '难度',
|
||||||
|
allies: '盟友数量'
|
||||||
|
},
|
||||||
// 通知类型
|
// 通知类型
|
||||||
notificationType: {
|
notificationType: {
|
||||||
tradeOffer: '贸易提议',
|
tradeOffer: '贸易提议',
|
||||||
@@ -1721,8 +1799,8 @@ export default {
|
|||||||
colonizer: '成功殖民星球次数',
|
colonizer: '成功殖民星球次数',
|
||||||
spy: '执行侦查任务次数',
|
spy: '执行侦查任务次数',
|
||||||
deployer: '执行部署任务次数',
|
deployer: '执行部署任务次数',
|
||||||
explorer: '远征总次数',
|
explorer: '探险总次数',
|
||||||
luckyExplorer: '远征成功次数',
|
luckyExplorer: '探险成功次数',
|
||||||
recycler: '回收任务次数',
|
recycler: '回收任务次数',
|
||||||
scavenger: '回收资源总量',
|
scavenger: '回收资源总量',
|
||||||
destroyer: '摧毁星球次数',
|
destroyer: '摧毁星球次数',
|
||||||
@@ -1782,6 +1860,8 @@ export default {
|
|||||||
branchUnlocked: '新的故事分支已解锁!'
|
branchUnlocked: '新的故事分支已解锁!'
|
||||||
},
|
},
|
||||||
dialogue: {
|
dialogue: {
|
||||||
|
title: '剧情对话',
|
||||||
|
description: '战役剧情对话内容',
|
||||||
skip: '跳过',
|
skip: '跳过',
|
||||||
continue: '继续',
|
continue: '继续',
|
||||||
finish: '完成',
|
finish: '完成',
|
||||||
@@ -1848,7 +1928,7 @@ export default {
|
|||||||
},
|
},
|
||||||
'2_2': {
|
'2_2': {
|
||||||
title: '深空探险',
|
title: '深空探险',
|
||||||
description: '派遣舰队进行远征探险'
|
description: '派遣舰队进行探险探险'
|
||||||
},
|
},
|
||||||
'2_3': {
|
'2_3': {
|
||||||
title: '神秘信号',
|
title: '神秘信号',
|
||||||
@@ -1944,13 +2024,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 +2052,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 +2115,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 +2140,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 +2165,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 +2186,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}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: '遊戲已暫停',
|
||||||
@@ -1045,6 +1080,20 @@ export default {
|
|||||||
},
|
},
|
||||||
gmView: {
|
gmView: {
|
||||||
title: 'GM 管理面板',
|
title: 'GM 管理面板',
|
||||||
|
presets: '預設',
|
||||||
|
choosePreset: '選擇預設',
|
||||||
|
defaultPreset: '預設範本',
|
||||||
|
applyPreset: '套用預設',
|
||||||
|
savePreset: '儲存預設',
|
||||||
|
presetName: '預設名稱',
|
||||||
|
presetNameRequired: '請輸入預設名稱',
|
||||||
|
presetSaved: '預設已儲存',
|
||||||
|
presetApplied: '預設已套用',
|
||||||
|
deletePreset: '刪除預設',
|
||||||
|
presetDeleted: '預設已刪除',
|
||||||
|
confirmOverwriteTitle: '預設已存在',
|
||||||
|
confirmOverwriteMessage: '名為 "{name}" 的預設已存在,是否覆蓋?',
|
||||||
|
cannotDeleteDefault: '無法刪除預設範本',
|
||||||
adminOnly: '僅管理員',
|
adminOnly: '僅管理員',
|
||||||
selectPlanet: '選擇星球',
|
selectPlanet: '選擇星球',
|
||||||
choosePlanet: '選擇一個星球',
|
choosePlanet: '選擇一個星球',
|
||||||
@@ -1144,6 +1193,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
diplomacy: {
|
diplomacy: {
|
||||||
|
sort: {
|
||||||
|
label: '排序',
|
||||||
|
ascending: '升序',
|
||||||
|
descending: '降序',
|
||||||
|
reputation: '聲望',
|
||||||
|
planets: '星球',
|
||||||
|
difficulty: '難度',
|
||||||
|
allies: '盟友'
|
||||||
|
},
|
||||||
title: '外交',
|
title: '外交',
|
||||||
description: '管理與NPC的外交關係',
|
description: '管理與NPC的外交關係',
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -1933,6 +1991,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 +2039,8 @@ export default {
|
|||||||
branchUnlocked: '新的故事分支已解鎖!'
|
branchUnlocked: '新的故事分支已解鎖!'
|
||||||
},
|
},
|
||||||
dialogue: {
|
dialogue: {
|
||||||
|
title: '劇情對話',
|
||||||
|
description: '戰役劇情對話內容',
|
||||||
skip: '跳過',
|
skip: '跳過',
|
||||||
continue: '繼續',
|
continue: '繼續',
|
||||||
finish: '完成',
|
finish: '完成',
|
||||||
@@ -2122,13 +2203,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 +2231,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 +2299,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 +2319,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 +2344,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 +2365,8 @@ export default {
|
|||||||
},
|
},
|
||||||
'5_5': {
|
'5_5': {
|
||||||
prologue_1: '你的傳奇才剛剛開始。繼續探索,征服更多的星系!',
|
prologue_1: '你的傳奇才剛剛開始。繼續探索,征服更多的星系!',
|
||||||
epilogue_1: '銀河系廣闘無垠,還有無數秘密等待你去發現...'
|
epilogue_1: '銀河系廣闘無垠,還有無數秘密等待你去發現...',
|
||||||
|
epilogue_2: '你的旅程還在繼續...新的冒險在星海彼岸等待著你...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Fleet, Resources, BattleResult, Officer, TechnologyType } from '@/
|
|||||||
import { DefenseType, OfficerType } from '@/types/game'
|
import { DefenseType, OfficerType } from '@/types/game'
|
||||||
import { workerManager } from '@/workers/workerManager'
|
import { workerManager } from '@/workers/workerManager'
|
||||||
import { MOON_CONFIG } from '@/config/gameConfig'
|
import { MOON_CONFIG } from '@/config/gameConfig'
|
||||||
|
import { generateId } from '@/utils/id'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行战斗模拟
|
* 执行战斗模拟
|
||||||
@@ -15,7 +16,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 +43,7 @@ export const simulateBattle = async (
|
|||||||
shieldTech: defenderShieldTech,
|
shieldTech: defenderShieldTech,
|
||||||
armorTech: defenderArmorTech
|
armorTech: defenderArmorTech
|
||||||
},
|
},
|
||||||
maxRounds: 6 // 最多6回合
|
maxRounds: battleToFinish ? 100 : 6 // 战斗到底模式最多100回合,经典模式6回合
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算掠夺(仅攻击方胜利时)
|
// 计算掠夺(仅攻击方胜利时)
|
||||||
@@ -65,7 +67,7 @@ export const simulateBattle = async (
|
|||||||
|
|
||||||
// 生成战斗报告
|
// 生成战斗报告
|
||||||
const battleResult: BattleResult = {
|
const battleResult: BattleResult = {
|
||||||
id: `battle_${Date.now()}`,
|
id: generateId('battle'),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
attackerId: '',
|
attackerId: '',
|
||||||
defenderId: '',
|
defenderId: '',
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
type ShipType
|
type ShipType
|
||||||
} from '@/types/game'
|
} from '@/types/game'
|
||||||
import { MAIN_CAMPAIGN, getAllQuests, getQuestById, getQuestsByChapter } from '@/config/campaignConfig'
|
import { MAIN_CAMPAIGN, getAllQuests, getQuestById, getQuestsByChapter } from '@/config/campaignConfig'
|
||||||
|
import { generateId } from '@/utils/id'
|
||||||
import * as resourceLogic from './resourceLogic'
|
import * as resourceLogic from './resourceLogic'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -364,7 +365,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) {
|
||||||
@@ -514,7 +516,7 @@ export const createQuestNotification = (
|
|||||||
): QuestNotification => {
|
): QuestNotification => {
|
||||||
const quest = getQuestById(questId)
|
const quest = getQuestById(questId)
|
||||||
return {
|
return {
|
||||||
id: `quest_notification_${Date.now()}`,
|
id: generateId('quest_notification'),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
questId,
|
questId,
|
||||||
questTitleKey: quest?.titleKey || '',
|
questTitleKey: quest?.titleKey || '',
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ 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 { generateId } from '@/utils/id'
|
||||||
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'
|
||||||
@@ -60,7 +62,7 @@ export const createFleetMission = (
|
|||||||
): FleetMission => {
|
): FleetMission => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
return {
|
return {
|
||||||
id: `mission_${now}`,
|
id: generateId('mission'),
|
||||||
playerId,
|
playerId,
|
||||||
originPlanetId,
|
originPlanetId,
|
||||||
// 深拷贝targetPosition,避免多个任务共享同一个引用
|
// 深拷贝targetPosition,避免多个任务共享同一个引用
|
||||||
@@ -156,6 +158,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,11 +167,12 @@ 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
|
||||||
battleResult.id = `battle_${Date.now()}`
|
battleResult.id = generateId('battle')
|
||||||
battleResult.attackerId = attacker.id
|
battleResult.attackerId = attacker.id
|
||||||
battleResult.defenderId = targetPlanet.ownerId || 'unknown'
|
battleResult.defenderId = targetPlanet.ownerId || 'unknown'
|
||||||
battleResult.attackerPlanetId = mission.originPlanetId
|
battleResult.attackerPlanetId = mission.originPlanetId
|
||||||
@@ -250,6 +254,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,11 +263,12 @@ 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和参与者信息
|
||||||
battleResult.id = `battle_${Date.now()}`
|
battleResult.id = generateId('battle')
|
||||||
battleResult.attackerId = npc.id
|
battleResult.attackerId = npc.id
|
||||||
battleResult.defenderId = defender.id
|
battleResult.defenderId = defender.id
|
||||||
battleResult.attackerPlanetId = mission.originPlanetId
|
battleResult.attackerPlanetId = mission.originPlanetId
|
||||||
@@ -409,7 +415,7 @@ export const processColonizeArrival = (
|
|||||||
|
|
||||||
// 创建新殖民地
|
// 创建新殖民地
|
||||||
const newPlanet: Planet = {
|
const newPlanet: Planet = {
|
||||||
id: `planet_${Date.now()}`,
|
id: generateId('planet'),
|
||||||
name: `${colonyNameTemplate} ${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`,
|
name: `${colonyNameTemplate} ${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`,
|
||||||
ownerId: player.id,
|
ownerId: player.id,
|
||||||
position: mission.targetPosition,
|
position: mission.targetPosition,
|
||||||
@@ -559,7 +565,7 @@ export const processSpyArrival = (
|
|||||||
const wasDetected = Math.random() < detectionChance
|
const wasDetected = Math.random() < detectionChance
|
||||||
|
|
||||||
const spyReport: SpyReport = {
|
const spyReport: SpyReport = {
|
||||||
id: `spy_${Date.now()}`,
|
id: generateId('spy'),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
spyId: attacker.id,
|
spyId: attacker.id,
|
||||||
targetPlanetId: targetPlanet.id,
|
targetPlanetId: targetPlanet.id,
|
||||||
@@ -721,12 +727,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 +743,8 @@ export interface ExpeditionResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理远征任务到达
|
* 处理探险任务到达
|
||||||
* 远征任务会随机触发各种事件,基于探险区域配置
|
* 探险任务会随机触发各种事件,基于探险区域配置
|
||||||
*/
|
*/
|
||||||
export const processExpeditionArrival = (mission: FleetMission): ExpeditionResult => {
|
export const processExpeditionArrival = (mission: FleetMission): ExpeditionResult => {
|
||||||
// 获取探险区域配置,默认为近空区域
|
// 获取探险区域配置,默认为近空区域
|
||||||
@@ -968,9 +974,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 +1010,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 +1023,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 = generateId('battle')
|
||||||
|
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 +1145,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 +1168,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 +1324,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 +1373,7 @@ export const updateFleetMissions = async (
|
|||||||
planets.delete(targetKey)
|
planets.delete(targetKey)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,66 @@
|
|||||||
* 处理星际导弹攻击、射程计算、拦截等
|
* 处理星际导弹攻击、射程计算、拦截等
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Planet, MissileAttack, DefenseType, TechnologyType, Position } from '@/types/game'
|
import type { Planet, MissileAttack, DefenseType, TechnologyType, Position, BuildQueueItem } from '@/types/game'
|
||||||
import { DefenseType as DefenseTypes } from '@/types/game'
|
import { DefenseType as DefenseTypes, BuildingType } from '@/types/game'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算导弹发射井容量
|
||||||
|
*/
|
||||||
|
export const calculateMissileSiloCapacity = (buildings: Partial<Record<BuildingType, number>>): number => {
|
||||||
|
const siloLevel = buildings[BuildingType.MissileSilo] || 0
|
||||||
|
return siloLevel * 10 // 每级存储10枚导弹
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算当前导弹总数
|
||||||
|
*/
|
||||||
|
export const calculateCurrentMissileCount = (defense: Partial<Record<DefenseType, number>>): number => {
|
||||||
|
const interplanetaryMissiles = defense[DefenseTypes.InterplanetaryMissile] || 0
|
||||||
|
const antiBallisticMissiles = defense[DefenseTypes.AntiBallisticMissile] || 0
|
||||||
|
return interplanetaryMissiles + antiBallisticMissiles
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算建造队列中的导弹总数
|
||||||
|
*/
|
||||||
|
export const calculateQueueMissileCount = (buildQueue: BuildQueueItem[]): number => {
|
||||||
|
let queueMissileCount = 0
|
||||||
|
|
||||||
|
for (const item of buildQueue) {
|
||||||
|
if (item.type === 'defense') {
|
||||||
|
const defenseType = item.itemType as DefenseType
|
||||||
|
if (defenseType === DefenseTypes.InterplanetaryMissile || defenseType === DefenseTypes.AntiBallisticMissile) {
|
||||||
|
queueMissileCount += item.quantity || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return queueMissileCount
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查导弹容量限制
|
||||||
|
*/
|
||||||
|
export const checkMissileSiloLimit = (
|
||||||
|
defenseType: DefenseType,
|
||||||
|
currentDefense: Partial<Record<DefenseType, number>>,
|
||||||
|
buildings: Partial<Record<BuildingType, number>>,
|
||||||
|
quantity: number,
|
||||||
|
buildQueue?: BuildQueueItem[]
|
||||||
|
): boolean => {
|
||||||
|
// 只对导弹类型进行检查
|
||||||
|
if (defenseType !== DefenseTypes.InterplanetaryMissile && defenseType !== DefenseTypes.AntiBallisticMissile) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxCapacity = calculateMissileSiloCapacity(buildings)
|
||||||
|
const currentCount = calculateCurrentMissileCount(currentDefense)
|
||||||
|
const queueCount = buildQueue ? calculateQueueMissileCount(buildQueue) : 0
|
||||||
|
const newCount = currentCount + queueCount + quantity
|
||||||
|
|
||||||
|
return newCount <= maxCapacity
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算导弹射程(基于脉冲引擎等级)
|
* 计算导弹射程(基于脉冲引擎等级)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Planet, Resources } from '@/types/game'
|
import type { Planet, Resources } from '@/types/game'
|
||||||
import { BuildingType, ShipType, DefenseType } from '@/types/game'
|
import { BuildingType, ShipType, DefenseType } from '@/types/game'
|
||||||
import { MOON_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
|
import { MOON_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
|
||||||
|
import { generateId } from '@/utils/id'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算月球生成概率
|
* 计算月球生成概率
|
||||||
@@ -79,7 +80,7 @@ export const tryGenerateMoon = (
|
|||||||
|
|
||||||
// 生成月球
|
// 生成月球
|
||||||
const moon: Planet = {
|
const moon: Planet = {
|
||||||
id: `moon_${Date.now()}`,
|
id: generateId('moon'),
|
||||||
name: `Moon [${planetPosition.galaxy}:${planetPosition.system}:${planetPosition.position}]`,
|
name: `Moon [${planetPosition.galaxy}:${planetPosition.system}:${planetPosition.position}]`,
|
||||||
ownerId: playerId,
|
ownerId: playerId,
|
||||||
position: planetPosition,
|
position: planetPosition,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Planet, Resources } from '@/types/game'
|
import type { Planet, Resources } from '@/types/game'
|
||||||
import { ShipType, DefenseType, BuildingType } from '@/types/game'
|
import { ShipType, DefenseType, BuildingType } from '@/types/game'
|
||||||
import { MOON_CONFIG, PLANET_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
|
import { MOON_CONFIG, PLANET_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
|
||||||
|
import { generateId } from '@/utils/id'
|
||||||
import * as oreDepositLogic from './oreDepositLogic'
|
import * as oreDepositLogic from './oreDepositLogic'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,7 +174,7 @@ export const createMoon = (
|
|||||||
moonSuffix: string = "'s Moon",
|
moonSuffix: string = "'s Moon",
|
||||||
diameter?: number
|
diameter?: number
|
||||||
): Planet => {
|
): Planet => {
|
||||||
const moonId = `moon_${Date.now()}`
|
const moonId = generateId('moon')
|
||||||
const moon: Planet = {
|
const moon: Planet = {
|
||||||
id: moonId,
|
id: moonId,
|
||||||
name: `${parentPlanet.name}${moonSuffix}`,
|
name: `${parentPlanet.name}${moonSuffix}`,
|
||||||
@@ -237,7 +238,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 +321,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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -156,64 +156,6 @@ export const checkShieldDomeLimit = (
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算导弹发射井容量
|
|
||||||
*/
|
|
||||||
export const calculateMissileSiloCapacity = (buildings: Partial<Record<BuildingType, number>>): number => {
|
|
||||||
const siloLevel = buildings[BuildingType.MissileSilo] || 0
|
|
||||||
return siloLevel * 10 // 每级存储10枚导弹
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算当前导弹总数
|
|
||||||
*/
|
|
||||||
export const calculateCurrentMissileCount = (defense: Partial<Record<DefenseType, number>>): number => {
|
|
||||||
const interplanetaryMissiles = defense[DefenseType.InterplanetaryMissile] || 0
|
|
||||||
const antiBallisticMissiles = defense[DefenseType.AntiBallisticMissile] || 0
|
|
||||||
return interplanetaryMissiles + antiBallisticMissiles
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算建造队列中的导弹总数
|
|
||||||
*/
|
|
||||||
export const calculateQueueMissileCount = (buildQueue: Array<{ type: string; itemType: string; quantity?: number }>): number => {
|
|
||||||
let queueMissileCount = 0
|
|
||||||
|
|
||||||
for (const item of buildQueue) {
|
|
||||||
if (item.type === 'defense') {
|
|
||||||
const defenseType = item.itemType as DefenseType
|
|
||||||
if (defenseType === DefenseType.InterplanetaryMissile || defenseType === DefenseType.AntiBallisticMissile) {
|
|
||||||
queueMissileCount += item.quantity || 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return queueMissileCount
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查导弹容量限制
|
|
||||||
*/
|
|
||||||
export const checkMissileSiloLimit = (
|
|
||||||
defenseType: DefenseType,
|
|
||||||
currentDefense: Partial<Record<DefenseType, number>>,
|
|
||||||
buildings: Partial<Record<BuildingType, number>>,
|
|
||||||
quantity: number,
|
|
||||||
buildQueue?: Array<{ type: string; itemType: string; quantity?: number }>
|
|
||||||
): boolean => {
|
|
||||||
// 只对导弹类型进行检查
|
|
||||||
if (defenseType !== DefenseType.InterplanetaryMissile && defenseType !== DefenseType.AntiBallisticMissile) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxCapacity = calculateMissileSiloCapacity(buildings)
|
|
||||||
const currentCount = calculateCurrentMissileCount(currentDefense)
|
|
||||||
const queueCount = buildQueue ? calculateQueueMissileCount(buildQueue) : 0
|
|
||||||
const newCount = currentCount + queueCount + quantity
|
|
||||||
|
|
||||||
return newCount <= maxCapacity
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建舰船建造队列项
|
* 创建舰船建造队列项
|
||||||
*/
|
*/
|
||||||
@@ -322,3 +264,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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Planet, Resources, BuildQueueItem, Fleet, Officer } from '@/types/game'
|
import type { Planet, Resources, BuildQueueItem, Fleet, Officer } from '@/types/game'
|
||||||
import { ShipType, DefenseType, TechnologyType, OfficerType, BuildingType } from '@/types/game'
|
import { ShipType, DefenseType, TechnologyType, OfficerType, BuildingType } from '@/types/game'
|
||||||
import * as shipLogic from './shipLogic'
|
import * as shipLogic from './shipLogic'
|
||||||
|
import * as missileLogic from './missileLogic'
|
||||||
import * as resourceLogic from './resourceLogic'
|
import * as resourceLogic from './resourceLogic'
|
||||||
import * as officerLogic from './officerLogic'
|
import * as officerLogic from './officerLogic'
|
||||||
import * as publicLogic from './publicLogic'
|
import * as publicLogic from './publicLogic'
|
||||||
@@ -101,7 +102,7 @@ export const validateDefenseBuild = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 导弹发射井容量限制
|
// 导弹发射井容量限制
|
||||||
if (!shipLogic.checkMissileSiloLimit(defenseType, planet.defense, planet.buildings, quantity, planet.buildQueue)) {
|
if (!missileLogic.checkMissileSiloLimit(defenseType, planet.defense, planet.buildings, quantity, planet.buildQueue)) {
|
||||||
return { valid: false, reason: 'errors.missileSiloLimit' }
|
return { valid: false, reason: 'errors.missileSiloLimit' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,3 +202,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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 资源数字更新动画 */
|
/* 资源数字更新动画 */
|
||||||
@@ -218,3 +222,21 @@ aside nav a:hover button {
|
|||||||
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;
|
||||||
|
}
|
||||||
@@ -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 // 存档存放路径
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
8
src/utils/id.ts
Normal file
8
src/utils/id.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* 统一生成带前缀的业务ID
|
||||||
|
* 便于后续集中调整ID规则
|
||||||
|
*/
|
||||||
|
export const generateId = (prefix: string): string => {
|
||||||
|
const timestamp = Date.now()
|
||||||
|
return `${prefix}_${timestamp}_${Math.random().toString(36).slice(2, 9)}`
|
||||||
|
}
|
||||||
@@ -1,4 +1,18 @@
|
|||||||
import type { Planet, DebrisField, NPC } from '@/types/game'
|
import type {
|
||||||
|
AllyDefenseNotification,
|
||||||
|
DebrisField,
|
||||||
|
FleetMission,
|
||||||
|
IncomingFleetAlert,
|
||||||
|
JointAttackInvite,
|
||||||
|
MissionReport,
|
||||||
|
NPC,
|
||||||
|
NPCActivityNotification,
|
||||||
|
Planet,
|
||||||
|
Player,
|
||||||
|
Position,
|
||||||
|
SpiedNotification,
|
||||||
|
SpyReport
|
||||||
|
} from '@/types/game'
|
||||||
import { decryptData, encryptData } from './crypto'
|
import { decryptData, encryptData } from './crypto'
|
||||||
import { generatePlanetTemperature } from '@/logic/planetLogic'
|
import { generatePlanetTemperature } from '@/logic/planetLogic'
|
||||||
import pkg from '../../package.json'
|
import pkg from '../../package.json'
|
||||||
@@ -8,6 +22,514 @@ import pkg from '../../package.json'
|
|||||||
* 用于从旧版本数据结构迁移到新版本
|
* 用于从旧版本数据结构迁移到新版本
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type PlanetKind = 'planet' | 'moon'
|
||||||
|
type RemappedPlanetEntry = { newId: string; name: string }
|
||||||
|
type DuplicatePlanetKindMap = Map<PlanetKind, RemappedPlanetEntry>
|
||||||
|
type DuplicatePlanetPositionMap = Map<string, DuplicatePlanetKindMap>
|
||||||
|
|
||||||
|
// oldPlanetId -> position -> planet/moon -> remapped target
|
||||||
|
type DuplicatePlanetIdMap = Map<string, DuplicatePlanetPositionMap>
|
||||||
|
|
||||||
|
interface MigratablePlayer extends Player {
|
||||||
|
diplomaticRelations?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MigratableGameData {
|
||||||
|
currentPlanetId?: string
|
||||||
|
player?: MigratablePlayer
|
||||||
|
npcs?: NPC[]
|
||||||
|
universePlanets?: Record<string, Planet>
|
||||||
|
debrisFields?: Record<string, DebrisField>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlanetReferenceContext {
|
||||||
|
position?: Position
|
||||||
|
isMoon?: boolean
|
||||||
|
planetName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HasTargetPlanetId {
|
||||||
|
targetPlanetId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HasOriginPlanetId {
|
||||||
|
originPlanetId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HasParentPlanetId {
|
||||||
|
parentPlanetId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HasCurrentPlanetId {
|
||||||
|
currentPlanetId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPlanetPositionKey = (position: Position): string => {
|
||||||
|
return `${position.galaxy}:${position.system}:${position.position}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPlanetKindKey = (isMoon?: boolean): PlanetKind => {
|
||||||
|
return isMoon ? 'moon' : 'planet'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPlanetEntriesFor = (
|
||||||
|
planetId: string,
|
||||||
|
idMap: DuplicatePlanetIdMap,
|
||||||
|
position?: Position
|
||||||
|
): DuplicatePlanetKindMap | undefined => {
|
||||||
|
if (!position) return undefined
|
||||||
|
|
||||||
|
return idMap.get(planetId)?.get(getPlanetPositionKey(position))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEntriesByName = (entries: Iterable<RemappedPlanetEntry>, planetName?: string): RemappedPlanetEntry[] => {
|
||||||
|
if (!planetName) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(entries).filter(entry => entry.name === planetName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUniqueEntryByName = (entries: Iterable<RemappedPlanetEntry>, planetName?: string): RemappedPlanetEntry | undefined => {
|
||||||
|
const matchedEntries = getEntriesByName(entries, planetName)
|
||||||
|
if (matchedEntries.length !== 1) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedEntries[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOnlyEntry = (entries: DuplicatePlanetKindMap): RemappedPlanetEntry | undefined => {
|
||||||
|
if (entries.size !== 1) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(entries.values())[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEntriesAcrossPositions = (byPosition: DuplicatePlanetPositionMap): RemappedPlanetEntry[] => {
|
||||||
|
const entries: RemappedPlanetEntry[] = []
|
||||||
|
|
||||||
|
byPosition.forEach(byKind => {
|
||||||
|
byKind.forEach(entry => {
|
||||||
|
entries.push(entry)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildDuplicatePlanetIdMap = (player: Player): DuplicatePlanetIdMap => {
|
||||||
|
const planetsByOriginalId = new Map<string, Planet[]>()
|
||||||
|
|
||||||
|
player.planets.forEach(planet => {
|
||||||
|
let group = planetsByOriginalId.get(planet.id)
|
||||||
|
if (!group) {
|
||||||
|
group = []
|
||||||
|
planetsByOriginalId.set(planet.id, group)
|
||||||
|
}
|
||||||
|
group.push(planet)
|
||||||
|
})
|
||||||
|
|
||||||
|
const idMap: DuplicatePlanetIdMap = new Map()
|
||||||
|
|
||||||
|
planetsByOriginalId.forEach((planets, originalId) => {
|
||||||
|
if (planets.length <= 1) return
|
||||||
|
|
||||||
|
planets.forEach((planet, index) => {
|
||||||
|
if (index === 0) return
|
||||||
|
|
||||||
|
const newId = `${originalId}_${Math.random().toString(36).substring(2, 9)}`
|
||||||
|
const positionKey = getPlanetPositionKey(planet.position)
|
||||||
|
|
||||||
|
let byPosition = idMap.get(originalId)
|
||||||
|
if (!byPosition) {
|
||||||
|
byPosition = new Map()
|
||||||
|
idMap.set(originalId, byPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
let byKind = byPosition.get(positionKey)
|
||||||
|
if (!byKind) {
|
||||||
|
byKind = new Map()
|
||||||
|
byPosition.set(positionKey, byKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
byKind.set(getPlanetKindKey(planet.isMoon), {
|
||||||
|
newId,
|
||||||
|
name: planet.name
|
||||||
|
})
|
||||||
|
|
||||||
|
planet.id = newId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return idMap
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveRemappedPlanetId = (
|
||||||
|
planetId: string | undefined,
|
||||||
|
idMap: DuplicatePlanetIdMap,
|
||||||
|
context: PlanetReferenceContext = {}
|
||||||
|
): string | undefined => {
|
||||||
|
if (!planetId) return undefined
|
||||||
|
|
||||||
|
const byPosition = idMap.get(planetId)
|
||||||
|
if (!byPosition) return undefined
|
||||||
|
|
||||||
|
if (context.position) {
|
||||||
|
const byKind = getPlanetEntriesFor(planetId, idMap, context.position)
|
||||||
|
if (!byKind) return undefined
|
||||||
|
|
||||||
|
// 只有在位置或名称足够区分目标时才重写引用,避免把旧引用误指到错误星球
|
||||||
|
if (context.isMoon !== undefined) {
|
||||||
|
return byKind.get(getPlanetKindKey(context.isMoon))?.newId
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchedByName = getUniqueEntryByName(byKind.values(), context.planetName)
|
||||||
|
if (matchedByName) {
|
||||||
|
return matchedByName.newId
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOnlyEntry(byKind)?.newId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.planetName) {
|
||||||
|
return getUniqueEntryByName(getEntriesAcrossPositions(byPosition), context.planetName)?.newId
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUpdatedPlanetId = (
|
||||||
|
currentPlanetId: string | undefined,
|
||||||
|
idMap: DuplicatePlanetIdMap,
|
||||||
|
context: PlanetReferenceContext = {}
|
||||||
|
): string | undefined => {
|
||||||
|
const remappedPlanetId = resolveRemappedPlanetId(currentPlanetId, idMap, context)
|
||||||
|
if (!remappedPlanetId || remappedPlanetId === currentPlanetId) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return remappedPlanetId
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTargetPlanetId = (
|
||||||
|
target: HasTargetPlanetId,
|
||||||
|
idMap: DuplicatePlanetIdMap,
|
||||||
|
context: PlanetReferenceContext = {}
|
||||||
|
): boolean => {
|
||||||
|
const remappedPlanetId = getUpdatedPlanetId(target.targetPlanetId, idMap, context)
|
||||||
|
if (!remappedPlanetId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
target.targetPlanetId = remappedPlanetId
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateOriginPlanetId = (
|
||||||
|
target: HasOriginPlanetId,
|
||||||
|
idMap: DuplicatePlanetIdMap,
|
||||||
|
context: PlanetReferenceContext = {}
|
||||||
|
): boolean => {
|
||||||
|
const remappedPlanetId = getUpdatedPlanetId(target.originPlanetId, idMap, context)
|
||||||
|
if (!remappedPlanetId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
target.originPlanetId = remappedPlanetId
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateParentPlanetId = (
|
||||||
|
target: HasParentPlanetId,
|
||||||
|
idMap: DuplicatePlanetIdMap,
|
||||||
|
context: PlanetReferenceContext = {}
|
||||||
|
): boolean => {
|
||||||
|
const remappedPlanetId = getUpdatedPlanetId(target.parentPlanetId, idMap, context)
|
||||||
|
if (!remappedPlanetId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
target.parentPlanetId = remappedPlanetId
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCurrentPlanetId = (
|
||||||
|
target: HasCurrentPlanetId,
|
||||||
|
idMap: DuplicatePlanetIdMap,
|
||||||
|
context: PlanetReferenceContext = {}
|
||||||
|
): boolean => {
|
||||||
|
const remappedPlanetId = getUpdatedPlanetId(target.currentPlanetId, idMap, context)
|
||||||
|
if (!remappedPlanetId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
target.currentPlanetId = remappedPlanetId
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMissionTargetPlanetId = (mission: FleetMission, idMap: DuplicatePlanetIdMap): boolean => {
|
||||||
|
return updateTargetPlanetId(mission, idMap, {
|
||||||
|
position: mission.targetPosition,
|
||||||
|
isMoon: mission.targetIsMoon
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSpyReportTargetPlanetId = (report: SpyReport, idMap: DuplicatePlanetIdMap): boolean => {
|
||||||
|
return updateTargetPlanetId(report, idMap, {
|
||||||
|
position: report.targetPosition,
|
||||||
|
planetName: report.targetPlanetName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSpiedNotificationTargetPlanetId = (
|
||||||
|
notification: SpiedNotification,
|
||||||
|
idMap: DuplicatePlanetIdMap
|
||||||
|
): boolean => {
|
||||||
|
return updateTargetPlanetId(notification, idMap, {
|
||||||
|
planetName: notification.targetPlanetName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateNPCActivityTargetPlanetId = (
|
||||||
|
notification: NPCActivityNotification,
|
||||||
|
idMap: DuplicatePlanetIdMap
|
||||||
|
): boolean => {
|
||||||
|
return updateTargetPlanetId(notification, idMap, {
|
||||||
|
position: notification.targetPosition,
|
||||||
|
planetName: notification.targetPlanetName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateIncomingAlertTargetPlanetId = (
|
||||||
|
alert: IncomingFleetAlert,
|
||||||
|
idMap: DuplicatePlanetIdMap
|
||||||
|
): boolean => {
|
||||||
|
return updateTargetPlanetId(alert, idMap, {
|
||||||
|
planetName: alert.targetPlanetName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateJointAttackTargetPlanetId = (
|
||||||
|
invite: JointAttackInvite,
|
||||||
|
idMap: DuplicatePlanetIdMap
|
||||||
|
): boolean => {
|
||||||
|
return updateTargetPlanetId(invite, idMap, {
|
||||||
|
position: invite.targetPosition
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAllyDefenseTargetPlanetId = (
|
||||||
|
notification: AllyDefenseNotification,
|
||||||
|
idMap: DuplicatePlanetIdMap
|
||||||
|
): boolean => {
|
||||||
|
return updateTargetPlanetId(notification, idMap, {
|
||||||
|
planetName: notification.targetPlanetName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMissionReportPlanetIds = (report: MissionReport, idMap: DuplicatePlanetIdMap): boolean => {
|
||||||
|
let mutated = false
|
||||||
|
|
||||||
|
if (updateOriginPlanetId(report, idMap, {
|
||||||
|
planetName: report.originPlanetName
|
||||||
|
})) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateTargetPlanetId(report, idMap, {
|
||||||
|
position: report.targetPosition,
|
||||||
|
planetName: report.targetPlanetName
|
||||||
|
})) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.details?.newPlanetId) {
|
||||||
|
const remappedNewPlanetId = getUpdatedPlanetId(report.details.newPlanetId, idMap, {
|
||||||
|
position: report.targetPosition,
|
||||||
|
planetName: report.details.newPlanetName || report.targetPlanetName
|
||||||
|
})
|
||||||
|
|
||||||
|
if (remappedNewPlanetId) {
|
||||||
|
report.details.newPlanetId = remappedNewPlanetId
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutated
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixPlayerPlanetsAndQueues = (player: Player, idMap: DuplicatePlanetIdMap): boolean => {
|
||||||
|
let mutated = false
|
||||||
|
|
||||||
|
player.planets.forEach(planet => {
|
||||||
|
if (planet.isMoon && updateParentPlanetId(planet, idMap, {
|
||||||
|
position: planet.position,
|
||||||
|
isMoon: false
|
||||||
|
})) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待队列里的 planetId 应始终与所属星球保持一致
|
||||||
|
planet.waitingBuildQueue?.forEach(item => {
|
||||||
|
if (item.planetId && item.planetId !== planet.id) {
|
||||||
|
item.planetId = planet.id
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return mutated
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixPlayerReferences = (
|
||||||
|
player: Player,
|
||||||
|
data: MigratableGameData,
|
||||||
|
idMap: DuplicatePlanetIdMap
|
||||||
|
): boolean => {
|
||||||
|
let mutated = false
|
||||||
|
|
||||||
|
if (updateCurrentPlanetId(data, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
player.fleetMissions?.forEach(mission => {
|
||||||
|
if (updateMissionTargetPlanetId(mission, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.spyReports?.forEach(report => {
|
||||||
|
if (updateSpyReportTargetPlanetId(report, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.spiedNotifications?.forEach(notification => {
|
||||||
|
if (updateSpiedNotificationTargetPlanetId(notification, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.npcActivityNotifications?.forEach(notification => {
|
||||||
|
if (updateNPCActivityTargetPlanetId(notification, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.missionReports?.forEach(report => {
|
||||||
|
if (updateMissionReportPlanetIds(report, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.incomingFleetAlerts?.forEach(alert => {
|
||||||
|
if (updateIncomingAlertTargetPlanetId(alert, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.jointAttackInvites?.forEach(invite => {
|
||||||
|
if (updateJointAttackTargetPlanetId(invite, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.allyDefenseNotifications?.forEach(notification => {
|
||||||
|
if (updateAllyDefenseTargetPlanetId(notification, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return mutated
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixNpcPlayerSpyReports = (npc: NPC, idMap: DuplicatePlanetIdMap): boolean => {
|
||||||
|
if (!npc.playerSpyReports) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let mutated = false
|
||||||
|
const remappedPlayerSpyReports: Record<string, SpyReport> = {}
|
||||||
|
|
||||||
|
// playerSpyReports 的 key 就是玩家星球 ID,需要和报告内容一起迁移
|
||||||
|
Object.entries(npc.playerSpyReports).forEach(([planetId, report]) => {
|
||||||
|
if (updateSpyReportTargetPlanetId(report, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const remappedPlanetId = getUpdatedPlanetId(planetId, idMap, {
|
||||||
|
position: report.targetPosition,
|
||||||
|
planetName: report.targetPlanetName
|
||||||
|
})
|
||||||
|
|
||||||
|
if (remappedPlanetId) {
|
||||||
|
remappedPlayerSpyReports[remappedPlanetId] = report
|
||||||
|
mutated = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remappedPlayerSpyReports[planetId] = report
|
||||||
|
})
|
||||||
|
|
||||||
|
npc.playerSpyReports = remappedPlayerSpyReports
|
||||||
|
return mutated
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixNpcReferences = (npcs: NPC[], idMap: DuplicatePlanetIdMap): boolean => {
|
||||||
|
let mutated = false
|
||||||
|
|
||||||
|
npcs.forEach(npc => {
|
||||||
|
if (fixNpcPlayerSpyReports(npc, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
npc.fleetMissions?.forEach(mission => {
|
||||||
|
if (updateMissionTargetPlanetId(mission, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return mutated
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修复玩家星球的重复ID,并同步更新可被可靠识别的旧引用。
|
||||||
|
* 缺少位置或名称上下文、无法安全判定归属的旧引用会保留原ID,
|
||||||
|
* 继续指向保留下来的首个星球,避免把数据误指到错误目标。
|
||||||
|
*/
|
||||||
|
const fixDuplicatePlanetIds = (data: MigratableGameData): boolean => {
|
||||||
|
const player = data.player
|
||||||
|
if (!player || !Array.isArray(player.planets) || player.planets.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const idMap = buildDuplicatePlanetIdMap(player)
|
||||||
|
if (idMap.size === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDuplicatePlanetIdMap 已经在上一步直接修复了重复星球 ID,
|
||||||
|
// 只要 idMap 非空,就说明当前迁移已经发生了实际修改。
|
||||||
|
let mutated = true
|
||||||
|
|
||||||
|
if (fixPlayerPlanetsAndQueues(player, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixPlayerReferences(player, data, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.npcs && fixNpcReferences(data.npcs, idMap)) {
|
||||||
|
mutated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutated
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行数据迁移
|
* 执行数据迁移
|
||||||
* 将旧版本的 universePlanets 和 debrisFields 从 gameStore 迁移到 universeStore
|
* 将旧版本的 universePlanets 和 debrisFields 从 gameStore 迁移到 universeStore
|
||||||
@@ -22,13 +544,13 @@ export const migrateGameData = (): void => {
|
|||||||
if (!oldEncryptedData) return
|
if (!oldEncryptedData) return
|
||||||
|
|
||||||
// 尝试解密(如果是加密格式)
|
// 尝试解密(如果是加密格式)
|
||||||
let oldData: any
|
let oldData: MigratableGameData
|
||||||
try {
|
try {
|
||||||
oldData = decryptData(oldEncryptedData)
|
oldData = decryptData(oldEncryptedData) as MigratableGameData
|
||||||
} catch {
|
} catch {
|
||||||
// 解密失败,可能是新格式(未加密),直接解析
|
// 解密失败,可能是新格式(未加密),直接解析
|
||||||
try {
|
try {
|
||||||
oldData = JSON.parse(oldEncryptedData)
|
oldData = JSON.parse(oldEncryptedData) as MigratableGameData
|
||||||
} catch {
|
} catch {
|
||||||
return // 无法解析,放弃迁移
|
return // 无法解析,放弃迁移
|
||||||
}
|
}
|
||||||
@@ -100,6 +622,11 @@ export const migrateGameData = (): void => {
|
|||||||
needsSave = true
|
needsSave = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修复重复的星球ID
|
||||||
|
if (fixDuplicatePlanetIds(oldData)) {
|
||||||
|
needsSave = true
|
||||||
|
}
|
||||||
|
|
||||||
// 迁移温度数据:为没有温度的星球生成温度
|
// 迁移温度数据:为没有温度的星球生成温度
|
||||||
// 玩家星球
|
// 玩家星球
|
||||||
if (oldData.player?.planets && Array.isArray(oldData.player.planets)) {
|
if (oldData.player?.planets && Array.isArray(oldData.player.planets)) {
|
||||||
@@ -164,10 +691,11 @@ export const migrateGameData = (): void => {
|
|||||||
// 新版本统一使用 npc.relations[playerId] 存储NPC对玩家的关系
|
// 新版本统一使用 npc.relations[playerId] 存储NPC对玩家的关系
|
||||||
if (oldData.player?.diplomaticRelations && oldData.npcs && Array.isArray(oldData.npcs)) {
|
if (oldData.player?.diplomaticRelations && oldData.npcs && Array.isArray(oldData.npcs)) {
|
||||||
const playerId = oldData.player.id
|
const playerId = oldData.player.id
|
||||||
|
const npcs = oldData.npcs
|
||||||
const playerRelations = oldData.player.diplomaticRelations as Record<string, any>
|
const playerRelations = oldData.player.diplomaticRelations as Record<string, any>
|
||||||
|
|
||||||
Object.entries(playerRelations).forEach(([npcId, relation]) => {
|
Object.entries(playerRelations).forEach(([npcId, relation]) => {
|
||||||
const npc = oldData.npcs.find((n: NPC) => n.id === npcId)
|
const npc = npcs.find((n: NPC) => n.id === npcId)
|
||||||
if (npc) {
|
if (npc) {
|
||||||
if (!npc.relations) {
|
if (!npc.relations) {
|
||||||
npc.relations = {}
|
npc.relations = {}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -190,6 +190,7 @@
|
|||||||
import * as publicLogic from '@/logic/publicLogic'
|
import * as publicLogic from '@/logic/publicLogic'
|
||||||
import * as shipValidation from '@/logic/shipValidation'
|
import * as shipValidation from '@/logic/shipValidation'
|
||||||
import * as shipLogic from '@/logic/shipLogic'
|
import * as shipLogic from '@/logic/shipLogic'
|
||||||
|
import * as missileLogic from '@/logic/missileLogic'
|
||||||
import * as gameLogic from '@/logic/gameLogic'
|
import * as gameLogic from '@/logic/gameLogic'
|
||||||
import * as waitingQueueLogic from '@/logic/waitingQueueLogic'
|
import * as waitingQueueLogic from '@/logic/waitingQueueLogic'
|
||||||
import * as officerLogic from '@/logic/officerLogic'
|
import * as officerLogic from '@/logic/officerLogic'
|
||||||
@@ -204,12 +205,12 @@
|
|||||||
// 导弹容量相关计算
|
// 导弹容量相关计算
|
||||||
const missileSiloCapacity = computed(() => {
|
const missileSiloCapacity = computed(() => {
|
||||||
if (!planet.value) return 0
|
if (!planet.value) return 0
|
||||||
return shipLogic.calculateMissileSiloCapacity(planet.value.buildings)
|
return missileLogic.calculateMissileSiloCapacity(planet.value.buildings)
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentMissileCount = computed(() => {
|
const currentMissileCount = computed(() => {
|
||||||
if (!planet.value) return 0
|
if (!planet.value) return 0
|
||||||
return shipLogic.calculateCurrentMissileCount(planet.value.defense)
|
return missileLogic.calculateCurrentMissileCount(planet.value.defense)
|
||||||
})
|
})
|
||||||
|
|
||||||
// AlertDialog 状态
|
// AlertDialog 状态
|
||||||
|
|||||||
@@ -181,137 +181,37 @@
|
|||||||
</ScrollableDialogContent>
|
</ScrollableDialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- NPC互动面板 - 贸易提议、情报、联合攻击邀请 -->
|
<!-- 搜索和排序工具栏 -->
|
||||||
<div v-if="hasNpcInteractions" class="space-y-4">
|
<div class="flex flex-col sm:flex-row gap-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="relative flex-1">
|
||||||
<div class="flex items-center gap-2">
|
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Handshake class="h-5 w-5 text-primary" />
|
<Input v-model="searchQuery" type="text" :placeholder="t('diplomacy.searchPlaceholder')" class="pl-10" />
|
||||||
<span class="font-semibold">{{ t('npcBehavior.trade.title') }} & {{ t('npcBehavior.intel.title') }}</span>
|
</div>
|
||||||
<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">
|
<div class="flex gap-2">
|
||||||
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
|
<Select v-model="sortBy">
|
||||||
<Eye class="h-4 w-4" />
|
<SelectTrigger class="w-[140px]">
|
||||||
{{ t('npcBehavior.intel.title') }} ({{ unreadIntelReports.length }})
|
<SelectValue :placeholder="t('diplomacy.sort.label')" />
|
||||||
</h3>
|
</SelectTrigger>
|
||||||
<div class="grid gap-2">
|
<SelectContent>
|
||||||
<Card v-for="intel in unreadIntelReports" :key="intel.id" class="p-3">
|
<SelectItem value="reputation">{{ t('diplomacy.sort.reputation') }}</SelectItem>
|
||||||
<div class="flex items-start justify-between gap-4">
|
<SelectItem value="planets">{{ t('diplomacy.sort.planets') }}</SelectItem>
|
||||||
<div class="flex-1 space-y-1">
|
<SelectItem value="difficulty">{{ t('diplomacy.sort.difficulty') }}</SelectItem>
|
||||||
<div class="font-medium">{{ t('npcBehavior.intel.from') }}: {{ getNpcName(intel.fromNpcId) }}</div>
|
<SelectItem value="allies">{{ t('diplomacy.sort.allies') }}</SelectItem>
|
||||||
<div class="text-sm text-muted-foreground">
|
</SelectContent>
|
||||||
{{ t('npcBehavior.intel.target') }}: {{ getNpcName(intel.targetNpcId) }}
|
</Select>
|
||||||
</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>
|
|
||||||
|
|
||||||
<!-- 联合攻击邀请 -->
|
<Button
|
||||||
<div v-if="activeJointAttackInvites.length > 0">
|
variant="outline"
|
||||||
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
|
size="icon"
|
||||||
<Swords class="h-4 w-4" />
|
@click="sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'"
|
||||||
{{ t('npcBehavior.jointAttack.title') }} ({{ activeJointAttackInvites.length }})
|
:title="sortOrder === 'asc' ? t('diplomacy.sort.ascending') : t('diplomacy.sort.descending')"
|
||||||
</h3>
|
>
|
||||||
<div class="grid gap-2">
|
<ArrowUpDown class="h-4 w-4" />
|
||||||
<Card v-for="invite in activeJointAttackInvites" :key="invite.id" class="p-3">
|
</Button>
|
||||||
<div class="flex items-start justify-between gap-4">
|
</div>
|
||||||
<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">
|
|
||||||
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input v-model="searchQuery" type="text" :placeholder="t('diplomacy.searchPlaceholder')" class="pl-10" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 关系状态过滤标签 -->
|
<!-- 关系状态过滤标签 -->
|
||||||
@@ -503,14 +403,18 @@
|
|||||||
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 {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from '@/components/ui/select'
|
||||||
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 +427,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, NPC } from '@/types/game'
|
||||||
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
|
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
@@ -534,13 +438,12 @@
|
|||||||
Activity,
|
Activity,
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
List,
|
List,
|
||||||
Handshake,
|
ArrowUpDown
|
||||||
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'
|
||||||
|
|
||||||
|
type NPCSortBy = 'reputation' | 'planets' | 'difficulty' | 'allies'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const npcStore = useNPCStore()
|
const npcStore = useNPCStore()
|
||||||
@@ -548,9 +451,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')
|
||||||
|
|
||||||
@@ -572,6 +472,56 @@
|
|||||||
// 搜索功能
|
// 搜索功能
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
// 排序状态
|
||||||
|
const sortBy = ref<NPCSortBy>('reputation')
|
||||||
|
const sortOrder = ref<'asc' | 'desc'>('desc')
|
||||||
|
|
||||||
|
const assertNever = (value: never): never => {
|
||||||
|
throw new Error(`Unexpected NPC sort type: ${value}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序函数
|
||||||
|
const sortNpcs = (npcs: NPC[], predicate: (npc: NPC) => boolean = () => true) => {
|
||||||
|
return npcs.filter(predicate).sort((a, b) => {
|
||||||
|
let valA = 0
|
||||||
|
let valB = 0
|
||||||
|
|
||||||
|
switch (sortBy.value) {
|
||||||
|
case 'reputation':
|
||||||
|
valA = getRelation(a.id)?.reputation || 0
|
||||||
|
valB = getRelation(b.id)?.reputation || 0
|
||||||
|
break
|
||||||
|
case 'planets':
|
||||||
|
valA = a.planets.length
|
||||||
|
valB = b.planets.length
|
||||||
|
break
|
||||||
|
case 'difficulty':
|
||||||
|
// 简单=1, 普通=2, 困难=3
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const getDifficultyVal = (diff: string) => {
|
||||||
|
if (diff === 'hard') return 3
|
||||||
|
if (diff === 'medium') return 2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
valA = a.difficultyLevel || getDifficultyVal(a.difficulty)
|
||||||
|
valB = b.difficultyLevel || getDifficultyVal(b.difficulty)
|
||||||
|
break
|
||||||
|
case 'allies':
|
||||||
|
valA = a.allies?.length || 0
|
||||||
|
valB = b.allies?.length || 0
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return assertNever(sortBy.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortOrder.value === 'asc') {
|
||||||
|
return valA - valB
|
||||||
|
} else {
|
||||||
|
return valB - valA
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// NPC诊断功能
|
// NPC诊断功能
|
||||||
const npcDiagnosticOpen = ref(false)
|
const npcDiagnosticOpen = ref(false)
|
||||||
const npcDiagnostics = ref<npcBehaviorLogic.NPCDiagnosticInfo[]>([])
|
const npcDiagnostics = ref<npcBehaviorLogic.NPCDiagnosticInfo[]>([])
|
||||||
@@ -757,34 +707,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 搜索过滤函数
|
// 搜索过滤函数
|
||||||
const matchesSearch = (npc: (typeof npcStore.npcs)[0]) => {
|
const matchesSearch = (npc: NPC) => {
|
||||||
if (!searchQuery.value.trim()) return true
|
if (!searchQuery.value.trim()) return true
|
||||||
const query = searchQuery.value.toLowerCase().trim()
|
const query = searchQuery.value.toLowerCase().trim()
|
||||||
return npc.name.toLowerCase().includes(query) || npc.id.toLowerCase().includes(query)
|
return npc.name.toLowerCase().includes(query) || npc.id.toLowerCase().includes(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按关系状态分类NPC(同时应用搜索过滤)
|
// 按关系状态分类NPC(同时应用搜索过滤)
|
||||||
const allNpcs = computed(() => npcStore.npcs.filter(matchesSearch))
|
// 先统一排序一次,避免不同标签页在同一批数据上重复排序
|
||||||
|
const sortedNpcs = computed(() => sortNpcs(npcStore.npcs, matchesSearch))
|
||||||
|
|
||||||
|
const allNpcs = computed(() => sortedNpcs.value)
|
||||||
|
|
||||||
const friendlyNpcs = computed(() => {
|
const friendlyNpcs = computed(() => {
|
||||||
return npcStore.npcs.filter(npc => {
|
return sortedNpcs.value.filter(npc => {
|
||||||
if (!matchesSearch(npc)) return false
|
|
||||||
const relation = getRelation(npc.id)
|
const relation = getRelation(npc.id)
|
||||||
return relation?.status === RelationStatus.Friendly
|
return relation?.status === RelationStatus.Friendly
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const neutralNpcs = computed(() => {
|
const neutralNpcs = computed(() => {
|
||||||
return npcStore.npcs.filter(npc => {
|
return sortedNpcs.value.filter(npc => {
|
||||||
if (!matchesSearch(npc)) return false
|
|
||||||
const relation = getRelation(npc.id)
|
const relation = getRelation(npc.id)
|
||||||
return !relation || relation.status === RelationStatus.Neutral
|
return !relation || relation.status === RelationStatus.Neutral
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const hostileNpcs = computed(() => {
|
const hostileNpcs = computed(() => {
|
||||||
return npcStore.npcs.filter(npc => {
|
return sortedNpcs.value.filter(npc => {
|
||||||
if (!matchesSearch(npc)) return false
|
|
||||||
const relation = getRelation(npc.id)
|
const relation = getRelation(npc.id)
|
||||||
return relation?.status === RelationStatus.Hostile
|
return relation?.status === RelationStatus.Hostile
|
||||||
})
|
})
|
||||||
@@ -864,188 +814,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>
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -602,6 +642,7 @@
|
|||||||
import * as diplomaticLogic from '@/logic/diplomaticLogic'
|
import * as diplomaticLogic from '@/logic/diplomaticLogic'
|
||||||
import * as gameLogic from '@/logic/gameLogic'
|
import * as gameLogic from '@/logic/gameLogic'
|
||||||
import * as moonLogic from '@/logic/moonLogic'
|
import * as moonLogic from '@/logic/moonLogic'
|
||||||
|
import { generateId } from '@/utils/id'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
@@ -628,6 +669,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 +888,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
|
||||||
@@ -882,7 +935,7 @@
|
|||||||
|
|
||||||
// 生成唯一ID
|
// 生成唯一ID
|
||||||
const generatePresetId = (): string => {
|
const generatePresetId = (): string => {
|
||||||
return `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
return generateId('fleet_preset')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存当前配置为预设
|
// 保存当前配置为预设
|
||||||
@@ -1413,4 +1466,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>
|
||||||
|
|||||||
@@ -79,6 +79,44 @@
|
|||||||
|
|
||||||
<!-- 建筑/科技/舰船/防御/军官 - 统一配置渲染 -->
|
<!-- 建筑/科技/舰船/防御/军官 - 统一配置渲染 -->
|
||||||
<TabsContent v-for="section in gmSections" :key="section.tabValue" :value="section.tabValue" class="space-y-4">
|
<TabsContent v-for="section in gmSections" :key="section.tabValue" :value="section.tabValue" class="space-y-4">
|
||||||
|
<!-- 预设操作区 -->
|
||||||
|
<Card v-if="isPresettableSection(section)" class="mb-4">
|
||||||
|
<CardHeader class="pb-3">
|
||||||
|
<CardTitle class="text-lg">{{ t('gmView.presets') || 'Presets' }}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 items-end sm:items-center">
|
||||||
|
<div class="flex gap-2 w-full sm:w-auto">
|
||||||
|
<Select v-model="selectedPresets[section.tabValue]">
|
||||||
|
<SelectTrigger class="w-[200px]">
|
||||||
|
<SelectValue :placeholder="t('gmView.choosePreset') || 'Choose Preset'" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="default">{{ t('gmView.defaultPreset') || 'Default Preset' }}</SelectItem>
|
||||||
|
<SelectItem v-for="p in customPresets[section.tabValue]" :key="p.id" :value="p.id">
|
||||||
|
{{ p.name }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button @click="handleApplyPreset(section)">{{ t('gmView.applyPreset') || 'Apply' }}</Button>
|
||||||
|
<Button
|
||||||
|
v-if="selectedPresets[section.tabValue] !== 'default'"
|
||||||
|
@click="handleDeletePreset(section)"
|
||||||
|
variant="destructive"
|
||||||
|
size="icon"
|
||||||
|
:title="t('gmView.deletePreset') || 'Delete Preset'"
|
||||||
|
>
|
||||||
|
<Trash2 class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 w-full sm:w-auto ml-auto">
|
||||||
|
<Input v-model="presetNames[section.tabValue]" :placeholder="t('gmView.presetName') || 'Preset Name'" class="w-[150px]" />
|
||||||
|
<Button @click="handleSavePreset(section)" variant="outline">{{ t('gmView.savePreset') || 'Save' }}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{{ t(section.titleKey) }}</CardTitle>
|
<CardTitle>{{ t(section.titleKey) }}</CardTitle>
|
||||||
@@ -213,6 +251,22 @@
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
|
<!-- 预设覆盖确认对话框 -->
|
||||||
|
<AlertDialog :open="presetOverwriteDialogOpen" @update:open="presetOverwriteDialogOpen = $event">
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{{ t('gmView.confirmOverwriteTitle') || 'Preset Already Exists' }}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{{ t('gmView.confirmOverwriteMessage', { name: pendingPresetToOverwrite?.name || '' }) || `Preset with name "${pendingPresetToOverwrite?.name}" already exists. Overwrite?` }}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel @click="presetOverwriteDialogOpen = false">{{ t('common.cancel') }}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction @click="handleConfirmOverwrite">{{ t('common.confirm') }}</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
<!-- AlertDialog 提示对话框 -->
|
<!-- AlertDialog 提示对话框 -->
|
||||||
<AlertDialog :open="alertDialogOpen" @update:open="alertDialogOpen = $event">
|
<AlertDialog :open="alertDialogOpen" @update:open="alertDialogOpen = $event">
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
@@ -263,7 +317,286 @@
|
|||||||
import { BuildingType, TechnologyType, ShipType, DefenseType, OfficerType } from '@/types/game'
|
import { BuildingType, TechnologyType, ShipType, DefenseType, OfficerType } from '@/types/game'
|
||||||
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
|
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
|
||||||
import * as publicLogic from '@/logic/publicLogic'
|
import * as publicLogic from '@/logic/publicLogic'
|
||||||
import { Home } from 'lucide-vue-next'
|
import { calculateMaxFleetStorage } from '@/logic/fleetStorageLogic'
|
||||||
|
import { calculateMissileSiloCapacity } from '@/logic/missileLogic'
|
||||||
|
import { generateId } from '@/utils/id'
|
||||||
|
import { Home, Trash2 } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
// --- 预设系统 ---
|
||||||
|
interface GMPreset {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
values: Record<string, number>
|
||||||
|
}
|
||||||
|
|
||||||
|
type GMSectionTabValue = 'buildings' | 'research' | 'ships' | 'defense' | 'officers'
|
||||||
|
type GMPresetSectionKey = Exclude<GMSectionTabValue, 'officers'>
|
||||||
|
|
||||||
|
type GMSection = {
|
||||||
|
tabValue: GMSectionTabValue
|
||||||
|
titleKey: string
|
||||||
|
descKey: string
|
||||||
|
items: string[]
|
||||||
|
max?: number
|
||||||
|
placeholder?: string
|
||||||
|
buttons: { label: string; value: number }[]
|
||||||
|
getItemName: (item: string) => string
|
||||||
|
getValue: (item: string) => number
|
||||||
|
setValue: (item: string, val: number) => void
|
||||||
|
onButtonClick: (item: string, val: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type GMPresetSection = GMSection & {
|
||||||
|
tabValue: GMPresetSectionKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type GMPresetNameMap = Record<GMPresetSectionKey, string>
|
||||||
|
type GMSelectedPresetMap = Record<GMPresetSectionKey, string>
|
||||||
|
type GMCustomPresetMap = Record<GMPresetSectionKey, GMPreset[]>
|
||||||
|
|
||||||
|
interface PendingPresetOverwrite {
|
||||||
|
section: GMPresetSection
|
||||||
|
name: string
|
||||||
|
values: Record<string, number>
|
||||||
|
existingIndex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验预设结构,避免历史脏数据污染当前视图
|
||||||
|
const isGMPreset = (value: unknown): value is GMPreset => {
|
||||||
|
if (typeof value !== 'object' || value === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const preset = value as Partial<GMPreset>
|
||||||
|
return typeof preset.id === 'string' && typeof preset.name === 'string' && typeof preset.values === 'object' && preset.values !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有建筑/科技/舰船/防御页支持预设
|
||||||
|
const isPresettableSection = (section: GMSection): section is GMPresetSection => {
|
||||||
|
return section.tabValue !== 'officers'
|
||||||
|
}
|
||||||
|
|
||||||
|
const presetOverwriteDialogOpen = ref(false)
|
||||||
|
const pendingPresetToOverwrite = ref<PendingPresetOverwrite | null>(null)
|
||||||
|
|
||||||
|
const getPresets = (type: GMPresetSectionKey): GMPreset[] => {
|
||||||
|
const key = `gm_presets_${type}`
|
||||||
|
const data = localStorage.getItem(key)
|
||||||
|
if (!data) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 兼容旧版本或手动修改导致的损坏数据,避免页面因解析失败崩溃
|
||||||
|
const parsed = JSON.parse(data)
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const presets = parsed.filter(isGMPreset)
|
||||||
|
// 过滤掉结构不完整的预设,并顺手回写清理后的结果
|
||||||
|
if (presets.length !== parsed.length) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(presets))
|
||||||
|
}
|
||||||
|
|
||||||
|
return presets
|
||||||
|
} catch {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savePresets = (type: GMPresetSectionKey, presets: GMPreset[]) => {
|
||||||
|
localStorage.setItem(`gm_presets_${type}`, JSON.stringify(presets))
|
||||||
|
}
|
||||||
|
|
||||||
|
const presetNames = ref<GMPresetNameMap>({
|
||||||
|
buildings: '',
|
||||||
|
research: '',
|
||||||
|
ships: '',
|
||||||
|
defense: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedPresets = ref<GMSelectedPresetMap>({
|
||||||
|
buildings: 'default',
|
||||||
|
research: 'default',
|
||||||
|
ships: 'default',
|
||||||
|
defense: 'default'
|
||||||
|
})
|
||||||
|
|
||||||
|
const customPresets = ref<GMCustomPresetMap>({
|
||||||
|
buildings: getPresets('buildings'),
|
||||||
|
research: getPresets('research'),
|
||||||
|
ships: getPresets('ships'),
|
||||||
|
defense: getPresets('defense')
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSavePreset = (section: GMSection) => {
|
||||||
|
if (!isPresettableSection(section)) return
|
||||||
|
|
||||||
|
const name = presetNames.value[section.tabValue]?.trim()
|
||||||
|
if (!name) {
|
||||||
|
toast.error(t('gmView.presetNameRequired') || '请输入预设名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const values: Record<string, number> = {}
|
||||||
|
section.items.forEach((item: string) => {
|
||||||
|
values[item] = section.getValue(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查是否存在同名预设
|
||||||
|
const presets = customPresets.value[section.tabValue]
|
||||||
|
const existingIndex = presets.findIndex(p => p.name === name)
|
||||||
|
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
pendingPresetToOverwrite.value = {
|
||||||
|
section,
|
||||||
|
name,
|
||||||
|
values,
|
||||||
|
existingIndex
|
||||||
|
}
|
||||||
|
presetOverwriteDialogOpen.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPreset: GMPreset = {
|
||||||
|
id: generateId('gm_preset'),
|
||||||
|
name,
|
||||||
|
values
|
||||||
|
}
|
||||||
|
|
||||||
|
presets.push(newPreset)
|
||||||
|
savePresets(section.tabValue, presets)
|
||||||
|
presetNames.value[section.tabValue] = ''
|
||||||
|
selectedPresets.value[section.tabValue] = newPreset.id
|
||||||
|
toast.success(t('gmView.presetSaved') || '预设保存成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirmOverwrite = () => {
|
||||||
|
if (!pendingPresetToOverwrite.value) return
|
||||||
|
|
||||||
|
const { section, values, existingIndex } = pendingPresetToOverwrite.value
|
||||||
|
|
||||||
|
const presets = customPresets.value[section.tabValue]
|
||||||
|
|
||||||
|
if (presets[existingIndex]) {
|
||||||
|
// 更新现有预设的值,保持ID不变
|
||||||
|
presets[existingIndex].values = values
|
||||||
|
|
||||||
|
savePresets(section.tabValue, presets)
|
||||||
|
|
||||||
|
presetNames.value[section.tabValue] = ''
|
||||||
|
selectedPresets.value[section.tabValue] = presets[existingIndex].id
|
||||||
|
|
||||||
|
toast.success(t('gmView.presetSaved') || '预设保存成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
presetOverwriteDialogOpen.value = false
|
||||||
|
pendingPresetToOverwrite.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeletePreset = (section: GMSection) => {
|
||||||
|
if (!isPresettableSection(section)) return
|
||||||
|
|
||||||
|
const presetId = selectedPresets.value[section.tabValue]
|
||||||
|
if (!presetId || presetId === 'default') {
|
||||||
|
toast.error(t('gmView.cannotDeleteDefault') || '无法删除默认预设')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const presets = customPresets.value[section.tabValue]
|
||||||
|
const index = presets.findIndex(p => p.id === presetId)
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
presets.splice(index, 1)
|
||||||
|
savePresets(section.tabValue, presets)
|
||||||
|
selectedPresets.value[section.tabValue] = 'default'
|
||||||
|
toast.success(t('gmView.presetDeleted') || '预设已删除')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleApplyPreset = (section: GMSection) => {
|
||||||
|
if (!isPresettableSection(section)) return
|
||||||
|
|
||||||
|
const presetId = selectedPresets.value[section.tabValue]
|
||||||
|
if (!presetId) return
|
||||||
|
|
||||||
|
if (presetId === 'default') {
|
||||||
|
if (section.tabValue === 'buildings') {
|
||||||
|
const explicitMax: Record<string, number> = {
|
||||||
|
[BuildingType.NaniteFactory]: 10,
|
||||||
|
[BuildingType.MissileSilo]: 10,
|
||||||
|
[BuildingType.JumpGate]: 5,
|
||||||
|
[BuildingType.PlanetDestroyerFactory]: 3,
|
||||||
|
[BuildingType.GeoResearchStation]: 10,
|
||||||
|
[BuildingType.DeepDrillingFacility]: 10,
|
||||||
|
[BuildingType.University]: 10
|
||||||
|
}
|
||||||
|
section.items.forEach((item: string) => {
|
||||||
|
section.setValue(item, explicitMax[item] || 50)
|
||||||
|
})
|
||||||
|
} else if (section.tabValue === 'research') {
|
||||||
|
const explicitMax: Record<string, number> = {
|
||||||
|
[TechnologyType.ComputerTechnology]: 10,
|
||||||
|
[TechnologyType.GravitonTechnology]: 1,
|
||||||
|
[TechnologyType.PlanetDestructionTech]: 10,
|
||||||
|
[TechnologyType.MiningTechnology]: 15,
|
||||||
|
[TechnologyType.IntergalacticResearchNetwork]: 10,
|
||||||
|
[TechnologyType.MineralResearch]: 20,
|
||||||
|
[TechnologyType.CrystalResearch]: 20,
|
||||||
|
[TechnologyType.FuelResearch]: 20
|
||||||
|
}
|
||||||
|
section.items.forEach((item: string) => {
|
||||||
|
section.setValue(item, explicitMax[item] || 100)
|
||||||
|
})
|
||||||
|
} else if (section.tabValue === 'ships') {
|
||||||
|
if (!selectedPlanet.value) return
|
||||||
|
// 某些过滤场景下舰船列表可能为空,避免平均分配时除以 0
|
||||||
|
if (!section.items.length) return
|
||||||
|
|
||||||
|
// 重新计算最大舰队仓储,确保数据是最新的
|
||||||
|
const maxStorage = calculateMaxFleetStorage(selectedPlanet.value, gameStore.player.technologies)
|
||||||
|
|
||||||
|
// 将总容量平均分配给每种舰船
|
||||||
|
const storagePerShip = maxStorage / section.items.length
|
||||||
|
|
||||||
|
section.items.forEach(item => {
|
||||||
|
const usage = SHIPS.value[item as ShipType]?.storageUsage || 1
|
||||||
|
// 如果 usage 为 0 (如某些特殊单位),则给予一个默认数量,或者跳过
|
||||||
|
if (usage <= 0) {
|
||||||
|
section.setValue(item, 100) // 防止除以0,给予固定值
|
||||||
|
} else {
|
||||||
|
section.setValue(item, Math.floor(storagePerShip / usage))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (section.tabValue === 'defense') {
|
||||||
|
if (!selectedPlanet.value) return
|
||||||
|
const missileCapacity = calculateMissileSiloCapacity(selectedPlanet.value.buildings)
|
||||||
|
const defaultMissileCount = Math.floor(missileCapacity / 2)
|
||||||
|
|
||||||
|
section.items.forEach((item: string) => {
|
||||||
|
// 两种导弹都占用1格空间,默认各分配一半容量
|
||||||
|
if (item === DefenseType.AntiBallisticMissile || item === DefenseType.InterplanetaryMissile) {
|
||||||
|
section.setValue(item, defaultMissileCount)
|
||||||
|
} else {
|
||||||
|
section.setValue(item, 10000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
toast.success(t('gmView.presetApplied') || '默认预设应用成功')
|
||||||
|
} else {
|
||||||
|
const customPreset = customPresets.value[section.tabValue].find((p: GMPreset) => p.id === presetId)
|
||||||
|
if (customPreset) {
|
||||||
|
Object.entries(customPreset.values).forEach(([k, v]) => {
|
||||||
|
section.setValue(k, v as number)
|
||||||
|
})
|
||||||
|
toast.success(t('gmView.presetApplied') || '预设应用成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --- 预设系统结束 ---
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
@@ -281,7 +614,8 @@
|
|||||||
router.push('/')
|
router.push('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedPlanetId = ref<string>(gameStore.player.planets[0]?.id || '')
|
// 默认选中当前正在游玩的星球
|
||||||
|
const selectedPlanetId = ref<string>(gameStore.currentPlanetId || gameStore.player.planets[0]?.id || '')
|
||||||
const officerDays = ref<Record<OfficerType, number>>({} as Record<OfficerType, number>)
|
const officerDays = ref<Record<OfficerType, number>>({} as Record<OfficerType, number>)
|
||||||
const selectedNPCId = ref<string>(npcStore.npcs[0]?.id || '')
|
const selectedNPCId = ref<string>(npcStore.npcs[0]?.id || '')
|
||||||
const targetPlanetIndex = ref<string>('0')
|
const targetPlanetIndex = ref<string>('0')
|
||||||
@@ -336,22 +670,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GM编辑区块配置 - 统一管理建筑/科技/舰船/防御/军官
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
type GMSection = {
|
|
||||||
tabValue: string
|
|
||||||
titleKey: string
|
|
||||||
descKey: string
|
|
||||||
items: string[]
|
|
||||||
max?: number
|
|
||||||
placeholder?: string
|
|
||||||
buttons: { label: string; value: number }[]
|
|
||||||
getItemName: (item: any) => string
|
|
||||||
getValue: (item: any) => number
|
|
||||||
setValue: (item: any, val: number) => void
|
|
||||||
onButtonClick: (item: any, val: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const gmSections = computed<GMSection[]>(() => [
|
const gmSections = computed<GMSection[]>(() => [
|
||||||
{
|
{
|
||||||
tabValue: 'buildings',
|
tabValue: 'buildings',
|
||||||
@@ -364,17 +682,17 @@
|
|||||||
{ label: 'Lv 10', value: 10 },
|
{ label: 'Lv 10', value: 10 },
|
||||||
{ label: 'Lv 30', value: 30 }
|
{ label: 'Lv 30', value: 30 }
|
||||||
],
|
],
|
||||||
getItemName: (item: BuildingType) => BUILDINGS.value[item].name,
|
getItemName: item => BUILDINGS.value[item as BuildingType].name,
|
||||||
getValue: (item: BuildingType) => selectedPlanet.value?.buildings[item] || 0,
|
getValue: item => selectedPlanet.value?.buildings[item as BuildingType] || 0,
|
||||||
setValue: (item: BuildingType, val: number) => {
|
setValue: (item, val) => {
|
||||||
if (selectedPlanet.value) {
|
if (selectedPlanet.value) {
|
||||||
selectedPlanet.value.buildings[item] = val
|
selectedPlanet.value.buildings[item as BuildingType] = val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onButtonClick: (item: BuildingType, val: number) => {
|
onButtonClick: (item, val) => {
|
||||||
if (selectedPlanet.value) {
|
if (selectedPlanet.value) {
|
||||||
selectedPlanet.value.buildings[item] = val
|
selectedPlanet.value.buildings[item as BuildingType] = val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,14 +708,14 @@
|
|||||||
{ label: 'Lv 10', value: 10 },
|
{ label: 'Lv 10', value: 10 },
|
||||||
{ label: 'Lv 20', value: 20 }
|
{ label: 'Lv 20', value: 20 }
|
||||||
],
|
],
|
||||||
getItemName: (item: TechnologyType) => TECHNOLOGIES.value[item].name,
|
getItemName: item => TECHNOLOGIES.value[item as TechnologyType].name,
|
||||||
getValue: (item: TechnologyType) => gameStore.player.technologies[item] || 0,
|
getValue: item => gameStore.player.technologies[item as TechnologyType] || 0,
|
||||||
setValue: (item: TechnologyType, val: number) => {
|
setValue: (item, val) => {
|
||||||
gameStore.player.technologies[item] = val
|
gameStore.player.technologies[item as TechnologyType] = val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
},
|
},
|
||||||
onButtonClick: (item: TechnologyType, val: number) => {
|
onButtonClick: (item, val) => {
|
||||||
gameStore.player.technologies[item] = val
|
gameStore.player.technologies[item as TechnologyType] = val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -412,17 +730,17 @@
|
|||||||
{ label: '+100', value: 100 },
|
{ label: '+100', value: 100 },
|
||||||
{ label: '+1K', value: 1000 }
|
{ label: '+1K', value: 1000 }
|
||||||
],
|
],
|
||||||
getItemName: (item: ShipType) => SHIPS.value[item].name,
|
getItemName: item => SHIPS.value[item as ShipType].name,
|
||||||
getValue: (item: ShipType) => selectedPlanet.value?.fleet[item] || 0,
|
getValue: item => selectedPlanet.value?.fleet[item as ShipType] || 0,
|
||||||
setValue: (item: ShipType, val: number) => {
|
setValue: (item, val) => {
|
||||||
if (selectedPlanet.value) {
|
if (selectedPlanet.value) {
|
||||||
selectedPlanet.value.fleet[item] = val
|
selectedPlanet.value.fleet[item as ShipType] = val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onButtonClick: (item: ShipType, val: number) => {
|
onButtonClick: (item, val) => {
|
||||||
if (selectedPlanet.value) {
|
if (selectedPlanet.value) {
|
||||||
selectedPlanet.value.fleet[item] = (selectedPlanet.value.fleet[item] || 0) + val
|
selectedPlanet.value.fleet[item as ShipType] = (selectedPlanet.value.fleet[item as ShipType] || 0) + val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,17 +756,17 @@
|
|||||||
{ label: '+100', value: 100 },
|
{ label: '+100', value: 100 },
|
||||||
{ label: '+1K', value: 1000 }
|
{ label: '+1K', value: 1000 }
|
||||||
],
|
],
|
||||||
getItemName: (item: DefenseType) => DEFENSES.value[item].name,
|
getItemName: item => DEFENSES.value[item as DefenseType].name,
|
||||||
getValue: (item: DefenseType) => selectedPlanet.value?.defense[item] || 0,
|
getValue: item => selectedPlanet.value?.defense[item as DefenseType] || 0,
|
||||||
setValue: (item: DefenseType, val: number) => {
|
setValue: (item, val) => {
|
||||||
if (selectedPlanet.value) {
|
if (selectedPlanet.value) {
|
||||||
selectedPlanet.value.defense[item] = val
|
selectedPlanet.value.defense[item as DefenseType] = val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onButtonClick: (item: DefenseType, val: number) => {
|
onButtonClick: (item, val) => {
|
||||||
if (selectedPlanet.value) {
|
if (selectedPlanet.value) {
|
||||||
selectedPlanet.value.defense[item] = (selectedPlanet.value.defense[item] || 0) + val
|
selectedPlanet.value.defense[item as DefenseType] = (selectedPlanet.value.defense[item as DefenseType] || 0) + val
|
||||||
updatePlayerPoints()
|
updatePlayerPoints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,27 +783,28 @@
|
|||||||
{ label: `30${t('gmView.days')}`, value: 30 },
|
{ label: `30${t('gmView.days')}`, value: 30 },
|
||||||
{ label: `365${t('gmView.days')}`, value: 365 }
|
{ label: `365${t('gmView.days')}`, value: 365 }
|
||||||
],
|
],
|
||||||
getItemName: (item: OfficerType) => OFFICERS.value[item].name,
|
getItemName: item => OFFICERS.value[item as OfficerType].name,
|
||||||
getValue: (item: OfficerType) => officerDays.value[item] || 0,
|
getValue: item => officerDays.value[item as OfficerType] || 0,
|
||||||
setValue: (item: OfficerType, val: number) => {
|
setValue: (item, val) => {
|
||||||
officerDays.value[item] = val
|
officerDays.value[item as OfficerType] = val
|
||||||
},
|
},
|
||||||
onButtonClick: (item: OfficerType, days: number) => {
|
onButtonClick: (item, days) => {
|
||||||
officerDays.value[item] = days
|
const officerType = item as OfficerType
|
||||||
|
officerDays.value[officerType] = days
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const expiresAt = now + days * 24 * 60 * 60 * 1000
|
const expiresAt = now + days * 24 * 60 * 60 * 1000
|
||||||
if (!gameStore.player.officers[item]) {
|
if (!gameStore.player.officers[officerType]) {
|
||||||
gameStore.player.officers[item] = {
|
gameStore.player.officers[officerType] = {
|
||||||
type: item,
|
type: officerType,
|
||||||
active: true,
|
active: true,
|
||||||
hiredAt: now,
|
hiredAt: now,
|
||||||
expiresAt: expiresAt
|
expiresAt: expiresAt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gameStore.player.officers[item].expiresAt = expiresAt
|
gameStore.player.officers[officerType].expiresAt = expiresAt
|
||||||
gameStore.player.officers[item].active = true
|
gameStore.player.officers[officerType].active = true
|
||||||
if (!gameStore.player.officers[item].hiredAt) {
|
if (!gameStore.player.officers[officerType].hiredAt) {
|
||||||
gameStore.player.officers[item].hiredAt = now
|
gameStore.player.officers[officerType].hiredAt = now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -659,11 +978,13 @@
|
|||||||
const maxAllResources = () => {
|
const maxAllResources = () => {
|
||||||
if (!selectedPlanet.value) return
|
if (!selectedPlanet.value) return
|
||||||
|
|
||||||
const maxAmount = 1000000000000000000
|
// 计算当前星球的资源存储上限
|
||||||
selectedPlanet.value.resources.metal = maxAmount
|
const capacity = publicLogic.getResourceCapacity(selectedPlanet.value, gameStore.player.officers)
|
||||||
selectedPlanet.value.resources.crystal = maxAmount
|
|
||||||
selectedPlanet.value.resources.deuterium = maxAmount
|
selectedPlanet.value.resources.metal = capacity.metal
|
||||||
selectedPlanet.value.resources.darkMatter = maxAmount
|
selectedPlanet.value.resources.crystal = capacity.crystal
|
||||||
|
selectedPlanet.value.resources.deuterium = capacity.deuterium
|
||||||
|
selectedPlanet.value.resources.darkMatter = capacity.darkMatter
|
||||||
|
|
||||||
toast.success(t('gmView.maxAllResourcesSuccess'))
|
toast.success(t('gmView.maxAllResourcesSuccess'))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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') }}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否可以添加到等待队列
|
// 检查是否可以添加到等待队列
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<p class="text-sm text-muted-foreground">{{ t('settings.importDataDesc') }}</p>
|
<p class="text-sm text-muted-foreground">{{ t('settings.importDataDesc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input ref="fileInputRef" type="file" accept=".json" class="hidden" @change="handleFileSelect" />
|
<input ref="fileInputRef" type="file" accept=".json,application/json,text/plain" class="hidden" @change="handleFileSelect" />
|
||||||
<Button @click="triggerFileInput" variant="outline">
|
<Button @click="triggerFileInput" variant="outline">
|
||||||
<Upload class="mr-2 h-4 w-4" />
|
<Upload class="mr-2 h-4 w-4" />
|
||||||
{{ t('settings.selectFile') }}
|
{{ t('settings.selectFile') }}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 优化依赖预构建
|
// 优化依赖预构建
|
||||||
|
|||||||
28
调整基础矿脉恢复速度.ini
Normal file
28
调整基础矿脉恢复速度.ini
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
OGame-Vue-Ts v1.6.0 更新说明
|
||||||
|
|
||||||
|
调整基础矿脉恢复速度
|
||||||
|
调整地质研究站每级恢复速率
|
||||||
|
调整建造死星所需资源
|
||||||
|
修复研究等待队列资源扣除但等级不增加的问题
|
||||||
|
修复攻击NPC后NPC拥有资源不减少的问题
|
||||||
|
修复导弹攻击后反弹道导弹不减少的问题
|
||||||
|
修复矿脉上限加成计算错误问题
|
||||||
|
为战斗模拟器新增加从侦查报告一键填入的功能
|
||||||
|
调整建筑与研究升级时间
|
||||||
|
修复文本丢失问题
|
||||||
|
调整月球基地初始建造成本
|
||||||
|
修复无法派遣舰队到自己星球位置的问题
|
||||||
|
修复敌对NPC会无脑派遣舰队回收残骸导致产生更多残骸的恶性循环问题
|
||||||
|
修复敌对NPC反复派回收船到玩家星球的问题
|
||||||
|
修复NPC无脑进攻问题
|
||||||
|
修复贸易邮件大量生成导致淹没其他邮件问题
|
||||||
|
添加舰船拆除功能
|
||||||
|
添加放弃殖民地功能
|
||||||
|
修复从月球切换回母星会触发白屏问题
|
||||||
|
修复死星毁灭任务跳过战斗的问题
|
||||||
|
修复对战动画偶现卡顿导致弹窗无响应问题
|
||||||
|
增加战斗到底模式, 开启后支持最多战斗100回合
|
||||||
|
|
||||||
|
添加建筑: 大学
|
||||||
|
添加科技: 星际研究网络, 矿物研究, 晶体研究, 燃料研究
|
||||||
|
|
||||||
Reference in New Issue
Block a user