feat: 新增Android平台支持及构建流程
集成Android平台相关目录与配置文件,包含Gradle构建脚本、资源文件、启动图标、Java入口、Proguard规则等,完善.gitignore以排除Android构建产物。更新CI流程,支持自动构建并发布Android APK。移除README中项目结构说明,简化文档。
49
.github/workflows/build.yml
vendored
@@ -49,7 +49,47 @@ jobs:
|
||||
name: server-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: ${{ matrix.executable }}
|
||||
|
||||
# 2. 构建 Electron 客户端
|
||||
# 2. 构建 Android APK (ARM64, ARMv7, x86_64)
|
||||
build-android:
|
||||
name: Build Android APK
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Build Frontend
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm run build
|
||||
|
||||
- name: Sync Capacitor
|
||||
run: npx cap sync android
|
||||
|
||||
- name: Build APK (Release)
|
||||
working-directory: android
|
||||
run: ./gradlew assembleRelease
|
||||
|
||||
- name: Upload APK Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: android-apk
|
||||
path: android/app/build/outputs/apk/release/*.apk
|
||||
|
||||
# 3. 构建 Electron 客户端
|
||||
build-electron:
|
||||
name: Build Electron (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -91,9 +131,9 @@ jobs:
|
||||
pkg/*.dmg
|
||||
pkg/*.AppImage
|
||||
|
||||
# 3. 发布 Release
|
||||
# 4. 发布 Release
|
||||
release:
|
||||
needs: [ build-server, build-electron ]
|
||||
needs: [ build-server, build-android, build-electron ]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -124,6 +164,9 @@ jobs:
|
||||
# 移动 Electron 安装包 (排除 unpacked 目录)
|
||||
find ./raw-assets/electron-* -type f \( -name "*.exe" -o -name "*.dmg" -o -name "*.AppImage" -o -name "*.zip" \) -exec cp {} ./final-release/ \;
|
||||
|
||||
# 移动 Android APK
|
||||
find ./raw-assets/android-apk -type f -name "*.apk" -exec cp {} ./final-release/ \; || true
|
||||
|
||||
# 检查结果
|
||||
echo "Final assets to upload:"
|
||||
ls -R ./final-release
|
||||
|
||||
9
.gitignore
vendored
@@ -28,3 +28,12 @@ docs
|
||||
*.sw?
|
||||
/docs
|
||||
/docs/assets
|
||||
|
||||
# Android build outputs
|
||||
android/.gradle
|
||||
android/app/build
|
||||
android/build
|
||||
android/local.properties
|
||||
android/.idea
|
||||
android/*.iml
|
||||
android/app/*.iml
|
||||
|
||||
51
README-EN.md
@@ -109,57 +109,6 @@ pnpm build
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
ogame-vue-ts/
|
||||
├── public/ # Static assets
|
||||
│ └── logo.svg # Application logo
|
||||
├── src/
|
||||
│ ├── assets/ # Dynamic assets
|
||||
│ ├── components/ # Vue components
|
||||
│ │ └── ui/ # shadcn-vue UI components
|
||||
│ ├── composables/ # Vue composables
|
||||
│ ├── config/ # Game configuration
|
||||
│ ├── lib/ # Utility libraries
|
||||
│ ├── locales/ # i18n translation files
|
||||
│ ├── logic/ # Game logic modules
|
||||
│ │ ├── buildingLogic.ts
|
||||
│ │ ├── buildingValidation.ts
|
||||
│ │ ├── fleetLogic.ts
|
||||
│ │ ├── moonLogic.ts
|
||||
│ │ ├── moonValidation.ts
|
||||
│ │ ├── researchLogic.ts
|
||||
│ │ ├── researchValidation.ts
|
||||
│ │ ├── shipLogic.ts
|
||||
│ │ └── shipValidation.ts
|
||||
│ ├── router/ # Vue Router configuration
|
||||
│ ├── stores/ # Pinia state stores
|
||||
│ ├── types/ # TypeScript type definitions
|
||||
│ ├── utils/ # Utility functions
|
||||
│ ├── views/ # Page components
|
||||
│ │ ├── OverviewView.vue
|
||||
│ │ ├── BuildingsView.vue
|
||||
│ │ ├── ResearchView.vue
|
||||
│ │ ├── ShipyardView.vue
|
||||
│ │ ├── DefenseView.vue
|
||||
│ │ ├── FleetView.vue
|
||||
│ │ ├── GalaxyView.vue
|
||||
│ │ ├── OfficersView.vue
|
||||
│ │ ├── BattleSimulatorView.vue
|
||||
│ │ ├── MessagesView.vue
|
||||
│ │ └── SettingsView.vue
|
||||
│ ├── App.vue # Root component
|
||||
│ ├── main.ts # Application entry point
|
||||
│ └── style.css # Global styles
|
||||
├── .github/
|
||||
│ └── ISSUE_TEMPLATE/ # GitHub issue templates
|
||||
├── LICENSE # CC BY-NC 4.0 License
|
||||
├── package.json # Project dependencies
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
└── vite.config.ts # Vite configuration
|
||||
```
|
||||
|
||||
## Available Languages
|
||||
|
||||
- 🇺🇸 English
|
||||
|
||||
51
README.md
@@ -109,57 +109,6 @@ pnpm build
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
ogame-vue-ts/
|
||||
├── public/ # 静态资源
|
||||
│ └── logo.svg # 应用图标
|
||||
├── src/
|
||||
│ ├── assets/ # 动态资源
|
||||
│ ├── components/ # Vue 组件
|
||||
│ │ └── ui/ # shadcn-vue UI 组件
|
||||
│ ├── composables/ # Vue 组合式函数
|
||||
│ ├── config/ # 游戏配置
|
||||
│ ├── lib/ # 工具库
|
||||
│ ├── locales/ # 国际化翻译文件
|
||||
│ ├── logic/ # 游戏逻辑模块
|
||||
│ │ ├── buildingLogic.ts # 建筑逻辑
|
||||
│ │ ├── buildingValidation.ts # 建筑验证
|
||||
│ │ ├── fleetLogic.ts # 舰队逻辑
|
||||
│ │ ├── moonLogic.ts # 月球逻辑
|
||||
│ │ ├── moonValidation.ts # 月球验证
|
||||
│ │ ├── researchLogic.ts # 研究逻辑
|
||||
│ │ ├── researchValidation.ts # 研究验证
|
||||
│ │ ├── shipLogic.ts # 舰船逻辑
|
||||
│ │ └── shipValidation.ts # 舰船验证
|
||||
│ ├── router/ # Vue Router 路由配置
|
||||
│ ├── stores/ # Pinia 状态存储
|
||||
│ ├── types/ # TypeScript 类型定义
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── views/ # 页面组件
|
||||
│ │ ├── OverviewView.vue # 概览页面
|
||||
│ │ ├── BuildingsView.vue # 建筑页面
|
||||
│ │ ├── ResearchView.vue # 研究页面
|
||||
│ │ ├── ShipyardView.vue # 船坞页面
|
||||
│ │ ├── DefenseView.vue # 防御页面
|
||||
│ │ ├── FleetView.vue # 舰队页面
|
||||
│ │ ├── GalaxyView.vue # 银河页面
|
||||
│ │ ├── OfficersView.vue # 军官页面
|
||||
│ │ ├── BattleSimulatorView.vue # 战斗模拟器
|
||||
│ │ ├── MessagesView.vue # 消息页面
|
||||
│ │ └── SettingsView.vue # 设置页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ ├── main.ts # 应用入口
|
||||
│ └── style.css # 全局样式
|
||||
├── .github/
|
||||
│ └── ISSUE_TEMPLATE/ # GitHub issue 模板
|
||||
├── LICENSE # CC BY-NC 4.0 许可证
|
||||
├── package.json # 项目依赖
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
└── vite.config.ts # Vite 配置
|
||||
```
|
||||
|
||||
## 支持的语言
|
||||
|
||||
- 🇺🇸 English (英语)
|
||||
|
||||
101
android/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-android-plugins
|
||||
|
||||
# Copied web assets
|
||||
app/src/main/assets/public
|
||||
|
||||
# Generated Config files
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
2
android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
82
android/app/build.gradle
Normal file
@@ -0,0 +1,82 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace = "games.wenzi.ogame"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "games.wenzi.ogame"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 14
|
||||
versionName "1.4.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
|
||||
// 按 ABI 拆分 APK (arm64-v8a, armeabi-v7a, x86_64)
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include "arm64-v8a", "armeabi-v7a", "x86_64"
|
||||
universalApk false
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个 ABI 设置不同的 versionCode
|
||||
applicationVariants.configureEach { variant ->
|
||||
variant.outputs.configureEach { output ->
|
||||
def abiVersionCode = [
|
||||
"armeabi-v7a": 1,
|
||||
"arm64-v8a": 2,
|
||||
"x86_64": 3
|
||||
]
|
||||
def abi = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
if (abi != null) {
|
||||
output.versionCodeOverride = abiVersionCode[abi] * 1000 + defaultConfig.versionCode
|
||||
output.outputFileName = "OGame-${defaultConfig.versionName}-${abi}.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir{
|
||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||
implementation project(':capacitor-android')
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||
}
|
||||
19
android/app/capacitor.build.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
||||
21
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
41
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package games.wenzi.ogame;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {}
|
||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
170
android/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
12
android/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
7
android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">OGame</string>
|
||||
<string name="title_activity_main">OGame</string>
|
||||
<string name="package_name">games.wenzi.ogame</string>
|
||||
<string name="custom_url_scheme">games.wenzi.ogame</string>
|
||||
</resources>
|
||||
22
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
||||
5
android/app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
29
android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.0'
|
||||
classpath 'com.google.gms:google-services:4.4.4'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
android/capacitor.settings.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
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')
|
||||
22
android/gradle.properties
Normal file
@@ -0,0 +1,22 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
251
android/gradlew
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
5
android/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
include ':app'
|
||||
include ':capacitor-cordova-android-plugins'
|
||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||
|
||||
apply from: 'capacitor.settings.gradle'
|
||||
16
android/variables.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
ext {
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 36
|
||||
targetSdkVersion = 36
|
||||
androidxActivityVersion = '1.11.0'
|
||||
androidxAppCompatVersion = '1.7.1'
|
||||
androidxCoordinatorLayoutVersion = '1.3.0'
|
||||
androidxCoreVersion = '1.17.0'
|
||||
androidxFragmentVersion = '1.8.9'
|
||||
coreSplashScreenVersion = '1.2.0'
|
||||
androidxWebkitVersion = '1.14.0'
|
||||
junitVersion = '4.13.2'
|
||||
androidxJunitVersion = '1.3.0'
|
||||
androidxEspressoCoreVersion = '3.7.0'
|
||||
cordovaAndroidVersion = '14.0.1'
|
||||
}
|
||||
18
capacitor.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { CapacitorConfig } from '@capacitor/cli'
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'games.wenzi.ogame',
|
||||
appName: 'OGame',
|
||||
webDir: 'docs',
|
||||
server: {
|
||||
androidScheme: 'https'
|
||||
},
|
||||
android: {
|
||||
buildOptions: {
|
||||
keystorePath: undefined,
|
||||
keystoreAlias: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
||||
11
package.json
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"private": true,
|
||||
"version": "1.4.0",
|
||||
"buildDate": "2025/12/19 12:29:46",
|
||||
"buildDate": "2025/12/20 00:39:08",
|
||||
"main": "dist-electron/main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -17,9 +17,14 @@
|
||||
"build": "vue-tsc -b && vite build && node update-build-date.js",
|
||||
"preview": "vite preview",
|
||||
"build:server": "pnpm run build && go build",
|
||||
"build:electron": "cross-env ELECTRON_BUILD=1 pnpm run build && electron-builder"
|
||||
"build:electron": "cross-env ELECTRON_BUILD=1 pnpm run build && electron-builder",
|
||||
"build:android": "pnpm run build && npx cap sync android",
|
||||
"build:apk": "pnpm run build:android && cd android && ./gradlew assembleRelease"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^8.0.0",
|
||||
"@capacitor/cli": "^8.0.0",
|
||||
"@capacitor/core": "^8.0.0",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
@@ -50,6 +55,8 @@
|
||||
"electron": "^39.2.7",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-vite": "^5.0.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-preset-env": "^10.5.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
|
||||
1406
pnpm-lock.yaml
generated
@@ -11,6 +11,15 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@capacitor/android':
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0(@capacitor/core@8.0.0)
|
||||
'@capacitor/cli':
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
'@capacitor/core':
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17(rolldown-vite@7.2.5(@types/node@24.10.2)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.44.1))
|
||||
@@ -96,6 +105,12 @@ importers:
|
||||
electron-vite:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(rolldown-vite@7.2.5(@types/node@24.10.2)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.44.1))
|
||||
postcss:
|
||||
specifier: ^8.5.6
|
||||
version: 8.5.6
|
||||
postcss-preset-env:
|
||||
specifier: ^10.5.0
|
||||
version: 10.5.0(postcss@8.5.6)
|
||||
tailwind-merge:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
@@ -630,6 +645,307 @@ packages:
|
||||
'@canvas/image-data@1.1.0':
|
||||
resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==}
|
||||
|
||||
'@capacitor/android@8.0.0':
|
||||
resolution: {integrity: sha512-FrBSvVAC5JuLaYHNyDnwQny0/SYnP+xDQbc/KA4wInmRkMXLDv22fkx9aBJIDrxjuUVd+jsRih4SAt8FgMEzCw==}
|
||||
peerDependencies:
|
||||
'@capacitor/core': ^8.0.0
|
||||
|
||||
'@capacitor/cli@8.0.0':
|
||||
resolution: {integrity: sha512-v9hEBi69xGxuuZhg55N031bMEenKaPSv71Il8C22VOOH6surDyv/MPeImN0oVfFc7eiklaW3rDFYVz6cmXfJWQ==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@capacitor/core@8.0.0':
|
||||
resolution: {integrity: sha512-250HTVd/W/KdMygoqaedisvNbHbpbQTN2Hy/8ZYGm1nAqE0Fx7sGss4l0nDg33STxEdDhtVRoL2fIaaiukKseA==}
|
||||
|
||||
'@csstools/cascade-layer-name-parser@2.0.5':
|
||||
resolution: {integrity: sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^3.0.5
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/color-helpers@5.1.0':
|
||||
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@csstools/css-calc@2.1.4':
|
||||
resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^3.0.5
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/css-color-parser@3.1.0':
|
||||
resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^3.0.5
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/css-parser-algorithms@3.0.5':
|
||||
resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/css-tokenizer@3.0.4':
|
||||
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@csstools/media-query-list-parser@4.0.3':
|
||||
resolution: {integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^3.0.5
|
||||
'@csstools/css-tokenizer': ^3.0.4
|
||||
|
||||
'@csstools/postcss-alpha-function@1.0.1':
|
||||
resolution: {integrity: sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-cascade-layers@5.0.2':
|
||||
resolution: {integrity: sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-color-function-display-p3-linear@1.0.1':
|
||||
resolution: {integrity: sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-color-function@4.0.12':
|
||||
resolution: {integrity: sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-color-mix-function@3.0.12':
|
||||
resolution: {integrity: sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-color-mix-variadic-function-arguments@1.0.2':
|
||||
resolution: {integrity: sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-content-alt-text@2.0.8':
|
||||
resolution: {integrity: sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-contrast-color-function@2.0.12':
|
||||
resolution: {integrity: sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-exponential-functions@2.0.9':
|
||||
resolution: {integrity: sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-font-format-keywords@4.0.0':
|
||||
resolution: {integrity: sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-gamut-mapping@2.0.11':
|
||||
resolution: {integrity: sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-gradients-interpolation-method@5.0.12':
|
||||
resolution: {integrity: sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-hwb-function@4.0.12':
|
||||
resolution: {integrity: sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-ic-unit@4.0.4':
|
||||
resolution: {integrity: sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-initial@2.0.1':
|
||||
resolution: {integrity: sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-is-pseudo-class@5.0.3':
|
||||
resolution: {integrity: sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-light-dark-function@2.0.11':
|
||||
resolution: {integrity: sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-logical-float-and-clear@3.0.0':
|
||||
resolution: {integrity: sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-logical-overflow@2.0.0':
|
||||
resolution: {integrity: sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-logical-overscroll-behavior@2.0.0':
|
||||
resolution: {integrity: sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-logical-resize@3.0.0':
|
||||
resolution: {integrity: sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-logical-viewport-units@3.0.4':
|
||||
resolution: {integrity: sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-media-minmax@2.0.9':
|
||||
resolution: {integrity: sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5':
|
||||
resolution: {integrity: sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-nested-calc@4.0.0':
|
||||
resolution: {integrity: sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-normalize-display-values@4.0.0':
|
||||
resolution: {integrity: sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-oklab-function@4.0.12':
|
||||
resolution: {integrity: sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-position-area-property@1.0.0':
|
||||
resolution: {integrity: sha512-fUP6KR8qV2NuUZV3Cw8itx0Ep90aRjAZxAEzC3vrl6yjFv+pFsQbR18UuQctEKmA72K9O27CoYiKEgXxkqjg8Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-progressive-custom-properties@4.2.1':
|
||||
resolution: {integrity: sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-random-function@2.0.1':
|
||||
resolution: {integrity: sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-relative-color-syntax@3.0.12':
|
||||
resolution: {integrity: sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-scope-pseudo-class@4.0.1':
|
||||
resolution: {integrity: sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-sign-functions@1.1.4':
|
||||
resolution: {integrity: sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-stepped-value-functions@4.0.9':
|
||||
resolution: {integrity: sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-system-ui-font-family@1.0.0':
|
||||
resolution: {integrity: sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-text-decoration-shorthand@4.0.3':
|
||||
resolution: {integrity: sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-trigonometric-functions@4.0.9':
|
||||
resolution: {integrity: sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/postcss-unset-value@4.0.0':
|
||||
resolution: {integrity: sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@csstools/selector-resolve-nested@3.1.0':
|
||||
resolution: {integrity: sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss-selector-parser: ^7.0.0
|
||||
|
||||
'@csstools/selector-specificity@5.0.0':
|
||||
resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss-selector-parser: ^7.0.0
|
||||
|
||||
'@csstools/utilities@2.0.0':
|
||||
resolution: {integrity: sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
'@develar/schema-utils@2.6.5':
|
||||
resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
|
||||
engines: {node: '>= 8.9.0'}
|
||||
@@ -984,6 +1300,38 @@ packages:
|
||||
'@internationalized/number@3.6.5':
|
||||
resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==}
|
||||
|
||||
'@ionic/cli-framework-output@2.2.8':
|
||||
resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-array@2.1.6':
|
||||
resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-fs@3.1.7':
|
||||
resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-object@2.1.6':
|
||||
resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-process@2.1.12':
|
||||
resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-stream@3.1.7':
|
||||
resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-subprocess@3.0.1':
|
||||
resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-terminal@2.3.5':
|
||||
resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@isaacs/balanced-match@4.0.1':
|
||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -1339,6 +1687,9 @@ packages:
|
||||
'@types/file-saver@2.0.7':
|
||||
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
|
||||
|
||||
'@types/fs-extra@8.1.5':
|
||||
resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==}
|
||||
|
||||
'@types/fs-extra@9.0.13':
|
||||
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
||||
|
||||
@@ -1366,6 +1717,9 @@ packages:
|
||||
'@types/responselike@1.0.3':
|
||||
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
|
||||
|
||||
'@types/slice-ansi@4.0.0':
|
||||
resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
@@ -1590,6 +1944,13 @@ packages:
|
||||
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1619,6 +1980,10 @@ packages:
|
||||
resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==}
|
||||
hasBin: true
|
||||
|
||||
big-integer@1.6.52:
|
||||
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
birpc@2.9.0:
|
||||
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||
|
||||
@@ -1629,6 +1994,10 @@ packages:
|
||||
resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
bplist-parser@0.3.2:
|
||||
resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==}
|
||||
engines: {node: '>= 5.10.0'}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
@@ -1757,6 +2126,10 @@ packages:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
commander@12.1.0:
|
||||
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
@@ -1821,6 +2194,32 @@ packages:
|
||||
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
css-blank-pseudo@7.0.1:
|
||||
resolution: {integrity: sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
css-has-pseudo@7.0.3:
|
||||
resolution: {integrity: sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
css-prefers-color-scheme@10.0.0:
|
||||
resolution: {integrity: sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
cssdb@8.5.2:
|
||||
resolution: {integrity: sha512-Pmoj9RmD8RIoIzA2EQWO4D4RMeDts0tgAH0VXdlNdxjuBGI3a9wMOIcUwaPNmD4r2qtIa06gqkIf7sECl+cBCg==}
|
||||
|
||||
cssesc@3.0.0:
|
||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
@@ -1872,6 +2271,10 @@ packages:
|
||||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
define-lazy-prop@2.0.0:
|
||||
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
define-properties@1.2.1:
|
||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1963,6 +2366,10 @@ packages:
|
||||
engines: {node: '>= 12.20.55'}
|
||||
hasBin: true
|
||||
|
||||
elementtree@0.1.7:
|
||||
resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
@@ -2106,6 +2513,9 @@ packages:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fraction.js@5.3.4:
|
||||
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
||||
|
||||
framer-motion@12.23.12:
|
||||
resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==}
|
||||
peerDependencies:
|
||||
@@ -2206,6 +2616,10 @@ packages:
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
glob@13.0.0:
|
||||
resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
@@ -2337,6 +2751,10 @@ packages:
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ini@4.1.3:
|
||||
resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
internal-slot@1.1.0:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2384,6 +2802,11 @@ packages:
|
||||
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-docker@2.2.1:
|
||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
is-finalizationregistry@1.1.1:
|
||||
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2474,6 +2897,10 @@ packages:
|
||||
resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
is-wsl@2.2.0:
|
||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
isarray@2.0.5:
|
||||
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
||||
|
||||
@@ -2549,6 +2976,14 @@ packages:
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
kleur@3.0.3:
|
||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
kleur@4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
lazy-val@1.0.5:
|
||||
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
|
||||
|
||||
@@ -2815,6 +3250,11 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
native-run@2.0.1:
|
||||
resolution: {integrity: sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
|
||||
negotiator@0.6.4:
|
||||
resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -2867,6 +3307,10 @@ packages:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
open@8.4.2:
|
||||
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ora@5.4.1:
|
||||
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2968,6 +3412,166 @@ packages:
|
||||
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
postcss-attribute-case-insensitive@7.0.1:
|
||||
resolution: {integrity: sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-clamp@4.1.0:
|
||||
resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==}
|
||||
engines: {node: '>=7.6.0'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4.6
|
||||
|
||||
postcss-color-functional-notation@7.0.12:
|
||||
resolution: {integrity: sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-color-hex-alpha@10.0.0:
|
||||
resolution: {integrity: sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-color-rebeccapurple@10.0.0:
|
||||
resolution: {integrity: sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-custom-media@11.0.6:
|
||||
resolution: {integrity: sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-custom-properties@14.0.6:
|
||||
resolution: {integrity: sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-custom-selectors@8.0.5:
|
||||
resolution: {integrity: sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-dir-pseudo-class@9.0.1:
|
||||
resolution: {integrity: sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-double-position-gradients@6.0.4:
|
||||
resolution: {integrity: sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-focus-visible@10.0.1:
|
||||
resolution: {integrity: sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-focus-within@9.0.1:
|
||||
resolution: {integrity: sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-font-variant@5.0.0:
|
||||
resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==}
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
|
||||
postcss-gap-properties@6.0.0:
|
||||
resolution: {integrity: sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-image-set-function@7.0.0:
|
||||
resolution: {integrity: sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-lab-function@7.0.12:
|
||||
resolution: {integrity: sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-logical@8.1.0:
|
||||
resolution: {integrity: sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-nesting@13.0.2:
|
||||
resolution: {integrity: sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-opacity-percentage@3.0.0:
|
||||
resolution: {integrity: sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-overflow-shorthand@6.0.0:
|
||||
resolution: {integrity: sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-page-break@3.0.4:
|
||||
resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==}
|
||||
peerDependencies:
|
||||
postcss: ^8
|
||||
|
||||
postcss-place@10.0.0:
|
||||
resolution: {integrity: sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-preset-env@10.5.0:
|
||||
resolution: {integrity: sha512-xgxFQPAPxeWmsgy8cR7GM1PGAL/smA5E9qU7K//D4vucS01es3M0fDujhDJn3kY8Ip7/vVYcecbe1yY+vBo3qQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-pseudo-class-any-link@10.0.1:
|
||||
resolution: {integrity: sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-replace-overflow-wrap@4.0.0:
|
||||
resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==}
|
||||
peerDependencies:
|
||||
postcss: ^8.0.3
|
||||
|
||||
postcss-selector-not@8.0.1:
|
||||
resolution: {integrity: sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
postcss-value-parser@4.2.0:
|
||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -3005,6 +3609,10 @@ packages:
|
||||
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
pump@3.0.3:
|
||||
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
||||
|
||||
@@ -3109,6 +3717,11 @@ packages:
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
rimraf@6.1.2:
|
||||
resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==}
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
roarr@2.15.4:
|
||||
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
|
||||
engines: {node: '>=8.0'}
|
||||
@@ -3184,6 +3797,9 @@ packages:
|
||||
sanitize-filename@1.6.3:
|
||||
resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==}
|
||||
|
||||
sax@1.1.4:
|
||||
resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==}
|
||||
|
||||
sax@1.4.3:
|
||||
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
|
||||
|
||||
@@ -3278,10 +3894,17 @@ packages:
|
||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
slice-ansi@3.0.0:
|
||||
resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
slice-ansi@4.0.0:
|
||||
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
@@ -3321,6 +3944,10 @@ packages:
|
||||
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
||||
sprintf-js@1.1.3:
|
||||
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
|
||||
|
||||
@@ -3433,6 +4060,9 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
through2@4.0.2:
|
||||
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
||||
|
||||
tiny-async-pool@1.3.0:
|
||||
resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==}
|
||||
|
||||
@@ -3457,6 +4087,10 @@ packages:
|
||||
tr46@1.0.1:
|
||||
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
truncate-utf8-bytes@1.0.2:
|
||||
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
|
||||
|
||||
@@ -3547,6 +4181,10 @@ packages:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
untildify@4.0.0:
|
||||
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
upath@1.2.0:
|
||||
resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3730,6 +4368,14 @@ packages:
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
xml2js@0.6.2:
|
||||
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
xmlbuilder@11.0.1:
|
||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
xmlbuilder@15.1.1:
|
||||
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
|
||||
engines: {node: '>=8.0'}
|
||||
@@ -4427,6 +5073,334 @@ snapshots:
|
||||
'@canvas/image-data@1.1.0':
|
||||
optional: true
|
||||
|
||||
'@capacitor/android@8.0.0(@capacitor/core@8.0.0)':
|
||||
dependencies:
|
||||
'@capacitor/core': 8.0.0
|
||||
|
||||
'@capacitor/cli@8.0.0':
|
||||
dependencies:
|
||||
'@ionic/cli-framework-output': 2.2.8
|
||||
'@ionic/utils-subprocess': 3.0.1
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
commander: 12.1.0
|
||||
debug: 4.4.3
|
||||
env-paths: 2.2.1
|
||||
fs-extra: 11.3.2
|
||||
kleur: 4.1.5
|
||||
native-run: 2.0.1
|
||||
open: 8.4.2
|
||||
plist: 3.1.0
|
||||
prompts: 2.4.2
|
||||
rimraf: 6.1.2
|
||||
semver: 7.7.3
|
||||
tar: 6.2.1
|
||||
tslib: 2.8.1
|
||||
xml2js: 0.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@capacitor/core@8.0.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@csstools/cascade-layer-name-parser@2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
|
||||
'@csstools/color-helpers@5.1.0': {}
|
||||
|
||||
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
|
||||
'@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
dependencies:
|
||||
'@csstools/color-helpers': 5.1.0
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
|
||||
'@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
|
||||
dependencies:
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
|
||||
'@csstools/css-tokenizer@3.0.4': {}
|
||||
|
||||
'@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
|
||||
'@csstools/postcss-alpha-function@1.0.1(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@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/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-color-function@4.0.12(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
'@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-hwb-function@4.0.12(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-ic-unit@4.0.4(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
'@csstools/postcss-initial@2.0.1(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-is-pseudo-class@5.0.3(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/postcss-light-dark-function@2.0.11(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-logical-resize@3.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
'@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-media-minmax@2.0.9(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-nested-calc@4.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
'@csstools/postcss-normalize-display-values@4.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
'@csstools/postcss-oklab-function@4.0.12(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-position-area-property@1.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
'@csstools/postcss-random-function@2.0.1(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
'@csstools/postcss-sign-functions@1.1.4(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/color-helpers': 5.1.0
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
'@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/postcss-unset-value@4.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
'@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.1)':
|
||||
dependencies:
|
||||
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
|
||||
|
||||
'@csstools/utilities@2.0.0(postcss@8.5.6)':
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
'@develar/schema-utils@2.6.5':
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
@@ -4741,6 +5715,82 @@ snapshots:
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.17
|
||||
|
||||
'@ionic/cli-framework-output@2.2.8':
|
||||
dependencies:
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-array@2.1.6':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-fs@3.1.7':
|
||||
dependencies:
|
||||
'@types/fs-extra': 8.1.5
|
||||
debug: 4.4.3
|
||||
fs-extra: 9.1.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-object@2.1.6':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-process@2.1.12':
|
||||
dependencies:
|
||||
'@ionic/utils-object': 2.1.6
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
debug: 4.4.3
|
||||
signal-exit: 3.0.7
|
||||
tree-kill: 1.2.2
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-stream@3.1.7':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-subprocess@3.0.1':
|
||||
dependencies:
|
||||
'@ionic/utils-array': 2.1.6
|
||||
'@ionic/utils-fs': 3.1.7
|
||||
'@ionic/utils-process': 2.1.12
|
||||
'@ionic/utils-stream': 3.1.7
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-terminal@2.3.5':
|
||||
dependencies:
|
||||
'@types/slice-ansi': 4.0.0
|
||||
debug: 4.4.3
|
||||
signal-exit: 3.0.7
|
||||
slice-ansi: 4.0.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
tslib: 2.8.1
|
||||
untildify: 4.0.0
|
||||
wrap-ansi: 7.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@isaacs/balanced-match@4.0.1': {}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
@@ -5041,6 +6091,10 @@ snapshots:
|
||||
|
||||
'@types/file-saver@2.0.7': {}
|
||||
|
||||
'@types/fs-extra@8.1.5':
|
||||
dependencies:
|
||||
'@types/node': 24.10.2
|
||||
|
||||
'@types/fs-extra@9.0.13':
|
||||
dependencies:
|
||||
'@types/node': 24.10.2
|
||||
@@ -5073,6 +6127,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 24.10.2
|
||||
|
||||
'@types/slice-ansi@4.0.0': {}
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
'@types/verror@1.10.11':
|
||||
@@ -5354,8 +6410,7 @@ snapshots:
|
||||
assert-plus@1.0.0:
|
||||
optional: true
|
||||
|
||||
astral-regex@2.0.0:
|
||||
optional: true
|
||||
astral-regex@2.0.0: {}
|
||||
|
||||
async-exit-hook@2.0.1: {}
|
||||
|
||||
@@ -5367,6 +6422,15 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
possible-typed-array-names: 1.1.0
|
||||
@@ -5401,6 +6465,8 @@ snapshots:
|
||||
|
||||
baseline-browser-mapping@2.9.7: {}
|
||||
|
||||
big-integer@1.6.52: {}
|
||||
|
||||
birpc@2.9.0: {}
|
||||
|
||||
bl@4.1.0:
|
||||
@@ -5412,6 +6478,10 @@ snapshots:
|
||||
boolean@3.2.0:
|
||||
optional: true
|
||||
|
||||
bplist-parser@0.3.2:
|
||||
dependencies:
|
||||
big-integer: 1.6.52
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@@ -5591,6 +6661,8 @@ snapshots:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@12.1.0: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@5.1.0: {}
|
||||
@@ -5647,6 +6719,26 @@ snapshots:
|
||||
|
||||
crypto-random-string@2.0.0: {}
|
||||
|
||||
css-blank-pseudo@7.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
css-has-pseudo@7.0.3(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
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
css-prefers-color-scheme@10.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
cssdb@8.5.2: {}
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
@@ -5702,6 +6794,8 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
define-lazy-prop@2.0.0: {}
|
||||
|
||||
define-properties@1.2.1:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@@ -5845,6 +6939,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
elementtree@0.1.7:
|
||||
dependencies:
|
||||
sax: 1.1.4
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
@@ -6059,6 +7157,8 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
fraction.js@5.3.4: {}
|
||||
|
||||
framer-motion@12.23.12:
|
||||
dependencies:
|
||||
motion-dom: 12.23.12
|
||||
@@ -6174,6 +7274,12 @@ snapshots:
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 2.0.1
|
||||
|
||||
glob@13.0.0:
|
||||
dependencies:
|
||||
minimatch: 10.1.1
|
||||
minipass: 7.1.2
|
||||
path-scurry: 2.0.1
|
||||
|
||||
glob@7.2.3:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
@@ -6332,6 +7438,8 @@ snapshots:
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ini@4.1.3: {}
|
||||
|
||||
internal-slot@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -6387,6 +7495,8 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-docker@2.2.1: {}
|
||||
|
||||
is-finalizationregistry@1.1.1:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -6465,6 +7575,10 @@ snapshots:
|
||||
|
||||
is-what@5.5.0: {}
|
||||
|
||||
is-wsl@2.2.0:
|
||||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
|
||||
isarray@2.0.5: {}
|
||||
|
||||
isbinaryfile@4.0.10: {}
|
||||
@@ -6528,6 +7642,10 @@ snapshots:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
kleur@4.1.5: {}
|
||||
|
||||
lazy-val@1.0.5: {}
|
||||
|
||||
leven@3.1.0: {}
|
||||
@@ -6758,6 +7876,22 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
native-run@2.0.1:
|
||||
dependencies:
|
||||
'@ionic/utils-fs': 3.1.7
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
bplist-parser: 0.3.2
|
||||
debug: 4.4.3
|
||||
elementtree: 0.1.7
|
||||
ini: 4.1.3
|
||||
plist: 3.1.0
|
||||
split2: 4.2.0
|
||||
through2: 4.0.2
|
||||
tslib: 2.8.1
|
||||
yauzl: 2.10.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
negotiator@0.6.4: {}
|
||||
|
||||
node-abi@3.85.0:
|
||||
@@ -6806,6 +7940,12 @@ snapshots:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
open@8.4.2:
|
||||
dependencies:
|
||||
define-lazy-prop: 2.0.0
|
||||
is-docker: 2.2.1
|
||||
is-wsl: 2.2.0
|
||||
|
||||
ora@5.4.1:
|
||||
dependencies:
|
||||
bl: 4.1.0
|
||||
@@ -6889,6 +8029,231 @@ snapshots:
|
||||
|
||||
possible-typed-array-names@1.1.0: {}
|
||||
|
||||
postcss-attribute-case-insensitive@7.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-clamp@4.1.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-color-functional-notation@7.0.12(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-color-hex-alpha@10.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-color-rebeccapurple@10.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-custom-media@11.0.6(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-custom-properties@14.0.6(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-custom-selectors@8.0.5(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-dir-pseudo-class@9.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-double-position-gradients@6.0.4(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-focus-visible@10.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-focus-within@9.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-font-variant@5.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-gap-properties@6.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-image-set-function@7.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-lab-function@7.0.12(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-logical@8.1.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-nesting@13.0.2(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.1)
|
||||
'@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1)
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-opacity-percentage@3.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-overflow-shorthand@6.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-page-break@3.0.4(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-place@10.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-preset-env@10.5.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
'@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.6)
|
||||
'@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.6)
|
||||
'@csstools/postcss-color-function': 4.0.12(postcss@8.5.6)
|
||||
'@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.6)
|
||||
'@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.6)
|
||||
'@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.6)
|
||||
'@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.6)
|
||||
'@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.6)
|
||||
'@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.6)
|
||||
'@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.6)
|
||||
'@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.6)
|
||||
'@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.6)
|
||||
'@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.6)
|
||||
'@csstools/postcss-initial': 2.0.1(postcss@8.5.6)
|
||||
'@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.6)
|
||||
'@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.6)
|
||||
'@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.6)
|
||||
'@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.6)
|
||||
'@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.6)
|
||||
'@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.6)
|
||||
'@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6)
|
||||
'@csstools/postcss-random-function': 2.0.1(postcss@8.5.6)
|
||||
'@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.6)
|
||||
'@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.6)
|
||||
'@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.6)
|
||||
'@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.6)
|
||||
'@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.6)
|
||||
'@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.6)
|
||||
'@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.6)
|
||||
'@csstools/postcss-unset-value': 4.0.0(postcss@8.5.6)
|
||||
autoprefixer: 10.4.23(postcss@8.5.6)
|
||||
browserslist: 4.28.1
|
||||
css-blank-pseudo: 7.0.1(postcss@8.5.6)
|
||||
css-has-pseudo: 7.0.3(postcss@8.5.6)
|
||||
css-prefers-color-scheme: 10.0.0(postcss@8.5.6)
|
||||
cssdb: 8.5.2
|
||||
postcss: 8.5.6
|
||||
postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.6)
|
||||
postcss-clamp: 4.1.0(postcss@8.5.6)
|
||||
postcss-color-functional-notation: 7.0.12(postcss@8.5.6)
|
||||
postcss-color-hex-alpha: 10.0.0(postcss@8.5.6)
|
||||
postcss-color-rebeccapurple: 10.0.0(postcss@8.5.6)
|
||||
postcss-custom-media: 11.0.6(postcss@8.5.6)
|
||||
postcss-custom-properties: 14.0.6(postcss@8.5.6)
|
||||
postcss-custom-selectors: 8.0.5(postcss@8.5.6)
|
||||
postcss-dir-pseudo-class: 9.0.1(postcss@8.5.6)
|
||||
postcss-double-position-gradients: 6.0.4(postcss@8.5.6)
|
||||
postcss-focus-visible: 10.0.1(postcss@8.5.6)
|
||||
postcss-focus-within: 9.0.1(postcss@8.5.6)
|
||||
postcss-font-variant: 5.0.0(postcss@8.5.6)
|
||||
postcss-gap-properties: 6.0.0(postcss@8.5.6)
|
||||
postcss-image-set-function: 7.0.0(postcss@8.5.6)
|
||||
postcss-lab-function: 7.0.12(postcss@8.5.6)
|
||||
postcss-logical: 8.1.0(postcss@8.5.6)
|
||||
postcss-nesting: 13.0.2(postcss@8.5.6)
|
||||
postcss-opacity-percentage: 3.0.0(postcss@8.5.6)
|
||||
postcss-overflow-shorthand: 6.0.0(postcss@8.5.6)
|
||||
postcss-page-break: 3.0.4(postcss@8.5.6)
|
||||
postcss-place: 10.0.0(postcss@8.5.6)
|
||||
postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.6)
|
||||
postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.6)
|
||||
postcss-selector-not: 8.0.1(postcss@8.5.6)
|
||||
|
||||
postcss-pseudo-class-any-link@10.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-replace-overflow-wrap@4.0.0(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
|
||||
postcss-selector-not@8.0.1(postcss@8.5.6):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.1
|
||||
|
||||
postcss-selector-parser@7.1.1:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
postcss-value-parser@4.2.0: {}
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
@@ -6915,6 +8280,11 @@ snapshots:
|
||||
err-code: 2.0.3
|
||||
retry: 0.12.0
|
||||
|
||||
prompts@2.4.2:
|
||||
dependencies:
|
||||
kleur: 3.0.3
|
||||
sisteransi: 1.0.5
|
||||
|
||||
pump@3.0.3:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
@@ -7040,6 +8410,11 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
rimraf@6.1.2:
|
||||
dependencies:
|
||||
glob: 13.0.0
|
||||
package-json-from-dist: 1.0.1
|
||||
|
||||
roarr@2.15.4:
|
||||
dependencies:
|
||||
boolean: 3.2.0
|
||||
@@ -7117,6 +8492,8 @@ snapshots:
|
||||
dependencies:
|
||||
truncate-utf8-bytes: 1.0.2
|
||||
|
||||
sax@1.1.4: {}
|
||||
|
||||
sax@1.4.3: {}
|
||||
|
||||
semver-compare@1.0.0:
|
||||
@@ -7267,6 +8644,8 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.7.3
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
slice-ansi@3.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@@ -7274,6 +8653,12 @@ snapshots:
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
optional: true
|
||||
|
||||
slice-ansi@4.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
astral-regex: 2.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
smob@1.5.0: {}
|
||||
@@ -7308,6 +8693,8 @@ snapshots:
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
sprintf-js@1.1.3:
|
||||
optional: true
|
||||
|
||||
@@ -7452,6 +8839,10 @@ snapshots:
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
through2@4.0.2:
|
||||
dependencies:
|
||||
readable-stream: 3.6.2
|
||||
|
||||
tiny-async-pool@1.3.0:
|
||||
dependencies:
|
||||
semver: 5.7.2
|
||||
@@ -7476,6 +8867,8 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
truncate-utf8-bytes@1.0.2:
|
||||
dependencies:
|
||||
utf8-byte-length: 1.0.5
|
||||
@@ -7577,6 +8970,8 @@ snapshots:
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
untildify@4.0.0: {}
|
||||
|
||||
upath@1.2.0: {}
|
||||
|
||||
update-browserslist-db@1.2.2(browserslist@4.28.1):
|
||||
@@ -7832,6 +9227,13 @@ snapshots:
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
xml2js@0.6.2:
|
||||
dependencies:
|
||||
sax: 1.4.3
|
||||
xmlbuilder: 11.0.1
|
||||
|
||||
xmlbuilder@11.0.1: {}
|
||||
|
||||
xmlbuilder@15.1.1: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
15
postcss.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-preset-env': {
|
||||
stage: 2,
|
||||
features: {
|
||||
'oklab-function': { preserve: true },
|
||||
'color-mix': true,
|
||||
'custom-selectors': true,
|
||||
'nesting-rules': true,
|
||||
'is-pseudo-class': false
|
||||
},
|
||||
browsers: ['Chrome >= 80', 'Firefox >= 80', 'Safari >= 14', 'Edge >= 80']
|
||||
}
|
||||
}
|
||||
}
|
||||
414
src/App.vue
@@ -117,7 +117,8 @@
|
||||
:data-nav-path="item.path"
|
||||
:is-active="$route.path === item.path"
|
||||
:tooltip="item.name.value"
|
||||
@click="handleNavClick(item.path, $event)"
|
||||
:disabled="!isFeatureUnlocked(item.path)"
|
||||
@click="router.push(item.path)"
|
||||
>
|
||||
<component :is="item.icon" />
|
||||
<span>{{ item.name.value }}</span>
|
||||
@@ -259,7 +260,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:展开按钮(仅移动端) -->
|
||||
<!-- 右侧:队列通知 + 展开按钮 -->
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0 justify-end">
|
||||
<!-- 移动端展开按钮 -->
|
||||
<Button @click="resourceBarExpanded = !resourceBarExpanded" variant="ghost" size="sm" class="lg:hidden h-8 w-8 p-0">
|
||||
@@ -330,6 +331,9 @@
|
||||
<!-- 即将到来的敌对舰队警告 -->
|
||||
<IncomingFleetAlerts @open-panel="openEnemyAlertPanel" />
|
||||
|
||||
<!-- 低电量警告 -->
|
||||
<LowEnergyWarning />
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1">
|
||||
<Transition name="page" mode="out-in">
|
||||
@@ -365,7 +369,6 @@
|
||||
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
|
||||
<!-- 返回顶部 -->
|
||||
<BackToTop />
|
||||
|
||||
<!-- 队列通知 -->
|
||||
<QueueNotifications />
|
||||
|
||||
@@ -443,6 +446,7 @@
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import IncomingFleetAlerts from '@/components/IncomingFleetAlerts.vue'
|
||||
import LowEnergyWarning from '@/components/LowEnergyWarning.vue'
|
||||
import DiplomaticNotifications from '@/components/DiplomaticNotifications.vue'
|
||||
import EnemyAlertNotifications from '@/components/EnemyAlertNotifications.vue'
|
||||
import QueueNotifications from '@/components/QueueNotifications.vue'
|
||||
@@ -503,7 +507,8 @@
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Handshake,
|
||||
Pencil
|
||||
Pencil,
|
||||
Trophy
|
||||
} from 'lucide-vue-next'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as planetLogic from '@/logic/planetLogic'
|
||||
@@ -570,7 +575,8 @@
|
||||
'/research': { building: BuildingType.ResearchLab, level: 1 },
|
||||
'/shipyard': { building: BuildingType.Shipyard, level: 1 },
|
||||
'/defense': { building: BuildingType.Shipyard, level: 1 },
|
||||
'/fleet': { building: BuildingType.Shipyard, level: 1 }
|
||||
'/fleet': { building: BuildingType.Shipyard, level: 1 },
|
||||
'/officers': { building: BuildingType.Shipyard, level: 1 }
|
||||
}
|
||||
|
||||
// 判断是否为首页
|
||||
@@ -599,6 +605,7 @@
|
||||
{ name: computed(() => t('nav.simulator')), path: '/battle-simulator', icon: Swords },
|
||||
{ name: computed(() => t('nav.galaxy')), path: '/galaxy', icon: Globe },
|
||||
{ name: computed(() => t('nav.diplomacy')), path: '/diplomacy', icon: Handshake },
|
||||
{ name: computed(() => t('nav.achievements')), path: '/achievements', icon: Trophy },
|
||||
{ name: computed(() => t('nav.messages')), path: '/messages', icon: Mail },
|
||||
{ name: computed(() => t('nav.settings')), path: '/settings', icon: Settings },
|
||||
// GM菜单在启用GM模式时显示
|
||||
@@ -809,6 +816,9 @@
|
||||
// NPC行为系统更新(侦查和攻击决策)
|
||||
updateNPCBehavior(1)
|
||||
|
||||
// 检查成就解锁
|
||||
checkAchievementUnlocks()
|
||||
|
||||
// 检查并处理被消灭的NPC(所有星球都被摧毁的NPC)
|
||||
const eliminatedNpcIds = diplomaticLogic.checkAndHandleEliminatedNPCs(npcStore.npcs, gameStore.player, gameStore.locale)
|
||||
if (eliminatedNpcIds.length > 0) {
|
||||
@@ -839,13 +849,28 @@
|
||||
mission.targetPosition.position
|
||||
)
|
||||
// 先从玩家星球中查找,再从宇宙地图中查找
|
||||
// 如果任务指定了targetIsMoon,需要精确匹配行星或月球
|
||||
const targetPlanet =
|
||||
gameStore.player.planets.find(p => {
|
||||
const positionMatch =
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
// 如果任务明确指定目标类型,按类型匹配
|
||||
if (mission.targetIsMoon !== undefined) {
|
||||
return positionMatch && p.isMoon === mission.targetIsMoon
|
||||
}
|
||||
// 兼容旧任务:默认优先匹配行星(非月球)
|
||||
return positionMatch && !p.isMoon
|
||||
}) ||
|
||||
// 如果没有匹配到指定类型,尝试匹配同位置的任何星球
|
||||
gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
) || universeStore.planets[targetKey]
|
||||
) ||
|
||||
universeStore.planets[targetKey]
|
||||
|
||||
// 获取起始星球名称(用于报告)
|
||||
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
|
||||
@@ -854,7 +879,32 @@
|
||||
if (mission.missionType === MissionType.Transport) {
|
||||
// 在处理任务之前保存货物信息(因为processTransportArrival会清空cargo)
|
||||
const transportedResources = { ...mission.cargo }
|
||||
const isGiftMission = mission.isGift && mission.giftTargetNpcId
|
||||
const result = fleetLogic.processTransportArrival(mission, targetPlanet, gameStore.player, npcStore.npcs)
|
||||
|
||||
// 更新成就统计(仅在成功时追踪)
|
||||
if (result.success) {
|
||||
const totalTransported =
|
||||
transportedResources.metal + transportedResources.crystal + transportedResources.deuterium + transportedResources.darkMatter
|
||||
if (isGiftMission) {
|
||||
// 送礼成功
|
||||
gameLogic.trackDiplomacyStats(gameStore.player, 'gift', { resourcesAmount: totalTransported })
|
||||
} else {
|
||||
// 普通运输任务成功
|
||||
gameLogic.trackMissionStats(gameStore.player, 'transport', { resourcesAmount: totalTransported })
|
||||
}
|
||||
}
|
||||
|
||||
// 生成失败原因消息
|
||||
let transportFailMessage = t('missionReports.transportFailed')
|
||||
if (!result.success && result.failReason) {
|
||||
if (result.failReason === 'targetNotFound') {
|
||||
transportFailMessage = t('missionReports.transportFailedTargetNotFound')
|
||||
} else if (result.failReason === 'giftRejected') {
|
||||
transportFailMessage = t('missionReports.transportFailedGiftRejected')
|
||||
}
|
||||
}
|
||||
|
||||
// 生成运输任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
@@ -870,9 +920,10 @@
|
||||
targetPlanetName:
|
||||
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
|
||||
success: result.success,
|
||||
message: result.success ? t('missionReports.transportSuccess') : t('missionReports.transportFailed'),
|
||||
message: result.success ? t('missionReports.transportSuccess') : transportFailMessage,
|
||||
details: {
|
||||
transportedResources
|
||||
transportedResources,
|
||||
failReason: result.failReason
|
||||
},
|
||||
read: false
|
||||
})
|
||||
@@ -881,6 +932,13 @@
|
||||
if (attackResult) {
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
|
||||
// 更新成就统计 - 攻击
|
||||
const debrisValue = attackResult.debrisField
|
||||
? attackResult.debrisField.resources.metal + attackResult.debrisField.resources.crystal
|
||||
: 0
|
||||
const won = attackResult.battleResult.winner === 'attacker'
|
||||
gameLogic.trackAttackStats(gameStore.player, attackResult.battleResult, won, debrisValue)
|
||||
|
||||
// 检查是否攻击了NPC星球,更新外交关系
|
||||
if (targetPlanet) {
|
||||
const targetNpc = npcStore.npcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
|
||||
@@ -898,7 +956,24 @@
|
||||
}
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Colonize) {
|
||||
const newPlanet = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player, t('planet.colonyPrefix'))
|
||||
const colonizeResult = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player, t('planet.colonyPrefix'))
|
||||
const newPlanet = colonizeResult.planet
|
||||
|
||||
// 更新成就统计 - 殖民
|
||||
if (colonizeResult.success && newPlanet) {
|
||||
gameLogic.trackMissionStats(gameStore.player, 'colonize')
|
||||
}
|
||||
|
||||
// 生成失败原因消息
|
||||
let failMessage = t('missionReports.colonizeFailed')
|
||||
if (!colonizeResult.success && colonizeResult.failReason) {
|
||||
if (colonizeResult.failReason === 'positionOccupied') {
|
||||
failMessage = t('missionReports.colonizeFailedOccupied')
|
||||
} else if (colonizeResult.failReason === 'maxColoniesReached') {
|
||||
failMessage = t('missionReports.colonizeFailedMaxColonies')
|
||||
}
|
||||
}
|
||||
|
||||
// 生成殖民任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
@@ -912,24 +987,72 @@
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: newPlanet?.id,
|
||||
targetPlanetName: newPlanet?.name,
|
||||
success: !!newPlanet,
|
||||
message: newPlanet ? t('missionReports.colonizeSuccess') : t('missionReports.colonizeFailed'),
|
||||
success: colonizeResult.success,
|
||||
message: colonizeResult.success ? t('missionReports.colonizeSuccess') : failMessage,
|
||||
details: newPlanet
|
||||
? {
|
||||
newPlanetId: newPlanet.id,
|
||||
newPlanetName: newPlanet.name
|
||||
}
|
||||
: undefined,
|
||||
: { failReason: colonizeResult.failReason },
|
||||
read: false
|
||||
})
|
||||
if (newPlanet) {
|
||||
gameStore.player.planets.push(newPlanet)
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Spy) {
|
||||
const spyReport = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player, null, npcStore.npcs)
|
||||
if (spyReport) gameStore.player.spyReports.push(spyReport)
|
||||
const spyResult = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player, null, npcStore.npcs)
|
||||
if (spyResult.success && spyResult.report) {
|
||||
gameStore.player.spyReports.push(spyResult.report)
|
||||
// 更新成就统计 - 侦查
|
||||
gameLogic.trackMissionStats(gameStore.player, 'spy')
|
||||
}
|
||||
|
||||
// 生成侦查任务报告(即使失败也生成)
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
|
||||
let spyFailMessage = t('missionReports.spyFailed')
|
||||
if (!spyResult.success && spyResult.failReason) {
|
||||
if (spyResult.failReason === 'targetNotFound') {
|
||||
spyFailMessage = t('missionReports.spyFailedTargetNotFound')
|
||||
}
|
||||
}
|
||||
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Spy,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName:
|
||||
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
|
||||
success: spyResult.success,
|
||||
message: spyResult.success ? t('missionReports.spySuccess') : spyFailMessage,
|
||||
details: spyResult.success ? { spyReportId: spyResult.report?.id } : { failReason: spyResult.failReason },
|
||||
read: false
|
||||
})
|
||||
} else if (mission.missionType === MissionType.Deploy) {
|
||||
const deployed = fleetLogic.processDeployArrival(mission, targetPlanet, gameStore.player.id, gameStore.player.technologies)
|
||||
|
||||
// 更新成就统计 - 部署
|
||||
if (deployed.success) {
|
||||
gameLogic.trackMissionStats(gameStore.player, 'deploy')
|
||||
}
|
||||
|
||||
// 生成失败原因消息
|
||||
let deployFailMessage = t('missionReports.deployFailed')
|
||||
if (!deployed.success && deployed.failReason) {
|
||||
if (deployed.failReason === 'targetNotFound') {
|
||||
deployFailMessage = t('missionReports.deployFailedTargetNotFound')
|
||||
} else if (deployed.failReason === 'notOwnPlanet') {
|
||||
deployFailMessage = t('missionReports.deployFailedNotOwnPlanet')
|
||||
}
|
||||
}
|
||||
|
||||
// 生成部署任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
@@ -945,9 +1068,10 @@
|
||||
targetPlanetName:
|
||||
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
|
||||
success: deployed.success,
|
||||
message: deployed.success ? t('missionReports.deploySuccess') : t('missionReports.deployFailed'),
|
||||
message: deployed.success ? t('missionReports.deploySuccess') : deployFailMessage,
|
||||
details: {
|
||||
deployedFleet: mission.fleet
|
||||
deployedFleet: mission.fleet,
|
||||
failReason: deployed.failReason
|
||||
},
|
||||
read: false
|
||||
})
|
||||
@@ -962,6 +1086,23 @@
|
||||
const debrisField = universeStore.debrisFields[debrisId]
|
||||
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
|
||||
|
||||
// 更新成就统计 - 回收(无论是否有残骸都算飞行任务,但只有成功回收才计入回收资源量)
|
||||
const totalRecycled =
|
||||
recycleResult.success && recycleResult.collectedResources
|
||||
? recycleResult.collectedResources.metal + recycleResult.collectedResources.crystal
|
||||
: 0
|
||||
gameLogic.trackMissionStats(gameStore.player, 'recycle', { resourcesAmount: totalRecycled })
|
||||
|
||||
// 生成失败原因消息
|
||||
let recycleFailMessage = t('missionReports.recycleFailed')
|
||||
if (!recycleResult.success && recycleResult.failReason) {
|
||||
if (recycleResult.failReason === 'noDebrisField') {
|
||||
recycleFailMessage = t('missionReports.recycleFailedNoDebris')
|
||||
} else if (recycleResult.failReason === 'debrisEmpty') {
|
||||
recycleFailMessage = t('missionReports.recycleFailedDebrisEmpty')
|
||||
}
|
||||
}
|
||||
|
||||
// 生成回收任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
@@ -973,18 +1114,18 @@
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
success: !!recycleResult,
|
||||
message: recycleResult ? t('missionReports.recycleSuccess') : t('missionReports.recycleFailed'),
|
||||
details: recycleResult
|
||||
success: recycleResult.success,
|
||||
message: recycleResult.success ? t('missionReports.recycleSuccess') : recycleFailMessage,
|
||||
details: recycleResult.success
|
||||
? {
|
||||
recycledResources: recycleResult.collectedResources,
|
||||
remainingDebris: recycleResult.remainingDebris || undefined
|
||||
}
|
||||
: undefined,
|
||||
: { failReason: recycleResult.failReason },
|
||||
read: false
|
||||
})
|
||||
|
||||
if (recycleResult && debrisField) {
|
||||
if (recycleResult.success && recycleResult.collectedResources && debrisField) {
|
||||
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
|
||||
// 更新残骸场
|
||||
universeStore.debrisFields[debrisId] = {
|
||||
@@ -1003,6 +1144,25 @@
|
||||
// 处理行星毁灭任务
|
||||
const destroyResult = fleetLogic.processDestroyArrival(mission, targetPlanet, gameStore.player)
|
||||
|
||||
// 更新成就统计 - 行星毁灭
|
||||
if (destroyResult.success) {
|
||||
gameLogic.trackMissionStats(gameStore.player, 'destroy')
|
||||
}
|
||||
|
||||
// 生成失败原因消息
|
||||
let destroyFailMessage = t('missionReports.destroyFailed')
|
||||
if (!destroyResult.success && destroyResult.failReason) {
|
||||
if (destroyResult.failReason === 'targetNotFound') {
|
||||
destroyFailMessage = t('missionReports.destroyFailedTargetNotFound')
|
||||
} else if (destroyResult.failReason === 'ownPlanet') {
|
||||
destroyFailMessage = t('missionReports.destroyFailedOwnPlanet')
|
||||
} else if (destroyResult.failReason === 'noDeathstar') {
|
||||
destroyFailMessage = t('missionReports.destroyFailedNoDeathstar')
|
||||
} else if (destroyResult.failReason === 'chanceFailed') {
|
||||
destroyFailMessage = t('missionReports.destroyFailedChance', { chance: destroyResult.destructionChance.toFixed(1) })
|
||||
}
|
||||
}
|
||||
|
||||
// 生成毁灭任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
@@ -1016,19 +1176,23 @@
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName: targetPlanet?.name,
|
||||
success: destroyResult?.success || false,
|
||||
message: destroyResult?.success ? t('missionReports.destroySuccess') : t('missionReports.destroyFailed'),
|
||||
details: destroyResult?.success
|
||||
success: destroyResult.success,
|
||||
message: destroyResult.success ? t('missionReports.destroySuccess') : destroyFailMessage,
|
||||
details: destroyResult.success
|
||||
? {
|
||||
destroyedPlanetName:
|
||||
targetPlanet?.name ||
|
||||
`[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`
|
||||
}
|
||||
: undefined,
|
||||
: {
|
||||
failReason: destroyResult.failReason,
|
||||
destructionChance: destroyResult.destructionChance,
|
||||
deathstarsLost: destroyResult.deathstarsLost
|
||||
},
|
||||
read: false
|
||||
})
|
||||
|
||||
if (destroyResult && destroyResult.success && destroyResult.planetId) {
|
||||
if (destroyResult.success && destroyResult.planetId) {
|
||||
// 星球被摧毁
|
||||
|
||||
// 处理外交关系(如果目标是NPC星球)
|
||||
@@ -1036,6 +1200,20 @@
|
||||
const planetOwner = npcStore.npcs.find(npc => npc.id === targetPlanet.ownerId)
|
||||
if (planetOwner) {
|
||||
diplomaticLogic.handlePlanetDestructionReputation(gameStore.player, targetPlanet, planetOwner, npcStore.npcs, gameStore.locale)
|
||||
|
||||
// 从NPC的星球列表中移除被摧毁的星球
|
||||
const npcPlanetIndex = planetOwner.planets.findIndex(p => p.id === destroyResult.planetId)
|
||||
if (npcPlanetIndex > -1) {
|
||||
planetOwner.planets.splice(npcPlanetIndex, 1)
|
||||
}
|
||||
|
||||
// 检查并处理被消灭的NPC(所有星球都被摧毁的NPC)
|
||||
const eliminatedNpcIds = diplomaticLogic.checkAndHandleEliminatedNPCs(npcStore.npcs, gameStore.player, gameStore.locale)
|
||||
|
||||
// 从npcStore中移除被消灭的NPC
|
||||
if (eliminatedNpcIds.length > 0) {
|
||||
npcStore.npcs = npcStore.npcs.filter(npc => !eliminatedNpcIds.includes(npc.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1052,6 +1230,11 @@
|
||||
// 处理远征任务
|
||||
const expeditionResult = fleetLogic.processExpeditionArrival(mission)
|
||||
|
||||
// 更新成就统计 - 远征
|
||||
const isSuccessful =
|
||||
expeditionResult.eventType === 'resources' || expeditionResult.eventType === 'darkMatter' || expeditionResult.eventType === 'fleet'
|
||||
gameLogic.trackMissionStats(gameStore.player, 'expedition', { successful: isSuccessful })
|
||||
|
||||
// 生成远征任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
@@ -1129,7 +1312,13 @@
|
||||
const debrisField = universeStore.debrisFields[debrisId]
|
||||
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
|
||||
|
||||
if (recycleResult && debrisField) {
|
||||
if (recycleResult && debrisField && recycleResult.collectedResources) {
|
||||
// 更新成就统计 - 被NPC回收残骸(如果残骸是玩家战斗产生的)
|
||||
const totalRecycled = recycleResult.collectedResources.metal + recycleResult.collectedResources.crystal
|
||||
if (totalRecycled > 0) {
|
||||
gameLogic.trackDiplomacyStats(gameStore.player, 'debrisRecycledByNPC', { resourcesAmount: totalRecycled })
|
||||
}
|
||||
|
||||
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
|
||||
// 更新残骸场
|
||||
universeStore.debrisFields[debrisId] = {
|
||||
@@ -1175,6 +1364,9 @@
|
||||
// NPC侦查到达
|
||||
const { spiedNotification, spyReport } = npcBehaviorLogic.processNPCSpyArrival(npc, mission, targetPlanet, gameStore.player)
|
||||
|
||||
// 更新成就统计 - 被NPC侦查
|
||||
gameLogic.trackDiplomacyStats(gameStore.player, 'spiedByNPC')
|
||||
|
||||
// 保存侦查报告到NPC(用于后续攻击决策)
|
||||
if (!npc.playerSpyReports) {
|
||||
npc.playerSpyReports = {}
|
||||
@@ -1193,6 +1385,14 @@
|
||||
// NPC攻击到达 - 使用专门的NPC攻击处理逻辑
|
||||
fleetLogic.processNPCAttackArrival(npc, mission, targetPlanet, gameStore.player, gameStore.player.planets).then(attackResult => {
|
||||
if (attackResult) {
|
||||
// 更新成就统计 - 被NPC攻击 + 防御统计
|
||||
gameLogic.trackDiplomacyStats(gameStore.player, 'attackedByNPC')
|
||||
const debrisValue = attackResult.debrisField
|
||||
? attackResult.debrisField.resources.metal + attackResult.debrisField.resources.crystal
|
||||
: 0
|
||||
const won = attackResult.battleResult.winner === 'defender'
|
||||
gameLogic.trackDefenseStats(gameStore.player, attackResult.battleResult, won, debrisValue)
|
||||
|
||||
// 添加战斗报告给玩家
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
|
||||
@@ -1372,6 +1572,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步NPC星球数据到universeStore
|
||||
* 解决npcStore和universeStore数据不同步的问题
|
||||
*/
|
||||
const syncNPCPlanetToUniverse = (npc: any) => {
|
||||
npc.planets.forEach((npcPlanet: any) => {
|
||||
const planetKey = gameLogic.generatePositionKey(npcPlanet.position.galaxy, npcPlanet.position.system, npcPlanet.position.position)
|
||||
const universePlanet = universeStore.planets[planetKey]
|
||||
if (universePlanet) {
|
||||
// 同步所有关键数据
|
||||
universePlanet.resources = { ...npcPlanet.resources }
|
||||
universePlanet.buildings = { ...npcPlanet.buildings }
|
||||
universePlanet.fleet = { ...npcPlanet.fleet }
|
||||
universePlanet.defense = { ...npcPlanet.defense }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateNPCGrowth = (deltaSeconds: number) => {
|
||||
// 累积时间
|
||||
npcUpdateCounter.value += deltaSeconds
|
||||
@@ -1432,20 +1650,20 @@
|
||||
// 保存到store
|
||||
npcStore.npcs = Array.from(npcMap.values())
|
||||
|
||||
// 如果有NPC,基于玩家实力初始化NPC
|
||||
// 如果有NPC,基于距离初始化NPC实力
|
||||
if (npcStore.npcs.length > 0) {
|
||||
const gameState: npcGrowthLogic.NPCGrowthGameState = {
|
||||
planets: allPlanets,
|
||||
player: gameStore.player,
|
||||
npcs: npcStore.npcs
|
||||
// 获取玩家母星(第一个非月球星球)
|
||||
const homeworld = gameStore.player.planets.find(p => !p.isMoon)
|
||||
|
||||
if (homeworld) {
|
||||
npcStore.npcs.forEach(npc => {
|
||||
// 基于距离初始化NPC实力
|
||||
npcGrowthLogic.initializeNPCByDistance(npc, homeworld.position)
|
||||
// 同步NPC星球数据到universeStore
|
||||
syncNPCPlanetToUniverse(npc)
|
||||
})
|
||||
}
|
||||
|
||||
const playerPower = npcGrowthLogic.calculatePlayerAveragePower(gameState)
|
||||
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcGrowthLogic.initializeNPCStartingPower(npc, playerPower)
|
||||
})
|
||||
|
||||
// 初始化NPC之间的外交关系(盟友/敌人)
|
||||
npcGrowthLogic.initializeNPCDiplomacy(npcStore.npcs)
|
||||
}
|
||||
@@ -1459,6 +1677,9 @@
|
||||
// 确保所有NPC都与玩家建立了关系(修复旧版本保存的数据)
|
||||
if (npcStore.npcs.length > 0) {
|
||||
const now = Date.now()
|
||||
// 获取玩家母星(用于计算距离)
|
||||
const homeworld = gameStore.player.planets.find(p => !p.isMoon)
|
||||
|
||||
npcStore.npcs.forEach(npc => {
|
||||
if (!npc.relations) {
|
||||
npc.relations = {}
|
||||
@@ -1474,6 +1695,19 @@
|
||||
history: []
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移旧存档:如果NPC没有距离数据,计算并设置
|
||||
if (homeworld && npc.distanceToHomeworld === undefined) {
|
||||
const npcPlanet = npc.planets[0]
|
||||
if (npcPlanet) {
|
||||
npc.distanceToHomeworld = npcGrowthLogic.calculateDistanceToHomeworld(npcPlanet.position, homeworld.position)
|
||||
npc.difficultyLevel = npcGrowthLogic.calculateDifficultyLevel(npc.distanceToHomeworld)
|
||||
// 重新初始化NPC实力以匹配新的距离难度系统
|
||||
npcGrowthLogic.initializeNPCByDistance(npc, homeworld.position)
|
||||
// 同步NPC星球数据到universeStore
|
||||
syncNPCPlanetToUniverse(npc)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1483,16 +1717,16 @@
|
||||
return
|
||||
}
|
||||
|
||||
// 构建游戏状态
|
||||
const gameState: npcGrowthLogic.NPCGrowthGameState = {
|
||||
planets: allPlanets,
|
||||
player: gameStore.player,
|
||||
npcs: npcStore.npcs
|
||||
}
|
||||
// 获取玩家母星用于距离计算
|
||||
const homeworldForGrowth = gameStore.player.planets.find(p => !p.isMoon)
|
||||
|
||||
// 使用累积的时间更新每个NPC(应用游戏速度倍率)
|
||||
// 使用累积的时间更新每个NPC(基于距离的成长系统)
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcGrowthLogic.updateNPCGrowth(npc, gameState, npcUpdateCounter.value, gameStore.gameSpeed)
|
||||
if (homeworldForGrowth) {
|
||||
npcGrowthLogic.updateNPCGrowthByDistance(npc, homeworldForGrowth.position, npcUpdateCounter.value, gameStore.gameSpeed)
|
||||
// 同步NPC星球数据到universeStore(确保侦查报告显示正确数据)
|
||||
syncNPCPlanetToUniverse(npc)
|
||||
}
|
||||
})
|
||||
|
||||
// 重置计数器
|
||||
@@ -1566,6 +1800,56 @@
|
||||
npcBehaviorCounter.value = 0
|
||||
}
|
||||
|
||||
// 更新NPC关系统计(友好/敌对数量)
|
||||
const updateNPCRelationStats = () => {
|
||||
let friendlyCount = 0
|
||||
let hostileCount = 0
|
||||
const playerId = gameStore.player.id
|
||||
npcStore.npcs.forEach(npc => {
|
||||
const relation = npc.relations?.[playerId]
|
||||
if (relation) {
|
||||
const status = diplomaticLogic.calculateRelationStatus(relation.reputation)
|
||||
if (status === 'friendly') {
|
||||
friendlyCount++
|
||||
} else if (status === 'hostile') {
|
||||
hostileCount++
|
||||
}
|
||||
}
|
||||
})
|
||||
gameLogic.trackDiplomacyStats(gameStore.player, 'updateRelations', { friendlyCount, hostileCount })
|
||||
}
|
||||
|
||||
// 检查成就解锁
|
||||
const achievementCheckCounter = ref(0)
|
||||
const ACHIEVEMENT_CHECK_INTERVAL = 5 // 每5秒检查一次成就
|
||||
|
||||
const checkAchievementUnlocks = () => {
|
||||
achievementCheckCounter.value += 1
|
||||
|
||||
// 只在达到更新间隔时才执行
|
||||
if (achievementCheckCounter.value < ACHIEVEMENT_CHECK_INTERVAL) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新NPC关系统计
|
||||
updateNPCRelationStats()
|
||||
|
||||
// 检查并解锁成就
|
||||
const unlocks = gameLogic.checkAndUnlockAchievements(gameStore.player)
|
||||
|
||||
// 显示成就解锁通知(奖励已在 checkAndUnlockAchievements 中应用)
|
||||
unlocks.forEach(unlock => {
|
||||
// 显示 toast 通知
|
||||
const tierName = t(`achievements.tiers.${unlock.tier}`)
|
||||
const achievementName = t(`achievements.names.${unlock.id}`)
|
||||
toast.success(t('achievements.unlocked'), {
|
||||
description: `${achievementName} (${tierName})`
|
||||
})
|
||||
})
|
||||
|
||||
achievementCheckCounter.value = 0
|
||||
}
|
||||
|
||||
// 启动游戏循环
|
||||
const startGameLoop = () => {
|
||||
if (gameStore.isPaused) return
|
||||
@@ -1665,45 +1949,13 @@
|
||||
}
|
||||
|
||||
// 检查功能是否解锁
|
||||
const checkFeatureUnlocked = (path: string): { unlocked: boolean; requirement?: { building: BuildingType; level: number } } => {
|
||||
const isFeatureUnlocked = (path: string): boolean => {
|
||||
const requirement = featureRequirements[path]
|
||||
if (!requirement) {
|
||||
return { unlocked: true }
|
||||
return true
|
||||
}
|
||||
|
||||
const currentLevel = planet.value?.buildings[requirement.building] || 0
|
||||
return {
|
||||
unlocked: currentLevel >= requirement.level,
|
||||
requirement
|
||||
}
|
||||
}
|
||||
|
||||
// 处理导航点击
|
||||
const handleNavClick = (path: string, event: Event) => {
|
||||
const { unlocked, requirement } = checkFeatureUnlocked(path)
|
||||
|
||||
if (!unlocked && requirement) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
const buildingName = BUILDINGS.value[requirement.building]?.name || requirement.building
|
||||
const currentLevel = planet.value?.buildings[requirement.building] || 0
|
||||
|
||||
toast.warning(t('common.featureLocked'), {
|
||||
description: `${t('common.requiredBuilding')}: ${buildingName} Lv ${requirement.level} (${t(
|
||||
'common.currentLevel'
|
||||
)}: Lv ${currentLevel})`,
|
||||
action: {
|
||||
label: t('common.goToBuildings'),
|
||||
onClick: () => router.push('/buildings')
|
||||
},
|
||||
duration: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 功能已解锁,正常导航
|
||||
router.push(path)
|
||||
return currentLevel >= requirement.level
|
||||
}
|
||||
|
||||
// 切换到月球
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty'
|
||||
import { Siren, Eye, Sword, Shield, Globe } from 'lucide-vue-next'
|
||||
import { Siren, Eye, Sword, Shield, Globe, Recycle } from 'lucide-vue-next'
|
||||
import { MissionType } from '@/types/game'
|
||||
import type { IncomingFleetAlert } from '@/types/game'
|
||||
import { formatDate, formatTime } from '@/utils/format'
|
||||
@@ -200,6 +200,8 @@
|
||||
return Eye
|
||||
case MissionType.Attack:
|
||||
return Sword
|
||||
case MissionType.Recycle:
|
||||
return Recycle
|
||||
default:
|
||||
return Siren
|
||||
}
|
||||
@@ -212,6 +214,8 @@
|
||||
return 'text-purple-500'
|
||||
case MissionType.Attack:
|
||||
return 'text-red-500'
|
||||
case MissionType.Recycle:
|
||||
return 'text-amber-500'
|
||||
default:
|
||||
return 'text-yellow-500'
|
||||
}
|
||||
@@ -229,6 +233,8 @@
|
||||
return t('enemyAlert.missionType.spy')
|
||||
case MissionType.Attack:
|
||||
return t('enemyAlert.missionType.attack')
|
||||
case MissionType.Recycle:
|
||||
return t('enemyAlert.missionType.recycle')
|
||||
default:
|
||||
return t('enemyAlert.missionType.unknown')
|
||||
}
|
||||
@@ -241,6 +247,8 @@
|
||||
return t('enemyAlert.warning.spy')
|
||||
case MissionType.Attack:
|
||||
return t('enemyAlert.warning.attack')
|
||||
case MissionType.Recycle:
|
||||
return t('enemyAlert.warning.recycle')
|
||||
default:
|
||||
return t('enemyAlert.warning.unknown')
|
||||
}
|
||||
|
||||
72
src/components/LowEnergyWarning.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div v-if="showWarning" class="bg-destructive/10 border-b border-destructive/20">
|
||||
<div class="px-4 sm:px-6 py-2 flex items-center justify-between gap-3">
|
||||
<!-- 警告图标和信息 -->
|
||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||
<Zap class="h-5 w-5 text-destructive flex-shrink-0 animate-pulse" />
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold text-destructive">
|
||||
{{ t('energy.lowWarning') }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ detailMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 建造电站按钮 -->
|
||||
<Button @click="goToBuildSolarPlant" variant="outline" size="sm" class="flex-shrink-0">
|
||||
{{ t('energy.buildSolarPlant') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Zap } from 'lucide-vue-next'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 获取当前星球
|
||||
const planet = computed(() => gameStore.currentPlanet)
|
||||
|
||||
// 计算能量产量
|
||||
const energyProduction = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
const now = Date.now()
|
||||
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now)
|
||||
return resourceLogic.calculateEnergyProduction(planet.value, { energyProductionBonus: bonuses.energyProductionBonus })
|
||||
})
|
||||
|
||||
// 计算能量消耗
|
||||
const energyConsumption = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
return resourceLogic.calculateEnergyConsumption(planet.value)
|
||||
})
|
||||
|
||||
// 是否显示警告(电力产量 < 消耗)
|
||||
const showWarning = computed(() => {
|
||||
if (!planet.value) return false
|
||||
return energyProduction.value < energyConsumption.value
|
||||
})
|
||||
|
||||
// 详细消息
|
||||
const detailMessage = computed(() => {
|
||||
const deficit = Math.ceil(energyConsumption.value - energyProduction.value)
|
||||
return t('energy.deficitDetail', { deficit: deficit.toString() })
|
||||
})
|
||||
|
||||
// 跳转到建筑页面建造太阳能电站
|
||||
const goToBuildSolarPlant = () => {
|
||||
router.push('/buildings')
|
||||
}
|
||||
</script>
|
||||
@@ -4,12 +4,20 @@
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<CardTitle class="flex items-center gap-2 flex-wrap">
|
||||
{{ npc.name }}
|
||||
<span v-if="npc.note" class="text-muted-foreground font-normal">({{ npc.note }})</span>
|
||||
<Badge :variant="statusBadgeVariant">
|
||||
{{ statusText }}
|
||||
</Badge>
|
||||
<!-- NPC难度等级徽章 -->
|
||||
<Badge
|
||||
v-if="npc.difficultyLevel"
|
||||
:variant="difficultyBadgeVariant"
|
||||
:class="difficultyLevelColor"
|
||||
>
|
||||
Lv.{{ npc.difficultyLevel }}
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
<CardDescription class="mt-1">
|
||||
{{ npc.planets.length }} {{ t('diplomacy.planets') }}
|
||||
@@ -208,6 +216,28 @@
|
||||
return 'text-muted-foreground'
|
||||
})
|
||||
|
||||
// NPC难度等级颜色
|
||||
const difficultyLevelColor = computed(() => {
|
||||
const level = props.npc.difficultyLevel
|
||||
if (!level) return 'text-muted-foreground'
|
||||
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
|
||||
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
|
||||
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
|
||||
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
|
||||
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
|
||||
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
|
||||
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
|
||||
})
|
||||
|
||||
// NPC难度等级Badge样式
|
||||
const difficultyBadgeVariant = computed((): 'default' | 'secondary' | 'destructive' | 'outline' => {
|
||||
const level = props.npc.difficultyLevel
|
||||
if (!level) return 'outline'
|
||||
if (level <= 2) return 'secondary'
|
||||
if (level <= 4) return 'default'
|
||||
return 'destructive'
|
||||
})
|
||||
|
||||
// 最近的外交事件
|
||||
const recentEvent = computed(() => {
|
||||
if (!props.relation?.history || props.relation.history.length === 0) return null
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium truncate">{{ npc.name }}</span>
|
||||
<span v-if="npc.note" class="text-muted-foreground text-sm truncate">({{ npc.note }})</span>
|
||||
<!-- NPC难度等级徽章 -->
|
||||
<Badge
|
||||
v-if="npc.difficultyLevel"
|
||||
:variant="difficultyBadgeVariant"
|
||||
class="text-xs"
|
||||
:class="difficultyLevelColor"
|
||||
>
|
||||
Lv.{{ npc.difficultyLevel }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
{{ npc.planets.length }} {{ t('diplomacy.planets') }}
|
||||
@@ -68,9 +77,18 @@
|
||||
'bg-gray-400': status === RelationStatus.Neutral
|
||||
}"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex-1 min-w-0 flex items-center gap-1 flex-wrap">
|
||||
<span class="font-medium truncate">{{ npc.name }}</span>
|
||||
<span v-if="npc.note" class="text-muted-foreground text-sm ml-1">({{ npc.note }})</span>
|
||||
<span v-if="npc.note" class="text-muted-foreground text-sm">({{ npc.note }})</span>
|
||||
<!-- NPC难度等级徽章 (移动端) -->
|
||||
<Badge
|
||||
v-if="npc.difficultyLevel"
|
||||
:variant="difficultyBadgeVariant"
|
||||
class="text-xs"
|
||||
:class="difficultyLevelColor"
|
||||
>
|
||||
Lv.{{ npc.difficultyLevel }}
|
||||
</Badge>
|
||||
</div>
|
||||
<ChevronDown class="h-4 w-4 text-muted-foreground transition-transform flex-shrink-0" :class="{ 'rotate-180': isExpanded }" />
|
||||
</div>
|
||||
@@ -214,6 +232,28 @@
|
||||
return 'text-muted-foreground'
|
||||
})
|
||||
|
||||
// NPC难度等级颜色
|
||||
const difficultyLevelColor = computed(() => {
|
||||
const level = props.npc.difficultyLevel
|
||||
if (!level) return 'text-muted-foreground'
|
||||
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
|
||||
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
|
||||
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
|
||||
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
|
||||
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
|
||||
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
|
||||
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
|
||||
})
|
||||
|
||||
// NPC难度等级Badge样式
|
||||
const difficultyBadgeVariant = computed((): 'default' | 'secondary' | 'destructive' | 'outline' => {
|
||||
const level = props.npc.difficultyLevel
|
||||
if (!level) return 'outline'
|
||||
if (level <= 2) return 'secondary'
|
||||
if (level <= 4) return 'default'
|
||||
return 'destructive'
|
||||
})
|
||||
|
||||
// 最近的外交事件
|
||||
const recentEvent = computed(() => {
|
||||
if (!props.relation?.history || props.relation.history.length === 0) return null
|
||||
|
||||
@@ -173,7 +173,8 @@
|
||||
const getQueueProgress = (item: BuildQueueItem): number => {
|
||||
const elapsed = currentTime.value - item.startTime
|
||||
const total = item.endTime - item.startTime
|
||||
return Math.min(100, (elapsed / total) * 100)
|
||||
if (total <= 0) return 100
|
||||
return Math.max(0, Math.min(100, (elapsed / total) * 100))
|
||||
}
|
||||
|
||||
// 统一的取消处理
|
||||
|
||||
@@ -106,6 +106,14 @@ const hints: Hint[] = [
|
||||
icon: 'swords',
|
||||
delay: 500
|
||||
},
|
||||
{
|
||||
id: 'achievements_intro',
|
||||
route: '/achievements',
|
||||
titleKey: 'hints.achievements.title',
|
||||
messageKey: 'hints.achievements.message',
|
||||
icon: 'trophy',
|
||||
delay: 500
|
||||
},
|
||||
{
|
||||
id: 'settings_intro',
|
||||
route: '/settings',
|
||||
|
||||
555
src/config/achievementConfig.ts
Normal file
@@ -0,0 +1,555 @@
|
||||
import { AchievementCategory, AchievementTier, type AchievementConfig } from '@/types/game'
|
||||
|
||||
// 成就配置
|
||||
// 每个成就有5个等级:青铜、白银、黄金、铂金、钻石
|
||||
// 每个等级有对应的目标值和奖励(暗物质 + 积分)
|
||||
|
||||
export const ACHIEVEMENTS: AchievementConfig[] = [
|
||||
// ==================== 资源类成就 ====================
|
||||
{
|
||||
id: 'metalCollector',
|
||||
category: AchievementCategory.Resource,
|
||||
icon: 'Gem',
|
||||
statKey: 'totalMetalProduced',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'crystalCollector',
|
||||
category: AchievementCategory.Resource,
|
||||
icon: 'Diamond',
|
||||
statKey: 'totalCrystalProduced',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5000, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 50000, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 500000, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 5000000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 50000000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'deuteriumCollector',
|
||||
category: AchievementCategory.Resource,
|
||||
icon: 'Droplet',
|
||||
statKey: 'totalDeuteriumProduced',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 2500, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 25000, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 250000, reward: { darkMatter: 300, points: 3000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 2500000, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 25000000, reward: { darkMatter: 7500, points: 75000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'darkMatterCollector',
|
||||
category: AchievementCategory.Resource,
|
||||
icon: 'Sparkles',
|
||||
statKey: 'totalDarkMatterProduced',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 100, reward: { darkMatter: 20, points: 200 } },
|
||||
{ tier: AchievementTier.Silver, target: 1000, reward: { darkMatter: 100, points: 1000 } },
|
||||
{ tier: AchievementTier.Gold, target: 10000, reward: { darkMatter: 500, points: 5000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 100000, reward: { darkMatter: 2500, points: 25000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 1000000, reward: { darkMatter: 10000, points: 100000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'resourceConsumer',
|
||||
category: AchievementCategory.Resource,
|
||||
icon: 'Flame',
|
||||
statKey: 'totalResourcesConsumed',
|
||||
checkType: 'sum',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 50000, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 500000, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 5000000, reward: { darkMatter: 350, points: 3500 } },
|
||||
{ tier: AchievementTier.Platinum, target: 50000000, reward: { darkMatter: 1750, points: 17500 } },
|
||||
{ tier: AchievementTier.Diamond, target: 500000000, reward: { darkMatter: 8500, points: 85000 } }
|
||||
]
|
||||
},
|
||||
|
||||
// ==================== 建造类成就 ====================
|
||||
{
|
||||
id: 'masterBuilder',
|
||||
category: AchievementCategory.Building,
|
||||
icon: 'Building2',
|
||||
statKey: 'buildingsUpgraded',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 50, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 200, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'researcher',
|
||||
category: AchievementCategory.Building,
|
||||
icon: 'FlaskConical',
|
||||
statKey: 'researchCompleted',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 300, points: 3000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 250, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 500, reward: { darkMatter: 7500, points: 75000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'shipwright',
|
||||
category: AchievementCategory.Building,
|
||||
icon: 'Rocket',
|
||||
statKey: 'totalShipsProduced',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'fortifier',
|
||||
category: AchievementCategory.Building,
|
||||
icon: 'Shield',
|
||||
statKey: 'totalDefensesBuilt',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
|
||||
// ==================== 战斗类成就 ====================
|
||||
{
|
||||
id: 'warmonger',
|
||||
category: AchievementCategory.Combat,
|
||||
icon: 'Swords',
|
||||
statKey: 'attacksLaunched',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 20, points: 200 } },
|
||||
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 100, points: 1000 } },
|
||||
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 400, points: 4000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 2000, points: 20000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 10000, points: 100000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'conqueror',
|
||||
category: AchievementCategory.Combat,
|
||||
icon: 'Crown',
|
||||
statKey: 'attacksWon',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 25, points: 250 } },
|
||||
{ tier: AchievementTier.Silver, target: 15, reward: { darkMatter: 125, points: 1250 } },
|
||||
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 500, points: 5000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 200, reward: { darkMatter: 2500, points: 25000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 12500, points: 125000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'defender',
|
||||
category: AchievementCategory.Combat,
|
||||
icon: 'ShieldCheck',
|
||||
statKey: 'defensesSuccessful',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 25, points: 250 } },
|
||||
{ tier: AchievementTier.Silver, target: 15, reward: { darkMatter: 125, points: 1250 } },
|
||||
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 500, points: 5000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 200, reward: { darkMatter: 2500, points: 25000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 12500, points: 125000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'fleetDestroyer',
|
||||
category: AchievementCategory.Combat,
|
||||
icon: 'Bomb',
|
||||
statKey: 'totalEnemyFleetDestroyedInDefense',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 300, points: 3000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 7500, points: 75000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'debrisCreator',
|
||||
category: AchievementCategory.Combat,
|
||||
icon: 'Trash2',
|
||||
statKey: 'totalDebrisCreated',
|
||||
checkType: 'sum',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'fleetSacrifice',
|
||||
category: AchievementCategory.Combat,
|
||||
icon: 'Skull',
|
||||
statKey: 'totalFleetLost',
|
||||
checkType: 'sum',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'defenseSacrifice',
|
||||
category: AchievementCategory.Combat,
|
||||
icon: 'ShieldOff',
|
||||
statKey: 'totalDefenseLostInDefense',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
|
||||
// ==================== 任务类成就 ====================
|
||||
{
|
||||
id: 'pilot',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Plane',
|
||||
statKey: 'totalFlightMissions',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 50, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 200, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 1000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 5000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'transporter',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Truck',
|
||||
statKey: 'transportMissions',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'cargoMaster',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Package',
|
||||
statKey: 'transportedResources',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'colonizer',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Flag',
|
||||
statKey: 'colonizations',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 150, points: 1500 } },
|
||||
{ tier: AchievementTier.Gold, target: 5, reward: { darkMatter: 500, points: 5000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 8, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 12, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'spy',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Eye',
|
||||
statKey: 'spyMissions',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'deployer',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'ArrowDownToLine',
|
||||
statKey: 'deployments',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'explorer',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Compass',
|
||||
statKey: 'expeditionsTotal',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 300, points: 3000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 7500, points: 75000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'luckyExplorer',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Sparkle',
|
||||
statKey: 'expeditionsSuccessful',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 20, points: 200 } },
|
||||
{ tier: AchievementTier.Silver, target: 15, reward: { darkMatter: 100, points: 1000 } },
|
||||
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 400, points: 4000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 200, reward: { darkMatter: 2000, points: 20000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 10000, points: 100000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'recycler',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Recycle',
|
||||
statKey: 'recyclingMissions',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'scavenger',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Pickaxe',
|
||||
statKey: 'recycledResources',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'destroyer',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Zap',
|
||||
statKey: 'planetDestructions',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 100, points: 1000 } },
|
||||
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 500, points: 5000 } },
|
||||
{ tier: AchievementTier.Gold, target: 10, reward: { darkMatter: 2000, points: 20000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 25, reward: { darkMatter: 10000, points: 100000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 50, reward: { darkMatter: 50000, points: 500000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'fuelBurner',
|
||||
category: AchievementCategory.Mission,
|
||||
icon: 'Fuel',
|
||||
statKey: 'fuelConsumed',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 1000, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 10000, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 100000, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 1000000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 10000000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
|
||||
// ==================== 外交类成就 ====================
|
||||
{
|
||||
id: 'diplomat',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'HandshakeIcon',
|
||||
statKey: 'friendlyNPCCount',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 20, points: 200 } },
|
||||
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 100, points: 1000 } },
|
||||
{ tier: AchievementTier.Gold, target: 5, reward: { darkMatter: 400, points: 4000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10, reward: { darkMatter: 2000, points: 20000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 20, reward: { darkMatter: 10000, points: 100000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'nemesis',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'Angry',
|
||||
statKey: 'hostileNPCCount',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 5, reward: { darkMatter: 300, points: 3000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 20, reward: { darkMatter: 7500, points: 75000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'generous',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'Gift',
|
||||
statKey: 'giftsSent',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 10, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 30, reward: { darkMatter: 300, points: 3000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 100, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 300, reward: { darkMatter: 7500, points: 75000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'philanthropist',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'HeartHandshake',
|
||||
statKey: 'giftResourcesTotal',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 15, points: 150 } },
|
||||
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 75, points: 750 } },
|
||||
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 300, points: 3000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1500, points: 15000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 7500, points: 75000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'target',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'Target',
|
||||
statKey: 'attackedByNPC',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 10, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 30, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 100, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 300, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'watched',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'ScanEye',
|
||||
statKey: 'spiedByNPC',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 20, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 150, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 500, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'robbed',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'Banknote',
|
||||
statKey: 'debrisRecycledByNPC',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 10, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 30, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 100, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 300, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'lostToNPC',
|
||||
category: AchievementCategory.Diplomacy,
|
||||
icon: 'BadgeDollarSign',
|
||||
statKey: 'debrisResourcesLostToNPC',
|
||||
checkType: 'gte',
|
||||
tiers: [
|
||||
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
|
||||
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
|
||||
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
|
||||
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
|
||||
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 成就ID映射,方便快速查找
|
||||
export const ACHIEVEMENT_MAP: Record<string, AchievementConfig> = Object.fromEntries(ACHIEVEMENTS.map(a => [a.id, a]))
|
||||
|
||||
// 按类别分组的成就
|
||||
export const ACHIEVEMENTS_BY_CATEGORY: Record<AchievementCategory, AchievementConfig[]> = {
|
||||
[AchievementCategory.Resource]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Resource),
|
||||
[AchievementCategory.Building]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Building),
|
||||
[AchievementCategory.Combat]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Combat),
|
||||
[AchievementCategory.Mission]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Mission),
|
||||
[AchievementCategory.Diplomacy]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Diplomacy)
|
||||
}
|
||||
|
||||
// 等级顺序(用于比较)
|
||||
export const TIER_ORDER: AchievementTier[] = [
|
||||
AchievementTier.Bronze,
|
||||
AchievementTier.Silver,
|
||||
AchievementTier.Gold,
|
||||
AchievementTier.Platinum,
|
||||
AchievementTier.Diamond
|
||||
]
|
||||
|
||||
// 获取等级索引
|
||||
export function getTierIndex(tier: AchievementTier): number {
|
||||
return TIER_ORDER.indexOf(tier)
|
||||
}
|
||||
|
||||
// 获取下一个等级
|
||||
export function getNextTier(tier: AchievementTier | null): AchievementTier | null {
|
||||
if (tier === null) return AchievementTier.Bronze
|
||||
const index = getTierIndex(tier)
|
||||
if (index >= TIER_ORDER.length - 1) return null
|
||||
return TIER_ORDER[index + 1] ?? null
|
||||
}
|
||||
@@ -59,6 +59,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 1.5,
|
||||
spaceUsage: 2,
|
||||
planetOnly: true, // OGame规则:月球不能建造太阳能电站
|
||||
levelRequirements: {
|
||||
15: { [BuildingType.RoboticsFactory]: 3 },
|
||||
25: { [BuildingType.RoboticsFactory]: 6, [BuildingType.ResearchLab]: 5 },
|
||||
@@ -73,6 +74,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
baseTime: 30,
|
||||
costMultiplier: 1.8,
|
||||
spaceUsage: 4,
|
||||
planetOnly: true, // OGame规则:月球不能建造核聚变反应堆
|
||||
requirements: {
|
||||
[TechnologyType.EnergyTechnology]: 3,
|
||||
[BuildingType.DeuteriumSynthesizer]: 5
|
||||
@@ -109,6 +111,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 8,
|
||||
maxLevel: 10, // 最多10级(最多11个建造队列)
|
||||
planetOnly: true, // OGame规则:月球不能建造纳米工厂
|
||||
requirements: { [BuildingType.RoboticsFactory]: 10 },
|
||||
levelRequirements: {
|
||||
3: { [BuildingType.ResearchLab]: 10, [BuildingType.Shipyard]: 8, [TechnologyType.ComputerTechnology]: 8 },
|
||||
@@ -139,6 +142,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
costMultiplier: 1.8,
|
||||
spaceUsage: 3,
|
||||
fleetStorageBonus: 1500, // 每级增加1500舰队仓储,比船坞更高
|
||||
planetOnly: true, // OGame规则:月球不能建造机库
|
||||
requirements: { [BuildingType.RoboticsFactory]: 1 }, // 只需要1级机器人工厂
|
||||
levelRequirements: {
|
||||
10: { [BuildingType.RoboticsFactory]: 3 },
|
||||
@@ -153,6 +157,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
baseTime: 30, // 减少建造时间:60→30秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 3,
|
||||
planetOnly: true, // OGame规则:月球不能建造研究实验室
|
||||
requirements: {
|
||||
[BuildingType.MetalMine]: 3,
|
||||
[BuildingType.CrystalMine]: 3,
|
||||
@@ -176,11 +181,8 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 1,
|
||||
requirements: { [BuildingType.MetalMine]: 2 },
|
||||
levelRequirements: {
|
||||
8: { [BuildingType.MetalMine]: 15, [BuildingType.RoboticsFactory]: 3 },
|
||||
12: { [BuildingType.MetalMine]: 25, [BuildingType.RoboticsFactory]: 6 }
|
||||
}
|
||||
planetOnly: true, // OGame规则:月球不能建造仓储(月球没有矿场)
|
||||
requirements: { [BuildingType.MetalMine]: 1 }
|
||||
},
|
||||
[BuildingType.CrystalStorage]: {
|
||||
id: BuildingType.CrystalStorage,
|
||||
@@ -190,11 +192,8 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 1,
|
||||
requirements: { [BuildingType.CrystalMine]: 2 },
|
||||
levelRequirements: {
|
||||
8: { [BuildingType.CrystalMine]: 15, [BuildingType.RoboticsFactory]: 3 },
|
||||
12: { [BuildingType.CrystalMine]: 25, [BuildingType.RoboticsFactory]: 6 }
|
||||
}
|
||||
planetOnly: true, // OGame规则:月球不能建造仓储(月球没有矿场)
|
||||
requirements: { [BuildingType.CrystalMine]: 1 }
|
||||
},
|
||||
[BuildingType.DeuteriumTank]: {
|
||||
id: BuildingType.DeuteriumTank,
|
||||
@@ -204,11 +203,8 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 1,
|
||||
requirements: { [BuildingType.DeuteriumSynthesizer]: 2 },
|
||||
levelRequirements: {
|
||||
8: { [BuildingType.DeuteriumSynthesizer]: 15, [BuildingType.RoboticsFactory]: 3 },
|
||||
12: { [BuildingType.DeuteriumSynthesizer]: 25, [BuildingType.RoboticsFactory]: 6 }
|
||||
}
|
||||
planetOnly: true, // OGame规则:月球不能建造仓储(月球没有矿场)
|
||||
requirements: { [BuildingType.DeuteriumSynthesizer]: 1 }
|
||||
},
|
||||
[BuildingType.DarkMatterCollector]: {
|
||||
id: BuildingType.DarkMatterCollector,
|
||||
@@ -255,6 +251,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 5,
|
||||
maxLevel: 10,
|
||||
planetOnly: true, // OGame规则:月球不能建造导弹发射井
|
||||
requirements: {
|
||||
[BuildingType.Shipyard]: 1
|
||||
},
|
||||
@@ -1097,12 +1094,18 @@ export const OFFICERS: Record<OfficerType, OfficerConfig> = {
|
||||
|
||||
// 月球配置
|
||||
export const MOON_CONFIG = {
|
||||
minDebrisField: 100000, // 最小残骸场 (金属+晶体)
|
||||
baseChance: 1, // 基础1%概率
|
||||
maxChance: 20, // 最大20%概率
|
||||
minDebrisField: 100000, // 最小残骸场 (金属+晶体) 100k = 1%概率
|
||||
baseChance: 0, // 基础0%概率(每100k残骸增加1%)
|
||||
maxChance: 20, // 最大20%概率(需要2M残骸)
|
||||
chancePerDebris: 100000, // 每10万资源增加1%概率
|
||||
baseSize: 100, // 月球基础空间
|
||||
lunarBaseSpaceBonus: 30 // 每级月球基地增加的空间
|
||||
baseFields: 1, // 月球初始空间(OGame规则:初始只有1格)
|
||||
lunarBaseFieldsBonus: 3, // 每级月球基地增加的空间(每级+3格,占用1格,净增2格)
|
||||
minDiameter: 3476, // 最小月球直径(km),1%概率时
|
||||
maxDiameter: 8944, // 最大月球直径(km),20%概率时保证>8000km
|
||||
baseDiameter: 3000, // 基础直径(km)
|
||||
diameterPerChance: 273, // 每1%概率增加的直径(km),(8944-3476)/20≈273
|
||||
jumpGateCooldown: 3600000 // 跳跃门冷却时间(ms) = 1小时 (OGame规则)
|
||||
// 注:月球资源容量与行星相同(基础10000),OGame规则允许资源超过容量存储
|
||||
}
|
||||
|
||||
// 行星配置
|
||||
|
||||
@@ -81,7 +81,8 @@ export default {
|
||||
officers: 'Offiziere',
|
||||
simulator: 'Simulator',
|
||||
galaxy: 'Galaxie',
|
||||
diplomacy: 'Diplomacy',
|
||||
diplomacy: 'Diplomatie',
|
||||
achievements: 'Erfolge',
|
||||
messages: 'Nachrichten',
|
||||
settings: 'Einstellungen',
|
||||
gm: 'GM'
|
||||
@@ -109,6 +110,14 @@ export default {
|
||||
hour: 'Stunde',
|
||||
noEnergy: 'Keine Energie'
|
||||
},
|
||||
energy: {
|
||||
lowWarning: 'Energiedefizit! Ressourcenproduktion gestoppt!',
|
||||
severeWarning: 'Energiedefizit! Ressourcenproduktion gestoppt!',
|
||||
criticalWarning: 'Energiedefizit! Ressourcenproduktion gestoppt!',
|
||||
noProduction: 'Energiedefizit! Ressourcenproduktion gestoppt!',
|
||||
deficitDetail: 'Energiedefizit: {deficit}, bauen Sie mehr Kraftwerke',
|
||||
buildSolarPlant: 'Kraftwerk bauen'
|
||||
},
|
||||
planet: {
|
||||
planet: 'Planet',
|
||||
moon: 'Mond',
|
||||
@@ -421,6 +430,7 @@ export default {
|
||||
shipyardView: {
|
||||
title: 'Raumschiffwerft',
|
||||
fleetStorage: 'Flottenspeicher',
|
||||
owned: 'Besitz',
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
shield: 'Schild',
|
||||
@@ -486,8 +496,10 @@ export default {
|
||||
available: 'Verfügbar',
|
||||
all: 'Alle',
|
||||
targetCoordinates: 'Zielkoordinaten',
|
||||
targetType: 'Zieltyp',
|
||||
planet: 'Planet',
|
||||
moon: 'Mond',
|
||||
galaxy: 'Galaxie',
|
||||
diplomacy: 'Diplomacy',
|
||||
system: 'System',
|
||||
position: 'Position',
|
||||
missionType: 'Missionstyp',
|
||||
@@ -531,7 +543,30 @@ export default {
|
||||
noDeathstar: 'Todesstern für Zerstörungsmission erforderlich',
|
||||
giftMode: 'Geschenkmodus',
|
||||
giftModeDescription: 'Ressourcen als Geschenk senden an',
|
||||
estimatedReputationGain: 'Geschätzter Reputationsgewinn'
|
||||
estimatedReputationGain: 'Geschätzter Reputationsgewinn',
|
||||
// Flotten-Vorlagen
|
||||
fleetPresets: 'Flotten-Vorlagen',
|
||||
fleetPresetsDescription: 'Speichere häufige Flottenkonfigurationen für schnellen Versand (max. 3)',
|
||||
savePreset: 'Vorlage speichern',
|
||||
noPresets: 'Noch keine Vorlagen, wähle eine Flotte und klicke "Vorlage speichern"',
|
||||
shipTypes: 'Schiffstypen',
|
||||
editPreset: 'Vorlageninhalt bearbeiten',
|
||||
renamePreset: 'Umbenennen',
|
||||
deletePreset: 'Vorlage löschen',
|
||||
editingPresetHint: 'Vorlage bearbeiten, Flottenkonfiguration ändern und "Speichern" klicken',
|
||||
presetLimitReached: 'Vorlagenlimit erreicht',
|
||||
presetLimitReachedMessage: 'Maximal {max} Vorlagen erlaubt',
|
||||
presetError: 'Speichern fehlgeschlagen',
|
||||
presetNoShips: 'Bitte wähle zuerst mindestens ein Schiff',
|
||||
presetDefaultName: 'Vorlage {number}',
|
||||
savePresetTitle: 'Flottenvorlage speichern',
|
||||
savePresetDescription: 'Benenne diese Flottenkonfiguration',
|
||||
renamePresetTitle: 'Vorlage umbenennen',
|
||||
renamePresetDescription: 'Neuen Vorlagennamen eingeben',
|
||||
presetName: 'Vorlagenname',
|
||||
presetNamePlaceholder: 'Vorlagennamen eingeben',
|
||||
deletePresetTitle: 'Vorlage löschen',
|
||||
deletePresetMessage: 'Vorlage "{name}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.'
|
||||
},
|
||||
officersView: {
|
||||
title: 'Offiziere',
|
||||
@@ -571,7 +606,6 @@ export default {
|
||||
title: 'Galaxie',
|
||||
selectCoordinates: 'Koordinaten auswählen',
|
||||
galaxy: 'Galaxie',
|
||||
diplomacy: 'Diplomacy',
|
||||
selectGalaxy: 'Galaxie auswählen',
|
||||
system: 'System',
|
||||
selectSystem: 'System auswählen',
|
||||
@@ -733,14 +767,29 @@ export default {
|
||||
missionReports: {
|
||||
transportSuccess: 'Transportmission erfolgreich abgeschlossen',
|
||||
transportFailed: 'Transportmission fehlgeschlagen',
|
||||
transportFailedTargetNotFound: 'Transport fehlgeschlagen: Zielplanet existiert nicht',
|
||||
transportFailedGiftRejected: 'Transport fehlgeschlagen: Geschenk wurde abgelehnt',
|
||||
colonizeSuccess: 'Kolonisierungsmission erfolgreich, neuer Planet gegründet',
|
||||
colonizeFailed: 'Kolonisierungsmission fehlgeschlagen',
|
||||
colonizeFailedOccupied: 'Kolonisierung fehlgeschlagen: Zielposition ist bereits von einem anderen Planeten besetzt',
|
||||
colonizeFailedMaxColonies: 'Kolonisierung fehlgeschlagen: Maximale Anzahl an Kolonien erreicht. Forsche Astrophysik, um das Limit zu erhöhen.',
|
||||
spySuccess: 'Spionagemission erfolgreich abgeschlossen',
|
||||
spyFailed: 'Spionagemission fehlgeschlagen',
|
||||
spyFailedTargetNotFound: 'Spionage fehlgeschlagen: Zielplanet existiert nicht',
|
||||
deploySuccess: 'Einsatzmission erfolgreich abgeschlossen',
|
||||
deployFailed: 'Einsatzmission fehlgeschlagen',
|
||||
deployFailedTargetNotFound: 'Einsatz fehlgeschlagen: Zielplanet existiert nicht',
|
||||
deployFailedNotOwnPlanet: 'Einsatz fehlgeschlagen: Zielplanet gehört nicht dir',
|
||||
recycleSuccess: 'Recyclingmission erfolgreich abgeschlossen',
|
||||
recycleFailed: 'Recyclingmission fehlgeschlagen, keine Trümmer am Zielort',
|
||||
recycleFailedNoDebris: 'Recycling fehlgeschlagen: Kein Trümmerfeld am Zielort',
|
||||
recycleFailedDebrisEmpty: 'Recycling fehlgeschlagen: Trümmerfeld ist bereits leer',
|
||||
destroySuccess: 'Planetenzerstörungsmission erfolgreich ausgeführt',
|
||||
destroyFailed: 'Planetenzerstörungsmission fehlgeschlagen',
|
||||
destroyFailedTargetNotFound: 'Zerstörung fehlgeschlagen: Zielplanet existiert nicht',
|
||||
destroyFailedOwnPlanet: 'Zerstörung fehlgeschlagen: Eigener Planet kann nicht zerstört werden',
|
||||
destroyFailedNoDeathstar: 'Zerstörung fehlgeschlagen: Kein Todesstern für die Mission',
|
||||
destroyFailedChance: 'Zerstörung fehlgeschlagen: Wahrscheinlichkeitsprüfung gescheitert (Erfolgsrate: {chance}%)',
|
||||
missileAttackSuccess: 'Raketenangriff erfolgreich',
|
||||
missileAttackFailed: 'Raketenangriff fehlgeschlagen, Zielplanet existiert nicht',
|
||||
missileAttackIntercepted: 'Alle Raketen abgefangen',
|
||||
@@ -1259,6 +1308,10 @@ export default {
|
||||
message:
|
||||
'Simulieren Sie Kampfergebnisse vor dem Angriff. Geben Sie Flotten und Technologiestufen ein, um Sieg, Verluste und Beute vorherzusagen.'
|
||||
},
|
||||
achievements: {
|
||||
title: 'Erfolgssystem',
|
||||
message: 'Schließen Sie Spielziele ab, um Erfolge freizuschalten und Dunkle Materie-Belohnungen zu erhalten! Erfolge haben mehrere Stufen - streben Sie höhere Herausforderungen an, um bessere Belohnungen zu erhalten.'
|
||||
},
|
||||
settings: {
|
||||
title: 'Einstellungen',
|
||||
message: 'Verwalten Sie hier Spieldaten, Benachrichtigungen und Import/Export. Sichern Sie regelmäßig Ihren Fortschritt!'
|
||||
@@ -1268,5 +1321,102 @@ export default {
|
||||
message:
|
||||
'Der GM-Modus ermöglicht schnelle Änderung von Ressourcen, Gebäuden und Technologiestufen. Nutzen Sie ihn zum Testen oder für vollständige Spielinhalte.'
|
||||
}
|
||||
},
|
||||
achievements: {
|
||||
title: 'Erfolge',
|
||||
unlocked: 'Erfolg freigeschaltet',
|
||||
progress: 'Fortschritt',
|
||||
nextTier: 'Nächste Stufe',
|
||||
maxTierReached: 'Höchste Stufe erreicht',
|
||||
tiers: {
|
||||
bronze: 'Bronze',
|
||||
silver: 'Silber',
|
||||
gold: 'Gold',
|
||||
platinum: 'Platin',
|
||||
diamond: 'Diamant'
|
||||
},
|
||||
categories: {
|
||||
resource: 'Ressourcen',
|
||||
building: 'Gebäude',
|
||||
combat: 'Kampf',
|
||||
mission: 'Missionen',
|
||||
diplomacy: 'Diplomatie'
|
||||
},
|
||||
names: {
|
||||
metalCollector: 'Metallsammler',
|
||||
crystalCollector: 'Kristallsammler',
|
||||
deuteriumCollector: 'Deuteriumsammler',
|
||||
darkMatterCollector: 'Dunkle-Materie-Sammler',
|
||||
resourceConsumer: 'Ressourcenverbraucher',
|
||||
masterBuilder: 'Meisterbauer',
|
||||
researcher: 'Forscher',
|
||||
shipwright: 'Schiffsbauer',
|
||||
fortifier: 'Befestiger',
|
||||
warmonger: 'Kriegstreiber',
|
||||
conqueror: 'Eroberer',
|
||||
defender: 'Verteidiger',
|
||||
fleetDestroyer: 'Flottenzerstörer',
|
||||
debrisCreator: 'Trümmererzeuger',
|
||||
fleetSacrifice: 'Flottenopfer',
|
||||
defenseSacrifice: 'Verteidigungsopfer',
|
||||
pilot: 'Pilot',
|
||||
transporter: 'Transporter',
|
||||
cargoMaster: 'Frachtmeister',
|
||||
colonizer: 'Kolonisator',
|
||||
spy: 'Spionagemeister',
|
||||
deployer: 'Stationierer',
|
||||
explorer: 'Entdecker',
|
||||
luckyExplorer: 'Glücklicher Entdecker',
|
||||
recycler: 'Recycler',
|
||||
scavenger: 'Sammler',
|
||||
destroyer: 'Vernichter',
|
||||
fuelBurner: 'Treibstoffverbrenner',
|
||||
diplomat: 'Diplomat',
|
||||
nemesis: 'Erzfeind',
|
||||
generous: 'Großzügig',
|
||||
philanthropist: 'Philanthrop',
|
||||
target: 'Zielscheibe',
|
||||
watched: 'Überwacht',
|
||||
robbed: 'Beraubt',
|
||||
lostToNPC: 'An NPC verloren'
|
||||
},
|
||||
descriptions: {
|
||||
metalCollector: 'Gesamtes produziertes Metall',
|
||||
crystalCollector: 'Gesamtes produziertes Kristall',
|
||||
deuteriumCollector: 'Gesamtes produziertes Deuterium',
|
||||
darkMatterCollector: 'Gesamte produzierte Dunkle Materie',
|
||||
resourceConsumer: 'Gesamte verbrauchte Ressourcen',
|
||||
masterBuilder: 'Gesamte Gebäudeupgrades',
|
||||
researcher: 'Gesamte abgeschlossene Forschungen',
|
||||
shipwright: 'Gesamte produzierte Schiffe',
|
||||
fortifier: 'Gesamte gebaute Verteidigungen',
|
||||
warmonger: 'Gestartete Angriffe',
|
||||
conqueror: 'Gewonnene Angriffe',
|
||||
defender: 'Erfolgreiche Verteidigungen',
|
||||
fleetDestroyer: 'In der Verteidigung zerstörte feindliche Flotte',
|
||||
debrisCreator: 'In Kämpfen erzeugte Trümmer',
|
||||
fleetSacrifice: 'Gesamte verlorene Flotte',
|
||||
defenseSacrifice: 'In der Verteidigung verlorene Verteidigungen',
|
||||
pilot: 'Gesamte Flugmissionen',
|
||||
transporter: 'Gesamte Transportmissionen',
|
||||
cargoMaster: 'Gesamte transportierte Ressourcen',
|
||||
colonizer: 'Kolonisierte Planeten',
|
||||
spy: 'Abgeschlossene Spionagemissionen',
|
||||
deployer: 'Abgeschlossene Stationierungen',
|
||||
explorer: 'Gesamte Expeditionen',
|
||||
luckyExplorer: 'Erfolgreiche Expeditionen',
|
||||
recycler: 'Gesamte Recycling-Missionen',
|
||||
scavenger: 'Gesamte recycelte Ressourcen',
|
||||
destroyer: 'Zerstörte Planeten',
|
||||
fuelBurner: 'Gesamter verbrauchter Treibstoff',
|
||||
diplomat: 'Anzahl freundlicher NPCs',
|
||||
nemesis: 'Anzahl feindlicher NPCs',
|
||||
generous: 'Gesendete Geschenke',
|
||||
philanthropist: 'Gesamte geschenkte Ressourcen',
|
||||
target: 'Von NPC angegriffen',
|
||||
watched: 'Von NPC ausspioniert',
|
||||
robbed: 'Von NPC gesammelte Trümmer',
|
||||
lostToNPC: 'An NPC verlorene Trümmerressourcen'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ export default {
|
||||
simulator: 'Simulator',
|
||||
galaxy: 'Galaxy',
|
||||
diplomacy: 'Diplomacy',
|
||||
achievements: 'Achievements',
|
||||
messages: 'Messages',
|
||||
settings: 'Settings',
|
||||
gm: 'GM'
|
||||
@@ -108,6 +109,14 @@ export default {
|
||||
hour: 'hour',
|
||||
noEnergy: 'No Energy'
|
||||
},
|
||||
energy: {
|
||||
lowWarning: 'Energy deficit! Resource production stopped!',
|
||||
severeWarning: 'Energy deficit! Resource production stopped!',
|
||||
criticalWarning: 'Energy deficit! Resource production stopped!',
|
||||
noProduction: 'Energy deficit! Resource production stopped!',
|
||||
deficitDetail: 'Energy deficit: {deficit}, build more power plants',
|
||||
buildSolarPlant: 'Build Power Plant'
|
||||
},
|
||||
planet: {
|
||||
planet: 'Planet',
|
||||
moon: 'Moon',
|
||||
@@ -419,6 +428,7 @@ export default {
|
||||
shipyardView: {
|
||||
title: 'Shipyard',
|
||||
fleetStorage: 'Fleet Storage',
|
||||
owned: 'Owned',
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
shield: 'Shield',
|
||||
@@ -482,6 +492,9 @@ export default {
|
||||
available: 'Available',
|
||||
all: 'All',
|
||||
targetCoordinates: 'Target Coordinates',
|
||||
targetType: 'Target Type',
|
||||
planet: 'Planet',
|
||||
moon: 'Moon',
|
||||
galaxy: 'Galaxy',
|
||||
system: 'System',
|
||||
position: 'Position',
|
||||
@@ -528,7 +541,47 @@ export default {
|
||||
noDeathstar: 'Deathstar required for destruction mission',
|
||||
giftMode: 'Gift Mode',
|
||||
giftModeDescription: 'Send resources as a gift to',
|
||||
estimatedReputationGain: 'Estimated reputation gain'
|
||||
estimatedReputationGain: 'Estimated reputation gain',
|
||||
// Fleet presets
|
||||
fleetPresets: 'Fleet Presets',
|
||||
fleetPresetsDescription: 'Save common fleet configurations for quick dispatch (max 3)',
|
||||
savePreset: 'Save Preset',
|
||||
noPresets: 'No presets yet, select fleet and click "Save Preset" to create',
|
||||
shipTypes: 'ship types',
|
||||
editPreset: 'Edit preset content',
|
||||
renamePreset: 'Rename',
|
||||
deletePreset: 'Delete preset',
|
||||
editingPresetHint: 'Editing preset, modify fleet configuration and click "Save" to update',
|
||||
presetLimitReached: 'Preset limit reached',
|
||||
presetLimitReachedMessage: 'Maximum of {max} presets allowed',
|
||||
presetError: 'Save failed',
|
||||
presetNoShips: 'Please select at least one ship first',
|
||||
presetDefaultName: 'Preset {number}',
|
||||
savePresetTitle: 'Save Fleet Preset',
|
||||
savePresetDescription: 'Name this fleet configuration',
|
||||
renamePresetTitle: 'Rename Preset',
|
||||
renamePresetDescription: 'Enter a new preset name',
|
||||
presetName: 'Preset Name',
|
||||
presetNamePlaceholder: 'Enter preset name',
|
||||
deletePresetTitle: 'Delete Preset',
|
||||
deletePresetMessage: 'Are you sure you want to delete preset "{name}"? This action cannot be undone.',
|
||||
// Jump Gate
|
||||
jumpGate: 'Jump Gate',
|
||||
jumpGateDescription: 'Use the Jump Gate to instantly transfer fleet to another moon with Jump Gate',
|
||||
jumpGateNotAvailable: 'Jump Gate Not Available',
|
||||
jumpGateRequiresMoon: 'Jump Gate can only be used on moons',
|
||||
jumpGateNotBuilt: 'Current moon does not have a Jump Gate',
|
||||
jumpGateCooldown: 'Jump Gate Cooling Down',
|
||||
jumpGateCooldownRemaining: 'Cooldown Remaining',
|
||||
jumpGateReady: 'Jump Gate Ready',
|
||||
jumpGateSelectTarget: 'Select Target Moon',
|
||||
jumpGateNoTargetMoons: 'No available target moons (requires Jump Gate and cooldown complete)',
|
||||
jumpGateSelectFleet: 'Select Fleet to Transfer',
|
||||
jumpGateTransfer: 'Transfer Fleet',
|
||||
jumpGateSuccess: 'Jump Gate Transfer Successful',
|
||||
jumpGateSuccessMessage: 'Fleet has been instantly transferred to {target}',
|
||||
jumpGateFailed: 'Jump Gate Transfer Failed',
|
||||
jumpGateFailedMessage: 'Please check Jump Gate status and fleet configuration'
|
||||
},
|
||||
officersView: {
|
||||
title: 'Officers',
|
||||
@@ -616,7 +669,26 @@ export default {
|
||||
giftPlanetTitle: 'Send Gift',
|
||||
giftPlanetMessage:
|
||||
'Are you sure you want to send resources as a gift to planet [{coordinates}]?\n\nPlease go to the fleet page to select transport ships and load resources.',
|
||||
npcPlanetName: "{name}'s Planet"
|
||||
npcPlanetName: "{name}'s Planet",
|
||||
// Sensor Phalanx Scan
|
||||
phalanxScan: 'Phalanx Scan',
|
||||
phalanxScanTitle: 'Sensor Phalanx Scan',
|
||||
phalanxScanDescription: 'Scanning fleet activity at planet [{coordinates}]',
|
||||
phalanxNoMoon: 'Requires a moon with Sensor Phalanx to scan',
|
||||
phalanxOutOfRange: 'Target is out of scan range',
|
||||
phalanxRange: 'Scan Range',
|
||||
phalanxCost: 'Scan Cost',
|
||||
phalanxNoFleets: 'No fleet activity detected',
|
||||
phalanxFleetDetected: '{count} fleet(s) detected',
|
||||
phalanxMission: 'Mission',
|
||||
phalanxOrigin: 'Origin',
|
||||
phalanxDestination: 'Destination',
|
||||
phalanxArrival: 'Arrival',
|
||||
phalanxReturn: 'Return',
|
||||
phalanxStatus: 'Status',
|
||||
phalanxStatusOutbound: 'Outbound',
|
||||
phalanxStatusReturning: 'Returning',
|
||||
phalanxInsufficientDeuterium: 'Insufficient Deuterium'
|
||||
},
|
||||
messagesView: {
|
||||
title: 'Messages',
|
||||
@@ -726,14 +798,29 @@ export default {
|
||||
missionReports: {
|
||||
transportSuccess: 'Transport mission completed successfully',
|
||||
transportFailed: 'Transport mission failed',
|
||||
transportFailedTargetNotFound: 'Transport failed: Target planet does not exist',
|
||||
transportFailedGiftRejected: 'Transport failed: Gift was rejected',
|
||||
colonizeSuccess: 'Colonization mission successful, new planet established',
|
||||
colonizeFailed: 'Colonization mission failed',
|
||||
colonizeFailedOccupied: 'Colonization failed: Target position is already occupied by another planet',
|
||||
colonizeFailedMaxColonies: 'Colonization failed: Maximum number of colonies reached. Research Astrophysics to increase the limit.',
|
||||
spySuccess: 'Espionage mission completed successfully',
|
||||
spyFailed: 'Espionage mission failed',
|
||||
spyFailedTargetNotFound: 'Espionage failed: Target planet does not exist',
|
||||
deploySuccess: 'Deployment mission completed successfully',
|
||||
deployFailed: 'Deployment mission failed',
|
||||
deployFailedTargetNotFound: 'Deployment failed: Target planet does not exist',
|
||||
deployFailedNotOwnPlanet: 'Deployment failed: Target planet does not belong to you',
|
||||
recycleSuccess: 'Recycling mission completed successfully',
|
||||
recycleFailed: 'Recycling mission failed, no debris at target location',
|
||||
recycleFailedNoDebris: 'Recycling failed: No debris field at target location',
|
||||
recycleFailedDebrisEmpty: 'Recycling failed: Debris field has been cleared',
|
||||
destroySuccess: 'Planet destruction mission executed successfully',
|
||||
destroyFailed: 'Planet destruction mission failed',
|
||||
destroyFailedTargetNotFound: 'Destruction failed: Target planet does not exist',
|
||||
destroyFailedOwnPlanet: 'Destruction failed: Cannot destroy your own planet',
|
||||
destroyFailedNoDeathstar: 'Destruction failed: No Death Star to execute the mission',
|
||||
destroyFailedChance: 'Destruction failed: Probability check failed (Success rate: {chance}%)',
|
||||
missileAttackSuccess: 'Missile attack successful',
|
||||
missileAttackFailed: 'Missile attack failed, target planet does not exist',
|
||||
missileAttackIntercepted: 'All missiles intercepted',
|
||||
@@ -957,11 +1044,13 @@ export default {
|
||||
missionType: {
|
||||
spy: 'Spy',
|
||||
attack: 'Attack',
|
||||
recycle: 'Recycle',
|
||||
unknown: 'Unknown'
|
||||
},
|
||||
warning: {
|
||||
spy: 'Enemy spy incoming!',
|
||||
attack: 'Enemy attack incoming!',
|
||||
recycle: 'Enemy is recycling debris near you!',
|
||||
unknown: 'Enemy fleet incoming!'
|
||||
}
|
||||
},
|
||||
@@ -1376,6 +1465,10 @@ export default {
|
||||
title: 'Battle Simulator',
|
||||
message: 'Simulate battle outcomes before attacking. Enter both fleets and tech levels to predict victory, losses, and loot.'
|
||||
},
|
||||
achievements: {
|
||||
title: 'Achievement System',
|
||||
message: 'Complete game objectives to unlock achievements and earn Dark Matter rewards! Achievements have multiple tiers - aim for higher challenges to get better rewards.'
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
message: 'Manage game data, adjust notifications, export/import saves here. Remember to backup your progress regularly!'
|
||||
@@ -1385,5 +1478,102 @@ export default {
|
||||
message:
|
||||
'GM mode allows quick modification of resources, buildings, and tech levels. Use it for testing or experiencing full game content.'
|
||||
}
|
||||
},
|
||||
achievements: {
|
||||
title: 'Achievements',
|
||||
unlocked: 'Achievement Unlocked',
|
||||
progress: 'Progress',
|
||||
nextTier: 'Next Tier',
|
||||
maxTierReached: 'Max Tier Reached',
|
||||
tiers: {
|
||||
bronze: 'Bronze',
|
||||
silver: 'Silver',
|
||||
gold: 'Gold',
|
||||
platinum: 'Platinum',
|
||||
diamond: 'Diamond'
|
||||
},
|
||||
categories: {
|
||||
resource: 'Resource',
|
||||
building: 'Building',
|
||||
combat: 'Combat',
|
||||
mission: 'Mission',
|
||||
diplomacy: 'Diplomacy'
|
||||
},
|
||||
names: {
|
||||
metalCollector: 'Metal Collector',
|
||||
crystalCollector: 'Crystal Collector',
|
||||
deuteriumCollector: 'Deuterium Collector',
|
||||
darkMatterCollector: 'Dark Matter Collector',
|
||||
resourceConsumer: 'Resource Consumer',
|
||||
masterBuilder: 'Master Builder',
|
||||
researcher: 'Researcher',
|
||||
shipwright: 'Shipwright',
|
||||
fortifier: 'Fortifier',
|
||||
warmonger: 'Warmonger',
|
||||
conqueror: 'Conqueror',
|
||||
defender: 'Defender',
|
||||
fleetDestroyer: 'Fleet Destroyer',
|
||||
debrisCreator: 'Debris Creator',
|
||||
fleetSacrifice: 'Fleet Sacrifice',
|
||||
defenseSacrifice: 'Defense Sacrifice',
|
||||
pilot: 'Pilot',
|
||||
transporter: 'Transporter',
|
||||
cargoMaster: 'Cargo Master',
|
||||
colonizer: 'Colonizer',
|
||||
spy: 'Spy Master',
|
||||
deployer: 'Deployer',
|
||||
explorer: 'Explorer',
|
||||
luckyExplorer: 'Lucky Explorer',
|
||||
recycler: 'Recycler',
|
||||
scavenger: 'Scavenger',
|
||||
destroyer: 'Destroyer',
|
||||
fuelBurner: 'Fuel Burner',
|
||||
diplomat: 'Diplomat',
|
||||
nemesis: 'Nemesis',
|
||||
generous: 'Generous',
|
||||
philanthropist: 'Philanthropist',
|
||||
target: 'Target',
|
||||
watched: 'Watched',
|
||||
robbed: 'Robbed',
|
||||
lostToNPC: 'Lost to NPC'
|
||||
},
|
||||
descriptions: {
|
||||
metalCollector: 'Total metal produced',
|
||||
crystalCollector: 'Total crystal produced',
|
||||
deuteriumCollector: 'Total deuterium produced',
|
||||
darkMatterCollector: 'Total dark matter produced',
|
||||
resourceConsumer: 'Total resources consumed',
|
||||
masterBuilder: 'Total buildings upgraded',
|
||||
researcher: 'Total researches completed',
|
||||
shipwright: 'Total ships produced',
|
||||
fortifier: 'Total defenses built',
|
||||
warmonger: 'Total attacks launched',
|
||||
conqueror: 'Total attacks won',
|
||||
defender: 'Total defenses successful',
|
||||
fleetDestroyer: 'Enemy fleet destroyed in defense',
|
||||
debrisCreator: 'Total debris created from battles',
|
||||
fleetSacrifice: 'Total fleet lost',
|
||||
defenseSacrifice: 'Total defenses lost in defense',
|
||||
pilot: 'Total flight missions',
|
||||
transporter: 'Total transport missions',
|
||||
cargoMaster: 'Total resources transported',
|
||||
colonizer: 'Planets colonized',
|
||||
spy: 'Spy missions completed',
|
||||
deployer: 'Deploy missions completed',
|
||||
explorer: 'Total expeditions',
|
||||
luckyExplorer: 'Successful expeditions',
|
||||
recycler: 'Total recycling missions',
|
||||
scavenger: 'Total resources recycled',
|
||||
destroyer: 'Planets destroyed',
|
||||
fuelBurner: 'Total fuel consumed',
|
||||
diplomat: 'Number of friendly NPCs',
|
||||
nemesis: 'Number of hostile NPCs',
|
||||
generous: 'Total gifts sent',
|
||||
philanthropist: 'Total resources gifted',
|
||||
target: 'Times attacked by NPC',
|
||||
watched: 'Times spied by NPC',
|
||||
robbed: 'Times debris recycled by NPC',
|
||||
lostToNPC: 'Total debris resources lost to NPC'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,11 @@ export default {
|
||||
officers: '士官',
|
||||
simulator: 'シミュレーター',
|
||||
galaxy: '銀河',
|
||||
diplomacy: 'Diplomacy',
|
||||
diplomacy: '外交',
|
||||
achievements: '実績',
|
||||
messages: 'メッセージ',
|
||||
settings: '設定',
|
||||
guide: 'ゲームガイド',
|
||||
gm: 'GM'
|
||||
},
|
||||
sidebar: {
|
||||
@@ -109,6 +111,14 @@ export default {
|
||||
hour: '時間',
|
||||
noEnergy: 'エネルギー不足'
|
||||
},
|
||||
energy: {
|
||||
lowWarning: 'エネルギー不足!資源生産が停止しています!',
|
||||
severeWarning: 'エネルギー不足!資源生産が停止しています!',
|
||||
criticalWarning: 'エネルギー不足!資源生産が停止しています!',
|
||||
noProduction: 'エネルギー不足!資源生産が停止しています!',
|
||||
deficitDetail: 'エネルギー不足: {deficit}、発電所を建設してください',
|
||||
buildSolarPlant: '発電所を建設'
|
||||
},
|
||||
planet: {
|
||||
planet: '惑星',
|
||||
moon: '月',
|
||||
@@ -434,6 +444,7 @@ export default {
|
||||
shipyardView: {
|
||||
title: '造船所',
|
||||
fleetStorage: '艦隊ストレージ',
|
||||
owned: '所有',
|
||||
attack: '攻撃力',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
shield: 'シールド',
|
||||
@@ -484,8 +495,10 @@ export default {
|
||||
available: '利用可能',
|
||||
all: '全て',
|
||||
targetCoordinates: '目標座標',
|
||||
targetType: '目標タイプ',
|
||||
planet: '惑星',
|
||||
moon: '月',
|
||||
galaxy: '銀河',
|
||||
diplomacy: 'Diplomacy',
|
||||
system: '星系',
|
||||
position: '位置',
|
||||
missionType: 'ミッションタイプ',
|
||||
@@ -529,7 +542,30 @@ export default {
|
||||
noDeathstar: '破壊ミッションにはデススターが必要です',
|
||||
giftMode: 'ギフトモード',
|
||||
giftModeDescription: '資源を贈り物として送る',
|
||||
estimatedReputationGain: '推定評判獲得'
|
||||
estimatedReputationGain: '推定評判獲得',
|
||||
// 艦隊プリセット
|
||||
fleetPresets: '艦隊プリセット',
|
||||
fleetPresetsDescription: 'よく使う艦隊構成を保存して素早く派遣(最大3つ)',
|
||||
savePreset: 'プリセット保存',
|
||||
noPresets: 'プリセットなし、艦隊を選択して「プリセット保存」をクリック',
|
||||
shipTypes: '種の艦船',
|
||||
editPreset: 'プリセット内容を編集',
|
||||
renamePreset: '名前変更',
|
||||
deletePreset: 'プリセット削除',
|
||||
editingPresetHint: 'プリセット編集中、艦隊構成を変更して「保存」をクリック',
|
||||
presetLimitReached: 'プリセット上限到達',
|
||||
presetLimitReachedMessage: '最大{max}個のプリセットまで',
|
||||
presetError: '保存失敗',
|
||||
presetNoShips: '艦船を1隻以上選択してください',
|
||||
presetDefaultName: 'プリセット {number}',
|
||||
savePresetTitle: '艦隊プリセットを保存',
|
||||
savePresetDescription: 'この艦隊構成に名前を付ける',
|
||||
renamePresetTitle: 'プリセット名変更',
|
||||
renamePresetDescription: '新しいプリセット名を入力',
|
||||
presetName: 'プリセット名',
|
||||
presetNamePlaceholder: 'プリセット名を入力',
|
||||
deletePresetTitle: 'プリセット削除',
|
||||
deletePresetMessage: 'プリセット「{name}」を削除しますか?この操作は取り消せません。'
|
||||
},
|
||||
officersView: {
|
||||
title: '士官',
|
||||
@@ -569,7 +605,6 @@ export default {
|
||||
title: '銀河',
|
||||
selectCoordinates: '座標選択',
|
||||
galaxy: '銀河',
|
||||
diplomacy: 'Diplomacy',
|
||||
selectGalaxy: '銀河を選択',
|
||||
system: '星系',
|
||||
selectSystem: '星系を選択',
|
||||
@@ -726,14 +761,29 @@ export default {
|
||||
missionReports: {
|
||||
transportSuccess: '輸送ミッションが正常に完了しました',
|
||||
transportFailed: '輸送ミッションが失敗しました',
|
||||
transportFailedTargetNotFound: '輸送失敗:目標惑星が存在しません',
|
||||
transportFailedGiftRejected: '輸送失敗:贈り物が拒否されました',
|
||||
colonizeSuccess: '植民ミッション成功、新しい惑星が確立されました',
|
||||
colonizeFailed: '植民ミッションが失敗しました',
|
||||
colonizeFailedOccupied: '植民失敗:目標位置は既に他の惑星に占有されています',
|
||||
colonizeFailedMaxColonies: '植民失敗:コロニー数の上限に達しました。天体物理学を研究して上限を増やしてください。',
|
||||
spySuccess: 'スパイミッションが正常に完了しました',
|
||||
spyFailed: 'スパイミッションが失敗しました',
|
||||
spyFailedTargetNotFound: 'スパイ失敗:目標惑星が存在しません',
|
||||
deploySuccess: '配備ミッションが正常に完了しました',
|
||||
deployFailed: '配備ミッションが失敗しました',
|
||||
deployFailedTargetNotFound: '配備失敗:目標惑星が存在しません',
|
||||
deployFailedNotOwnPlanet: '配備失敗:目標惑星はあなたのものではありません',
|
||||
recycleSuccess: '回収ミッションが正常に完了しました',
|
||||
recycleFailed: '回収ミッションが失敗しました。目標位置にデブリがありません',
|
||||
recycleFailedNoDebris: '回収失敗:目標位置にデブリフィールドがありません',
|
||||
recycleFailedDebrisEmpty: '回収失敗:デブリフィールドは既に空です',
|
||||
destroySuccess: '惑星破壊ミッションが正常に実行されました',
|
||||
destroyFailed: '惑星破壊ミッションが失敗しました',
|
||||
destroyFailedTargetNotFound: '破壊失敗:目標惑星が存在しません',
|
||||
destroyFailedOwnPlanet: '破壊失敗:自分の惑星を破壊することはできません',
|
||||
destroyFailedNoDeathstar: '破壊失敗:ミッションを実行するデススターがありません',
|
||||
destroyFailedChance: '破壊失敗:確率判定に失敗しました(成功率:{chance}%)',
|
||||
missileAttackSuccess: 'ミサイル攻撃成功',
|
||||
missileAttackFailed: 'ミサイル攻撃失敗、目標惑星が存在しません',
|
||||
missileAttackIntercepted: '全てのミサイルが迎撃されました',
|
||||
@@ -946,11 +996,13 @@ export default {
|
||||
missionType: {
|
||||
spy: '偵察',
|
||||
attack: '攻撃',
|
||||
recycle: '回収',
|
||||
unknown: '不明'
|
||||
},
|
||||
warning: {
|
||||
spy: '敵の偵察が接近中!',
|
||||
attack: '敵の攻撃が接近中!',
|
||||
recycle: '敵があなたの近くでデブリを回収中!',
|
||||
unknown: '敵艦隊が接近中!'
|
||||
}
|
||||
},
|
||||
@@ -1238,6 +1290,10 @@ export default {
|
||||
title: '戦闘シミュレーター',
|
||||
message: '攻撃前に戦闘結果をシミュレート。双方の艦隊と技術レベルを入力して、勝敗と損失を予測。'
|
||||
},
|
||||
achievements: {
|
||||
title: '実績システム',
|
||||
message: 'ゲーム目標を達成して実績をアンロックし、ダークマター報酬を獲得!実績には複数のティアがあり、高い目標に挑戦してより良い報酬を手に入れましょう。'
|
||||
},
|
||||
settings: {
|
||||
title: '設定',
|
||||
message: 'ここでゲームデータの管理、通知設定、セーブのエクスポート/インポートができます。定期的にバックアップを!'
|
||||
@@ -1246,5 +1302,102 @@ export default {
|
||||
title: 'GM管理パネル',
|
||||
message: 'GMモードでは資源、建物、技術レベルを素早く変更できます。テストや完全なゲームコンテンツの体験に使用。'
|
||||
}
|
||||
},
|
||||
achievements: {
|
||||
title: '実績',
|
||||
unlocked: '実績解除',
|
||||
progress: '進捗',
|
||||
nextTier: '次のティア',
|
||||
maxTierReached: '最高ティア達成',
|
||||
tiers: {
|
||||
bronze: 'ブロンズ',
|
||||
silver: 'シルバー',
|
||||
gold: 'ゴールド',
|
||||
platinum: 'プラチナ',
|
||||
diamond: 'ダイヤモンド'
|
||||
},
|
||||
categories: {
|
||||
resource: '資源',
|
||||
building: '建設',
|
||||
combat: '戦闘',
|
||||
mission: 'ミッション',
|
||||
diplomacy: '外交'
|
||||
},
|
||||
names: {
|
||||
metalCollector: 'メタルコレクター',
|
||||
crystalCollector: 'クリスタルコレクター',
|
||||
deuteriumCollector: 'デューテリウムコレクター',
|
||||
darkMatterCollector: 'ダークマターコレクター',
|
||||
resourceConsumer: '資源消費者',
|
||||
masterBuilder: 'マスタービルダー',
|
||||
researcher: '研究者',
|
||||
shipwright: '造船士',
|
||||
fortifier: '要塞化者',
|
||||
warmonger: '戦争屋',
|
||||
conqueror: '征服者',
|
||||
defender: '防衛者',
|
||||
fleetDestroyer: '艦隊破壊者',
|
||||
debrisCreator: '残骸生成者',
|
||||
fleetSacrifice: '艦隊犠牲者',
|
||||
defenseSacrifice: '防衛犠牲者',
|
||||
pilot: 'パイロット',
|
||||
transporter: '輸送者',
|
||||
cargoMaster: '貨物マスター',
|
||||
colonizer: '植民者',
|
||||
spy: 'スパイマスター',
|
||||
deployer: '配備者',
|
||||
explorer: '探検家',
|
||||
luckyExplorer: '幸運な探検家',
|
||||
recycler: 'リサイクラー',
|
||||
scavenger: 'スカベンジャー',
|
||||
destroyer: '破壊者',
|
||||
fuelBurner: '燃料消費者',
|
||||
diplomat: '外交官',
|
||||
nemesis: '宿敵',
|
||||
generous: '寛大な者',
|
||||
philanthropist: '博愛者',
|
||||
target: '標的',
|
||||
watched: '監視下',
|
||||
robbed: '略奪される',
|
||||
lostToNPC: 'NPCに奪われた'
|
||||
},
|
||||
descriptions: {
|
||||
metalCollector: '総メタル生産量',
|
||||
crystalCollector: '総クリスタル生産量',
|
||||
deuteriumCollector: '総デューテリウム生産量',
|
||||
darkMatterCollector: '総ダークマター生産量',
|
||||
resourceConsumer: '総資源消費量',
|
||||
masterBuilder: '建物アップグレード総数',
|
||||
researcher: '研究完了総数',
|
||||
shipwright: '艦船生産総数',
|
||||
fortifier: '防衛施設建設総数',
|
||||
warmonger: '攻撃開始回数',
|
||||
conqueror: '攻撃勝利回数',
|
||||
defender: '防衛成功回数',
|
||||
fleetDestroyer: '防衛で破壊した敵艦隊数',
|
||||
debrisCreator: '戦闘で生成した残骸総量',
|
||||
fleetSacrifice: '艦隊損失総数',
|
||||
defenseSacrifice: '防衛で損失した防衛施設数',
|
||||
pilot: '飛行ミッション総数',
|
||||
transporter: '輸送ミッション総数',
|
||||
cargoMaster: '輸送資源総量',
|
||||
colonizer: '植民した惑星数',
|
||||
spy: 'スパイミッション完了数',
|
||||
deployer: '配備ミッション完了数',
|
||||
explorer: '遠征総数',
|
||||
luckyExplorer: '遠征成功回数',
|
||||
recycler: 'リサイクルミッション総数',
|
||||
scavenger: 'リサイクル資源総量',
|
||||
destroyer: '破壊した惑星数',
|
||||
fuelBurner: '燃料消費総量',
|
||||
diplomat: '友好的なNPCの数',
|
||||
nemesis: '敵対的なNPCの数',
|
||||
generous: '贈り物送信回数',
|
||||
philanthropist: '贈与資源総量',
|
||||
target: 'NPCに攻撃された回数',
|
||||
watched: 'NPCにスパイされた回数',
|
||||
robbed: 'NPCに残骸を回収された回数',
|
||||
lostToNPC: 'NPCに奪われた残骸資源総量'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export default {
|
||||
officers: '장교',
|
||||
simulator: '시뮬레이터',
|
||||
galaxy: '은하계',
|
||||
diplomacy: 'Diplomacy',
|
||||
diplomacy: '외교',
|
||||
achievements: '업적',
|
||||
messages: '메시지',
|
||||
settings: '설정',
|
||||
gm: 'GM'
|
||||
@@ -109,6 +110,14 @@ export default {
|
||||
hour: '시간',
|
||||
noEnergy: '에너지 부족'
|
||||
},
|
||||
energy: {
|
||||
lowWarning: '에너지 부족! 자원 생산 중단!',
|
||||
severeWarning: '에너지 부족! 자원 생산 중단!',
|
||||
criticalWarning: '에너지 부족! 자원 생산 중단!',
|
||||
noProduction: '에너지 부족! 자원 생산 중단!',
|
||||
deficitDetail: '에너지 부족: {deficit}, 발전소를 더 건설하세요',
|
||||
buildSolarPlant: '발전소 건설'
|
||||
},
|
||||
planet: {
|
||||
planet: '행성',
|
||||
moon: '위성',
|
||||
@@ -420,6 +429,7 @@ export default {
|
||||
shipyardView: {
|
||||
title: '조선소',
|
||||
fleetStorage: '함대 저장소',
|
||||
owned: '보유',
|
||||
attack: '공격력',
|
||||
missileAttack: '미사일 공격',
|
||||
shield: '실드',
|
||||
@@ -484,8 +494,10 @@ export default {
|
||||
available: '사용 가능',
|
||||
all: '전체',
|
||||
targetCoordinates: '목표 좌표',
|
||||
targetType: '목표 유형',
|
||||
planet: '행성',
|
||||
moon: '달',
|
||||
galaxy: '은하계',
|
||||
diplomacy: 'Diplomacy',
|
||||
system: '행성계',
|
||||
position: '위치',
|
||||
missionType: '임무 유형',
|
||||
@@ -529,7 +541,30 @@ export default {
|
||||
noDeathstar: '파괴 임무를 위해 데스스타가 필요합니다',
|
||||
giftMode: '선물 모드',
|
||||
giftModeDescription: '자원을 선물로 보내기',
|
||||
estimatedReputationGain: '예상 평판 획득'
|
||||
estimatedReputationGain: '예상 평판 획득',
|
||||
// 함대 프리셋
|
||||
fleetPresets: '함대 프리셋',
|
||||
fleetPresetsDescription: '자주 사용하는 함대 구성을 저장하여 빠르게 파견 (최대 3개)',
|
||||
savePreset: '프리셋 저장',
|
||||
noPresets: '프리셋 없음, 함대를 선택하고 "프리셋 저장"을 클릭',
|
||||
shipTypes: '종류의 함선',
|
||||
editPreset: '프리셋 내용 편집',
|
||||
renamePreset: '이름 변경',
|
||||
deletePreset: '프리셋 삭제',
|
||||
editingPresetHint: '프리셋 편집 중, 함대 구성을 변경하고 "저장"을 클릭',
|
||||
presetLimitReached: '프리셋 한도 도달',
|
||||
presetLimitReachedMessage: '최대 {max}개의 프리셋만 허용',
|
||||
presetError: '저장 실패',
|
||||
presetNoShips: '최소 하나의 함선을 선택하세요',
|
||||
presetDefaultName: '프리셋 {number}',
|
||||
savePresetTitle: '함대 프리셋 저장',
|
||||
savePresetDescription: '이 함대 구성에 이름 지정',
|
||||
renamePresetTitle: '프리셋 이름 변경',
|
||||
renamePresetDescription: '새 프리셋 이름 입력',
|
||||
presetName: '프리셋 이름',
|
||||
presetNamePlaceholder: '프리셋 이름 입력',
|
||||
deletePresetTitle: '프리셋 삭제',
|
||||
deletePresetMessage: '프리셋 "{name}"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.'
|
||||
},
|
||||
officersView: {
|
||||
title: '장교',
|
||||
@@ -569,7 +604,6 @@ export default {
|
||||
title: '은하계',
|
||||
selectCoordinates: '좌표 선택',
|
||||
galaxy: '은하계',
|
||||
diplomacy: 'Diplomacy',
|
||||
selectGalaxy: '은하계 선택',
|
||||
system: '행성계',
|
||||
selectSystem: '행성계 선택',
|
||||
@@ -728,14 +762,29 @@ export default {
|
||||
missionReports: {
|
||||
transportSuccess: '수송 임무가 성공적으로 완료되었습니다',
|
||||
transportFailed: '수송 임무 실패',
|
||||
transportFailedTargetNotFound: '수송 실패: 목표 행성이 존재하지 않습니다',
|
||||
transportFailedGiftRejected: '수송 실패: 선물이 거절되었습니다',
|
||||
colonizeSuccess: '식민 임무 성공, 새로운 행성이 건설되었습니다',
|
||||
colonizeFailed: '식민 임무 실패',
|
||||
colonizeFailedOccupied: '식민 실패: 목표 위치가 이미 다른 행성에 의해 점유되어 있습니다',
|
||||
colonizeFailedMaxColonies: '식민 실패: 최대 식민지 수에 도달했습니다. 천체물리학을 연구하여 한도를 늘리세요.',
|
||||
spySuccess: '정찰 임무가 성공적으로 완료되었습니다',
|
||||
spyFailed: '정찰 임무 실패',
|
||||
spyFailedTargetNotFound: '정찰 실패: 목표 행성이 존재하지 않습니다',
|
||||
deploySuccess: '배치 임무가 성공적으로 완료되었습니다',
|
||||
deployFailed: '배치 임무 실패',
|
||||
deployFailedTargetNotFound: '배치 실패: 목표 행성이 존재하지 않습니다',
|
||||
deployFailedNotOwnPlanet: '배치 실패: 목표 행성이 당신의 것이 아닙니다',
|
||||
recycleSuccess: '회수 임무가 성공적으로 완료되었습니다',
|
||||
recycleFailed: '회수 임무 실패, 목표 위치에 잔해가 없습니다',
|
||||
recycleFailedNoDebris: '회수 실패: 목표 위치에 잔해장이 없습니다',
|
||||
recycleFailedDebrisEmpty: '회수 실패: 잔해장이 이미 비어 있습니다',
|
||||
destroySuccess: '행성 파괴 임무가 성공적으로 실행되었습니다',
|
||||
destroyFailed: '행성 파괴 임무 실패',
|
||||
destroyFailedTargetNotFound: '파괴 실패: 목표 행성이 존재하지 않습니다',
|
||||
destroyFailedOwnPlanet: '파괴 실패: 자신의 행성을 파괴할 수 없습니다',
|
||||
destroyFailedNoDeathstar: '파괴 실패: 임무를 수행할 데스스타가 없습니다',
|
||||
destroyFailedChance: '파괴 실패: 확률 판정 실패 (성공률: {chance}%)',
|
||||
missileAttackSuccess: '미사일 공격 성공',
|
||||
missileAttackFailed: '미사일 공격 실패, 목표 행성이 존재하지 않습니다',
|
||||
missileAttackIntercepted: '모든 미사일이 요격되었습니다',
|
||||
@@ -948,11 +997,13 @@ export default {
|
||||
missionType: {
|
||||
spy: '정찰',
|
||||
attack: '공격',
|
||||
recycle: '회수',
|
||||
unknown: '알 수 없음'
|
||||
},
|
||||
warning: {
|
||||
spy: '적 정찰 접근 중!',
|
||||
attack: '적 공격 접근 중!',
|
||||
recycle: '적이 근처에서 잔해를 회수 중!',
|
||||
unknown: '적 함대 접근 중!'
|
||||
}
|
||||
},
|
||||
@@ -1240,6 +1291,10 @@ export default {
|
||||
title: '전투 시뮬레이터',
|
||||
message: '공격 전에 전투 결과를 시뮬레이션하세요. 양측 함대와 기술 레벨을 입력하여 승패와 손실을 예측.'
|
||||
},
|
||||
achievements: {
|
||||
title: '업적 시스템',
|
||||
message: '게임 목표를 완료하여 업적을 해제하고 암흑 물질 보상을 획득하세요! 업적은 여러 등급이 있으며, 더 높은 도전으로 더 좋은 보상을 받으세요.'
|
||||
},
|
||||
settings: {
|
||||
title: '설정',
|
||||
message: '여기서 게임 데이터 관리, 알림 설정, 저장 내보내기/가져오기가 가능합니다. 정기적으로 백업하세요!'
|
||||
@@ -1248,5 +1303,102 @@ export default {
|
||||
title: 'GM 관리 패널',
|
||||
message: 'GM 모드에서는 자원, 건물, 기술 레벨을 빠르게 수정할 수 있습니다. 테스트나 전체 게임 콘텐츠 체험에 사용.'
|
||||
}
|
||||
},
|
||||
achievements: {
|
||||
title: '업적',
|
||||
unlocked: '업적 해제',
|
||||
progress: '진행도',
|
||||
nextTier: '다음 등급',
|
||||
maxTierReached: '최고 등급 달성',
|
||||
tiers: {
|
||||
bronze: '브론즈',
|
||||
silver: '실버',
|
||||
gold: '골드',
|
||||
platinum: '플래티넘',
|
||||
diamond: '다이아몬드'
|
||||
},
|
||||
categories: {
|
||||
resource: '자원',
|
||||
building: '건설',
|
||||
combat: '전투',
|
||||
mission: '임무',
|
||||
diplomacy: '외교'
|
||||
},
|
||||
names: {
|
||||
metalCollector: '금속 수집가',
|
||||
crystalCollector: '크리스탈 수집가',
|
||||
deuteriumCollector: '중수소 수집가',
|
||||
darkMatterCollector: '암흑 물질 수집가',
|
||||
resourceConsumer: '자원 소비자',
|
||||
masterBuilder: '마스터 빌더',
|
||||
researcher: '연구원',
|
||||
shipwright: '조선공',
|
||||
fortifier: '요새화자',
|
||||
warmonger: '전쟁광',
|
||||
conqueror: '정복자',
|
||||
defender: '방어자',
|
||||
fleetDestroyer: '함대 파괴자',
|
||||
debrisCreator: '잔해 생성자',
|
||||
fleetSacrifice: '함대 희생자',
|
||||
defenseSacrifice: '방어 희생자',
|
||||
pilot: '파일럿',
|
||||
transporter: '운송가',
|
||||
cargoMaster: '화물 마스터',
|
||||
colonizer: '식민자',
|
||||
spy: '스파이 마스터',
|
||||
deployer: '배치자',
|
||||
explorer: '탐험가',
|
||||
luckyExplorer: '행운의 탐험가',
|
||||
recycler: '재활용자',
|
||||
scavenger: '스캐빈저',
|
||||
destroyer: '파괴자',
|
||||
fuelBurner: '연료 소비자',
|
||||
diplomat: '외교관',
|
||||
nemesis: '숙적',
|
||||
generous: '관대한 자',
|
||||
philanthropist: '자선가',
|
||||
target: '표적',
|
||||
watched: '감시 중',
|
||||
robbed: '약탈당함',
|
||||
lostToNPC: 'NPC에게 손실'
|
||||
},
|
||||
descriptions: {
|
||||
metalCollector: '총 금속 생산량',
|
||||
crystalCollector: '총 크리스탈 생산량',
|
||||
deuteriumCollector: '총 중수소 생산량',
|
||||
darkMatterCollector: '총 암흑 물질 생산량',
|
||||
resourceConsumer: '총 자원 소비량',
|
||||
masterBuilder: '건물 업그레이드 총 횟수',
|
||||
researcher: '연구 완료 총 횟수',
|
||||
shipwright: '함선 생산 총 수량',
|
||||
fortifier: '방어 시설 건설 총 수량',
|
||||
warmonger: '공격 발동 횟수',
|
||||
conqueror: '공격 승리 횟수',
|
||||
defender: '방어 성공 횟수',
|
||||
fleetDestroyer: '방어에서 파괴한 적 함대 수',
|
||||
debrisCreator: '전투에서 생성된 잔해 총량',
|
||||
fleetSacrifice: '손실된 함대 총 수',
|
||||
defenseSacrifice: '방어에서 손실된 방어 시설 수',
|
||||
pilot: '비행 임무 총 횟수',
|
||||
transporter: '운송 임무 총 횟수',
|
||||
cargoMaster: '운송 자원 총량',
|
||||
colonizer: '식민화한 행성 수',
|
||||
spy: '스파이 임무 완료 횟수',
|
||||
deployer: '배치 임무 완료 횟수',
|
||||
explorer: '탐험 총 횟수',
|
||||
luckyExplorer: '탐험 성공 횟수',
|
||||
recycler: '재활용 임무 총 횟수',
|
||||
scavenger: '재활용 자원 총량',
|
||||
destroyer: '파괴한 행성 수',
|
||||
fuelBurner: '연료 소비 총량',
|
||||
diplomat: '우호적인 NPC 수',
|
||||
nemesis: '적대적인 NPC 수',
|
||||
generous: '선물 보낸 횟수',
|
||||
philanthropist: '선물한 자원 총량',
|
||||
target: 'NPC에게 공격당한 횟수',
|
||||
watched: 'NPC에게 정찰당한 횟수',
|
||||
robbed: 'NPC에게 잔해 회수당한 횟수',
|
||||
lostToNPC: 'NPC에게 빼앗긴 잔해 자원 총량'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export default {
|
||||
officers: 'Офицеры',
|
||||
simulator: 'Симулятор',
|
||||
galaxy: 'Галактика',
|
||||
diplomacy: 'Diplomacy',
|
||||
diplomacy: 'Дипломатия',
|
||||
achievements: 'Достижения',
|
||||
messages: 'Сообщения',
|
||||
settings: 'Настройки',
|
||||
gm: 'GM'
|
||||
@@ -109,6 +110,14 @@ export default {
|
||||
hour: 'час',
|
||||
noEnergy: 'Нет энергии'
|
||||
},
|
||||
energy: {
|
||||
lowWarning: 'Дефицит энергии! Производство ресурсов остановлено!',
|
||||
severeWarning: 'Дефицит энергии! Производство ресурсов остановлено!',
|
||||
criticalWarning: 'Дефицит энергии! Производство ресурсов остановлено!',
|
||||
noProduction: 'Дефицит энергии! Производство ресурсов остановлено!',
|
||||
deficitDetail: 'Дефицит энергии: {deficit}, постройте больше электростанций',
|
||||
buildSolarPlant: 'Построить электростанцию'
|
||||
},
|
||||
planet: {
|
||||
planet: 'Планета',
|
||||
moon: 'Луна',
|
||||
@@ -422,6 +431,7 @@ export default {
|
||||
shipyardView: {
|
||||
title: 'Верфь',
|
||||
fleetStorage: 'Хранилище флота',
|
||||
owned: 'В наличии',
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
shield: 'Щит',
|
||||
@@ -487,8 +497,10 @@ export default {
|
||||
available: 'Доступно',
|
||||
all: 'Все',
|
||||
targetCoordinates: 'Целевые координаты',
|
||||
targetType: 'Тип цели',
|
||||
planet: 'Планета',
|
||||
moon: 'Луна',
|
||||
galaxy: 'Галактика',
|
||||
diplomacy: 'Diplomacy',
|
||||
system: 'Система',
|
||||
position: 'Позиция',
|
||||
missionType: 'Тип миссии',
|
||||
@@ -532,7 +544,30 @@ export default {
|
||||
noDeathstar: 'Для миссии разрушения требуется Звезда Смерти',
|
||||
giftMode: 'Режим подарка',
|
||||
giftModeDescription: 'Отправить ресурсы в подарок',
|
||||
estimatedReputationGain: 'Ожидаемый прирост репутации'
|
||||
estimatedReputationGain: 'Ожидаемый прирост репутации',
|
||||
// Шаблоны флота
|
||||
fleetPresets: 'Шаблоны флота',
|
||||
fleetPresetsDescription: 'Сохраняйте часто используемые конфигурации флота (макс. 3)',
|
||||
savePreset: 'Сохранить шаблон',
|
||||
noPresets: 'Нет шаблонов, выберите флот и нажмите "Сохранить шаблон"',
|
||||
shipTypes: 'типов кораблей',
|
||||
editPreset: 'Редактировать содержимое шаблона',
|
||||
renamePreset: 'Переименовать',
|
||||
deletePreset: 'Удалить шаблон',
|
||||
editingPresetHint: 'Редактирование шаблона, измените конфигурацию и нажмите "Сохранить"',
|
||||
presetLimitReached: 'Достигнут лимит шаблонов',
|
||||
presetLimitReachedMessage: 'Максимум {max} шаблонов',
|
||||
presetError: 'Ошибка сохранения',
|
||||
presetNoShips: 'Выберите хотя бы один корабль',
|
||||
presetDefaultName: 'Шаблон {number}',
|
||||
savePresetTitle: 'Сохранить шаблон флота',
|
||||
savePresetDescription: 'Назовите эту конфигурацию флота',
|
||||
renamePresetTitle: 'Переименовать шаблон',
|
||||
renamePresetDescription: 'Введите новое имя шаблона',
|
||||
presetName: 'Имя шаблона',
|
||||
presetNamePlaceholder: 'Введите имя шаблона',
|
||||
deletePresetTitle: 'Удалить шаблон',
|
||||
deletePresetMessage: 'Вы уверены, что хотите удалить шаблон "{name}"? Это действие нельзя отменить.'
|
||||
},
|
||||
officersView: {
|
||||
title: 'Офицеры',
|
||||
@@ -572,7 +607,6 @@ export default {
|
||||
title: 'Галактика',
|
||||
selectCoordinates: 'Выбрать координаты',
|
||||
galaxy: 'Галактика',
|
||||
diplomacy: 'Diplomacy',
|
||||
selectGalaxy: 'Выбрать галактику',
|
||||
system: 'Система',
|
||||
selectSystem: 'Выбрать систему',
|
||||
@@ -734,14 +768,29 @@ export default {
|
||||
missionReports: {
|
||||
transportSuccess: 'Миссия транспортировки успешно завершена',
|
||||
transportFailed: 'Миссия транспортировки провалена',
|
||||
transportFailedTargetNotFound: 'Транспортировка провалена: Целевая планета не существует',
|
||||
transportFailedGiftRejected: 'Транспортировка провалена: Подарок был отклонён',
|
||||
colonizeSuccess: 'Миссия колонизации успешна, новая планета создана',
|
||||
colonizeFailed: 'Миссия колонизации провалена',
|
||||
colonizeFailedOccupied: 'Колонизация провалена: Целевая позиция уже занята другой планетой',
|
||||
colonizeFailedMaxColonies: 'Колонизация провалена: Достигнуто максимальное количество колоний. Исследуйте Астрофизику для увеличения лимита.',
|
||||
spySuccess: 'Миссия шпионажа успешно завершена',
|
||||
spyFailed: 'Миссия шпионажа провалена',
|
||||
spyFailedTargetNotFound: 'Шпионаж провален: Целевая планета не существует',
|
||||
deploySuccess: 'Миссия размещения успешно завершена',
|
||||
deployFailed: 'Миссия размещения провалена',
|
||||
deployFailedTargetNotFound: 'Размещение провалено: Целевая планета не существует',
|
||||
deployFailedNotOwnPlanet: 'Размещение провалено: Целевая планета не принадлежит вам',
|
||||
recycleSuccess: 'Миссия переработки успешно завершена',
|
||||
recycleFailed: 'Миссия переработки провалена, нет обломков в целевой позиции',
|
||||
recycleFailedNoDebris: 'Переработка провалена: Нет поля обломков в целевой позиции',
|
||||
recycleFailedDebrisEmpty: 'Переработка провалена: Поле обломков уже пусто',
|
||||
destroySuccess: 'Миссия уничтожения планеты успешно выполнена',
|
||||
destroyFailed: 'Миссия уничтожения планеты провалена',
|
||||
destroyFailedTargetNotFound: 'Уничтожение провалено: Целевая планета не существует',
|
||||
destroyFailedOwnPlanet: 'Уничтожение провалено: Нельзя уничтожить собственную планету',
|
||||
destroyFailedNoDeathstar: 'Уничтожение провалено: Нет Звезды Смерти для выполнения миссии',
|
||||
destroyFailedChance: 'Уничтожение провалено: Проверка вероятности не удалась (Шанс успеха: {chance}%)',
|
||||
expeditionResources: 'Экспедиция обнаружила ресурсы!',
|
||||
expeditionDarkMatter: 'Экспедиция обнаружила тёмную материю!',
|
||||
expeditionFleet: 'Экспедиция обнаружила заброшенные корабли!',
|
||||
@@ -1256,6 +1305,10 @@ export default {
|
||||
title: 'Симулятор боя',
|
||||
message: 'Симулируйте результаты боя перед атакой. Введите флоты и уровни технологий для прогноза победы, потерь и добычи.'
|
||||
},
|
||||
achievements: {
|
||||
title: 'Система достижений',
|
||||
message: 'Выполняйте игровые цели для разблокировки достижений и получения наград в виде тёмной материи! Достижения имеют несколько уровней - стремитесь к более высоким целям для лучших наград.'
|
||||
},
|
||||
settings: {
|
||||
title: 'Настройки',
|
||||
message: 'Управляйте игровыми данными, уведомлениями, импортом/экспортом сохранений. Регулярно создавайте резервные копии!'
|
||||
@@ -1265,5 +1318,102 @@ export default {
|
||||
message:
|
||||
'Режим ГМ позволяет быстро изменять ресурсы, здания и уровни технологий. Используйте для тестирования или полного доступа к контенту.'
|
||||
}
|
||||
},
|
||||
achievements: {
|
||||
title: 'Достижения',
|
||||
unlocked: 'Достижение разблокировано',
|
||||
progress: 'Прогресс',
|
||||
nextTier: 'Следующий уровень',
|
||||
maxTierReached: 'Достигнут максимальный уровень',
|
||||
tiers: {
|
||||
bronze: 'Бронза',
|
||||
silver: 'Серебро',
|
||||
gold: 'Золото',
|
||||
platinum: 'Платина',
|
||||
diamond: 'Алмаз'
|
||||
},
|
||||
categories: {
|
||||
resource: 'Ресурсы',
|
||||
building: 'Строительство',
|
||||
combat: 'Бой',
|
||||
mission: 'Миссии',
|
||||
diplomacy: 'Дипломатия'
|
||||
},
|
||||
names: {
|
||||
metalCollector: 'Коллекционер металла',
|
||||
crystalCollector: 'Коллекционер кристаллов',
|
||||
deuteriumCollector: 'Коллекционер дейтерия',
|
||||
darkMatterCollector: 'Коллекционер тёмной материи',
|
||||
resourceConsumer: 'Потребитель ресурсов',
|
||||
masterBuilder: 'Мастер-строитель',
|
||||
researcher: 'Исследователь',
|
||||
shipwright: 'Кораблестроитель',
|
||||
fortifier: 'Укрепитель',
|
||||
warmonger: 'Поджигатель войны',
|
||||
conqueror: 'Завоеватель',
|
||||
defender: 'Защитник',
|
||||
fleetDestroyer: 'Уничтожитель флота',
|
||||
debrisCreator: 'Создатель обломков',
|
||||
fleetSacrifice: 'Жертва флота',
|
||||
defenseSacrifice: 'Жертва обороны',
|
||||
pilot: 'Пилот',
|
||||
transporter: 'Транспортёр',
|
||||
cargoMaster: 'Мастер груза',
|
||||
colonizer: 'Колонизатор',
|
||||
spy: 'Мастер шпионажа',
|
||||
deployer: 'Размещатель',
|
||||
explorer: 'Исследователь',
|
||||
luckyExplorer: 'Счастливый исследователь',
|
||||
recycler: 'Переработчик',
|
||||
scavenger: 'Мусорщик',
|
||||
destroyer: 'Уничтожитель',
|
||||
fuelBurner: 'Сжигатель топлива',
|
||||
diplomat: 'Дипломат',
|
||||
nemesis: 'Заклятый враг',
|
||||
generous: 'Щедрый',
|
||||
philanthropist: 'Филантроп',
|
||||
target: 'Мишень',
|
||||
watched: 'Под наблюдением',
|
||||
robbed: 'Ограблен',
|
||||
lostToNPC: 'Потеряно НПС'
|
||||
},
|
||||
descriptions: {
|
||||
metalCollector: 'Всего добыто металла',
|
||||
crystalCollector: 'Всего добыто кристаллов',
|
||||
deuteriumCollector: 'Всего добыто дейтерия',
|
||||
darkMatterCollector: 'Всего добыто тёмной материи',
|
||||
resourceConsumer: 'Всего потреблено ресурсов',
|
||||
masterBuilder: 'Всего улучшений зданий',
|
||||
researcher: 'Всего завершённых исследований',
|
||||
shipwright: 'Всего произведённых кораблей',
|
||||
fortifier: 'Всего построенной обороны',
|
||||
warmonger: 'Всего начатых атак',
|
||||
conqueror: 'Всего выигранных атак',
|
||||
defender: 'Всего успешных защит',
|
||||
fleetDestroyer: 'Вражеский флот уничтожен в обороне',
|
||||
debrisCreator: 'Всего обломков созданных в боях',
|
||||
fleetSacrifice: 'Всего потерянного флота',
|
||||
defenseSacrifice: 'Всего потерянной обороны',
|
||||
pilot: 'Всего лётных миссий',
|
||||
transporter: 'Всего транспортных миссий',
|
||||
cargoMaster: 'Всего перевезённых ресурсов',
|
||||
colonizer: 'Колонизировано планет',
|
||||
spy: 'Завершённых шпионских миссий',
|
||||
deployer: 'Завершённых миссий размещения',
|
||||
explorer: 'Всего экспедиций',
|
||||
luckyExplorer: 'Успешных экспедиций',
|
||||
recycler: 'Всего миссий переработки',
|
||||
scavenger: 'Всего переработанных ресурсов',
|
||||
destroyer: 'Уничтожено планет',
|
||||
fuelBurner: 'Всего израсходовано топлива',
|
||||
diplomat: 'Количество дружественных НПС',
|
||||
nemesis: 'Количество враждебных НПС',
|
||||
generous: 'Всего отправленных подарков',
|
||||
philanthropist: 'Всего подаренных ресурсов',
|
||||
target: 'Раз атакован НПС',
|
||||
watched: 'Раз шпионил НПС',
|
||||
robbed: 'Раз НПС собрал обломки',
|
||||
lostToNPC: 'Всего обломков потеряно НПС'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ export default {
|
||||
simulator: '模拟',
|
||||
galaxy: '星系',
|
||||
diplomacy: '外交',
|
||||
achievements: '成就',
|
||||
messages: '消息',
|
||||
settings: '设置',
|
||||
gm: 'GM'
|
||||
@@ -108,6 +109,14 @@ export default {
|
||||
hour: '小时',
|
||||
noEnergy: '电力不足'
|
||||
},
|
||||
energy: {
|
||||
lowWarning: '电力不足,资源生产已停止!',
|
||||
severeWarning: '电力不足,资源生产已停止!',
|
||||
criticalWarning: '电力不足,资源生产已停止!',
|
||||
noProduction: '电力不足,资源生产已停止!',
|
||||
deficitDetail: '电力缺口: {deficit},请建造更多电站',
|
||||
buildSolarPlant: '建造电站'
|
||||
},
|
||||
planet: {
|
||||
planet: '星球',
|
||||
moon: '月球',
|
||||
@@ -416,6 +425,7 @@ export default {
|
||||
shipyardView: {
|
||||
title: '船坞',
|
||||
fleetStorage: '舰队仓储',
|
||||
owned: '拥有',
|
||||
attack: '攻击力',
|
||||
shield: '护盾',
|
||||
speed: '速度',
|
||||
@@ -475,6 +485,9 @@ export default {
|
||||
available: '可用',
|
||||
all: '全部',
|
||||
targetCoordinates: '目标坐标',
|
||||
targetType: '目标类型',
|
||||
planet: '行星',
|
||||
moon: '月球',
|
||||
galaxy: '银河系',
|
||||
system: '星系',
|
||||
position: '位置',
|
||||
@@ -520,7 +533,47 @@ export default {
|
||||
noDeathstar: '需要死星才能执行毁灭任务',
|
||||
giftMode: '赠送模式',
|
||||
giftModeDescription: '将资源作为礼物赠送给',
|
||||
estimatedReputationGain: '预计好感度增加'
|
||||
estimatedReputationGain: '预计好感度增加',
|
||||
// 舰队预设
|
||||
fleetPresets: '舰队预设',
|
||||
fleetPresetsDescription: '保存常用的舰队配置,快速派遣(最多3个)',
|
||||
savePreset: '保存预设',
|
||||
noPresets: '暂无预设,选择舰队后点击"保存预设"创建',
|
||||
shipTypes: '种舰船',
|
||||
editPreset: '编辑预设内容',
|
||||
renamePreset: '重命名',
|
||||
deletePreset: '删除预设',
|
||||
editingPresetHint: '正在编辑预设,修改舰队配置后点击"保存"更新预设内容',
|
||||
presetLimitReached: '预设数量已满',
|
||||
presetLimitReachedMessage: '最多只能保存 {max} 个预设',
|
||||
presetError: '保存失败',
|
||||
presetNoShips: '请先选择至少一艘舰船',
|
||||
presetDefaultName: '预设 {number}',
|
||||
savePresetTitle: '保存舰队预设',
|
||||
savePresetDescription: '为这个舰队配置命名',
|
||||
renamePresetTitle: '重命名预设',
|
||||
renamePresetDescription: '输入新的预设名称',
|
||||
presetName: '预设名称',
|
||||
presetNamePlaceholder: '输入预设名称',
|
||||
deletePresetTitle: '删除预设',
|
||||
deletePresetMessage: '确定要删除预设"{name}"吗?此操作不可撤销。',
|
||||
// 跳跃门
|
||||
jumpGate: '跳跃门',
|
||||
jumpGateDescription: '使用跳跃门瞬间传送舰队到其他有跳跃门的月球',
|
||||
jumpGateNotAvailable: '跳跃门不可用',
|
||||
jumpGateRequiresMoon: '跳跃门只能在月球上使用',
|
||||
jumpGateNotBuilt: '当前月球没有建造跳跃门',
|
||||
jumpGateCooldown: '跳跃门冷却中',
|
||||
jumpGateCooldownRemaining: '剩余冷却时间',
|
||||
jumpGateReady: '跳跃门就绪',
|
||||
jumpGateSelectTarget: '选择目标月球',
|
||||
jumpGateNoTargetMoons: '没有可用的目标月球(需要有跳跃门且冷却完成)',
|
||||
jumpGateSelectFleet: '选择传送舰队',
|
||||
jumpGateTransfer: '传送舰队',
|
||||
jumpGateSuccess: '跳跃门传送成功',
|
||||
jumpGateSuccessMessage: '舰队已瞬间传送到 {target}',
|
||||
jumpGateFailed: '跳跃门传送失败',
|
||||
jumpGateFailedMessage: '请检查跳跃门状态和舰队配置'
|
||||
},
|
||||
officersView: {
|
||||
title: '军官',
|
||||
@@ -602,7 +655,26 @@ export default {
|
||||
colonizePlanetMessage: '确定要殖民位置 [{coordinates}] 吗?\n\n请前往舰队页面派遣殖民船。',
|
||||
recyclePlanetMessage: '确定要回收位置 [{coordinates}] 的残骸吗?\n\n请前往舰队页面派遣回收船。',
|
||||
giftPlanetMessage: '确定要向星球 [{coordinates}] 赠送资源吗?\n\n请前往舰队页面选择运输船并装载资源。',
|
||||
npcPlanetName: '{name}的星球'
|
||||
npcPlanetName: '{name}的星球',
|
||||
// 传感器阵列扫描
|
||||
phalanxScan: '传感器扫描',
|
||||
phalanxScanTitle: '传感器阵列扫描',
|
||||
phalanxScanDescription: '扫描星球 [{coordinates}] 的舰队活动',
|
||||
phalanxNoMoon: '需要有传感器阵列的月球才能扫描',
|
||||
phalanxOutOfRange: '目标超出扫描范围',
|
||||
phalanxRange: '扫描范围',
|
||||
phalanxCost: '扫描消耗',
|
||||
phalanxNoFleets: '未检测到舰队活动',
|
||||
phalanxFleetDetected: '检测到 {count} 支舰队',
|
||||
phalanxMission: '任务',
|
||||
phalanxOrigin: '出发地',
|
||||
phalanxDestination: '目的地',
|
||||
phalanxArrival: '到达时间',
|
||||
phalanxReturn: '返回时间',
|
||||
phalanxStatus: '状态',
|
||||
phalanxStatusOutbound: '前往中',
|
||||
phalanxStatusReturning: '返回中',
|
||||
phalanxInsufficientDeuterium: '氘不足'
|
||||
},
|
||||
messagesView: {
|
||||
title: '消息中心',
|
||||
@@ -718,14 +790,29 @@ export default {
|
||||
missionReports: {
|
||||
transportSuccess: '运输任务成功完成',
|
||||
transportFailed: '运输任务失败',
|
||||
transportFailedTargetNotFound: '运输失败:目标星球不存在',
|
||||
transportFailedGiftRejected: '运输失败:礼物被拒绝',
|
||||
colonizeSuccess: '殖民任务成功,新星球已建立',
|
||||
colonizeFailed: '殖民任务失败',
|
||||
colonizeFailedOccupied: '殖民失败:目标位置已被其他星球占用',
|
||||
colonizeFailedMaxColonies: '殖民失败:已达到殖民地数量上限。研究天体物理学可增加上限。',
|
||||
spySuccess: '侦查任务成功完成',
|
||||
spyFailed: '侦查任务失败',
|
||||
spyFailedTargetNotFound: '侦查失败:目标星球不存在',
|
||||
deploySuccess: '部署任务成功完成',
|
||||
deployFailed: '部署任务失败',
|
||||
deployFailedTargetNotFound: '部署失败:目标星球不存在',
|
||||
deployFailedNotOwnPlanet: '部署失败:目标星球不属于你',
|
||||
recycleSuccess: '回收任务成功完成',
|
||||
recycleFailed: '回收任务失败,目标位置没有残骸',
|
||||
recycleFailedNoDebris: '回收失败:目标位置没有残骸场',
|
||||
recycleFailedDebrisEmpty: '回收失败:残骸场已被清空',
|
||||
destroySuccess: '行星毁灭任务成功执行',
|
||||
destroyFailed: '行星毁灭任务失败',
|
||||
destroyFailedTargetNotFound: '毁灭失败:目标星球不存在',
|
||||
destroyFailedOwnPlanet: '毁灭失败:无法摧毁自己的星球',
|
||||
destroyFailedNoDeathstar: '毁灭失败:没有死星执行任务',
|
||||
destroyFailedChance: '毁灭失败:概率判定失败(成功率:{chance}%)',
|
||||
missileAttackSuccess: '导弹攻击成功',
|
||||
missileAttackFailed: '导弹攻击失败,目标星球不存在',
|
||||
missileAttackIntercepted: '所有导弹被拦截',
|
||||
@@ -943,11 +1030,13 @@ export default {
|
||||
missionType: {
|
||||
spy: '侦查',
|
||||
attack: '攻击',
|
||||
recycle: '回收',
|
||||
unknown: '未知'
|
||||
},
|
||||
warning: {
|
||||
spy: '敌方侦查即将到达!',
|
||||
attack: '敌方攻击即将到达!',
|
||||
recycle: '敌方正在回收你附近的残骸!',
|
||||
unknown: '敌方舰队即将到达!'
|
||||
}
|
||||
},
|
||||
@@ -1336,6 +1425,10 @@ export default {
|
||||
title: '战斗模拟器',
|
||||
message: '在发动攻击前模拟战斗结果。输入双方舰队和科技等级,预测胜负和损失。'
|
||||
},
|
||||
achievements: {
|
||||
title: '成就系统',
|
||||
message: '完成各类游戏目标解锁成就,获得暗物质奖励!成就分为多个等级,挑战更高难度获得更丰厚的奖励。'
|
||||
},
|
||||
settings: {
|
||||
title: '设置',
|
||||
message: '在这里管理游戏数据、调整通知设置、导出/导入存档。记得定期备份你的进度!'
|
||||
@@ -1344,5 +1437,102 @@ export default {
|
||||
title: 'GM管理面板',
|
||||
message: 'GM模式可以快速修改资源、建筑、科技等级。用于测试或体验完整游戏内容。'
|
||||
}
|
||||
},
|
||||
achievements: {
|
||||
title: '成就',
|
||||
unlocked: '成就解锁',
|
||||
progress: '进度',
|
||||
nextTier: '下一等级',
|
||||
maxTierReached: '已达最高等级',
|
||||
tiers: {
|
||||
bronze: '青铜',
|
||||
silver: '白银',
|
||||
gold: '黄金',
|
||||
platinum: '铂金',
|
||||
diamond: '钻石'
|
||||
},
|
||||
categories: {
|
||||
resource: '资源',
|
||||
building: '建造',
|
||||
combat: '战斗',
|
||||
mission: '任务',
|
||||
diplomacy: '外交'
|
||||
},
|
||||
names: {
|
||||
metalCollector: '金属收藏家',
|
||||
crystalCollector: '晶体收藏家',
|
||||
deuteriumCollector: '重氢收藏家',
|
||||
darkMatterCollector: '暗物质收藏家',
|
||||
resourceConsumer: '资源消耗者',
|
||||
masterBuilder: '建造大师',
|
||||
researcher: '科学家',
|
||||
shipwright: '造船匠',
|
||||
fortifier: '防御专家',
|
||||
warmonger: '好战者',
|
||||
conqueror: '征服者',
|
||||
defender: '防御者',
|
||||
fleetDestroyer: '舰队毁灭者',
|
||||
debrisCreator: '残骸制造者',
|
||||
fleetSacrifice: '舰队牺牲者',
|
||||
defenseSacrifice: '防御牺牲者',
|
||||
pilot: '飞行员',
|
||||
transporter: '运输专家',
|
||||
cargoMaster: '货运大师',
|
||||
colonizer: '殖民者',
|
||||
spy: '间谍大师',
|
||||
deployer: '部署专家',
|
||||
explorer: '探险家',
|
||||
luckyExplorer: '幸运探险家',
|
||||
recycler: '回收专家',
|
||||
scavenger: '拾荒者',
|
||||
destroyer: '毁灭者',
|
||||
fuelBurner: '燃料消耗者',
|
||||
diplomat: '外交官',
|
||||
nemesis: '宿敌',
|
||||
generous: '慷慨者',
|
||||
philanthropist: '慈善家',
|
||||
target: '目标',
|
||||
watched: '被监视',
|
||||
robbed: '被抢夺',
|
||||
lostToNPC: '资源损失者'
|
||||
},
|
||||
descriptions: {
|
||||
metalCollector: '累计生产金属',
|
||||
crystalCollector: '累计生产晶体',
|
||||
deuteriumCollector: '累计生产重氢',
|
||||
darkMatterCollector: '累计生产暗物质',
|
||||
resourceConsumer: '累计消耗资源',
|
||||
masterBuilder: '累计升级建筑次数',
|
||||
researcher: '累计完成研究次数',
|
||||
shipwright: '累计生产舰船数量',
|
||||
fortifier: '累计建造防御设施',
|
||||
warmonger: '发起攻击次数',
|
||||
conqueror: '攻击胜利次数',
|
||||
defender: '防御成功次数',
|
||||
fleetDestroyer: '防御中消灭的敌方舰队数量',
|
||||
debrisCreator: '战斗中产生的残骸资源总量',
|
||||
fleetSacrifice: '损失的舰队总数',
|
||||
defenseSacrifice: '防御中损失的防御设施数量',
|
||||
pilot: '完成飞行任务次数',
|
||||
transporter: '运输任务次数',
|
||||
cargoMaster: '运输资源总量',
|
||||
colonizer: '成功殖民星球次数',
|
||||
spy: '执行侦查任务次数',
|
||||
deployer: '执行部署任务次数',
|
||||
explorer: '远征总次数',
|
||||
luckyExplorer: '远征成功次数',
|
||||
recycler: '回收任务次数',
|
||||
scavenger: '回收资源总量',
|
||||
destroyer: '摧毁星球次数',
|
||||
fuelBurner: '消耗燃料总量',
|
||||
diplomat: '友好NPC数量',
|
||||
nemesis: '敌对NPC数量',
|
||||
generous: '送礼次数',
|
||||
philanthropist: '赠送资源总量',
|
||||
target: '被NPC攻击次数',
|
||||
watched: '被NPC侦查次数',
|
||||
robbed: '被NPC回收残骸次数',
|
||||
lostToNPC: '被NPC回收的残骸资源总量'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,11 @@ export default {
|
||||
officers: '軍官',
|
||||
simulator: '模擬',
|
||||
galaxy: '星系',
|
||||
diplomacy: 'Diplomacy',
|
||||
diplomacy: '外交',
|
||||
achievements: '成就',
|
||||
messages: '訊息',
|
||||
settings: '設定',
|
||||
guide: '遊戲指南',
|
||||
gm: 'GM'
|
||||
},
|
||||
sidebar: {
|
||||
@@ -109,6 +111,14 @@ export default {
|
||||
hour: '小時',
|
||||
noEnergy: '電力不足'
|
||||
},
|
||||
energy: {
|
||||
lowWarning: '電力不足,資源生產已停止!',
|
||||
severeWarning: '電力不足,資源生產已停止!',
|
||||
criticalWarning: '電力不足,資源生產已停止!',
|
||||
noProduction: '電力不足,資源生產已停止!',
|
||||
deficitDetail: '電力缺口: {deficit},請建造更多電站',
|
||||
buildSolarPlant: '建造電站'
|
||||
},
|
||||
planet: {
|
||||
planet: '星球',
|
||||
moon: '月球',
|
||||
@@ -422,6 +432,7 @@ export default {
|
||||
shipyardView: {
|
||||
title: '船塢',
|
||||
fleetStorage: '艦隊倉儲',
|
||||
owned: '擁有',
|
||||
attack: '攻擊力',
|
||||
missileAttack: '導彈攻擊',
|
||||
shield: '護盾',
|
||||
@@ -486,8 +497,10 @@ export default {
|
||||
available: '可用',
|
||||
all: '全部',
|
||||
targetCoordinates: '目標座標',
|
||||
targetType: '目標類型',
|
||||
planet: '行星',
|
||||
moon: '月球',
|
||||
galaxy: '銀河系',
|
||||
diplomacy: 'Diplomacy',
|
||||
system: '星系',
|
||||
position: '位置',
|
||||
missionType: '任務類型',
|
||||
@@ -531,7 +544,30 @@ export default {
|
||||
noDeathstar: '需要死星才能執行毀滅任務',
|
||||
giftMode: '贈送模式',
|
||||
giftModeDescription: '將資源作為禮物贈送給',
|
||||
estimatedReputationGain: '預計好感度增加'
|
||||
estimatedReputationGain: '預計好感度增加',
|
||||
// 艦隊預設
|
||||
fleetPresets: '艦隊預設',
|
||||
fleetPresetsDescription: '儲存常用的艦隊配置,快速派遣(最多3個)',
|
||||
savePreset: '儲存預設',
|
||||
noPresets: '暫無預設,選擇艦隊後點擊「儲存預設」建立',
|
||||
shipTypes: '種艦船',
|
||||
editPreset: '編輯預設內容',
|
||||
renamePreset: '重新命名',
|
||||
deletePreset: '刪除預設',
|
||||
editingPresetHint: '正在編輯預設,修改艦隊配置後點擊「儲存」更新預設內容',
|
||||
presetLimitReached: '預設數量已滿',
|
||||
presetLimitReachedMessage: '最多只能儲存 {max} 個預設',
|
||||
presetError: '儲存失敗',
|
||||
presetNoShips: '請先選擇至少一艘艦船',
|
||||
presetDefaultName: '預設 {number}',
|
||||
savePresetTitle: '儲存艦隊預設',
|
||||
savePresetDescription: '為這個艦隊配置命名',
|
||||
renamePresetTitle: '重新命名預設',
|
||||
renamePresetDescription: '輸入新的預設名稱',
|
||||
presetName: '預設名稱',
|
||||
presetNamePlaceholder: '輸入預設名稱',
|
||||
deletePresetTitle: '刪除預設',
|
||||
deletePresetMessage: '確定要刪除預設「{name}」嗎?此操作不可撤銷。'
|
||||
},
|
||||
officersView: {
|
||||
title: '軍官',
|
||||
@@ -571,7 +607,6 @@ export default {
|
||||
title: '星系',
|
||||
selectCoordinates: '選擇座標',
|
||||
galaxy: '銀河系',
|
||||
diplomacy: 'Diplomacy',
|
||||
selectGalaxy: '選擇銀河系',
|
||||
system: '星系',
|
||||
selectSystem: '選擇星系',
|
||||
@@ -728,14 +763,29 @@ export default {
|
||||
missionReports: {
|
||||
transportSuccess: '運輸任務成功完成',
|
||||
transportFailed: '運輸任務失敗',
|
||||
transportFailedTargetNotFound: '運輸失敗:目標星球不存在',
|
||||
transportFailedGiftRejected: '運輸失敗:禮物被拒絕',
|
||||
colonizeSuccess: '殖民任務成功,新星球已建立',
|
||||
colonizeFailed: '殖民任務失敗',
|
||||
colonizeFailedOccupied: '殖民失敗:目標位置已被其他星球佔用',
|
||||
colonizeFailedMaxColonies: '殖民失敗:已達到殖民地數量上限。研究天體物理學可增加上限。',
|
||||
spySuccess: '偵查任務成功完成',
|
||||
spyFailed: '偵查任務失敗',
|
||||
spyFailedTargetNotFound: '偵查失敗:目標星球不存在',
|
||||
deploySuccess: '部署任務成功完成',
|
||||
deployFailed: '部署任務失敗',
|
||||
deployFailedTargetNotFound: '部署失敗:目標星球不存在',
|
||||
deployFailedNotOwnPlanet: '部署失敗:目標星球不屬於你',
|
||||
recycleSuccess: '回收任務成功完成',
|
||||
recycleFailed: '回收任務失敗,目標位置沒有殘骸',
|
||||
recycleFailedNoDebris: '回收失敗:目標位置沒有殘骸場',
|
||||
recycleFailedDebrisEmpty: '回收失敗:殘骸場已被清空',
|
||||
destroySuccess: '行星毀滅任務成功執行',
|
||||
destroyFailed: '行星毀滅任務失敗',
|
||||
destroyFailedTargetNotFound: '毀滅失敗:目標星球不存在',
|
||||
destroyFailedOwnPlanet: '毀滅失敗:無法摧毀自己的星球',
|
||||
destroyFailedNoDeathstar: '毀滅失敗:沒有死星執行任務',
|
||||
destroyFailedChance: '毀滅失敗:概率判定失敗(成功率:{chance}%)',
|
||||
expeditionResources: '探險隊發現了資源!',
|
||||
expeditionDarkMatter: '探險隊發現了暗物質!',
|
||||
expeditionFleet: '探險隊發現了廢棄的艦船!',
|
||||
@@ -948,11 +998,13 @@ export default {
|
||||
missionType: {
|
||||
spy: '偵查',
|
||||
attack: '攻擊',
|
||||
recycle: '回收',
|
||||
unknown: '未知'
|
||||
},
|
||||
warning: {
|
||||
spy: '敵方偵查即將到達!',
|
||||
attack: '敵方攻擊即將到達!',
|
||||
recycle: '敵方正在回收你附近的殘骸!',
|
||||
unknown: '敵方艦隊即將到達!'
|
||||
}
|
||||
},
|
||||
@@ -1235,6 +1287,10 @@ export default {
|
||||
title: '戰鬥模擬器',
|
||||
message: '在發動攻擊前模擬戰鬥結果。輸入雙方艦隊和科技等級,預測勝負和損失。'
|
||||
},
|
||||
achievements: {
|
||||
title: '成就系統',
|
||||
message: '完成各類遊戲目標解鎖成就,獲得暗物質獎勵!成就分為多個等級,挑戰更高難度獲得更豐厚的獎勵。'
|
||||
},
|
||||
settings: {
|
||||
title: '設置',
|
||||
message: '在這裡管理遊戲數據、調整通知設置、導出/導入存檔。記得定期備份你的進度!'
|
||||
@@ -1243,5 +1299,239 @@ export default {
|
||||
title: 'GM管理面板',
|
||||
message: 'GM模式可以快速修改資源、建築、科技等級。用於測試或體驗完整遊戲內容。'
|
||||
}
|
||||
},
|
||||
achievements: {
|
||||
title: '成就',
|
||||
unlocked: '成就解鎖',
|
||||
progress: '進度',
|
||||
nextTier: '下一等級',
|
||||
maxTierReached: '已達最高等級',
|
||||
tiers: {
|
||||
bronze: '青銅',
|
||||
silver: '白銀',
|
||||
gold: '黃金',
|
||||
platinum: '白金',
|
||||
diamond: '鑽石'
|
||||
},
|
||||
categories: {
|
||||
resource: '資源',
|
||||
building: '建造',
|
||||
combat: '戰鬥',
|
||||
mission: '任務',
|
||||
diplomacy: '外交'
|
||||
},
|
||||
names: {
|
||||
metalCollector: '金屬收藏家',
|
||||
crystalCollector: '晶體收藏家',
|
||||
deuteriumCollector: '重氫收藏家',
|
||||
darkMatterCollector: '暗物質收藏家',
|
||||
resourceConsumer: '資源消耗者',
|
||||
masterBuilder: '建造大師',
|
||||
researcher: '科學家',
|
||||
shipwright: '造船匠',
|
||||
fortifier: '防禦專家',
|
||||
warmonger: '好戰者',
|
||||
conqueror: '征服者',
|
||||
defender: '防禦者',
|
||||
fleetDestroyer: '艦隊毀滅者',
|
||||
debrisCreator: '殘骸製造者',
|
||||
fleetSacrifice: '艦隊犧牲者',
|
||||
defenseSacrifice: '防禦犧牲者',
|
||||
pilot: '飛行員',
|
||||
transporter: '運輸專家',
|
||||
cargoMaster: '貨運大師',
|
||||
colonizer: '殖民者',
|
||||
spy: '間諜大師',
|
||||
deployer: '部署專家',
|
||||
explorer: '探險家',
|
||||
luckyExplorer: '幸運探險家',
|
||||
recycler: '回收專家',
|
||||
scavenger: '拾荒者',
|
||||
destroyer: '毀滅者',
|
||||
fuelBurner: '燃料消耗者',
|
||||
diplomat: '外交官',
|
||||
nemesis: '宿敵',
|
||||
generous: '慷慨者',
|
||||
philanthropist: '慈善家',
|
||||
target: '目標',
|
||||
watched: '被監視',
|
||||
robbed: '被搶奪',
|
||||
lostToNPC: '資源損失者'
|
||||
},
|
||||
descriptions: {
|
||||
metalCollector: '累計生產金屬',
|
||||
crystalCollector: '累計生產晶體',
|
||||
deuteriumCollector: '累計生產重氫',
|
||||
darkMatterCollector: '累計生產暗物質',
|
||||
resourceConsumer: '累計消耗資源',
|
||||
masterBuilder: '累計升級建築次數',
|
||||
researcher: '累計完成研究次數',
|
||||
shipwright: '累計生產艦船數量',
|
||||
fortifier: '累計建造防禦設施',
|
||||
warmonger: '發起攻擊次數',
|
||||
conqueror: '攻擊勝利次數',
|
||||
defender: '防禦成功次數',
|
||||
fleetDestroyer: '防禦中消滅的敵方艦隊數量',
|
||||
debrisCreator: '戰鬥中產生的殘骸資源總量',
|
||||
fleetSacrifice: '損失的艦隊總數',
|
||||
defenseSacrifice: '防禦中損失的防禦設施數量',
|
||||
pilot: '完成飛行任務次數',
|
||||
transporter: '運輸任務次數',
|
||||
cargoMaster: '運輸資源總量',
|
||||
colonizer: '成功殖民星球次數',
|
||||
spy: '執行偵查任務次數',
|
||||
deployer: '執行部署任務次數',
|
||||
explorer: '遠征總次數',
|
||||
luckyExplorer: '遠征成功次數',
|
||||
recycler: '回收任務次數',
|
||||
scavenger: '回收資源總量',
|
||||
destroyer: '摧毀星球次數',
|
||||
fuelBurner: '消耗燃料總量',
|
||||
diplomat: '友好NPC數量',
|
||||
nemesis: '敵對NPC數量',
|
||||
generous: '送禮次數',
|
||||
philanthropist: '贈送資源總量',
|
||||
target: '被NPC攻擊次數',
|
||||
watched: '被NPC偵查次數',
|
||||
robbed: '被NPC回收殘骸次數',
|
||||
lostToNPC: '被NPC回收的殘骸資源總量'
|
||||
}
|
||||
},
|
||||
guide: {
|
||||
title: '遊戲指南',
|
||||
tableOfContents: '目錄',
|
||||
quickstart: {
|
||||
title: '新手入門',
|
||||
description: '快速了解遊戲核心機制,開始你的星際征程',
|
||||
step1: {
|
||||
title: '建造資源礦場',
|
||||
content1: '資源是發展的基礎。首先建造金屬礦、晶體礦和重氫合成器。',
|
||||
item1: '金屬礦 - 產出金屬,是最基礎的資源',
|
||||
item2: '晶體礦 - 產出晶體,用於高級建築和艦船',
|
||||
item3: '重氫合成器 - 產出重氫,用於燃料和高級科技',
|
||||
item4: '建議優先發展金屬礦,其次晶體礦'
|
||||
},
|
||||
step2: {
|
||||
title: '確保能源供應',
|
||||
content1: '礦場需要電力才能運作。建造太陽能電站提供電力。',
|
||||
item1: '太陽能電站 - 提供基礎電力,無需消耗資源',
|
||||
item2: '核聚變反應堆 - 後期可用,消耗重氫但產電量更高'
|
||||
},
|
||||
step3: {
|
||||
title: '建設基礎設施',
|
||||
content1: '設施是發展的重要支撐。',
|
||||
item1: '機器人工廠 - 加速所有建築的建造速度',
|
||||
item2: '船塢 - 用於建造艦船和防禦設施',
|
||||
item3: '研究實驗室 - 用於研發科技'
|
||||
},
|
||||
step4: {
|
||||
title: '研發科技',
|
||||
content1: '科技可以解鎖更多內容並提供加成。',
|
||||
item1: '能量技術 - 解鎖更多科技的前置',
|
||||
item2: '燃燒引擎 - 提高艦船速度',
|
||||
item3: '電腦技術 - 增加可同時執行的艦隊任務數'
|
||||
},
|
||||
step5: {
|
||||
title: '建造艦隊',
|
||||
content1: '艦隊是探索和戰鬥的關鍵。',
|
||||
item1: '小型貨船 - 用於運輸資源',
|
||||
item2: '輕型戰鬥機 - 基礎戰鬥單位',
|
||||
item3: '間諜衛星 - 偵查敵人情報'
|
||||
},
|
||||
step6: {
|
||||
title: '探索宇宙',
|
||||
content1: '使用艦隊探索宇宙,獲取資源和發展。',
|
||||
item1: '偵查 - 獲取敵人情報',
|
||||
item2: '攻擊 - 掠奪資源',
|
||||
item3: '運輸 - 在星球間轉移資源',
|
||||
item4: '殖民 - 建立新的殖民地'
|
||||
}
|
||||
},
|
||||
buildings: {
|
||||
title: '建築',
|
||||
description: '了解遊戲中所有建築的功能和作用',
|
||||
resource: { title: '資源建築' },
|
||||
energy: { title: '能源建築' },
|
||||
facility: { title: '設施建築' },
|
||||
storage: { title: '倉儲建築' },
|
||||
moon: { title: '月球建築' }
|
||||
},
|
||||
research: {
|
||||
title: '研究',
|
||||
description: '了解所有科技的功能和解鎖條件',
|
||||
basic: { title: '基礎科技' },
|
||||
drive: { title: '驅動科技' },
|
||||
combat: { title: '戰鬥科技' },
|
||||
advanced: { title: '高級科技' }
|
||||
},
|
||||
ships: {
|
||||
title: '艦船',
|
||||
description: '了解各類艦船的屬性和用途',
|
||||
combat: { title: '戰鬥艦船' },
|
||||
civil: { title: '民用艦船' },
|
||||
special: { title: '特殊艦船' },
|
||||
stats: '攻擊: {attack} | 護盾: {shield} | 速度: {speed}',
|
||||
cargo: '載貨量: {cargo}'
|
||||
},
|
||||
defense: {
|
||||
title: '防禦',
|
||||
description: '了解各類防禦設施的屬性和作用',
|
||||
basic: { title: '基礎防禦' },
|
||||
advanced: { title: '高級防禦' },
|
||||
shields: { title: '護盾與導彈' },
|
||||
stats: '攻擊: {attack} | 護盾: {shield}'
|
||||
},
|
||||
systems: {
|
||||
title: '遊戲系統',
|
||||
description: '了解遊戲的核心系統機制',
|
||||
resource: {
|
||||
title: '資源系統',
|
||||
content1: '資源是遊戲的核心,用於建造建築、研發科技和生產艦船。',
|
||||
item1: '金屬 - 最基礎的資源,產量最高',
|
||||
item2: '晶體 - 用於高級建築和艦船',
|
||||
item3: '重氫 - 用於燃料和高級科技',
|
||||
item4: '暗物質 - 稀有資源,用於購買軍官和特殊功能'
|
||||
},
|
||||
combat: {
|
||||
title: '戰鬥系統',
|
||||
content1: '戰鬥採用回合制,每輪艦船和防禦設施都會互相攻擊。',
|
||||
item1: '攻擊力 - 決定對敵方造成的傷害',
|
||||
item2: '護盾值 - 吸收部分傷害',
|
||||
item3: '結構值 - 單位的生命值',
|
||||
item4: '速射 - 某些艦船可以在一輪內多次攻擊特定目標'
|
||||
},
|
||||
missions: {
|
||||
title: '艦隊任務',
|
||||
content1: '艦隊可以執行多種類型的任務。',
|
||||
item1: '攻擊 - 攻擊敵方星球,掠奪資源',
|
||||
item2: '運輸 - 在自己的星球間運送資源',
|
||||
item3: '部署 - 將艦隊永久轉移到另一個星球',
|
||||
item4: '殖民 - 使用殖民船建立新殖民地',
|
||||
item5: '偵查 - 使用間諜衛星獲取情報',
|
||||
item6: '回收 - 回收戰場殘骸中的資源'
|
||||
},
|
||||
diplomacy: {
|
||||
title: '外交系統',
|
||||
content1: 'NPC勢力具有好感度系統,影響他們對你的態度。',
|
||||
item1: '友好 - NPC不會主動攻擊你',
|
||||
item2: '中立 - NPC可能會偵查你',
|
||||
item3: '敵對 - NPC會主動攻擊你的星球'
|
||||
},
|
||||
achievements: {
|
||||
title: '成就系統',
|
||||
content1: '完成各類遊戲目標解鎖成就,獲得暗物質獎勵。',
|
||||
item1: '成就分為多個等級,難度遞增',
|
||||
item2: '完成成就可獲得暗物質獎勵',
|
||||
item3: '成就進度會自動追蹤'
|
||||
},
|
||||
officers: {
|
||||
title: '軍官系統',
|
||||
content1: '雇傭軍官可以獲得各種加成效果。',
|
||||
item1: '指揮官 - 減少建造時間',
|
||||
item2: '提督 - 增加艦隊任務槽位',
|
||||
item3: '工程師 - 減少防禦修復時間',
|
||||
item4: '地質學家 - 增加資源產量'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
463
src/logic/achievementLogic.ts
Normal file
@@ -0,0 +1,463 @@
|
||||
import {
|
||||
AchievementTier,
|
||||
BuildingType,
|
||||
TechnologyType,
|
||||
ShipType,
|
||||
DefenseType,
|
||||
type AchievementStats,
|
||||
type AchievementProgress,
|
||||
type AchievementReward,
|
||||
type Player,
|
||||
type Resources,
|
||||
type BattleResult
|
||||
} from '@/types/game'
|
||||
import { ACHIEVEMENTS, ACHIEVEMENT_MAP, getNextTier, getTierIndex } from '@/config/achievementConfig'
|
||||
|
||||
// 初始化空的成就统计数据
|
||||
export function initializeAchievementStats(): AchievementStats {
|
||||
const emptyShipRecord = Object.values(ShipType).reduce((acc, type) => {
|
||||
acc[type] = 0
|
||||
return acc
|
||||
}, {} as Record<ShipType, number>)
|
||||
|
||||
const emptyDefenseRecord = Object.values(DefenseType).reduce((acc, type) => {
|
||||
acc[type] = 0
|
||||
return acc
|
||||
}, {} as Record<DefenseType, number>)
|
||||
|
||||
const emptyBuildingRecord = Object.values(BuildingType).reduce((acc, type) => {
|
||||
acc[type] = 0
|
||||
return acc
|
||||
}, {} as Record<BuildingType, number>)
|
||||
|
||||
const emptyTechRecord = Object.values(TechnologyType).reduce((acc, type) => {
|
||||
acc[type] = 0
|
||||
return acc
|
||||
}, {} as Record<TechnologyType, number>)
|
||||
|
||||
return {
|
||||
// 资源统计
|
||||
totalMetalProduced: 0,
|
||||
totalCrystalProduced: 0,
|
||||
totalDeuteriumProduced: 0,
|
||||
totalDarkMatterProduced: 0,
|
||||
totalMetalConsumed: 0,
|
||||
totalCrystalConsumed: 0,
|
||||
totalDeuteriumConsumed: 0,
|
||||
totalDarkMatterConsumed: 0,
|
||||
|
||||
// 建造统计
|
||||
buildingsUpgraded: 0,
|
||||
maxBuildingLevel: { ...emptyBuildingRecord },
|
||||
researchCompleted: 0,
|
||||
maxTechnologyLevel: { ...emptyTechRecord },
|
||||
shipsProduced: { ...emptyShipRecord },
|
||||
totalShipsProduced: 0,
|
||||
defensesBuilt: { ...emptyDefenseRecord },
|
||||
totalDefensesBuilt: 0,
|
||||
|
||||
// 战斗统计
|
||||
attacksLaunched: 0,
|
||||
attacksWon: 0,
|
||||
attacksLost: 0,
|
||||
fleetLostInAttack: { ...emptyShipRecord },
|
||||
totalFleetLostInAttack: 0,
|
||||
debrisCreatedFromAttacks: 0,
|
||||
defensesSuccessful: 0,
|
||||
defensesFailed: 0,
|
||||
fleetLostInDefense: { ...emptyShipRecord },
|
||||
totalFleetLostInDefense: 0,
|
||||
defenseLostInDefense: { ...emptyDefenseRecord },
|
||||
totalDefenseLostInDefense: 0,
|
||||
enemyFleetDestroyedInDefense: { ...emptyShipRecord },
|
||||
totalEnemyFleetDestroyedInDefense: 0,
|
||||
debrisCreatedFromDefenses: 0,
|
||||
|
||||
// 任务统计
|
||||
totalFlightMissions: 0,
|
||||
transportMissions: 0,
|
||||
transportedResources: 0,
|
||||
colonizations: 0,
|
||||
spyMissions: 0,
|
||||
deployments: 0,
|
||||
expeditionsTotal: 0,
|
||||
expeditionsSuccessful: 0,
|
||||
recyclingMissions: 0,
|
||||
recycledResources: 0,
|
||||
planetDestructions: 0,
|
||||
fuelConsumed: 0,
|
||||
|
||||
// 外交统计
|
||||
friendlyNPCCount: 0,
|
||||
hostileNPCCount: 0,
|
||||
giftsSent: 0,
|
||||
giftResourcesTotal: 0,
|
||||
attackedByNPC: 0,
|
||||
spiedByNPC: 0,
|
||||
debrisRecycledByNPC: 0,
|
||||
debrisResourcesLostToNPC: 0
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化所有成就进度
|
||||
export function initializeAchievements(): Record<string, AchievementProgress> {
|
||||
const achievements: Record<string, AchievementProgress> = {}
|
||||
|
||||
for (const config of ACHIEVEMENTS) {
|
||||
achievements[config.id] = {
|
||||
id: config.id,
|
||||
currentTier: null,
|
||||
currentValue: 0,
|
||||
tierUnlocks: {
|
||||
[AchievementTier.Bronze]: null,
|
||||
[AchievementTier.Silver]: null,
|
||||
[AchievementTier.Gold]: null,
|
||||
[AchievementTier.Platinum]: null,
|
||||
[AchievementTier.Diamond]: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return achievements
|
||||
}
|
||||
|
||||
// 获取成就的当前值
|
||||
function getAchievementValue(stats: AchievementStats, statKey: string, checkType: string): number {
|
||||
// 处理特殊的组合统计键
|
||||
if (statKey === 'totalResourcesConsumed') {
|
||||
return stats.totalMetalConsumed + stats.totalCrystalConsumed + stats.totalDeuteriumConsumed + stats.totalDarkMatterConsumed
|
||||
}
|
||||
|
||||
if (statKey === 'totalDebrisCreated') {
|
||||
return stats.debrisCreatedFromAttacks + stats.debrisCreatedFromDefenses
|
||||
}
|
||||
|
||||
if (statKey === 'totalFleetLost') {
|
||||
return stats.totalFleetLostInAttack + stats.totalFleetLostInDefense
|
||||
}
|
||||
|
||||
// 处理普通统计键
|
||||
const value = stats[statKey as keyof AchievementStats]
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
|
||||
// 处理 Record 类型的值(如果需要求和)
|
||||
if (checkType === 'sum' && typeof value === 'object') {
|
||||
return Object.values(value as Record<string, number>).reduce((a, b) => a + b, 0)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// 检查并更新成就进度,返回新解锁的成就
|
||||
export interface AchievementUnlock {
|
||||
id: string
|
||||
tier: AchievementTier
|
||||
reward: AchievementReward
|
||||
}
|
||||
|
||||
export function checkAchievements(player: Player): AchievementUnlock[] {
|
||||
if (!player.achievementStats || !player.achievements) {
|
||||
return []
|
||||
}
|
||||
|
||||
const unlocks: AchievementUnlock[] = []
|
||||
const stats = player.achievementStats
|
||||
const achievements = player.achievements
|
||||
|
||||
for (const config of ACHIEVEMENTS) {
|
||||
const progress = achievements[config.id]
|
||||
if (!progress) continue
|
||||
|
||||
// 获取当前值
|
||||
const currentValue = getAchievementValue(stats, config.statKey, config.checkType)
|
||||
progress.currentValue = currentValue
|
||||
|
||||
// 检查每个等级
|
||||
for (const tierConfig of config.tiers) {
|
||||
// 如果已经解锁这个等级,跳过
|
||||
if (progress.tierUnlocks[tierConfig.tier] !== null) continue
|
||||
|
||||
// 检查是否达到目标
|
||||
let achieved = false
|
||||
if (config.checkType === 'gte' || config.checkType === 'sum') {
|
||||
achieved = currentValue >= tierConfig.target
|
||||
} else if (config.checkType === 'eq') {
|
||||
achieved = currentValue === tierConfig.target
|
||||
} else if (config.checkType === 'max') {
|
||||
achieved = currentValue >= tierConfig.target
|
||||
}
|
||||
|
||||
if (achieved) {
|
||||
// 解锁成就
|
||||
const now = Date.now()
|
||||
progress.tierUnlocks[tierConfig.tier] = now
|
||||
|
||||
// 更新当前最高等级
|
||||
if (progress.currentTier === null || getTierIndex(tierConfig.tier) > getTierIndex(progress.currentTier)) {
|
||||
progress.currentTier = tierConfig.tier
|
||||
progress.unlockedAt = now
|
||||
}
|
||||
|
||||
unlocks.push({
|
||||
id: config.id,
|
||||
tier: tierConfig.tier,
|
||||
reward: tierConfig.reward
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unlocks
|
||||
}
|
||||
|
||||
// 应用成就奖励
|
||||
export function applyAchievementReward(player: Player, reward: AchievementReward): void {
|
||||
const firstPlanet = player.planets[0]
|
||||
if (reward.darkMatter && firstPlanet) {
|
||||
// 奖励添加到第一个星球的资源中
|
||||
firstPlanet.resources.darkMatter += reward.darkMatter
|
||||
}
|
||||
|
||||
if (reward.points) {
|
||||
player.points += reward.points
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 统计更新函数 ====================
|
||||
|
||||
// 更新资源生产统计
|
||||
export function updateResourceProductionStats(
|
||||
stats: AchievementStats,
|
||||
produced: { metal?: number; crystal?: number; deuterium?: number; darkMatter?: number }
|
||||
): void {
|
||||
if (produced.metal) stats.totalMetalProduced += produced.metal
|
||||
if (produced.crystal) stats.totalCrystalProduced += produced.crystal
|
||||
if (produced.deuterium) stats.totalDeuteriumProduced += produced.deuterium
|
||||
if (produced.darkMatter) stats.totalDarkMatterProduced += produced.darkMatter
|
||||
}
|
||||
|
||||
// 更新资源消耗统计
|
||||
export function updateResourceConsumptionStats(stats: AchievementStats, consumed: Partial<Resources>): void {
|
||||
if (consumed.metal) stats.totalMetalConsumed += consumed.metal
|
||||
if (consumed.crystal) stats.totalCrystalConsumed += consumed.crystal
|
||||
if (consumed.deuterium) stats.totalDeuteriumConsumed += consumed.deuterium
|
||||
if (consumed.darkMatter) stats.totalDarkMatterConsumed += consumed.darkMatter
|
||||
}
|
||||
|
||||
// 更新建筑升级统计
|
||||
export function updateBuildingStats(stats: AchievementStats, buildingType: BuildingType, level: number): void {
|
||||
stats.buildingsUpgraded += 1
|
||||
|
||||
// 更新最高等级记录
|
||||
if (level > (stats.maxBuildingLevel[buildingType] || 0)) {
|
||||
stats.maxBuildingLevel[buildingType] = level
|
||||
}
|
||||
}
|
||||
|
||||
// 更新科技研究统计
|
||||
export function updateResearchStats(stats: AchievementStats, techType: TechnologyType, level: number): void {
|
||||
stats.researchCompleted += 1
|
||||
|
||||
// 更新最高等级记录
|
||||
if (level > (stats.maxTechnologyLevel[techType] || 0)) {
|
||||
stats.maxTechnologyLevel[techType] = level
|
||||
}
|
||||
}
|
||||
|
||||
// 更新舰船生产统计
|
||||
export function updateShipProductionStats(stats: AchievementStats, shipType: ShipType, quantity: number): void {
|
||||
stats.shipsProduced[shipType] = (stats.shipsProduced[shipType] || 0) + quantity
|
||||
stats.totalShipsProduced += quantity
|
||||
}
|
||||
|
||||
// 更新防御建造统计
|
||||
export function updateDefenseProductionStats(stats: AchievementStats, defenseType: DefenseType, quantity: number): void {
|
||||
stats.defensesBuilt[defenseType] = (stats.defensesBuilt[defenseType] || 0) + quantity
|
||||
stats.totalDefensesBuilt += quantity
|
||||
}
|
||||
|
||||
// 更新攻击统计(玩家作为攻击者)
|
||||
export function updateAttackStats(stats: AchievementStats, battleResult: BattleResult, won: boolean, debrisValue: number): void {
|
||||
// 攻击也算飞行任务
|
||||
stats.totalFlightMissions += 1
|
||||
stats.attacksLaunched += 1
|
||||
|
||||
if (won) {
|
||||
stats.attacksWon += 1
|
||||
} else if (battleResult.winner === 'defender') {
|
||||
stats.attacksLost += 1
|
||||
}
|
||||
|
||||
// 记录攻击中损失的舰队
|
||||
if (battleResult.attackerLosses) {
|
||||
for (const [shipType, count] of Object.entries(battleResult.attackerLosses)) {
|
||||
if (count && count > 0) {
|
||||
stats.fleetLostInAttack[shipType as ShipType] = (stats.fleetLostInAttack[shipType as ShipType] || 0) + count
|
||||
stats.totalFleetLostInAttack += count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 记录产生的残骸
|
||||
stats.debrisCreatedFromAttacks += debrisValue
|
||||
}
|
||||
|
||||
// 更新防御统计(玩家作为防御者)
|
||||
export function updateDefenseStats(stats: AchievementStats, battleResult: BattleResult, won: boolean, debrisValue: number): void {
|
||||
if (won) {
|
||||
stats.defensesSuccessful += 1
|
||||
} else {
|
||||
stats.defensesFailed += 1
|
||||
}
|
||||
|
||||
// 记录防御中损失的舰队
|
||||
if (battleResult.defenderLosses?.fleet) {
|
||||
for (const [shipType, count] of Object.entries(battleResult.defenderLosses.fleet)) {
|
||||
if (count && count > 0) {
|
||||
stats.fleetLostInDefense[shipType as ShipType] = (stats.fleetLostInDefense[shipType as ShipType] || 0) + count
|
||||
stats.totalFleetLostInDefense += count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 记录防御中损失的防御设施
|
||||
if (battleResult.defenderLosses?.defense) {
|
||||
for (const [defenseType, count] of Object.entries(battleResult.defenderLosses.defense)) {
|
||||
if (count && count > 0) {
|
||||
stats.defenseLostInDefense[defenseType as DefenseType] = (stats.defenseLostInDefense[defenseType as DefenseType] || 0) + count
|
||||
stats.totalDefenseLostInDefense += count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 记录防御中消灭的敌方舰队
|
||||
if (battleResult.attackerLosses) {
|
||||
for (const [shipType, count] of Object.entries(battleResult.attackerLosses)) {
|
||||
if (count && count > 0) {
|
||||
stats.enemyFleetDestroyedInDefense[shipType as ShipType] = (stats.enemyFleetDestroyedInDefense[shipType as ShipType] || 0) + count
|
||||
stats.totalEnemyFleetDestroyedInDefense += count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 记录产生的残骸
|
||||
stats.debrisCreatedFromDefenses += debrisValue
|
||||
}
|
||||
|
||||
// 更新飞行任务统计
|
||||
export function updateFlightMissionStats(stats: AchievementStats): void {
|
||||
stats.totalFlightMissions += 1
|
||||
}
|
||||
|
||||
// 更新运输任务统计
|
||||
export function updateTransportStats(stats: AchievementStats, resourcesAmount: number): void {
|
||||
stats.transportMissions += 1
|
||||
stats.transportedResources += resourcesAmount
|
||||
}
|
||||
|
||||
// 更新殖民统计
|
||||
export function updateColonizationStats(stats: AchievementStats): void {
|
||||
stats.colonizations += 1
|
||||
}
|
||||
|
||||
// 更新侦查统计
|
||||
export function updateSpyStats(stats: AchievementStats): void {
|
||||
stats.spyMissions += 1
|
||||
}
|
||||
|
||||
// 更新部署统计
|
||||
export function updateDeploymentStats(stats: AchievementStats): void {
|
||||
stats.deployments += 1
|
||||
}
|
||||
|
||||
// 更新探险统计
|
||||
export function updateExpeditionStats(stats: AchievementStats, successful: boolean): void {
|
||||
stats.expeditionsTotal += 1
|
||||
if (successful) {
|
||||
stats.expeditionsSuccessful += 1
|
||||
}
|
||||
}
|
||||
|
||||
// 更新回收统计
|
||||
export function updateRecyclingStats(stats: AchievementStats, resourcesAmount: number): void {
|
||||
stats.recyclingMissions += 1
|
||||
stats.recycledResources += resourcesAmount
|
||||
}
|
||||
|
||||
// 更新行星毁灭统计
|
||||
export function updatePlanetDestructionStats(stats: AchievementStats): void {
|
||||
stats.planetDestructions += 1
|
||||
}
|
||||
|
||||
// 更新燃料消耗统计
|
||||
export function updateFuelConsumptionStats(stats: AchievementStats, fuelAmount: number): void {
|
||||
stats.fuelConsumed += fuelAmount
|
||||
}
|
||||
|
||||
// 更新友好NPC数量
|
||||
export function updateFriendlyNPCStats(stats: AchievementStats, count: number): void {
|
||||
stats.friendlyNPCCount = count
|
||||
}
|
||||
|
||||
// 更新敌对NPC数量
|
||||
export function updateHostileNPCStats(stats: AchievementStats, count: number): void {
|
||||
stats.hostileNPCCount = count
|
||||
}
|
||||
|
||||
// 更新送礼统计
|
||||
export function updateGiftStats(stats: AchievementStats, resourcesAmount: number): void {
|
||||
stats.giftsSent += 1
|
||||
stats.giftResourcesTotal += resourcesAmount
|
||||
}
|
||||
|
||||
// 更新被NPC攻击统计
|
||||
export function updateAttackedByNPCStats(stats: AchievementStats): void {
|
||||
stats.attackedByNPC += 1
|
||||
}
|
||||
|
||||
// 更新被NPC侦查统计
|
||||
export function updateSpiedByNPCStats(stats: AchievementStats): void {
|
||||
stats.spiedByNPC += 1
|
||||
}
|
||||
|
||||
// 更新被NPC回收残骸统计
|
||||
export function updateDebrisRecycledByNPCStats(stats: AchievementStats, resourcesAmount: number): void {
|
||||
stats.debrisRecycledByNPC += 1
|
||||
stats.debrisResourcesLostToNPC += resourcesAmount
|
||||
}
|
||||
|
||||
// 获取成就的下一个目标
|
||||
export function getNextTarget(achievementId: string, currentTier: AchievementTier | null): number | null {
|
||||
const config = ACHIEVEMENT_MAP[achievementId]
|
||||
if (!config) return null
|
||||
|
||||
const nextTier = getNextTier(currentTier)
|
||||
if (!nextTier) return null
|
||||
|
||||
const tierConfig = config.tiers.find(t => t.tier === nextTier)
|
||||
return tierConfig?.target ?? null
|
||||
}
|
||||
|
||||
// 获取成就进度百分比
|
||||
export function getAchievementProgress(achievementId: string, currentValue: number, currentTier: AchievementTier | null): number {
|
||||
const config = ACHIEVEMENT_MAP[achievementId]
|
||||
if (!config) return 0
|
||||
|
||||
const nextTier = getNextTier(currentTier)
|
||||
if (!nextTier) return 100 // 已达到最高等级
|
||||
|
||||
const tierConfig = config.tiers.find(t => t.tier === nextTier)
|
||||
if (!tierConfig) return 0
|
||||
|
||||
// 计算当前等级的起始值
|
||||
let startValue = 0
|
||||
if (currentTier) {
|
||||
const currentTierConfig = config.tiers.find(t => t.tier === currentTier)
|
||||
startValue = currentTierConfig?.target ?? 0
|
||||
}
|
||||
|
||||
const progress = ((currentValue - startValue) / (tierConfig.target - startValue)) * 100
|
||||
return Math.min(100, Math.max(0, progress))
|
||||
}
|
||||
@@ -73,6 +73,21 @@ export const createFleetMission = (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运输任务失败原因
|
||||
*/
|
||||
export type TransportFailReason = 'targetNotFound' | 'giftRejected'
|
||||
|
||||
/**
|
||||
* 运输任务结果
|
||||
*/
|
||||
export interface TransportResult {
|
||||
success: boolean
|
||||
reputationGain?: number
|
||||
overflow?: Resources
|
||||
failReason?: TransportFailReason
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运输任务到达
|
||||
*/
|
||||
@@ -83,7 +98,7 @@ export const processTransportArrival = (
|
||||
allNpcs?: NPC[],
|
||||
locale: Locale = 'zh-CN',
|
||||
storageCapacityBonus: number = 0
|
||||
): { success: boolean; reputationGain?: number; overflow?: Resources } => {
|
||||
): TransportResult => {
|
||||
// 检查是否是赠送任务
|
||||
if (mission.isGift && mission.giftTargetNpcId && player && allNpcs) {
|
||||
const targetNpc = allNpcs.find(n => n.id === mission.giftTargetNpcId)
|
||||
@@ -94,7 +109,7 @@ export const processTransportArrival = (
|
||||
// 如果礼物被拒绝,资源返还给玩家
|
||||
if (!giftResult.accepted) {
|
||||
// 资源保留在cargo中,返回时会退还给玩家
|
||||
return { success: false, reputationGain: undefined }
|
||||
return { success: false, reputationGain: undefined, failReason: 'giftRejected' }
|
||||
}
|
||||
|
||||
// 礼物被接受,清空货物
|
||||
@@ -114,10 +129,13 @@ export const processTransportArrival = (
|
||||
mission.cargo = result.overflow
|
||||
return { success: true, overflow: result.overflow }
|
||||
}
|
||||
// 运输成功,清空货物
|
||||
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
return { success: true }
|
||||
}
|
||||
// 目标星球不存在
|
||||
mission.status = 'returning'
|
||||
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
return { success: false }
|
||||
return { success: false, failReason: 'targetNotFound' }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +374,15 @@ export const canColonize = (player: Player): boolean => {
|
||||
return currentPlanets < maxPlanets
|
||||
}
|
||||
|
||||
/**
|
||||
* 殖民任务结果
|
||||
*/
|
||||
export interface ColonizeResult {
|
||||
success: boolean
|
||||
planet: Planet | null
|
||||
failReason?: 'positionOccupied' | 'maxColoniesReached'
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理殖民任务到达
|
||||
*/
|
||||
@@ -364,18 +391,18 @@ export const processColonizeArrival = (
|
||||
targetPlanet: Planet | undefined,
|
||||
player: Player,
|
||||
colonyNameTemplate: string = 'Colony'
|
||||
): Planet | null => {
|
||||
): ColonizeResult => {
|
||||
if (targetPlanet) {
|
||||
// 位置已被占用
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
return { success: false, planet: null, failReason: 'positionOccupied' }
|
||||
}
|
||||
|
||||
// 检查殖民地槽位限制
|
||||
if (!canColonize(player)) {
|
||||
// 超出殖民地数量限制,殖民船返回
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
return { success: false, planet: null, failReason: 'maxColoniesReached' }
|
||||
}
|
||||
|
||||
// 创建新殖民地
|
||||
@@ -431,7 +458,7 @@ export const processColonizeArrival = (
|
||||
mission.fleet[ShipType.ColonyShip] = (mission.fleet[ShipType.ColonyShip] || 1) - 1
|
||||
mission.status = 'returning'
|
||||
|
||||
return newPlanet
|
||||
return { success: true, planet: newPlanet }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,6 +508,20 @@ export const calculateSpyDetectionChance = (attackerSpyLevel: number, defenderSp
|
||||
return Math.max(0.01, Math.min(0.99, baseChance))
|
||||
}
|
||||
|
||||
/**
|
||||
* 侦查任务失败原因
|
||||
*/
|
||||
export type SpyFailReason = 'targetNotFound'
|
||||
|
||||
/**
|
||||
* 侦查任务结果
|
||||
*/
|
||||
export interface SpyResult {
|
||||
success: boolean
|
||||
report?: SpyReport
|
||||
failReason?: SpyFailReason
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理间谍任务到达
|
||||
*/
|
||||
@@ -491,10 +532,10 @@ export const processSpyArrival = (
|
||||
defender: Player | null,
|
||||
allNpcs?: NPC[],
|
||||
locale: Locale = 'zh-CN'
|
||||
): SpyReport | null => {
|
||||
): SpyResult => {
|
||||
if (!targetPlanet) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
return { success: false, failReason: 'targetNotFound' }
|
||||
}
|
||||
|
||||
// 获取间谍技术等级
|
||||
@@ -536,7 +577,21 @@ export const processSpyArrival = (
|
||||
}
|
||||
|
||||
mission.status = 'returning'
|
||||
return spyReport
|
||||
return { success: true, report: spyReport }
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署任务失败原因
|
||||
*/
|
||||
export type DeployFailReason = 'targetNotFound' | 'notOwnPlanet'
|
||||
|
||||
/**
|
||||
* 部署任务结果
|
||||
*/
|
||||
export interface DeployResult {
|
||||
success: boolean
|
||||
overflow?: Partial<Record<ShipType, number>>
|
||||
failReason?: DeployFailReason
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -547,10 +602,14 @@ export const processDeployArrival = (
|
||||
targetPlanet: Planet | undefined,
|
||||
playerId: string,
|
||||
technologies: Record<TechnologyType, number>
|
||||
): { success: boolean; overflow?: Partial<Record<ShipType, number>> } => {
|
||||
if (!targetPlanet || targetPlanet.ownerId !== playerId) {
|
||||
): DeployResult => {
|
||||
if (!targetPlanet) {
|
||||
mission.status = 'returning'
|
||||
return { success: false }
|
||||
return { success: false, failReason: 'targetNotFound' }
|
||||
}
|
||||
if (targetPlanet.ownerId !== playerId) {
|
||||
mission.status = 'returning'
|
||||
return { success: false, failReason: 'notOwnPlanet' }
|
||||
}
|
||||
|
||||
// 使用安全添加函数,防止舰队仓储溢出
|
||||
@@ -566,6 +625,21 @@ export const processDeployArrival = (
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收任务失败原因
|
||||
*/
|
||||
export type RecycleFailReason = 'noDebrisField' | 'debrisEmpty'
|
||||
|
||||
/**
|
||||
* 回收任务结果
|
||||
*/
|
||||
export interface RecycleResult {
|
||||
success: boolean
|
||||
collectedResources?: Pick<Resources, 'metal' | 'crystal'>
|
||||
remainingDebris?: Pick<Resources, 'metal' | 'crystal'> | null
|
||||
failReason?: RecycleFailReason
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理回收任务到达
|
||||
*/
|
||||
@@ -575,10 +649,10 @@ export const processRecycleArrival = (
|
||||
player?: Player,
|
||||
allNpcs?: NPC[],
|
||||
locale: Locale = 'zh-CN'
|
||||
): { collectedResources: Pick<Resources, 'metal' | 'crystal'>; remainingDebris: Pick<Resources, 'metal' | 'crystal'> | null } | null => {
|
||||
): RecycleResult => {
|
||||
if (!debrisField) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
return { success: false, failReason: 'noDebrisField' }
|
||||
}
|
||||
|
||||
// 计算回收船的货舱容量
|
||||
@@ -599,7 +673,7 @@ export const processRecycleArrival = (
|
||||
// 防止除零:如果残骸为0,直接返回
|
||||
if (totalDebris === 0) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
return { success: false, failReason: 'debrisEmpty' }
|
||||
}
|
||||
|
||||
// 按比例收集金属和晶体
|
||||
@@ -625,6 +699,7 @@ export const processRecycleArrival = (
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
collectedResources: {
|
||||
metal: collectedMetal,
|
||||
crystal: collectedCrystal
|
||||
@@ -840,42 +915,114 @@ export const calculatePlanetDefensePower = (fleet: Partial<Fleet>, defense: Part
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理行星毁灭任务到达
|
||||
* 处理行星/月球毁灭任务到达
|
||||
* OGame规则:
|
||||
* - 月球销毁概率 = (100 - √diameter) × √deathstars
|
||||
* - 死星反向销毁概率 = √diameter / 2
|
||||
* - 两个概率独立判定
|
||||
*/
|
||||
|
||||
/**
|
||||
* 毁灭任务失败原因
|
||||
*/
|
||||
export type DestroyFailReason = 'targetNotFound' | 'ownPlanet' | 'noDeathstar' | 'chanceFailed'
|
||||
|
||||
/**
|
||||
* 销毁任务结果
|
||||
*/
|
||||
export interface DestroyResult {
|
||||
success: boolean // 目标是否被销毁
|
||||
destructionChance: number // 销毁概率
|
||||
planetId?: string // 被销毁的星球/月球ID
|
||||
deathstarsLost: boolean // 死星是否被反向销毁
|
||||
deathstarDestructionChance: number // 死星反向销毁概率
|
||||
isMoon: boolean // 目标是否为月球
|
||||
failReason?: DestroyFailReason // 失败原因
|
||||
}
|
||||
|
||||
export const processDestroyArrival = (
|
||||
mission: FleetMission,
|
||||
targetPlanet: Planet | undefined,
|
||||
attacker: Player
|
||||
): { success: boolean; destructionChance: number; planetId?: string } | null => {
|
||||
if (!targetPlanet || targetPlanet.ownerId === attacker.id) {
|
||||
): DestroyResult => {
|
||||
if (!targetPlanet) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
return {
|
||||
success: false,
|
||||
destructionChance: 0,
|
||||
deathstarsLost: false,
|
||||
deathstarDestructionChance: 0,
|
||||
isMoon: false,
|
||||
failReason: 'targetNotFound'
|
||||
}
|
||||
}
|
||||
if (targetPlanet.ownerId === attacker.id) {
|
||||
mission.status = 'returning'
|
||||
return {
|
||||
success: false,
|
||||
destructionChance: 0,
|
||||
deathstarsLost: false,
|
||||
deathstarDestructionChance: 0,
|
||||
isMoon: targetPlanet.isMoon || false,
|
||||
failReason: 'ownPlanet'
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有死星
|
||||
const deathstarCount = mission.fleet[ShipType.Deathstar] || 0
|
||||
if (deathstarCount === 0) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
return {
|
||||
success: false,
|
||||
destructionChance: 0,
|
||||
deathstarsLost: false,
|
||||
deathstarDestructionChance: 0,
|
||||
isMoon: targetPlanet.isMoon || false,
|
||||
failReason: 'noDeathstar'
|
||||
}
|
||||
}
|
||||
|
||||
// 计算目标星球的防御力量
|
||||
const planetaryShieldCount = targetPlanet.defense[DefenseType.PlanetaryShield] || 0
|
||||
const defensePower = calculatePlanetDefensePower(targetPlanet.fleet, targetPlanet.defense)
|
||||
// 根据目标类型使用不同的销毁逻辑
|
||||
if (targetPlanet.isMoon) {
|
||||
// 月球销毁使用 OGame 公式
|
||||
const result = moonLogic.tryDestroyMoon(targetPlanet, deathstarCount)
|
||||
|
||||
// 计算摧毁概率
|
||||
const destructionChance = calculateDestructionChance(deathstarCount, planetaryShieldCount, defensePower)
|
||||
// 如果死星被反向销毁,从任务舰队中移除
|
||||
if (result.deathstarsDestroyed) {
|
||||
mission.fleet[ShipType.Deathstar] = 0
|
||||
}
|
||||
|
||||
// 随机判断是否成功
|
||||
const randomValue = Math.random() * 100
|
||||
const success = randomValue < destructionChance
|
||||
mission.status = 'returning'
|
||||
|
||||
mission.status = 'returning'
|
||||
return {
|
||||
success: result.moonDestroyed,
|
||||
destructionChance: result.moonDestructionChance,
|
||||
planetId: result.moonDestroyed ? targetPlanet.id : undefined,
|
||||
deathstarsLost: result.deathstarsDestroyed,
|
||||
deathstarDestructionChance: result.deathstarDestructionChance,
|
||||
isMoon: true,
|
||||
failReason: result.moonDestroyed ? undefined : 'chanceFailed'
|
||||
}
|
||||
} else {
|
||||
// 行星销毁使用原有逻辑
|
||||
const planetaryShieldCount = targetPlanet.defense[DefenseType.PlanetaryShield] || 0
|
||||
const defensePower = calculatePlanetDefensePower(targetPlanet.fleet, targetPlanet.defense)
|
||||
const destructionChance = calculateDestructionChance(deathstarCount, planetaryShieldCount, defensePower)
|
||||
|
||||
return {
|
||||
success,
|
||||
destructionChance,
|
||||
planetId: success ? targetPlanet.id : undefined
|
||||
const randomValue = Math.random() * 100
|
||||
const success = randomValue < destructionChance
|
||||
|
||||
mission.status = 'returning'
|
||||
|
||||
return {
|
||||
success,
|
||||
destructionChance,
|
||||
planetId: success ? targetPlanet.id : undefined,
|
||||
deathstarsLost: false,
|
||||
deathstarDestructionChance: 0,
|
||||
isMoon: false,
|
||||
failReason: success ? undefined : 'chanceFailed'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -985,17 +1132,17 @@ export const updateFleetMissions = async (
|
||||
}
|
||||
|
||||
case MissionType.Colonize:
|
||||
const newColony = processColonizeArrival(mission, targetPlanet, attacker)
|
||||
if (newColony) {
|
||||
newColonies.push(newColony)
|
||||
planets.set(targetKey, newColony)
|
||||
const colonizeResult = processColonizeArrival(mission, targetPlanet, attacker)
|
||||
if (colonizeResult.success && colonizeResult.planet) {
|
||||
newColonies.push(colonizeResult.planet)
|
||||
planets.set(targetKey, colonizeResult.planet)
|
||||
}
|
||||
break
|
||||
|
||||
case MissionType.Spy:
|
||||
const spyReport = processSpyArrival(mission, targetPlanet, attacker, defender)
|
||||
if (spyReport) {
|
||||
spyReports.push(spyReport)
|
||||
const spyResult = processSpyArrival(mission, targetPlanet, attacker, defender)
|
||||
if (spyResult.success && spyResult.report) {
|
||||
spyReports.push(spyResult.report)
|
||||
}
|
||||
break
|
||||
|
||||
@@ -1010,7 +1157,7 @@ export const updateFleetMissions = async (
|
||||
const debrisId = `debris_${mission.targetPosition.galaxy}_${mission.targetPosition.system}_${mission.targetPosition.position}`
|
||||
const debrisField = debrisFields.get(debrisId)
|
||||
const recycleResult = processRecycleArrival(mission, debrisField, attacker, allNpcs)
|
||||
if (recycleResult) {
|
||||
if (recycleResult.success && recycleResult.collectedResources) {
|
||||
if (recycleResult.remainingDebris) {
|
||||
// 更新残骸场
|
||||
const updatedDebris: DebrisField = {
|
||||
@@ -1029,7 +1176,7 @@ export const updateFleetMissions = async (
|
||||
|
||||
case MissionType.Destroy:
|
||||
const destroyResult = processDestroyArrival(mission, targetPlanet, attacker)
|
||||
if (destroyResult && destroyResult.success && destroyResult.planetId) {
|
||||
if (destroyResult.success && destroyResult.planetId) {
|
||||
// 星球被摧毁
|
||||
destroyedPlanetIds.push(destroyResult.planetId)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as researchLogic from './researchLogic'
|
||||
import * as pointsLogic from './pointsLogic'
|
||||
import * as planetLogic from './planetLogic'
|
||||
import * as resourceLogic from './resourceLogic'
|
||||
import * as achievementLogic from './achievementLogic'
|
||||
|
||||
/**
|
||||
* 初始化玩家数据
|
||||
@@ -105,6 +106,14 @@ export const processGameUpdate = (
|
||||
): {
|
||||
updatedResearchQueue: BuildQueueItem[]
|
||||
} => {
|
||||
// 确保成就统计数据存在
|
||||
if (!player.achievementStats) {
|
||||
player.achievementStats = achievementLogic.initializeAchievementStats()
|
||||
}
|
||||
if (!player.achievements) {
|
||||
player.achievements = achievementLogic.initializeAchievements()
|
||||
}
|
||||
|
||||
// 获取军官加成
|
||||
const bonuses = officerLogic.calculateActiveBonuses(player.officers, now)
|
||||
|
||||
@@ -113,16 +122,48 @@ export const processGameUpdate = (
|
||||
pointsLogic.addPoints(player, points)
|
||||
}
|
||||
|
||||
// 通知回调
|
||||
const onCompleted = (type: string, itemType: string, level?: number, _quantity?: number) => {
|
||||
// 通知回调 + 成就统计更新
|
||||
const onCompleted = (type: string, itemType: string, level?: number, quantity?: number) => {
|
||||
if (onNotification) {
|
||||
onNotification(type, itemType, level)
|
||||
}
|
||||
// 更新成就统计
|
||||
if (player.achievementStats) {
|
||||
if (type === 'building' && level !== undefined) {
|
||||
achievementLogic.updateBuildingStats(player.achievementStats, itemType as any, level)
|
||||
} else if (type === 'technology' && level !== undefined) {
|
||||
achievementLogic.updateResearchStats(player.achievementStats, itemType as any, level)
|
||||
} else if (type === 'ship' && quantity !== undefined) {
|
||||
achievementLogic.updateShipProductionStats(player.achievementStats, itemType as any, quantity)
|
||||
} else if (type === 'defense' && quantity !== undefined) {
|
||||
achievementLogic.updateDefenseProductionStats(player.achievementStats, itemType as any, quantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新所有星球资源(直接同步计算,避免 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
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 更新所有星球其他状态
|
||||
@@ -159,3 +200,161 @@ export const processGameUpdate = (
|
||||
export const checkOfficersExpiration = (officers: Record<OfficerType, Officer>, now: number): void => {
|
||||
officerLogic.checkAndDeactivateExpiredOfficers(officers, now)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查成就进度并解锁新成就
|
||||
*/
|
||||
export const checkAndUnlockAchievements = (player: Player): achievementLogic.AchievementUnlock[] => {
|
||||
if (!player.achievementStats || !player.achievements) {
|
||||
return []
|
||||
}
|
||||
|
||||
const unlocks = achievementLogic.checkAchievements(player)
|
||||
|
||||
// 应用奖励
|
||||
unlocks.forEach(unlock => {
|
||||
achievementLogic.applyAchievementReward(player, unlock.reward)
|
||||
})
|
||||
|
||||
return unlocks
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新资源消耗统计
|
||||
*/
|
||||
export const trackResourceConsumption = (
|
||||
player: Player,
|
||||
consumed: { metal?: number; crystal?: number; deuterium?: number; darkMatter?: number }
|
||||
): void => {
|
||||
if (!player.achievementStats) {
|
||||
player.achievementStats = achievementLogic.initializeAchievementStats()
|
||||
}
|
||||
achievementLogic.updateResourceConsumptionStats(player.achievementStats, consumed)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新攻击统计(玩家作为攻击者)
|
||||
*/
|
||||
export const trackAttackStats = (player: Player, battleResult: any, won: boolean, debrisValue: number): void => {
|
||||
if (!player.achievementStats) {
|
||||
player.achievementStats = achievementLogic.initializeAchievementStats()
|
||||
}
|
||||
achievementLogic.updateAttackStats(player.achievementStats, battleResult, won, debrisValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新防御统计(玩家作为防御者)
|
||||
*/
|
||||
export const trackDefenseStats = (player: Player, battleResult: any, won: boolean, debrisValue: number): void => {
|
||||
if (!player.achievementStats) {
|
||||
player.achievementStats = achievementLogic.initializeAchievementStats()
|
||||
}
|
||||
achievementLogic.updateDefenseStats(player.achievementStats, battleResult, won, debrisValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务统计
|
||||
*/
|
||||
export const trackMissionStats = (
|
||||
player: Player,
|
||||
missionType: string,
|
||||
details?: {
|
||||
resourcesAmount?: number
|
||||
successful?: boolean
|
||||
fuelAmount?: number
|
||||
}
|
||||
): void => {
|
||||
if (!player.achievementStats) {
|
||||
player.achievementStats = achievementLogic.initializeAchievementStats()
|
||||
}
|
||||
|
||||
const stats = player.achievementStats
|
||||
achievementLogic.updateFlightMissionStats(stats)
|
||||
|
||||
switch (missionType) {
|
||||
case 'transport':
|
||||
if (details?.resourcesAmount) {
|
||||
achievementLogic.updateTransportStats(stats, details.resourcesAmount)
|
||||
}
|
||||
break
|
||||
case 'colonize':
|
||||
achievementLogic.updateColonizationStats(stats)
|
||||
break
|
||||
case 'spy':
|
||||
achievementLogic.updateSpyStats(stats)
|
||||
break
|
||||
case 'deploy':
|
||||
achievementLogic.updateDeploymentStats(stats)
|
||||
break
|
||||
case 'expedition':
|
||||
achievementLogic.updateExpeditionStats(stats, details?.successful ?? false)
|
||||
break
|
||||
case 'recycle':
|
||||
// 回收任务总是计入任务次数,但只有有资源时才增加回收资源量
|
||||
achievementLogic.updateRecyclingStats(stats, details?.resourcesAmount ?? 0)
|
||||
break
|
||||
case 'destroy':
|
||||
achievementLogic.updatePlanetDestructionStats(stats)
|
||||
break
|
||||
}
|
||||
|
||||
if (details?.fuelAmount) {
|
||||
achievementLogic.updateFuelConsumptionStats(stats, details.fuelAmount)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新外交统计
|
||||
*/
|
||||
export const trackDiplomacyStats = (
|
||||
player: Player,
|
||||
eventType: string,
|
||||
details?: {
|
||||
resourcesAmount?: number
|
||||
friendlyCount?: number
|
||||
hostileCount?: number
|
||||
}
|
||||
): void => {
|
||||
if (!player.achievementStats) {
|
||||
player.achievementStats = achievementLogic.initializeAchievementStats()
|
||||
}
|
||||
|
||||
const stats = player.achievementStats
|
||||
|
||||
switch (eventType) {
|
||||
case 'gift':
|
||||
if (details?.resourcesAmount) {
|
||||
achievementLogic.updateGiftStats(stats, details.resourcesAmount)
|
||||
}
|
||||
break
|
||||
case 'attackedByNPC':
|
||||
achievementLogic.updateAttackedByNPCStats(stats)
|
||||
break
|
||||
case 'spiedByNPC':
|
||||
achievementLogic.updateSpiedByNPCStats(stats)
|
||||
break
|
||||
case 'debrisRecycledByNPC':
|
||||
if (details?.resourcesAmount) {
|
||||
achievementLogic.updateDebrisRecycledByNPCStats(stats, details.resourcesAmount)
|
||||
}
|
||||
break
|
||||
case 'updateRelations':
|
||||
if (details?.friendlyCount !== undefined) {
|
||||
achievementLogic.updateFriendlyNPCStats(stats, details.friendlyCount)
|
||||
}
|
||||
if (details?.hostileCount !== undefined) {
|
||||
achievementLogic.updateHostileNPCStats(stats, details.hostileCount)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 追踪燃料消耗(在舰队出发时调用)
|
||||
*/
|
||||
export const trackFuelConsumption = (player: Player, fuelAmount: number): void => {
|
||||
if (!player.achievementStats) {
|
||||
player.achievementStats = achievementLogic.initializeAchievementStats()
|
||||
}
|
||||
achievementLogic.updateFuelConsumptionStats(player.achievementStats, fuelAmount)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import { MOON_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
|
||||
|
||||
/**
|
||||
* 计算月球生成概率
|
||||
* OGame规则:每100,000残骸(金属+晶体) = 1%概率,最高20%(需要2,000,000残骸)
|
||||
* @param debrisField 战斗产生的残骸场
|
||||
* @returns 生成概率(0-100)
|
||||
* @returns 生成概率(0-20)
|
||||
*/
|
||||
export const calculateMoonGenerationChance = (debrisField: Resources): number => {
|
||||
const totalDebris = debrisField.metal + debrisField.crystal
|
||||
@@ -15,18 +16,42 @@ export const calculateMoonGenerationChance = (debrisField: Resources): number =>
|
||||
return 0
|
||||
}
|
||||
|
||||
// 计算概率:基础概率 + (残骸量 / 每单位增加量) * 1%
|
||||
const additionalChance = Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)
|
||||
const chance = MOON_CONFIG.baseChance + additionalChance
|
||||
// 计算概率:每100k残骸 = 1%
|
||||
const chance = Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)
|
||||
|
||||
// 限制在最大概率内
|
||||
// 限制在最大概率内(20%)
|
||||
return Math.min(chance, MOON_CONFIG.maxChance)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算月球直径
|
||||
* OGame规则:直径基于生成时的moonchance,20%概率保证直径>8000km
|
||||
* @param moonChance 月球生成概率(1-20)
|
||||
* @returns 月球直径(km)
|
||||
*/
|
||||
export const calculateMoonDiameter = (moonChance: number): number => {
|
||||
// 基础直径 + 每%概率增加的直径 + 随机波动
|
||||
const baseDiameter = MOON_CONFIG.baseDiameter
|
||||
const bonusDiameter = moonChance * MOON_CONFIG.diameterPerChance
|
||||
// 添加±10%的随机波动
|
||||
const randomFactor = 0.9 + Math.random() * 0.2
|
||||
const diameter = Math.floor((baseDiameter + bonusDiameter) * randomFactor)
|
||||
|
||||
// 确保在合理范围内
|
||||
return Math.max(MOON_CONFIG.minDiameter, Math.min(MOON_CONFIG.maxDiameter, diameter))
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试生成月球
|
||||
* OGame规则:
|
||||
* - 每100k残骸 = 1%概率
|
||||
* - 最高20%概率(需要2M残骸)
|
||||
* - 月球初始只有1格空间
|
||||
* - 直径基于moonchance计算
|
||||
*
|
||||
* @param debrisField 战斗产生的残骸场
|
||||
* @param planetPosition 星球坐标
|
||||
* @param planetId 母星ID
|
||||
* @param playerId 玩家ID
|
||||
* @returns 生成的月球对象,如果未生成则返回null
|
||||
*/
|
||||
@@ -49,6 +74,9 @@ export const tryGenerateMoon = (
|
||||
return null
|
||||
}
|
||||
|
||||
// 计算月球直径
|
||||
const diameter = calculateMoonDiameter(chance)
|
||||
|
||||
// 生成月球
|
||||
const moon: Planet = {
|
||||
id: `moon_${Date.now()}`,
|
||||
@@ -89,10 +117,11 @@ export const tryGenerateMoon = (
|
||||
},
|
||||
buildQueue: [],
|
||||
lastUpdate: Date.now(),
|
||||
maxSpace: MOON_CONFIG.baseSize,
|
||||
maxSpace: MOON_CONFIG.baseFields, // OGame规则:月球初始只有1格空间
|
||||
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
|
||||
isMoon: true,
|
||||
parentPlanetId: planetId
|
||||
parentPlanetId: planetId,
|
||||
diameter: diameter // 月球直径(km)
|
||||
}
|
||||
|
||||
// 初始化所有建筑为0级
|
||||
@@ -118,3 +147,182 @@ export const hasMoonAtPosition = (planets: Planet[], position: { galaxy: number;
|
||||
p.position.position === position.position
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 月球销毁结果类型
|
||||
*/
|
||||
export interface MoonDestructionResult {
|
||||
moonDestroyed: boolean // 月球是否被销毁
|
||||
deathstarsDestroyed: boolean // 死星是否被反向销毁
|
||||
moonDestructionChance: number // 月球销毁概率
|
||||
deathstarDestructionChance: number // 死星被销毁概率
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算月球销毁概率
|
||||
* OGame规则:销毁概率 = (100 - √diameter) × √deathstars
|
||||
* @param moonDiameter 月球直径(km)
|
||||
* @param deathstarCount 死星数量
|
||||
* @returns 销毁概率(0-100)
|
||||
*/
|
||||
export const calculateMoonDestructionChance = (moonDiameter: number, deathstarCount: number): number => {
|
||||
if (deathstarCount <= 0) return 0
|
||||
|
||||
// 公式:(100 - √diameter) × √deathstars
|
||||
const chance = (100 - Math.sqrt(moonDiameter)) * Math.sqrt(deathstarCount)
|
||||
|
||||
// 限制在0-100之间
|
||||
return Math.max(0, Math.min(100, chance))
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算死星被反向销毁的概率
|
||||
* OGame规则:反向销毁概率 = √diameter / 2
|
||||
* @param moonDiameter 月球直径(km)
|
||||
* @returns 死星被销毁概率(0-100)
|
||||
*/
|
||||
export const calculateDeathstarDestructionChance = (moonDiameter: number): number => {
|
||||
// 公式:√diameter / 2
|
||||
const chance = Math.sqrt(moonDiameter) / 2
|
||||
|
||||
// 限制在0-100之间
|
||||
return Math.max(0, Math.min(100, chance))
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试销毁月球(死星攻击月球时调用)
|
||||
* OGame规则:
|
||||
* - 死星攻击月球时,先进行正常战斗
|
||||
* - 战斗后,如果死星存活,则判定月球销毁
|
||||
* - 月球销毁概率 = (100 - √diameter) × √deathstars
|
||||
* - 死星反向销毁概率 = √diameter / 2
|
||||
* - 两个概率独立判定
|
||||
*
|
||||
* @param moon 目标月球
|
||||
* @param deathstarCount 攻击的死星数量
|
||||
* @returns 销毁结果
|
||||
*/
|
||||
export const tryDestroyMoon = (moon: Planet, deathstarCount: number): MoonDestructionResult => {
|
||||
if (!moon.isMoon || deathstarCount <= 0) {
|
||||
return {
|
||||
moonDestroyed: false,
|
||||
deathstarsDestroyed: false,
|
||||
moonDestructionChance: 0,
|
||||
deathstarDestructionChance: 0
|
||||
}
|
||||
}
|
||||
|
||||
const diameter = moon.diameter || MOON_CONFIG.minDiameter
|
||||
|
||||
// 计算概率
|
||||
const moonDestructionChance = calculateMoonDestructionChance(diameter, deathstarCount)
|
||||
const deathstarDestructionChance = calculateDeathstarDestructionChance(diameter)
|
||||
|
||||
// 独立判定
|
||||
const moonRoll = Math.random() * 100
|
||||
const deathstarRoll = Math.random() * 100
|
||||
|
||||
const moonDestroyed = moonRoll < moonDestructionChance
|
||||
const deathstarsDestroyed = deathstarRoll < deathstarDestructionChance
|
||||
|
||||
return {
|
||||
moonDestroyed,
|
||||
deathstarsDestroyed,
|
||||
moonDestructionChance,
|
||||
deathstarDestructionChance
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算传感器阵列扫描范围
|
||||
* OGame规则:范围 = level² - 1 个星系
|
||||
* @param sensorPhalanxLevel 传感器阵列等级
|
||||
* @returns 可扫描的星系范围(单向)
|
||||
*/
|
||||
export const calculateSensorPhalanxRange = (sensorPhalanxLevel: number): number => {
|
||||
if (sensorPhalanxLevel <= 0) return 0
|
||||
return sensorPhalanxLevel * sensorPhalanxLevel - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目标坐标是否在传感器阵列扫描范围内
|
||||
* @param moonPosition 月球坐标
|
||||
* @param targetPosition 目标坐标
|
||||
* @param sensorPhalanxLevel 传感器阵列等级
|
||||
* @returns 是否在扫描范围内
|
||||
*/
|
||||
export const isInSensorPhalanxRange = (
|
||||
moonPosition: { galaxy: number; system: number; position: number },
|
||||
targetPosition: { galaxy: number; system: number; position: number },
|
||||
sensorPhalanxLevel: number
|
||||
): boolean => {
|
||||
// 必须在同一银河系
|
||||
if (moonPosition.galaxy !== targetPosition.galaxy) return false
|
||||
|
||||
const range = calculateSensorPhalanxRange(sensorPhalanxLevel)
|
||||
const systemDistance = Math.abs(moonPosition.system - targetPosition.system)
|
||||
|
||||
return systemDistance <= range
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查跳跃门是否可用(冷却是否结束)
|
||||
* OGame规则:跳跃门使用后有1小时冷却时间
|
||||
* @param moon 月球对象
|
||||
* @returns 是否可以使用跳跃门
|
||||
*/
|
||||
export const isJumpGateReady = (moon: Planet): boolean => {
|
||||
if (!moon.isMoon) return false
|
||||
if (!moon.buildings[BuildingType.JumpGate] || moon.buildings[BuildingType.JumpGate] <= 0) return false
|
||||
|
||||
const lastUsed = moon.jumpGateLastUsed || 0
|
||||
const cooldownEnd = lastUsed + MOON_CONFIG.jumpGateCooldown
|
||||
|
||||
return Date.now() >= cooldownEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取跳跃门剩余冷却时间(毫秒)
|
||||
* @param moon 月球对象
|
||||
* @returns 剩余冷却时间(毫秒),0表示已冷却完成
|
||||
*/
|
||||
export const getJumpGateCooldownRemaining = (moon: Planet): number => {
|
||||
if (!moon.isMoon) return 0
|
||||
if (!moon.buildings[BuildingType.JumpGate] || moon.buildings[BuildingType.JumpGate] <= 0) return 0
|
||||
|
||||
const lastUsed = moon.jumpGateLastUsed || 0
|
||||
const cooldownEnd = lastUsed + MOON_CONFIG.jumpGateCooldown
|
||||
const remaining = cooldownEnd - Date.now()
|
||||
|
||||
return Math.max(0, remaining)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用跳跃门(记录使用时间)
|
||||
* @param moon 月球对象
|
||||
*/
|
||||
export const useJumpGate = (moon: Planet): void => {
|
||||
if (moon.isMoon) {
|
||||
moon.jumpGateLastUsed = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查两个月球之间是否可以使用跳跃门传送
|
||||
* OGame规则:两个月球都必须有跳跃门,且都必须冷却完成
|
||||
* @param sourceMoon 源月球
|
||||
* @param targetMoon 目标月球
|
||||
* @returns 是否可以传送
|
||||
*/
|
||||
export const canUseJumpGate = (sourceMoon: Planet, targetMoon: Planet): boolean => {
|
||||
// 两个都必须是月球
|
||||
if (!sourceMoon.isMoon || !targetMoon.isMoon) return false
|
||||
|
||||
// 两个都必须有跳跃门
|
||||
const sourceHasGate = sourceMoon.buildings[BuildingType.JumpGate] > 0
|
||||
const targetHasGate = targetMoon.buildings[BuildingType.JumpGate] > 0
|
||||
if (!sourceHasGate || !targetHasGate) return false
|
||||
|
||||
// 两个跳跃门都必须冷却完成
|
||||
return isJumpGateReady(sourceMoon) && isJumpGateReady(targetMoon)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { NPC, Planet, Player } from '@/types/game'
|
||||
import { TechnologyType, BuildingType, ShipType } from '@/types/game'
|
||||
import { TechnologyType, BuildingType, ShipType, DefenseType } from '@/types/game'
|
||||
import { BUILDINGS, SHIPS, TECHNOLOGIES } from '@/config/gameConfig'
|
||||
import * as buildingLogic from './buildingLogic'
|
||||
import * as researchLogic from './researchLogic'
|
||||
@@ -632,3 +632,347 @@ export const initializeNPCDiplomacy = (npcs: NPC[]): void => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 距离难度系统 ====================
|
||||
|
||||
/**
|
||||
* 计算NPC星球到玩家母星的距离
|
||||
* 银河系距离权重最大,星系次之,位置最小
|
||||
*/
|
||||
export const calculateDistanceToHomeworld = (
|
||||
npcPosition: { galaxy: number; system: number; position: number },
|
||||
homeworldPosition: { galaxy: number; system: number; position: number }
|
||||
): number => {
|
||||
const galaxyDistance = Math.abs(npcPosition.galaxy - homeworldPosition.galaxy)
|
||||
const systemDistance = Math.abs(npcPosition.system - homeworldPosition.system)
|
||||
const positionDistance = Math.abs(npcPosition.position - homeworldPosition.position)
|
||||
|
||||
// 银河系跨度权重最大
|
||||
return galaxyDistance * 100 + systemDistance * 10 + positionDistance
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于距离计算难度等级(1-无限)
|
||||
*/
|
||||
export const calculateDifficultyLevel = (distance: number): number => {
|
||||
if (distance <= 10) return 1 // 新手
|
||||
if (distance <= 30) return 2 // 简单
|
||||
if (distance <= 60) return 3 // 普通
|
||||
if (distance <= 100) return 4 // 困难
|
||||
if (distance <= 200) return 5 // 专家
|
||||
if (distance <= 400) return 6 // 大师
|
||||
// 超过400继续增长
|
||||
return 6 + Math.floor((distance - 400) / 200)
|
||||
}
|
||||
|
||||
/**
|
||||
* 距离难度倍率配置
|
||||
*/
|
||||
export interface DistanceDifficultyMultipliers {
|
||||
buildingMultiplier: number
|
||||
techMultiplier: number
|
||||
fleetMultiplier: number
|
||||
resourceMultiplier: number
|
||||
defenseMultiplier: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于距离计算实力倍率
|
||||
* 使用对数增长,确保数值合理
|
||||
*
|
||||
* 建筑等级上限:30
|
||||
* 科技等级上限:20
|
||||
*
|
||||
* 建筑等级示例(baseLevel=5):
|
||||
* - 距离 0: 5级
|
||||
* - 距离 10: 8级
|
||||
* - 距离 50: 14级
|
||||
* - 距离 100: 18级
|
||||
* - 距离 200: 22级
|
||||
* - 距离 400: 26级
|
||||
* - 距离 800+: 30级(上限)
|
||||
*/
|
||||
export const calculateDistanceDifficultyMultiplier = (distance: number): DistanceDifficultyMultipliers => {
|
||||
// 使用对数增长,确保建筑等级在合理范围内
|
||||
// 公式: 1 + ln(1 + distance) * 0.8
|
||||
// 这样可以确保最大建筑倍率约为6倍(5*6=30)
|
||||
const logMultiplier = 1 + Math.log(1 + distance) * 0.8
|
||||
|
||||
// 限制建筑倍率最大为6(因为基础等级是5,5*6=30)
|
||||
const buildingMultiplier = Math.min(6, Math.max(1, Math.round(logMultiplier)))
|
||||
|
||||
// 科技倍率稍低,上限4倍(5*4=20)
|
||||
const techMultiplier = Math.min(4, Math.max(1, Math.round(logMultiplier * 0.7)))
|
||||
|
||||
// 舰队倍率:使用更平缓的增长
|
||||
// 基础舰队10艘,最大约500艘左右
|
||||
const fleetMultiplier = Math.min(50, Math.max(1, Math.round(logMultiplier * 8)))
|
||||
|
||||
// 资源倍率:限制最大20倍,避免Infinity
|
||||
const resourceMultiplier = Math.min(20, Math.max(1, logMultiplier * 3))
|
||||
|
||||
// 防御倍率
|
||||
const defenseMultiplier = Math.min(30, Math.max(1, Math.round(logMultiplier * 5)))
|
||||
|
||||
return {
|
||||
buildingMultiplier,
|
||||
techMultiplier,
|
||||
fleetMultiplier,
|
||||
resourceMultiplier,
|
||||
defenseMultiplier
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于距离难度初始化NPC星球
|
||||
* 替代旧的 initializeNPCStartingPower
|
||||
*
|
||||
* 建筑等级上限:30
|
||||
* 科技等级上限:20
|
||||
* 资源上限:基于仓储建筑等级计算 (10000 * 2^level)
|
||||
* 舰队数量:基于船坞等级和难度等级合理计算
|
||||
*/
|
||||
export const initializeNPCByDistance = (
|
||||
npc: NPC,
|
||||
homeworldPosition: { galaxy: number; system: number; position: number }
|
||||
): void => {
|
||||
const planet = npc.planets[0]
|
||||
if (!planet) return
|
||||
|
||||
const distance = calculateDistanceToHomeworld(planet.position, homeworldPosition)
|
||||
const multipliers = calculateDistanceDifficultyMultiplier(distance)
|
||||
|
||||
// 保存距离和难度等级到NPC
|
||||
npc.distanceToHomeworld = distance
|
||||
npc.difficultyLevel = calculateDifficultyLevel(distance)
|
||||
|
||||
// 基础等级 * 倍率,并限制上限
|
||||
const baseLevel = 5
|
||||
const MAX_BUILDING_LEVEL = 30
|
||||
const MAX_TECH_LEVEL = 20
|
||||
|
||||
const targetBuildingLevel = Math.min(MAX_BUILDING_LEVEL, Math.max(1, Math.floor(baseLevel * multipliers.buildingMultiplier)))
|
||||
const targetTechLevel = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(baseLevel * multipliers.techMultiplier)))
|
||||
|
||||
// 设置资源建筑(上限30)
|
||||
planet.buildings[BuildingType.MetalMine] = Math.min(MAX_BUILDING_LEVEL, targetBuildingLevel)
|
||||
planet.buildings[BuildingType.CrystalMine] = Math.min(MAX_BUILDING_LEVEL, Math.max(1, Math.floor(targetBuildingLevel * 0.8)))
|
||||
planet.buildings[BuildingType.DeuteriumSynthesizer] = Math.min(MAX_BUILDING_LEVEL, Math.max(1, Math.floor(targetBuildingLevel * 0.6)))
|
||||
planet.buildings[BuildingType.SolarPlant] = Math.min(MAX_BUILDING_LEVEL, targetBuildingLevel + 2)
|
||||
|
||||
// 设置设施建筑
|
||||
planet.buildings[BuildingType.RoboticsFactory] = Math.min(15, Math.max(1, Math.floor(targetBuildingLevel * 0.5)))
|
||||
planet.buildings[BuildingType.Shipyard] = Math.min(12, Math.max(1, Math.floor(targetBuildingLevel * 0.4)))
|
||||
planet.buildings[BuildingType.ResearchLab] = Math.min(12, Math.max(1, Math.floor(targetBuildingLevel * 0.4)))
|
||||
|
||||
// 设置仓储(上限10级,对应10000*2^10=10,240,000容量)
|
||||
const storageLevel = Math.min(10, Math.max(1, Math.floor(targetBuildingLevel * 0.3)))
|
||||
planet.buildings[BuildingType.MetalStorage] = storageLevel
|
||||
planet.buildings[BuildingType.CrystalStorage] = storageLevel
|
||||
planet.buildings[BuildingType.DeuteriumTank] = storageLevel
|
||||
|
||||
// 设置科技(上限20)
|
||||
npc.technologies[TechnologyType.EnergyTechnology] = Math.min(MAX_TECH_LEVEL, targetTechLevel)
|
||||
npc.technologies[TechnologyType.ComputerTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.8)))
|
||||
npc.technologies[TechnologyType.WeaponsTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.7)))
|
||||
npc.technologies[TechnologyType.ShieldingTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.7)))
|
||||
npc.technologies[TechnologyType.ArmourTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.7)))
|
||||
npc.technologies[TechnologyType.CombustionDrive] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.6)))
|
||||
npc.technologies[TechnologyType.EspionageTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.5)))
|
||||
|
||||
// 计算舰队仓储容量(船坞每级+1000)
|
||||
const shipyardLevel = planet.buildings[BuildingType.Shipyard] || 1
|
||||
const fleetStorageCapacity = shipyardLevel * 1000
|
||||
|
||||
// 基于难度等级和船坞容量计算舰队数量
|
||||
// 难度等级1-7对应不同的舰队规模
|
||||
const difficultyLevel = npc.difficultyLevel || 1
|
||||
const fleetScale = Math.min(1, difficultyLevel / 7) // 0.14 ~ 1.0
|
||||
|
||||
// 设置舰队(基于船坞容量和难度等级)
|
||||
// 总舰队数量不超过船坞容量的80%
|
||||
const maxTotalFleet = Math.floor(fleetStorageCapacity * 0.8)
|
||||
const baseFleetCount = Math.floor(maxTotalFleet * fleetScale)
|
||||
|
||||
// 分配舰队比例
|
||||
planet.fleet[ShipType.EspionageProbe] = Math.max(5, Math.floor(baseFleetCount * 0.05))
|
||||
planet.fleet[ShipType.LightFighter] = Math.floor(baseFleetCount * 0.35)
|
||||
planet.fleet[ShipType.HeavyFighter] = Math.floor(baseFleetCount * 0.20)
|
||||
planet.fleet[ShipType.Cruiser] = Math.floor(baseFleetCount * 0.15)
|
||||
planet.fleet[ShipType.Battleship] = Math.floor(baseFleetCount * 0.05)
|
||||
planet.fleet[ShipType.SmallCargo] = Math.floor(baseFleetCount * 0.10)
|
||||
planet.fleet[ShipType.Recycler] = Math.floor(baseFleetCount * 0.10)
|
||||
|
||||
// 设置防御设施(基于难度等级,合理范围)
|
||||
const defenseScale = difficultyLevel * 5
|
||||
planet.defense[DefenseType.RocketLauncher] = Math.floor(defenseScale * 2)
|
||||
planet.defense[DefenseType.LightLaser] = Math.floor(defenseScale * 1.5)
|
||||
planet.defense[DefenseType.HeavyLaser] = Math.floor(defenseScale * 0.8)
|
||||
planet.defense[DefenseType.GaussCannon] = Math.floor(defenseScale * 0.3)
|
||||
planet.defense[DefenseType.IonCannon] = Math.floor(defenseScale * 0.3)
|
||||
// 高级防御设施只有高难度NPC才有
|
||||
if (difficultyLevel >= 4) {
|
||||
planet.defense[DefenseType.PlasmaTurret] = Math.floor(defenseScale * 0.1)
|
||||
planet.defense[DefenseType.SmallShieldDome] = 1
|
||||
}
|
||||
if (difficultyLevel >= 6) {
|
||||
planet.defense[DefenseType.LargeShieldDome] = 1
|
||||
}
|
||||
|
||||
// 设置资源(基于仓储建筑等级计算容量上限)
|
||||
// 容量公式:10000 * 2^level
|
||||
const metalCapacity = 10000 * Math.pow(2, planet.buildings[BuildingType.MetalStorage] || 0)
|
||||
const crystalCapacity = 10000 * Math.pow(2, planet.buildings[BuildingType.CrystalStorage] || 0)
|
||||
const deuteriumCapacity = 10000 * Math.pow(2, planet.buildings[BuildingType.DeuteriumTank] || 0)
|
||||
const darkMatterCapacity = 1000 * Math.pow(2, planet.buildings[BuildingType.DarkMatterTank] || 0)
|
||||
|
||||
// 资源设置为容量的50%-80%(基于难度等级)
|
||||
const resourceFillRate = 0.5 + (difficultyLevel / 7) * 0.3
|
||||
planet.resources.metal = Math.floor(metalCapacity * resourceFillRate)
|
||||
planet.resources.crystal = Math.floor(crystalCapacity * resourceFillRate)
|
||||
planet.resources.deuterium = Math.floor(deuteriumCapacity * resourceFillRate)
|
||||
planet.resources.darkMatter = Math.floor(darkMatterCapacity * resourceFillRate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于距离的NPC成长配置
|
||||
*/
|
||||
export interface DistanceBasedGrowthConfig {
|
||||
resourceGrowthRate: number
|
||||
buildingGrowthSpeed: number
|
||||
techGrowthSpeed: number
|
||||
checkInterval: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据距离计算NPC成长配置
|
||||
*/
|
||||
export const calculateDistanceBasedGrowthConfig = (distance: number): DistanceBasedGrowthConfig => {
|
||||
const multipliers = calculateDistanceDifficultyMultiplier(distance)
|
||||
|
||||
return {
|
||||
resourceGrowthRate: multipliers.resourceMultiplier,
|
||||
buildingGrowthSpeed: multipliers.buildingMultiplier,
|
||||
techGrowthSpeed: multipliers.techMultiplier,
|
||||
checkInterval: 180 // 3分钟检查一次
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于距离更新NPC成长
|
||||
* 替代旧的 updateNPCGrowth 中基于玩家积分的逻辑
|
||||
*/
|
||||
export const updateNPCGrowthByDistance = (
|
||||
npc: NPC,
|
||||
homeworldPosition: { galaxy: number; system: number; position: number },
|
||||
deltaSeconds: number,
|
||||
gameSpeed: number = 1
|
||||
): void => {
|
||||
const planet = npc.planets[0]
|
||||
if (!planet) return
|
||||
|
||||
// 如果没有距离信息,先计算
|
||||
if (npc.distanceToHomeworld === undefined) {
|
||||
npc.distanceToHomeworld = calculateDistanceToHomeworld(planet.position, homeworldPosition)
|
||||
npc.difficultyLevel = calculateDifficultyLevel(npc.distanceToHomeworld)
|
||||
}
|
||||
|
||||
const config = calculateDistanceBasedGrowthConfig(npc.distanceToHomeworld)
|
||||
|
||||
// 1. 持续生成资源(应用游戏速度倍率)
|
||||
generateNPCResourcesByDistance(npc, deltaSeconds, config, gameSpeed)
|
||||
|
||||
// 2. 定期评估并调整实力
|
||||
const now = Date.now()
|
||||
const lastGrowthCheck = (npc as any).lastGrowthCheck || 0
|
||||
|
||||
if (now - lastGrowthCheck >= config.checkInterval * 1000) {
|
||||
;(npc as any).lastGrowthCheck = now
|
||||
|
||||
// 计算目标实力(基于距离倍率)
|
||||
const multipliers = calculateDistanceDifficultyMultiplier(npc.distanceToHomeworld)
|
||||
const baseLevel = 5
|
||||
const targetBuildingLevel = Math.floor(baseLevel * multipliers.buildingMultiplier)
|
||||
const targetTechLevel = Math.floor(baseLevel * multipliers.techMultiplier)
|
||||
|
||||
// 获取当前平均建筑等级
|
||||
let totalBuildingLevels = 0
|
||||
let buildingCount = 0
|
||||
Object.values(planet.buildings).forEach(level => {
|
||||
totalBuildingLevels += level
|
||||
buildingCount++
|
||||
})
|
||||
const avgBuildingLevel = buildingCount > 0 ? totalBuildingLevels / buildingCount : 0
|
||||
|
||||
// 获取当前平均科技等级
|
||||
const techLevels = Object.values(npc.technologies)
|
||||
const avgTechLevel = techLevels.length > 0 ? techLevels.reduce((sum, level) => sum + level, 0) / techLevels.length : 0
|
||||
|
||||
// 如果实力不足,进行升级
|
||||
if (avgBuildingLevel < targetBuildingLevel) {
|
||||
autoUpgradeNPCBuildings(npc)
|
||||
}
|
||||
|
||||
if (avgTechLevel < targetTechLevel) {
|
||||
autoResearchNPCTechnologies(npc)
|
||||
}
|
||||
|
||||
// 计算目标舰队战力
|
||||
const targetFleetPower = 1000 * multipliers.fleetMultiplier
|
||||
let currentFleetPower = 0
|
||||
Object.entries(planet.fleet).forEach(([shipType, count]) => {
|
||||
const shipConfig = SHIPS[shipType as ShipType]
|
||||
const power = shipConfig.attack + shipConfig.shield + shipConfig.armor / 10
|
||||
currentFleetPower += power * (count as number)
|
||||
})
|
||||
|
||||
if (currentFleetPower < targetFleetPower) {
|
||||
autoBuildNPCFleet(npc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于距离生成NPC资源
|
||||
*/
|
||||
export const generateNPCResourcesByDistance = (
|
||||
npc: NPC,
|
||||
deltaSeconds: number,
|
||||
config: DistanceBasedGrowthConfig,
|
||||
gameSpeed: number = 1
|
||||
): void => {
|
||||
const planet = npc.planets[0]
|
||||
if (!planet) return
|
||||
|
||||
// 基于建筑等级计算资源产量
|
||||
const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0
|
||||
const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0
|
||||
const deuteriumLevel = planet.buildings[BuildingType.DeuteriumSynthesizer] || 0
|
||||
const darkMatterLevel = planet.buildings[BuildingType.DarkMatterCollector] || 0
|
||||
|
||||
// 简化的资源产量计算(每秒产量)
|
||||
const metalProduction = 30 * metalMineLevel * Math.pow(1.1, metalMineLevel) * config.resourceGrowthRate
|
||||
const crystalProduction = 20 * crystalMineLevel * Math.pow(1.1, crystalMineLevel) * config.resourceGrowthRate
|
||||
const deuteriumProduction = 10 * deuteriumLevel * Math.pow(1.1, deuteriumLevel) * config.resourceGrowthRate
|
||||
const darkMatterProduction = ((25 * darkMatterLevel * Math.pow(1.5, darkMatterLevel)) / 3600) * config.resourceGrowthRate
|
||||
|
||||
// 应用游戏速度倍率到时间
|
||||
const effectiveDeltaSeconds = deltaSeconds * gameSpeed
|
||||
|
||||
// 增加资源
|
||||
planet.resources.metal += metalProduction * effectiveDeltaSeconds
|
||||
planet.resources.crystal += crystalProduction * effectiveDeltaSeconds
|
||||
planet.resources.deuterium += deuteriumProduction * effectiveDeltaSeconds
|
||||
planet.resources.darkMatter += darkMatterProduction * effectiveDeltaSeconds
|
||||
|
||||
// 确保不超过存储上限
|
||||
const metalStorage = planet.buildings[BuildingType.MetalStorage] || 0
|
||||
const crystalStorage = planet.buildings[BuildingType.CrystalStorage] || 0
|
||||
const deuteriumStorage = planet.buildings[BuildingType.DeuteriumTank] || 0
|
||||
const darkMatterStorage = planet.buildings[BuildingType.DarkMatterTank] || 0
|
||||
|
||||
planet.resources.metal = Math.min(planet.resources.metal, 10000 * Math.pow(2, metalStorage))
|
||||
planet.resources.crystal = Math.min(planet.resources.crystal, 10000 * Math.pow(2, crystalStorage))
|
||||
planet.resources.deuterium = Math.min(planet.resources.deuterium, 10000 * Math.pow(2, deuteriumStorage))
|
||||
planet.resources.darkMatter = Math.min(planet.resources.darkMatter, 1000 * Math.pow(2, darkMatterStorage))
|
||||
}
|
||||
|
||||
@@ -143,12 +143,18 @@ export const calculateMoonChance = (debrisField: Resources): number => {
|
||||
|
||||
/**
|
||||
* 创建月球
|
||||
* @param parentPlanet 母星球
|
||||
* @param position 坐标
|
||||
* @param playerId 玩家ID
|
||||
* @param moonSuffix 月球名称后缀
|
||||
* @param diameter 月球直径(km),用于计算销毁概率
|
||||
*/
|
||||
export const createMoon = (
|
||||
parentPlanet: Planet,
|
||||
position: { galaxy: number; system: number; position: number },
|
||||
playerId: string,
|
||||
moonSuffix: string = "'s Moon"
|
||||
moonSuffix: string = "'s Moon",
|
||||
diameter?: number
|
||||
): Planet => {
|
||||
const moonId = `moon_${Date.now()}`
|
||||
const moon: Planet = {
|
||||
@@ -196,10 +202,11 @@ export const createMoon = (
|
||||
},
|
||||
buildQueue: [],
|
||||
lastUpdate: Date.now(),
|
||||
maxSpace: MOON_CONFIG.baseSize,
|
||||
maxSpace: MOON_CONFIG.baseFields, // OGame规则:月球初始只有1格空间
|
||||
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
|
||||
isMoon: true,
|
||||
parentPlanetId: parentPlanet.id
|
||||
parentPlanetId: parentPlanet.id,
|
||||
diameter: diameter || MOON_CONFIG.minDiameter // 月球直径(km)
|
||||
}
|
||||
|
||||
// 初始化建筑等级
|
||||
@@ -212,11 +219,12 @@ export const createMoon = (
|
||||
|
||||
/**
|
||||
* 计算月球空间上限
|
||||
* OGame规则:月球初始1格,月球基地每级+3格(但月球基地本身占用1格,净增2格)
|
||||
*/
|
||||
export const calculateMoonMaxSpace = (moon: Planet): number => {
|
||||
if (!moon.isMoon) return 0
|
||||
const lunarBaseLevel = moon.buildings[BuildingType.LunarBase] || 0
|
||||
return MOON_CONFIG.baseSize + lunarBaseLevel * MOON_CONFIG.lunarBaseSpaceBonus
|
||||
return MOON_CONFIG.baseFields + lunarBaseLevel * MOON_CONFIG.lunarBaseFieldsBonus
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,31 +120,31 @@ export const getResourceCapacity = (planet: Planet, officers: Record<OfficerType
|
||||
* 计算最大建造队列数量
|
||||
* @param planet 星球对象
|
||||
* @param additionalBuildQueue 军官提供的额外队列数量
|
||||
* @returns 最大建造队列数量(基础1个 + 纳米工厂等级 + 军官加成,最多10个)
|
||||
* @returns 最大建造队列数量(基础3个 + 纳米工厂等级 + 军官加成,最多10个)
|
||||
*/
|
||||
export const getMaxBuildQueue = (planet: Planet, additionalBuildQueue: number = 0): number => {
|
||||
const naniteFactoryLevel = planet.buildings[BuildingType.NaniteFactory] || 0
|
||||
return Math.min(1 + naniteFactoryLevel + additionalBuildQueue, 10)
|
||||
return Math.min(3 + naniteFactoryLevel + additionalBuildQueue, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算最大研究队列数量
|
||||
* @param technologies 已研究的科技等级
|
||||
* @returns 最大研究队列数量(基础1个 + 计算机技术等级,最多10个)
|
||||
* @returns 最大研究队列数量(基础3个 + 计算机技术等级,最多10个)
|
||||
*/
|
||||
export const getMaxResearchQueue = (technologies: Partial<Record<TechnologyType, number>>): number => {
|
||||
const computerTechLevel = technologies[TechnologyType.ComputerTechnology] || 0
|
||||
return Math.min(1 + computerTechLevel, 10)
|
||||
return Math.min(3 + computerTechLevel, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算最大舰队任务数量
|
||||
* @param additionalFleetSlots 军官提供的额外槽位数量
|
||||
* @param computerTechnologyLevel 计算机技术等级
|
||||
* @returns 最大舰队任务数量(基础1个 + 计算机技术等级 + 军官加成,最多20个)
|
||||
* @returns 最大舰队任务数量(基础3个 + 计算机技术等级 + 军官加成,最多20个)
|
||||
*/
|
||||
export const getMaxFleetMissions = (additionalFleetSlots: number = 0, computerTechnologyLevel: number = 0): number => {
|
||||
return Math.min(1 + computerTechnologyLevel + additionalFleetSlots, 20)
|
||||
return Math.min(3 + computerTechnologyLevel + additionalFleetSlots, 20)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -97,6 +97,8 @@ export const calculateResourceCapacity = (planet: Planet, storageCapacityBonus:
|
||||
|
||||
const bonus = 1 + (storageCapacityBonus || 0) / 100
|
||||
|
||||
// OGame规则:基础容量10000,资源可以超过容量(只影响生产,不会丢失)
|
||||
// 月球没有矿场,所以超过容量没有影响,玩家可以从行星运输资源到月球
|
||||
const baseCapacity = 10000
|
||||
const darkMatterBaseCapacity = 1000 // 暗物质基础容量较小
|
||||
return {
|
||||
|
||||
@@ -16,6 +16,7 @@ const router = createRouter({
|
||||
{ path: '/messages', name: 'messages', component: () => import('@/views/MessagesView.vue') },
|
||||
{ path: '/galaxy', name: 'galaxy', component: () => import('@/views/GalaxyView.vue') },
|
||||
{ path: '/diplomacy', name: 'diplomacy', component: () => import('@/views/DiplomacyView.vue') },
|
||||
{ path: '/achievements', name: 'achievements', component: () => import('@/views/AchievementsView.vue') },
|
||||
{ path: '/settings', name: 'settings', component: () => import('@/views/SettingsView.vue') },
|
||||
{ path: '/gm', name: 'gm', component: () => import('@/views/GMView.vue') },
|
||||
{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFoundView.vue') }
|
||||
|
||||
@@ -10,9 +10,12 @@ import type {
|
||||
SpiedNotification,
|
||||
NPCActivityNotification,
|
||||
IncomingFleetAlert,
|
||||
MissileAttack
|
||||
MissileAttack,
|
||||
AchievementStats,
|
||||
AchievementProgress
|
||||
} from '@/types/game'
|
||||
import { TechnologyType, OfficerType } from '@/types/game'
|
||||
import { initializeAchievementStats, initializeAchievements } from '@/logic/achievementLogic'
|
||||
import type { Locale } from '@/locales'
|
||||
import pkg from '../../package.json'
|
||||
import { encryptData, decryptData } from '@/utils/crypto'
|
||||
@@ -41,7 +44,9 @@ export const useGameStore = defineStore('game', {
|
||||
giftRejectedNotifications: [],
|
||||
points: 0,
|
||||
isGMEnabled: false, // 明确设置 GM 模式默认为 false
|
||||
lastVersionCheckTime: 0 // 最后一次检查版本的时间戳,默认为0
|
||||
lastVersionCheckTime: 0, // 最后一次检查版本的时间戳,默认为0
|
||||
achievementStats: initializeAchievementStats() as AchievementStats,
|
||||
achievements: initializeAchievements() as Record<string, AchievementProgress>
|
||||
} as Player,
|
||||
currentPlanetId: '',
|
||||
isDark: '',
|
||||
|
||||
@@ -267,6 +267,16 @@ export interface DiplomaticReport {
|
||||
read?: boolean // 已读状态
|
||||
}
|
||||
|
||||
// 舰队预设
|
||||
export interface FleetPreset {
|
||||
id: string
|
||||
name: string // 自定义预设名称
|
||||
fleet: Partial<Fleet> // 预设舰队数量
|
||||
targetPosition?: { galaxy: number; system: number; position: number } // 预设目标坐标
|
||||
missionType?: MissionType // 预设任务类型
|
||||
cargo?: Partial<Resources> // 预设运输资源
|
||||
}
|
||||
|
||||
// 舰队任务
|
||||
export interface FleetMission {
|
||||
id: string
|
||||
@@ -275,6 +285,7 @@ export interface FleetMission {
|
||||
isHostile?: boolean // 是否是敌对任务(用于警告显示)
|
||||
originPlanetId: string
|
||||
targetPosition: { galaxy: number; system: number; position: number }
|
||||
targetIsMoon?: boolean // 目标是否为月球(用于区分同坐标的行星和月球)
|
||||
targetPlanetId?: string
|
||||
debrisFieldId?: string // 残骸场ID(用于回收任务)
|
||||
missionType: MissionType
|
||||
@@ -413,6 +424,8 @@ export interface MissionReport {
|
||||
message: string // 任务结果描述
|
||||
// 任务特定的详细信息
|
||||
details?: {
|
||||
// 通用:失败原因
|
||||
failReason?: string
|
||||
// 运输任务:运输的资源
|
||||
transportedResources?: Resources
|
||||
// 殖民任务:新星球信息
|
||||
@@ -423,6 +436,9 @@ export interface MissionReport {
|
||||
remainingDebris?: Pick<Resources, 'metal' | 'crystal'>
|
||||
// 毁灭任务:摧毁的星球
|
||||
destroyedPlanetName?: string
|
||||
// 毁灭任务:概率和死星损失
|
||||
destructionChance?: number
|
||||
deathstarsLost?: boolean
|
||||
// 部署任务:部署的舰队
|
||||
deployedFleet?: Partial<Fleet>
|
||||
// 导弹攻击任务:导弹信息
|
||||
@@ -436,6 +452,8 @@ export interface MissionReport {
|
||||
foundFleet?: Partial<Fleet>
|
||||
// 探险任务:损失的舰船
|
||||
fleetLost?: Partial<Fleet>
|
||||
// 侦查任务:报告ID
|
||||
spyReportId?: string
|
||||
}
|
||||
read?: boolean
|
||||
}
|
||||
@@ -501,6 +519,8 @@ export interface Planet {
|
||||
maxFleetStorage: number // 舰队仓储上限
|
||||
isMoon: boolean // 是否为月球
|
||||
parentPlanetId?: string // 如果是月球,指向母星的ID
|
||||
diameter?: number // 月球直径(km),用于销毁概率计算
|
||||
jumpGateLastUsed?: number // 跳跃门上次使用时间戳(ms),用于冷却计算
|
||||
}
|
||||
|
||||
// 月球特殊配置
|
||||
@@ -586,6 +606,11 @@ export interface Player {
|
||||
hintsEnabled?: boolean // 是否启用弱引导提示(默认true)
|
||||
// 显示设置
|
||||
backgroundEnabled?: boolean // 是否启用背景动画(默认false)
|
||||
// 舰队预设
|
||||
fleetPresets?: FleetPreset[] // 舰队预设列表(最多3个)
|
||||
// 成就系统
|
||||
achievementStats?: AchievementStats // 成就统计数据
|
||||
achievements?: Record<string, AchievementProgress> // 成就进度
|
||||
}
|
||||
|
||||
export interface NotificationSettings {
|
||||
@@ -624,7 +649,10 @@ export interface NPC {
|
||||
note?: string // 玩家添加的备注
|
||||
planets: Planet[]
|
||||
technologies: Record<TechnologyType, number>
|
||||
difficulty: 'easy' | 'medium' | 'hard'
|
||||
difficulty: 'easy' | 'medium' | 'hard' // 保留兼容,不再使用
|
||||
// 距离难度系统
|
||||
difficultyLevel?: number // 基于距离的难度等级(1-无限)
|
||||
distanceToHomeworld?: number // 到玩家母星的距离
|
||||
// 行为跟踪字段
|
||||
lastSpyTime?: number // 上次侦查玩家的时间
|
||||
lastAttackTime?: number // 上次攻击玩家的时间
|
||||
@@ -676,3 +704,121 @@ export interface TutorialProgress {
|
||||
currentStep: string | null // 当前步骤ID
|
||||
skippedAt?: number // 跳过的时间戳
|
||||
}
|
||||
|
||||
// ==================== 成就系统类型 ====================
|
||||
|
||||
// 成就类别枚举
|
||||
export const AchievementCategory = {
|
||||
Resource: 'resource',
|
||||
Building: 'building',
|
||||
Combat: 'combat',
|
||||
Mission: 'mission',
|
||||
Diplomacy: 'diplomacy'
|
||||
} as const
|
||||
export type AchievementCategory = (typeof AchievementCategory)[keyof typeof AchievementCategory]
|
||||
|
||||
// 成就等级枚举
|
||||
export const AchievementTier = {
|
||||
Bronze: 'bronze',
|
||||
Silver: 'silver',
|
||||
Gold: 'gold',
|
||||
Platinum: 'platinum',
|
||||
Diamond: 'diamond'
|
||||
} as const
|
||||
export type AchievementTier = (typeof AchievementTier)[keyof typeof AchievementTier]
|
||||
|
||||
// 成就统计数据接口
|
||||
export interface AchievementStats {
|
||||
// 资源统计
|
||||
totalMetalProduced: number
|
||||
totalCrystalProduced: number
|
||||
totalDeuteriumProduced: number
|
||||
totalDarkMatterProduced: number
|
||||
totalMetalConsumed: number
|
||||
totalCrystalConsumed: number
|
||||
totalDeuteriumConsumed: number
|
||||
totalDarkMatterConsumed: number
|
||||
|
||||
// 建造统计
|
||||
buildingsUpgraded: number
|
||||
maxBuildingLevel: Record<BuildingType, number>
|
||||
researchCompleted: number
|
||||
maxTechnologyLevel: Record<TechnologyType, number>
|
||||
shipsProduced: Record<ShipType, number>
|
||||
totalShipsProduced: number
|
||||
defensesBuilt: Record<DefenseType, number>
|
||||
totalDefensesBuilt: number
|
||||
|
||||
// 战斗统计
|
||||
attacksLaunched: number
|
||||
attacksWon: number
|
||||
attacksLost: number
|
||||
fleetLostInAttack: Record<ShipType, number>
|
||||
totalFleetLostInAttack: number
|
||||
debrisCreatedFromAttacks: number
|
||||
defensesSuccessful: number
|
||||
defensesFailed: number
|
||||
fleetLostInDefense: Record<ShipType, number>
|
||||
totalFleetLostInDefense: number
|
||||
defenseLostInDefense: Record<DefenseType, number>
|
||||
totalDefenseLostInDefense: number
|
||||
enemyFleetDestroyedInDefense: Record<ShipType, number>
|
||||
totalEnemyFleetDestroyedInDefense: number
|
||||
debrisCreatedFromDefenses: number
|
||||
|
||||
// 任务统计
|
||||
totalFlightMissions: number
|
||||
transportMissions: number
|
||||
transportedResources: number
|
||||
colonizations: number
|
||||
spyMissions: number
|
||||
deployments: number
|
||||
expeditionsTotal: number
|
||||
expeditionsSuccessful: number
|
||||
recyclingMissions: number
|
||||
recycledResources: number
|
||||
planetDestructions: number
|
||||
fuelConsumed: number
|
||||
|
||||
// 外交统计
|
||||
friendlyNPCCount: number
|
||||
hostileNPCCount: number
|
||||
giftsSent: number
|
||||
giftResourcesTotal: number
|
||||
attackedByNPC: number
|
||||
spiedByNPC: number
|
||||
debrisRecycledByNPC: number
|
||||
debrisResourcesLostToNPC: number
|
||||
}
|
||||
|
||||
// 成就等级配置
|
||||
export interface AchievementTierConfig {
|
||||
tier: AchievementTier
|
||||
target: number
|
||||
reward: AchievementReward
|
||||
}
|
||||
|
||||
// 成就奖励
|
||||
export interface AchievementReward {
|
||||
darkMatter?: number
|
||||
points?: number
|
||||
}
|
||||
|
||||
// 成就配置接口
|
||||
export interface AchievementConfig {
|
||||
id: string
|
||||
category: AchievementCategory
|
||||
icon: string
|
||||
tiers: AchievementTierConfig[]
|
||||
statKey: keyof AchievementStats | string
|
||||
checkType: 'gte' | 'eq' | 'sum' | 'max'
|
||||
}
|
||||
|
||||
// 玩家成就进度
|
||||
export interface AchievementProgress {
|
||||
id: string
|
||||
currentTier: AchievementTier | null
|
||||
currentValue: number
|
||||
unlockedAt?: number
|
||||
tierUnlocks: Record<AchievementTier, number | null>
|
||||
}
|
||||
|
||||
343
src/views/AchievementsView.vue
Normal file
@@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<div class="container mx-auto p-4 sm:p-6 space-y-4 sm:space-y-6">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold">{{ t('achievements.title') }}</h1>
|
||||
<div class="flex items-center gap-2">{{ unlockedCount }} / {{ totalCount }} {{ t('achievements.unlocked') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 分类标签 -->
|
||||
<Tabs v-model="activeCategory" class="w-full">
|
||||
<TabsList class="w-full grid grid-cols-5 h-10">
|
||||
<TabsTrigger v-for="category in categories" :key="category.value" :value="category.value" class="text-xs sm:text-sm">
|
||||
{{ t(`achievements.categories.${category.value}`) }}
|
||||
<Badge v-if="getCategoryUnlockedCount(category.value) > 0" class="ml-1 h-5 px-1.5 text-[10px] bg-primary text-primary-foreground">
|
||||
{{ getCategoryUnlockedCount(category.value) }}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<!-- 成就卡片网格 -->
|
||||
<TabsContent v-for="category in categories" :key="category.value" :value="category.value" class="mt-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<Card v-for="achievement in getAchievementsByCategory(category.value)" :key="achievement.id" class="relative overflow-hidden">
|
||||
<!-- 等级指示条 -->
|
||||
<div class="absolute top-0 left-0 right-0 h-1 flex">
|
||||
<div v-for="tier in tierOrder" :key="tier" class="flex-1" :class="getTierBarClass(achievement.id, tier)" />
|
||||
</div>
|
||||
|
||||
<CardHeader class="pt-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="p-2 rounded-lg" :class="getIconBgClass(achievement.id)">
|
||||
<component :is="getIcon(achievement.icon)" class="h-6 w-6" :class="getIconClass(achievement.id)" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<CardTitle class="text-sm sm:text-base flex items-center gap-2">
|
||||
{{ t(`achievements.names.${achievement.id}`) }}
|
||||
<Badge v-if="getCurrentTier(achievement.id)" :class="getTierBadgeClass(getCurrentTier(achievement.id)!)">
|
||||
{{ t(`achievements.tiers.${getCurrentTier(achievement.id)}`) }}
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
<CardDescription class="text-xs mt-1">
|
||||
{{ t(`achievements.descriptions.${achievement.id}`) }}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="space-y-3">
|
||||
<!-- 进度条 -->
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-muted-foreground">{{ t('achievements.progress') }}</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getCurrentValue(achievement.id)) }} /
|
||||
{{ formatNumber(getNextTarget(achievement.id) || getCurrentValue(achievement.id)) }}
|
||||
</span>
|
||||
</div>
|
||||
<Progress :model-value="getProgressPercentage(achievement.id)" class="h-2" />
|
||||
</div>
|
||||
|
||||
<!-- 下一等级奖励 -->
|
||||
<div v-if="getNextTierConfig(achievement.id)" class="p-2 bg-muted/50 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-1">
|
||||
{{ t('achievements.nextTier') }}: {{ t(`achievements.tiers.${getNextTierConfig(achievement.id)!.tier}`) }}
|
||||
</p>
|
||||
<div class="flex items-center gap-3 text-xs">
|
||||
<div v-if="getNextTierConfig(achievement.id)!.reward.darkMatter" class="flex items-center gap-1">
|
||||
<Sparkles class="h-3 w-3 text-purple-500" />
|
||||
<span>+{{ formatNumber(getNextTierConfig(achievement.id)!.reward.darkMatter!) }}</span>
|
||||
</div>
|
||||
<div v-if="getNextTierConfig(achievement.id)!.reward.points" class="flex items-center gap-1">
|
||||
<Star class="h-3 w-3 text-yellow-500" />
|
||||
<span>+{{ formatNumber(getNextTierConfig(achievement.id)!.reward.points!) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已达最高等级 -->
|
||||
<div
|
||||
v-else-if="getCurrentTier(achievement.id) === 'diamond'"
|
||||
class="p-2 bg-gradient-to-r from-purple-500/10 to-blue-500/10 rounded-lg"
|
||||
>
|
||||
<p class="text-xs text-center font-medium text-purple-600 dark:text-purple-400">
|
||||
{{ t('achievements.maxTierReached') }}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { AchievementCategory, AchievementTier, type AchievementTierConfig } from '@/types/game'
|
||||
import { ACHIEVEMENTS, ACHIEVEMENT_MAP, TIER_ORDER, getNextTier } from '@/config/achievementConfig'
|
||||
import { getAchievementProgress } from '@/logic/achievementLogic'
|
||||
import {
|
||||
Sparkles,
|
||||
Star,
|
||||
Gem,
|
||||
Diamond,
|
||||
Droplet,
|
||||
Flame,
|
||||
Building2,
|
||||
FlaskConical,
|
||||
Rocket,
|
||||
Shield,
|
||||
Swords,
|
||||
Crown,
|
||||
ShieldCheck,
|
||||
Bomb,
|
||||
Trash2,
|
||||
Skull,
|
||||
ShieldOff,
|
||||
Plane,
|
||||
Truck,
|
||||
Package,
|
||||
Flag,
|
||||
Eye,
|
||||
ArrowDownToLine,
|
||||
Compass,
|
||||
Sparkle,
|
||||
Recycle,
|
||||
Pickaxe,
|
||||
Zap,
|
||||
Fuel,
|
||||
Handshake as HandshakeIcon,
|
||||
Angry,
|
||||
Gift,
|
||||
HeartHandshake,
|
||||
Target,
|
||||
ScanEye,
|
||||
Banknote,
|
||||
BadgeDollarSign
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
const { t } = useI18n()
|
||||
const gameStore = useGameStore()
|
||||
|
||||
const activeCategory = ref<AchievementCategory>(AchievementCategory.Resource)
|
||||
|
||||
const categories = [
|
||||
{ value: AchievementCategory.Resource },
|
||||
{ value: AchievementCategory.Building },
|
||||
{ value: AchievementCategory.Combat },
|
||||
{ value: AchievementCategory.Mission },
|
||||
{ value: AchievementCategory.Diplomacy }
|
||||
]
|
||||
|
||||
const tierOrder = TIER_ORDER
|
||||
|
||||
// 图标映射
|
||||
const iconMap: Record<string, any> = {
|
||||
Gem,
|
||||
Diamond,
|
||||
Droplet,
|
||||
Sparkles,
|
||||
Flame,
|
||||
Building2,
|
||||
FlaskConical,
|
||||
Rocket,
|
||||
Shield,
|
||||
Swords,
|
||||
Crown,
|
||||
ShieldCheck,
|
||||
Bomb,
|
||||
Trash2,
|
||||
Skull,
|
||||
ShieldOff,
|
||||
Plane,
|
||||
Truck,
|
||||
Package,
|
||||
Flag,
|
||||
Eye,
|
||||
ArrowDownToLine,
|
||||
Compass,
|
||||
Sparkle,
|
||||
Recycle,
|
||||
Pickaxe,
|
||||
Zap,
|
||||
Fuel,
|
||||
HandshakeIcon,
|
||||
Angry,
|
||||
Gift,
|
||||
HeartHandshake,
|
||||
Target,
|
||||
ScanEye,
|
||||
Banknote,
|
||||
BadgeDollarSign
|
||||
}
|
||||
|
||||
const getIcon = (iconName: string) => {
|
||||
return iconMap[iconName] || Sparkles
|
||||
}
|
||||
|
||||
// 获取成就进度
|
||||
const getProgress = (achievementId: string) => {
|
||||
return gameStore.player.achievements?.[achievementId]
|
||||
}
|
||||
|
||||
const getCurrentTier = (achievementId: string) => {
|
||||
return getProgress(achievementId)?.currentTier || null
|
||||
}
|
||||
|
||||
const getCurrentValue = (achievementId: string) => {
|
||||
return getProgress(achievementId)?.currentValue || 0
|
||||
}
|
||||
|
||||
const getNextTarget = (achievementId: string) => {
|
||||
const config = ACHIEVEMENT_MAP[achievementId]
|
||||
if (!config) return null
|
||||
|
||||
const currentTier = getCurrentTier(achievementId)
|
||||
const nextTier = getNextTier(currentTier)
|
||||
if (!nextTier) return null
|
||||
|
||||
const tierConfig = config.tiers.find(t => t.tier === nextTier)
|
||||
return tierConfig?.target ?? null
|
||||
}
|
||||
|
||||
const getNextTierConfig = (achievementId: string): AchievementTierConfig | null => {
|
||||
const config = ACHIEVEMENT_MAP[achievementId]
|
||||
if (!config) return null
|
||||
|
||||
const currentTier = getCurrentTier(achievementId)
|
||||
const nextTier = getNextTier(currentTier)
|
||||
if (!nextTier) return null
|
||||
|
||||
return config.tiers.find(t => t.tier === nextTier) || null
|
||||
}
|
||||
|
||||
const getProgressPercentage = (achievementId: string) => {
|
||||
const currentValue = getCurrentValue(achievementId)
|
||||
const currentTier = getCurrentTier(achievementId)
|
||||
return getAchievementProgress(achievementId, currentValue, currentTier)
|
||||
}
|
||||
|
||||
// 按类别获取成就
|
||||
const getAchievementsByCategory = (category: AchievementCategory) => {
|
||||
return ACHIEVEMENTS.filter(a => a.category === category)
|
||||
}
|
||||
|
||||
// 统计
|
||||
const unlockedCount = computed(() => {
|
||||
if (!gameStore.player.achievements) return 0
|
||||
return Object.values(gameStore.player.achievements).filter(p => p.currentTier !== null).length
|
||||
})
|
||||
|
||||
const totalCount = computed(() => ACHIEVEMENTS.length)
|
||||
|
||||
const getCategoryUnlockedCount = (category: AchievementCategory) => {
|
||||
if (!gameStore.player.achievements) return 0
|
||||
const categoryAchievements = ACHIEVEMENTS.filter(a => a.category === category)
|
||||
return categoryAchievements.filter(a => {
|
||||
const progress = gameStore.player.achievements?.[a.id]
|
||||
return progress?.currentTier !== null
|
||||
}).length
|
||||
}
|
||||
|
||||
// 样式函数
|
||||
const getTierBarClass = (achievementId: string, tier: AchievementTier) => {
|
||||
const progress = getProgress(achievementId)
|
||||
if (!progress) return 'bg-muted'
|
||||
|
||||
const tierUnlock = progress.tierUnlocks[tier]
|
||||
if (tierUnlock !== null) {
|
||||
// 已解锁
|
||||
switch (tier) {
|
||||
case AchievementTier.Bronze:
|
||||
return 'bg-amber-600'
|
||||
case AchievementTier.Silver:
|
||||
return 'bg-gray-400'
|
||||
case AchievementTier.Gold:
|
||||
return 'bg-yellow-500'
|
||||
case AchievementTier.Platinum:
|
||||
return 'bg-cyan-400'
|
||||
case AchievementTier.Diamond:
|
||||
return 'bg-purple-500'
|
||||
}
|
||||
}
|
||||
return 'bg-muted'
|
||||
}
|
||||
|
||||
const getTierBadgeClass = (tier: AchievementTier) => {
|
||||
switch (tier) {
|
||||
case AchievementTier.Bronze:
|
||||
return 'bg-amber-600 text-white'
|
||||
case AchievementTier.Silver:
|
||||
return 'bg-gray-400 text-white'
|
||||
case AchievementTier.Gold:
|
||||
return 'bg-yellow-500 text-black'
|
||||
case AchievementTier.Platinum:
|
||||
return 'bg-cyan-400 text-black'
|
||||
case AchievementTier.Diamond:
|
||||
return 'bg-gradient-to-r from-purple-500 to-blue-500 text-white'
|
||||
}
|
||||
}
|
||||
|
||||
const getIconBgClass = (achievementId: string) => {
|
||||
const tier = getCurrentTier(achievementId)
|
||||
if (!tier) return 'bg-muted'
|
||||
|
||||
switch (tier) {
|
||||
case AchievementTier.Bronze:
|
||||
return 'bg-amber-100 dark:bg-amber-900/30'
|
||||
case AchievementTier.Silver:
|
||||
return 'bg-gray-100 dark:bg-gray-800'
|
||||
case AchievementTier.Gold:
|
||||
return 'bg-yellow-100 dark:bg-yellow-900/30'
|
||||
case AchievementTier.Platinum:
|
||||
return 'bg-cyan-100 dark:bg-cyan-900/30'
|
||||
case AchievementTier.Diamond:
|
||||
return 'bg-purple-100 dark:bg-purple-900/30'
|
||||
}
|
||||
}
|
||||
|
||||
const getIconClass = (achievementId: string) => {
|
||||
const tier = getCurrentTier(achievementId)
|
||||
if (!tier) return 'text-muted-foreground'
|
||||
|
||||
switch (tier) {
|
||||
case AchievementTier.Bronze:
|
||||
return 'text-amber-600'
|
||||
case AchievementTier.Silver:
|
||||
return 'text-gray-500'
|
||||
case AchievementTier.Gold:
|
||||
return 'text-yellow-600'
|
||||
case AchievementTier.Platinum:
|
||||
return 'text-cyan-500'
|
||||
case AchievementTier.Diamond:
|
||||
return 'text-purple-500'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -194,6 +194,7 @@
|
||||
import * as buildingValidation from '@/logic/buildingValidation'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const detailDialog = useDetailDialogStore()
|
||||
@@ -227,8 +228,9 @@
|
||||
return (Object.values(BuildingType) as BuildingType[]).filter(buildingType => {
|
||||
const config = BUILDINGS.value[buildingType]
|
||||
if (planet.value!.isMoon) {
|
||||
// 月球只能建造月球专属建筑
|
||||
return config.moonOnly === true
|
||||
// 月球可以建造:月球专属建筑 + 非行星专属建筑(如机器人工厂、船坞、机库等)
|
||||
// OGame规则:月球不能建造 planetOnly 的建筑(矿场、研究实验室、纳米工厂等)
|
||||
return config.planetOnly !== true
|
||||
} else {
|
||||
// 行星不能建造月球专属建筑
|
||||
return config.moonOnly !== true
|
||||
@@ -245,6 +247,12 @@
|
||||
gameStore.player.officers
|
||||
)
|
||||
if (!validation.valid) return { success: false, reason: validation.reason }
|
||||
|
||||
// 追踪资源消耗(在扣除前计算成本)
|
||||
const currentLevel = gameStore.currentPlanet.buildings[buildingType] || 0
|
||||
const cost = buildingLogic.calculateBuildingCost(buildingType, currentLevel + 1)
|
||||
gameLogic.trackResourceConsumption(gameStore.player, cost)
|
||||
|
||||
const queueItem = buildingValidation.executeBuildingUpgrade(gameStore.currentPlanet, buildingType, gameStore.player.officers)
|
||||
gameStore.currentPlanet.buildQueue.push(queueItem)
|
||||
return { success: true }
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as shipValidation from '@/logic/shipValidation'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const detailDialog = useDetailDialogStore()
|
||||
@@ -236,6 +237,11 @@
|
||||
if (!currentPlanet) return { success: false }
|
||||
const validation = shipValidation.validateDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.technologies)
|
||||
if (!validation.valid) return { success: false, reason: validation.reason }
|
||||
|
||||
// 追踪资源消耗(在扣除前计算成本)
|
||||
const totalCost = shipLogic.calculateDefenseCost(defenseType, quantity)
|
||||
gameLogic.trackResourceConsumption(gameStore.player, totalCost)
|
||||
|
||||
const queueItem = shipValidation.executeDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.officers)
|
||||
currentPlanet.buildQueue.push(queueItem)
|
||||
return { success: true }
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
|
||||
<!-- 标签切换 -->
|
||||
<Tabs v-model="activeTab" class="w-full">
|
||||
<TabsList class="grid w-full grid-cols-3">
|
||||
<TabsTrigger v-for="tab in fleetTabs" :key="tab.value" :value="tab.value">
|
||||
<TabsList :class="['grid', 'w-full', showJumpGateTab ? 'grid-cols-4' : 'grid-cols-3']">
|
||||
<TabsTrigger v-for="tab in visibleTabs" :key="tab.value" :value="tab.value">
|
||||
{{ t(`fleetView.${tab.labelKey}`) }}
|
||||
<Badge v-if="tab.value === 'missions' && gameStore.player.fleetMissions.length > 0" variant="destructive" class="ml-1">
|
||||
{{ gameStore.player.fleetMissions.length }}
|
||||
</Badge>
|
||||
<Badge v-if="tab.value === 'jumpGate' && jumpGateReady" variant="default" class="ml-1">✓</Badge>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -59,6 +60,80 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 舰队预设 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<CardTitle>{{ t('fleetView.fleetPresets') }}</CardTitle>
|
||||
<CardDescription>{{ t('fleetView.fleetPresetsDescription') }}</CardDescription>
|
||||
</div>
|
||||
<Button @click="saveAsPreset" variant="outline" size="sm" :disabled="fleetPresets.length >= MAX_PRESETS">
|
||||
<Save class="h-4 w-4 mr-1" />
|
||||
{{ t('fleetView.savePreset') }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="fleetPresets.length === 0" class="text-center py-4 text-muted-foreground text-sm">
|
||||
{{ t('fleetView.noPresets') }}
|
||||
</div>
|
||||
<div v-else class="space-y-2">
|
||||
<div
|
||||
v-for="preset in fleetPresets"
|
||||
:key="preset.id"
|
||||
class="flex items-center justify-between p-3 border rounded-lg hover:bg-muted/50 transition-colors"
|
||||
:class="{ 'ring-2 ring-primary': editingPresetId === preset.id }"
|
||||
>
|
||||
<div class="flex-1 cursor-pointer" @click="loadPreset(preset)">
|
||||
<div class="flex items-center gap-2">
|
||||
<Star class="h-4 w-4 text-yellow-500" />
|
||||
<span class="font-medium">{{ preset.name }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground mt-1 flex flex-wrap gap-2">
|
||||
<span v-if="preset.targetPosition">
|
||||
[{{ preset.targetPosition.galaxy }}:{{ preset.targetPosition.system }}:{{ preset.targetPosition.position }}]
|
||||
</span>
|
||||
<span v-if="preset.missionType">
|
||||
{{ getMissionName(preset.missionType) }}
|
||||
</span>
|
||||
<span>{{ Object.entries(preset.fleet).filter(([_, count]) => count > 0).length }} {{ t('fleetView.shipTypes') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button v-if="editingPresetId === preset.id" @click="updatePreset(preset.id)" variant="default" size="sm">
|
||||
{{ t('common.save') }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="editingPresetId !== preset.id"
|
||||
@click="editingPresetId = preset.id"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
:title="t('fleetView.editPreset')"
|
||||
>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button @click.stop="startRenamePreset(preset)" variant="ghost" size="sm" :title="t('fleetView.renamePreset')">
|
||||
<Type class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
@click.stop="deletePreset(preset.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="text-destructive hover:text-destructive"
|
||||
:title="t('fleetView.deletePreset')"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="editingPresetId" class="text-xs text-muted-foreground mt-2">
|
||||
{{ t('fleetView.editingPresetHint') }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 选择舰队 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -93,13 +168,27 @@
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.targetCoordinates') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="grid grid-cols-3 gap-2 sm:gap-4">
|
||||
<div v-for="coord in coordinateFields" :key="coord.key" class="space-y-2">
|
||||
<Label :for="coord.key" class="text-xs sm:text-sm">{{ t(`fleetView.${coord.key}`) }}</Label>
|
||||
<Input :id="coord.key" v-model.number="targetPosition[coord.key]" type="number" :min="1" :max="coord.max" placeholder="1" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 目标类型选择(行星/月球) -->
|
||||
<div v-if="hasMoonAtTargetPosition" class="flex items-center gap-4 p-3 bg-muted/50 rounded-lg">
|
||||
<span class="text-sm font-medium">{{ t('fleetView.targetType') }}:</span>
|
||||
<div class="flex gap-2">
|
||||
<Button @click="targetIsMoon = false" :variant="!targetIsMoon ? 'default' : 'outline'" size="sm">
|
||||
<Globe class="h-4 w-4 mr-1" />
|
||||
{{ t('fleetView.planet') }}
|
||||
</Button>
|
||||
<Button @click="targetIsMoon = true" :variant="targetIsMoon ? 'default' : 'outline'" size="sm">
|
||||
<Moon class="h-4 w-4 mr-1" />
|
||||
{{ t('fleetView.moon') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -278,6 +367,108 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 跳跃门 -->
|
||||
<TabsContent v-if="showJumpGateTab" value="jumpGate" class="mt-4 space-y-4">
|
||||
<!-- 跳跃门状态 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Zap class="h-5 w-5" />
|
||||
{{ t('fleetView.jumpGate') }}
|
||||
</CardTitle>
|
||||
<CardDescription>{{ t('fleetView.jumpGateDescription') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<!-- 冷却状态 -->
|
||||
<div v-if="!jumpGateReady" class="p-4 bg-muted/50 rounded-lg">
|
||||
<div class="flex items-center gap-2 text-amber-600 dark:text-amber-400">
|
||||
<Clock class="h-4 w-4" />
|
||||
<span class="font-medium">{{ t('fleetView.jumpGateCooldown') }}</span>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<span class="text-sm text-muted-foreground">{{ t('fleetView.jumpGateCooldownRemaining') }}:</span>
|
||||
<span class="font-bold">{{ formatTime(Math.floor(jumpGateCooldownRemaining / 1000)) }}</span>
|
||||
</div>
|
||||
<Progress :model-value="100 - (jumpGateCooldownRemaining / 3600000) * 100" class="mt-2" />
|
||||
</div>
|
||||
<!-- 就绪状态 -->
|
||||
<div v-else class="p-4 bg-green-500/10 rounded-lg">
|
||||
<div class="flex items-center gap-2 text-green-600 dark:text-green-400">
|
||||
<Check class="h-4 w-4" />
|
||||
<span class="font-medium">{{ t('fleetView.jumpGateReady') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 选择目标月球 -->
|
||||
<Card v-if="jumpGateReady">
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.jumpGateSelectTarget') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="availableJumpGateMoons.length === 0" class="text-center py-4 text-muted-foreground">
|
||||
{{ t('fleetView.jumpGateNoTargetMoons') }}
|
||||
</div>
|
||||
<div v-else class="space-y-2">
|
||||
<div
|
||||
v-for="moon in availableJumpGateMoons"
|
||||
:key="moon.id"
|
||||
class="p-3 border rounded-lg cursor-pointer transition-colors"
|
||||
:class="selectedJumpGateTarget?.id === moon.id ? 'ring-2 ring-primary bg-primary/10' : 'hover:bg-muted/50'"
|
||||
@click="selectedJumpGateTarget = moon"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="font-medium">{{ moon.name }}</span>
|
||||
<span class="text-sm text-muted-foreground ml-2">
|
||||
[{{ moon.position.galaxy }}:{{ moon.position.system }}:{{ moon.position.position }}]
|
||||
</span>
|
||||
</div>
|
||||
<Badge v-if="isJumpGateMoonReady(moon)" variant="default">{{ t('fleetView.jumpGateReady') }}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 选择传送舰队 -->
|
||||
<Card v-if="jumpGateReady && selectedJumpGateTarget">
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.jumpGateSelectFleet') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||
<div v-for="(count, shipType) in planet.fleet" :key="shipType" class="space-y-2">
|
||||
<Label :for="`jump-ship-${shipType}`" class="text-xs sm:text-sm">
|
||||
{{ SHIPS[shipType].name }} ({{ t('fleetView.available') }}: {{ count }})
|
||||
</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
:id="`jump-ship-${shipType}`"
|
||||
v-model.number="jumpGateFleet[shipType]"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="count"
|
||||
placeholder="0"
|
||||
class="text-sm"
|
||||
/>
|
||||
<Button @click="jumpGateFleet[shipType] = count" variant="outline" size="sm">{{ t('fleetView.all') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 传送按钮 -->
|
||||
<div class="mt-6">
|
||||
<Button @click="executeJumpGateTransfer" :disabled="!canExecuteJumpGate" class="w-full">
|
||||
<Zap class="h-4 w-4 mr-2" />
|
||||
{{ t('fleetView.jumpGateTransfer') }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<!-- 提示对话框 -->
|
||||
@@ -295,6 +486,40 @@
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- 预设名称对话框 -->
|
||||
<AlertDialog :open="showPresetNameDialog" @update:open="showPresetNameDialog = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{{ pendingPresetAction === 'save' ? t('fleetView.savePresetTitle') : t('fleetView.renamePresetTitle') }}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{{ pendingPresetAction === 'save' ? t('fleetView.savePresetDescription') : t('fleetView.renamePresetDescription') }}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div class="py-4">
|
||||
<Label for="preset-name">{{ t('fleetView.presetName') }}</Label>
|
||||
<Input
|
||||
id="preset-name"
|
||||
v-model="editingPresetName"
|
||||
:placeholder="t('fleetView.presetNamePlaceholder')"
|
||||
class="mt-2"
|
||||
@keyup.enter="handlePresetNameConfirm"
|
||||
/>
|
||||
</div>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel
|
||||
@click="() => { showPresetNameDialog = false; pendingPresetAction = null }"
|
||||
>
|
||||
{{ t('common.cancel') }}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handlePresetNameConfirm" :disabled="!editingPresetName.trim()">
|
||||
{{ t('common.confirm') }}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -307,7 +532,7 @@
|
||||
import { computed, ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ShipType, MissionType, BuildingType, TechnologyType } from '@/types/game'
|
||||
import type { Fleet, Resources } from '@/types/game'
|
||||
import type { Fleet, Resources, FleetPreset } from '@/types/game'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -329,7 +554,27 @@
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import UnlockRequirement from '@/components/UnlockRequirement.vue'
|
||||
import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty'
|
||||
import { Sword, Package, Rocket as RocketIcon, Eye, Users, Recycle, Skull, Gift, Compass } from 'lucide-vue-next'
|
||||
import {
|
||||
Sword,
|
||||
Package,
|
||||
Rocket as RocketIcon,
|
||||
Eye,
|
||||
Users,
|
||||
Recycle,
|
||||
Skull,
|
||||
Gift,
|
||||
Compass,
|
||||
Save,
|
||||
Trash2,
|
||||
Pencil,
|
||||
Star,
|
||||
Type,
|
||||
Zap,
|
||||
Clock,
|
||||
Check,
|
||||
Globe,
|
||||
Moon
|
||||
} from 'lucide-vue-next'
|
||||
import { formatNumber, formatTime } from '@/utils/format'
|
||||
import * as shipValidation from '@/logic/shipValidation'
|
||||
import * as fleetLogic from '@/logic/fleetLogic'
|
||||
@@ -337,6 +582,8 @@
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as diplomaticLogic from '@/logic/diplomaticLogic'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as moonLogic from '@/logic/moonLogic'
|
||||
|
||||
const route = useRoute()
|
||||
const gameStore = useGameStore()
|
||||
@@ -363,15 +610,127 @@
|
||||
return publicLogic.getMaxFleetMissions(bonuses.additionalFleetSlots, computerTechLevel)
|
||||
})
|
||||
|
||||
const activeTab = ref<'fleet' | 'send' | 'missions'>('fleet')
|
||||
const activeTab = ref<'fleet' | 'send' | 'missions' | 'jumpGate'>('fleet')
|
||||
|
||||
// Tab 配置
|
||||
const fleetTabs = [
|
||||
{ value: 'fleet', labelKey: 'fleetOverview' },
|
||||
{ value: 'send', labelKey: 'sendFleet' },
|
||||
{ value: 'missions', labelKey: 'flightMissions' }
|
||||
{ value: 'missions', labelKey: 'flightMissions' },
|
||||
{ value: 'jumpGate', labelKey: 'jumpGate' }
|
||||
] as const
|
||||
|
||||
// 跳跃门相关
|
||||
const selectedJumpGateTarget = ref<typeof planet.value | null>(null)
|
||||
const jumpGateFleet = ref<Partial<Fleet>>({
|
||||
[ShipType.LightFighter]: 0,
|
||||
[ShipType.HeavyFighter]: 0,
|
||||
[ShipType.Cruiser]: 0,
|
||||
[ShipType.Battleship]: 0,
|
||||
[ShipType.SmallCargo]: 0,
|
||||
[ShipType.LargeCargo]: 0,
|
||||
[ShipType.ColonyShip]: 0,
|
||||
[ShipType.Recycler]: 0,
|
||||
[ShipType.EspionageProbe]: 0,
|
||||
[ShipType.DarkMatterHarvester]: 0,
|
||||
[ShipType.Deathstar]: 0
|
||||
})
|
||||
|
||||
// 是否显示跳跃门标签页(当前在月球上且有跳跃门)
|
||||
const showJumpGateTab = computed(() => {
|
||||
if (!planet.value) return false
|
||||
if (!planet.value.isMoon) return false
|
||||
const jumpGateLevel = planet.value.buildings[BuildingType.JumpGate] || 0
|
||||
return jumpGateLevel > 0
|
||||
})
|
||||
|
||||
// 跳跃门是否就绪(冷却完成)
|
||||
const jumpGateReady = computed(() => {
|
||||
if (!planet.value) return false
|
||||
return moonLogic.isJumpGateReady(planet.value)
|
||||
})
|
||||
|
||||
// 跳跃门剩余冷却时间
|
||||
const jumpGateCooldownRemaining = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
return moonLogic.getJumpGateCooldownRemaining(planet.value)
|
||||
})
|
||||
|
||||
// 可用的目标月球(有跳跃门且冷却完成的其他月球)
|
||||
const availableJumpGateMoons = computed(() => {
|
||||
if (!planet.value) return []
|
||||
return gameStore.player.planets.filter(p => {
|
||||
if (p.id === planet.value?.id) return false // 排除当前月球
|
||||
if (!p.isMoon) return false
|
||||
const jumpGateLevel = p.buildings[BuildingType.JumpGate] || 0
|
||||
if (jumpGateLevel <= 0) return false
|
||||
return moonLogic.isJumpGateReady(p)
|
||||
})
|
||||
})
|
||||
|
||||
// 检查目标月球的跳跃门是否就绪
|
||||
const isJumpGateMoonReady = (moon: typeof planet.value) => {
|
||||
if (!moon) return false
|
||||
return moonLogic.isJumpGateReady(moon)
|
||||
}
|
||||
|
||||
// 是否可以执行跳跃门传送
|
||||
const canExecuteJumpGate = computed(() => {
|
||||
if (!planet.value || !selectedJumpGateTarget.value) return false
|
||||
if (!jumpGateReady.value) return false
|
||||
// 检查是否选择了至少一艘舰船
|
||||
const totalShips = Object.values(jumpGateFleet.value).reduce((sum, count) => sum + (count || 0), 0)
|
||||
return totalShips > 0
|
||||
})
|
||||
|
||||
// 执行跳跃门传送
|
||||
const executeJumpGateTransfer = () => {
|
||||
if (!planet.value || !selectedJumpGateTarget.value) return
|
||||
if (!canExecuteJumpGate.value) return
|
||||
|
||||
const sourceMoon = planet.value
|
||||
const targetMoon = selectedJumpGateTarget.value
|
||||
|
||||
// 转移舰队
|
||||
Object.entries(jumpGateFleet.value).forEach(([shipType, count]) => {
|
||||
if (count && count > 0) {
|
||||
const ship = shipType as ShipType
|
||||
// 从源月球扣除
|
||||
if (sourceMoon.fleet[ship] >= count) {
|
||||
sourceMoon.fleet[ship] -= count
|
||||
// 添加到目标月球
|
||||
targetMoon.fleet[ship] = (targetMoon.fleet[ship] || 0) + count
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 设置两个跳跃门的冷却时间
|
||||
moonLogic.useJumpGate(sourceMoon)
|
||||
moonLogic.useJumpGate(targetMoon)
|
||||
|
||||
// 重置跳跃门舰队选择
|
||||
Object.keys(jumpGateFleet.value).forEach(key => {
|
||||
jumpGateFleet.value[key as ShipType] = 0
|
||||
})
|
||||
selectedJumpGateTarget.value = null
|
||||
|
||||
// 显示成功对话框
|
||||
alertDialogTitle.value = t('fleetView.jumpGateSuccess')
|
||||
alertDialogMessage.value = t('fleetView.jumpGateSuccessMessage', {
|
||||
target: `${targetMoon.name} [${targetMoon.position.galaxy}:${targetMoon.position.system}:${targetMoon.position.position}]`
|
||||
})
|
||||
alertDialogCallback.value = null
|
||||
alertDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 可见的标签页(根据是否有跳跃门动态显示)
|
||||
const visibleTabs = computed(() => {
|
||||
if (showJumpGateTab.value) {
|
||||
return fleetTabs
|
||||
}
|
||||
return fleetTabs.filter(tab => tab.value !== 'jumpGate')
|
||||
})
|
||||
|
||||
// 选择的舰队
|
||||
const selectedFleet = ref<Partial<Fleet>>({
|
||||
[ShipType.LightFighter]: 0,
|
||||
@@ -390,6 +749,20 @@
|
||||
// 目标坐标
|
||||
const targetPosition = ref({ galaxy: 1, system: 1, position: 1 })
|
||||
|
||||
// 目标是否为月球(用于区分同坐标的行星和月球)
|
||||
const targetIsMoon = ref(false)
|
||||
|
||||
// 检查目标位置是否有月球(玩家自己的)
|
||||
const hasMoonAtTargetPosition = computed(() => {
|
||||
return gameStore.player.planets.some(
|
||||
p =>
|
||||
p.isMoon &&
|
||||
p.position.galaxy === targetPosition.value.galaxy &&
|
||||
p.position.system === targetPosition.value.system &&
|
||||
p.position.position === targetPosition.value.position
|
||||
)
|
||||
})
|
||||
|
||||
// 坐标字段配置
|
||||
const coordinateFields: { key: keyof typeof targetPosition.value; max: number }[] = [
|
||||
{ key: 'galaxy', max: 9 },
|
||||
@@ -463,6 +836,227 @@
|
||||
// 是否为赠送模式
|
||||
const isGiftMode = ref(false)
|
||||
|
||||
// 舰队预设相关状态
|
||||
const MAX_PRESETS = 3
|
||||
const editingPresetId = ref<string | null>(null)
|
||||
const editingPresetName = ref('')
|
||||
const showPresetNameDialog = ref(false)
|
||||
const pendingPresetAction = ref<'save' | 'rename' | null>(null)
|
||||
|
||||
// 获取预设列表
|
||||
const fleetPresets = computed(() => gameStore.player.fleetPresets || [])
|
||||
|
||||
// 生成唯一ID
|
||||
const generatePresetId = (): string => {
|
||||
return `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
}
|
||||
|
||||
// 保存当前配置为预设
|
||||
const saveAsPreset = () => {
|
||||
if (fleetPresets.value.length >= MAX_PRESETS) {
|
||||
alertDialogTitle.value = t('fleetView.presetLimitReached')
|
||||
alertDialogMessage.value = t('fleetView.presetLimitReachedMessage', { max: MAX_PRESETS.toString() })
|
||||
alertDialogCallback.value = null
|
||||
alertDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有选择舰船
|
||||
const hasShips = Object.values(selectedFleet.value).some(count => count > 0)
|
||||
if (!hasShips) {
|
||||
alertDialogTitle.value = t('fleetView.presetError')
|
||||
alertDialogMessage.value = t('fleetView.presetNoShips')
|
||||
alertDialogCallback.value = null
|
||||
alertDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
pendingPresetAction.value = 'save'
|
||||
editingPresetName.value = t('fleetView.presetDefaultName', { number: (fleetPresets.value.length + 1).toString() })
|
||||
showPresetNameDialog.value = true
|
||||
}
|
||||
|
||||
// 确认保存预设
|
||||
const confirmSavePreset = () => {
|
||||
if (!editingPresetName.value.trim()) return
|
||||
|
||||
// 只保存数量大于0的舰船
|
||||
const fleetToSave: Partial<Fleet> = {}
|
||||
for (const [shipType, count] of Object.entries(selectedFleet.value)) {
|
||||
if (count && count > 0) {
|
||||
fleetToSave[shipType as ShipType] = count
|
||||
}
|
||||
}
|
||||
|
||||
// 只保存数量大于0的资源
|
||||
const cargoToSave: Partial<Resources> | undefined =
|
||||
selectedMission.value === MissionType.Transport
|
||||
? {
|
||||
metal: cargo.value.metal || 0,
|
||||
crystal: cargo.value.crystal || 0,
|
||||
deuterium: cargo.value.deuterium || 0,
|
||||
darkMatter: cargo.value.darkMatter || 0
|
||||
}
|
||||
: undefined
|
||||
|
||||
const newPreset: FleetPreset = {
|
||||
id: generatePresetId(),
|
||||
name: editingPresetName.value.trim(),
|
||||
fleet: fleetToSave,
|
||||
targetPosition: {
|
||||
galaxy: targetPosition.value.galaxy,
|
||||
system: targetPosition.value.system,
|
||||
position: targetPosition.value.position
|
||||
},
|
||||
missionType: selectedMission.value,
|
||||
cargo: cargoToSave
|
||||
}
|
||||
|
||||
if (!gameStore.player.fleetPresets) {
|
||||
gameStore.player.fleetPresets = []
|
||||
}
|
||||
gameStore.player.fleetPresets.push(newPreset)
|
||||
|
||||
showPresetNameDialog.value = false
|
||||
editingPresetName.value = ''
|
||||
pendingPresetAction.value = null
|
||||
}
|
||||
|
||||
// 加载预设
|
||||
const loadPreset = (preset: FleetPreset) => {
|
||||
// 加载舰队配置
|
||||
Object.keys(selectedFleet.value).forEach(key => {
|
||||
selectedFleet.value[key as ShipType] = preset.fleet[key as ShipType] || 0
|
||||
})
|
||||
|
||||
// 加载目标坐标
|
||||
if (preset.targetPosition) {
|
||||
targetPosition.value = { ...preset.targetPosition }
|
||||
}
|
||||
|
||||
// 加载任务类型
|
||||
if (preset.missionType) {
|
||||
selectedMission.value = preset.missionType
|
||||
}
|
||||
|
||||
// 加载运输资源
|
||||
if (preset.cargo && preset.missionType === MissionType.Transport) {
|
||||
cargo.value = {
|
||||
metal: preset.cargo.metal || 0,
|
||||
crystal: preset.cargo.crystal || 0,
|
||||
deuterium: preset.cargo.deuterium || 0,
|
||||
darkMatter: preset.cargo.darkMatter || 0,
|
||||
energy: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新预设(点击预设后修改内容)
|
||||
const updatePreset = (presetId: string) => {
|
||||
const presetIndex = gameStore.player.fleetPresets?.findIndex(p => p.id === presetId)
|
||||
if (presetIndex === undefined || presetIndex === -1) return
|
||||
|
||||
const hasShips = Object.values(selectedFleet.value).some(count => count > 0)
|
||||
if (!hasShips) {
|
||||
alertDialogTitle.value = t('fleetView.presetError')
|
||||
alertDialogMessage.value = t('fleetView.presetNoShips')
|
||||
alertDialogCallback.value = null
|
||||
alertDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
const existingPreset = gameStore.player.fleetPresets![presetIndex]
|
||||
if (!existingPreset) return
|
||||
|
||||
// 只保存数量大于0的舰船
|
||||
const fleetToSave: Partial<Fleet> = {}
|
||||
for (const [shipType, count] of Object.entries(selectedFleet.value)) {
|
||||
if (count && count > 0) {
|
||||
fleetToSave[shipType as ShipType] = count
|
||||
}
|
||||
}
|
||||
|
||||
// 只保存数量大于0的资源
|
||||
const cargoToSave: Partial<Resources> | undefined =
|
||||
selectedMission.value === MissionType.Transport
|
||||
? {
|
||||
metal: cargo.value.metal || 0,
|
||||
crystal: cargo.value.crystal || 0,
|
||||
deuterium: cargo.value.deuterium || 0,
|
||||
darkMatter: cargo.value.darkMatter || 0
|
||||
}
|
||||
: undefined
|
||||
|
||||
const updatedPreset: FleetPreset = {
|
||||
id: existingPreset.id,
|
||||
name: existingPreset.name,
|
||||
fleet: fleetToSave,
|
||||
targetPosition: {
|
||||
galaxy: targetPosition.value.galaxy,
|
||||
system: targetPosition.value.system,
|
||||
position: targetPosition.value.position
|
||||
},
|
||||
missionType: selectedMission.value,
|
||||
cargo: cargoToSave
|
||||
}
|
||||
|
||||
gameStore.player.fleetPresets![presetIndex] = updatedPreset
|
||||
editingPresetId.value = null
|
||||
}
|
||||
|
||||
// 开始编辑预设名称
|
||||
const startRenamePreset = (preset: FleetPreset) => {
|
||||
// 保存要重命名的预设ID,但不进入编辑内容模式
|
||||
editingPresetName.value = preset.name
|
||||
pendingPresetAction.value = 'rename'
|
||||
// 使用临时变量存储要重命名的预设ID
|
||||
renameTargetPresetId.value = preset.id
|
||||
showPresetNameDialog.value = true
|
||||
}
|
||||
|
||||
// 要重命名的预设ID(与编辑预设内容分开)
|
||||
const renameTargetPresetId = ref<string | null>(null)
|
||||
|
||||
// 确认重命名预设
|
||||
const confirmRenamePreset = () => {
|
||||
if (!editingPresetName.value.trim() || !renameTargetPresetId.value) return
|
||||
|
||||
const preset = gameStore.player.fleetPresets?.find(p => p.id === renameTargetPresetId.value)
|
||||
if (preset) {
|
||||
preset.name = editingPresetName.value.trim()
|
||||
}
|
||||
|
||||
showPresetNameDialog.value = false
|
||||
renameTargetPresetId.value = null
|
||||
editingPresetName.value = ''
|
||||
pendingPresetAction.value = null
|
||||
}
|
||||
|
||||
// 删除预设
|
||||
const deletePreset = (presetId: string) => {
|
||||
const preset = gameStore.player.fleetPresets?.find(p => p.id === presetId)
|
||||
if (!preset) return
|
||||
|
||||
alertDialogTitle.value = t('fleetView.deletePresetTitle')
|
||||
alertDialogMessage.value = t('fleetView.deletePresetMessage', { name: preset.name })
|
||||
alertDialogCallback.value = () => {
|
||||
const index = gameStore.player.fleetPresets?.findIndex(p => p.id === presetId)
|
||||
if (index !== undefined && index > -1) {
|
||||
gameStore.player.fleetPresets!.splice(index, 1)
|
||||
}
|
||||
}
|
||||
alertDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 处理预设名称对话框确认
|
||||
const handlePresetNameConfirm = () => {
|
||||
if (pendingPresetAction.value === 'save') {
|
||||
confirmSavePreset()
|
||||
} else if (pendingPresetAction.value === 'rename') {
|
||||
confirmRenamePreset()
|
||||
}
|
||||
}
|
||||
|
||||
// 监听目标NPC变化,当目标不再是NPC时自动禁用赠送模式
|
||||
watch(targetNpc, newValue => {
|
||||
if (!newValue && isGiftMode.value) {
|
||||
@@ -543,8 +1137,16 @@
|
||||
if (!hasShips) return { valid: false, errorKey: 'fleetView.noShipsSelected' }
|
||||
|
||||
// 检查是否派遣到自己的星球
|
||||
// 回收任务和部署任务除外(回收残骸可能在同位置,部署可能到自己的月球)
|
||||
if (planet.value && selectedMission.value !== MissionType.Recycle && selectedMission.value !== MissionType.Deploy) {
|
||||
// 回收任务、部署任务和运输任务除外:
|
||||
// - 回收任务:可能回收同位置的残骸
|
||||
// - 部署任务:可能部署到自己的月球
|
||||
// - 运输任务:可能从行星向同位置的月球运输资源(OGame规则允许)
|
||||
if (
|
||||
planet.value &&
|
||||
selectedMission.value !== MissionType.Recycle &&
|
||||
selectedMission.value !== MissionType.Deploy &&
|
||||
selectedMission.value !== MissionType.Transport
|
||||
) {
|
||||
const isSamePlanet =
|
||||
targetPosition.value.galaxy === planet.value.position.galaxy &&
|
||||
targetPosition.value.system === planet.value.position.system &&
|
||||
@@ -591,7 +1193,8 @@
|
||||
targetPosition: { galaxy: number; system: number; position: number },
|
||||
missionType: MissionType,
|
||||
fleet: Partial<Fleet>,
|
||||
cargo: Resources = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
cargo: Resources = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
isMoonTarget: boolean = false
|
||||
): boolean => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const currentMissions = gameStore.player.fleetMissions.length
|
||||
@@ -604,6 +1207,13 @@
|
||||
gameStore.player.technologies
|
||||
)
|
||||
if (!validation.valid) return false
|
||||
|
||||
// 追踪燃料消耗(同时计入资源消耗和燃料统计)
|
||||
if (validation.fuelNeeded && validation.fuelNeeded > 0) {
|
||||
gameLogic.trackResourceConsumption(gameStore.player, { deuterium: validation.fuelNeeded })
|
||||
gameLogic.trackFuelConsumption(gameStore.player, validation.fuelNeeded)
|
||||
}
|
||||
|
||||
const shouldDeductCargo = missionType === MissionType.Transport
|
||||
shipValidation.executeFleetDispatch(gameStore.currentPlanet, fleet, validation.fuelNeeded!, shouldDeductCargo, cargo)
|
||||
const distance = fleetLogic.calculateDistance(gameStore.currentPlanet.position, targetPosition)
|
||||
@@ -620,6 +1230,11 @@
|
||||
flightTime
|
||||
)
|
||||
|
||||
// 如果目标是月球,设置标记
|
||||
if (isMoonTarget) {
|
||||
mission.targetIsMoon = true
|
||||
}
|
||||
|
||||
// 如果是赠送模式,标记任务
|
||||
if (missionType === MissionType.Transport && isGiftMode.value && targetNpc.value) {
|
||||
mission.isGift = true
|
||||
@@ -655,7 +1270,8 @@
|
||||
targetPosition.value,
|
||||
selectedMission.value,
|
||||
fleet,
|
||||
selectedMission.value === MissionType.Transport ? cargo.value : undefined
|
||||
selectedMission.value === MissionType.Transport ? cargo.value : undefined,
|
||||
targetIsMoon.value
|
||||
)
|
||||
|
||||
if (success) {
|
||||
|
||||
@@ -229,6 +229,15 @@
|
||||
</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<!-- NPC难度等级徽章 -->
|
||||
<Badge
|
||||
v-if="getNpcDifficultyLevel(slot.planet) !== null"
|
||||
:variant="getDifficultyBadgeVariant(getNpcDifficultyLevel(slot.planet))"
|
||||
class="text-xs flex-shrink-0"
|
||||
:class="getDifficultyLevelColor(getNpcDifficultyLevel(slot.planet))"
|
||||
>
|
||||
Lv.{{ getNpcDifficultyLevel(slot.planet) }}
|
||||
</Badge>
|
||||
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<PopoverTrigger as-child>
|
||||
<Badge
|
||||
@@ -260,6 +269,16 @@
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<!-- 月球徽章 -->
|
||||
<Badge
|
||||
v-if="slot.moon"
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-800 border-slate-400 dark:border-slate-600 text-slate-600 dark:text-slate-400 gap-1"
|
||||
@click.stop="switchToPlanet(slot.moon.id)"
|
||||
>
|
||||
<Moon class="h-3 w-3" />
|
||||
<span>{{ slot.moon.name }}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 空位置 -->
|
||||
@@ -345,6 +364,16 @@
|
||||
<p>{{ t('galaxyView.sendGift') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && canScanPlanet(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPhalanxScanDialog(slot.planet)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Radar class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.phalanxScan') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="!slot.planet">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPlanetActions(null, 'colonize', slot.position)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
@@ -417,6 +446,15 @@
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<!-- NPC难度等级徽章 -->
|
||||
<Badge
|
||||
v-if="getNpcDifficultyLevel(slot.planet) !== null"
|
||||
:variant="getDifficultyBadgeVariant(getNpcDifficultyLevel(slot.planet))"
|
||||
class="text-xs"
|
||||
:class="getDifficultyLevelColor(getNpcDifficultyLevel(slot.planet))"
|
||||
>
|
||||
Lv.{{ getNpcDifficultyLevel(slot.planet) }}
|
||||
</Badge>
|
||||
<!-- 残骸场徽章 -->
|
||||
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<PopoverTrigger as-child>
|
||||
@@ -450,6 +488,16 @@
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<!-- 月球徽章 -->
|
||||
<Badge
|
||||
v-if="slot.moon"
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-800 border-slate-400 dark:border-slate-600 text-slate-600 dark:text-slate-400 gap-1"
|
||||
@click.stop="switchToPlanet(slot.moon.id)"
|
||||
>
|
||||
<Moon class="h-3 w-3" />
|
||||
<span>{{ slot.moon.name }}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
<!-- PC端:坐标 -->
|
||||
<p class="text-xs text-muted-foreground">
|
||||
@@ -539,6 +587,16 @@
|
||||
<p>{{ t('galaxyView.sendGift') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && canScanPlanet(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPhalanxScanDialog(slot.planet)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Radar class="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.phalanxScan') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="!slot.planet">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPlanetActions(null, 'colonize', slot.position)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
@@ -650,6 +708,97 @@
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- 传感器阵列扫描对话框 -->
|
||||
<Dialog :open="phalanxDialogOpen" @update:open="phalanxDialogOpen = $event">
|
||||
<DialogContent class="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<Radar class="h-5 w-5" />
|
||||
{{ t('galaxyView.phalanxScanTitle') }}
|
||||
</DialogTitle>
|
||||
<DialogDescription v-if="phalanxTargetPlanet">
|
||||
{{
|
||||
t('galaxyView.phalanxScanDescription').replace(
|
||||
'{coordinates}',
|
||||
`${phalanxTargetPlanet.position.galaxy}:${phalanxTargetPlanet.position.system}:${phalanxTargetPlanet.position.position}`
|
||||
)
|
||||
}}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 扫描信息 -->
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.phalanxCost') }}:</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<ResourceIcon type="deuterium" size="sm" />
|
||||
<span>{{ formatNumber(PHALANX_SCAN_COST) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 扫描按钮 -->
|
||||
<Button v-if="phalanxScanResults.length === 0 && !phalanxScanning" @click="executePhalanxScan" class="w-full">
|
||||
<Radar class="h-4 w-4 mr-2" />
|
||||
{{ t('galaxyView.phalanxScan') }}
|
||||
</Button>
|
||||
|
||||
<!-- 扫描中 -->
|
||||
<div v-if="phalanxScanning" class="flex items-center justify-center py-8">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
|
||||
<!-- 扫描结果 -->
|
||||
<div v-if="!phalanxScanning && phalanxScanResults.length > 0" class="space-y-3">
|
||||
<div class="text-sm font-medium">
|
||||
{{ t('galaxyView.phalanxFleetDetected').replace('{count}', String(phalanxScanResults.length)) }}
|
||||
</div>
|
||||
<div class="space-y-2 max-h-64 overflow-y-auto">
|
||||
<div v-for="fleet in phalanxScanResults" :key="fleet.id" class="p-3 border rounded-lg space-y-2 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<Badge>{{ getMissionTypeText(fleet.missionType) }}</Badge>
|
||||
<Badge :variant="fleet.status === 'outbound' ? 'default' : 'secondary'">
|
||||
{{ fleet.status === 'outbound' ? t('galaxyView.phalanxStatusOutbound') : t('galaxyView.phalanxStatusReturning') }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.phalanxOrigin') }}:</span>
|
||||
<span class="ml-1">
|
||||
{{ formatCoords(getPlanetPositionById(fleet.originPlanetId) || { galaxy: 0, system: 0, position: 0 }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.phalanxDestination') }}:</span>
|
||||
<span class="ml-1">{{ formatCoords(fleet.targetPosition) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.phalanxArrival') }}:</span>
|
||||
<span class="ml-1">{{ formatTime(Math.max(0, Math.floor((fleet.arrivalTime - Date.now()) / 1000))) }}</span>
|
||||
</div>
|
||||
<div v-if="fleet.returnTime">
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.phalanxReturn') }}:</span>
|
||||
<span class="ml-1">{{ formatTime(Math.max(0, Math.floor((fleet.returnTime - Date.now()) / 1000))) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无舰队 -->
|
||||
<div
|
||||
v-if="!phalanxScanning && phalanxScanResults.length === 0 && phalanxDialogOpen"
|
||||
class="text-center py-4 text-muted-foreground"
|
||||
>
|
||||
{{ t('galaxyView.phalanxNoFleets') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="phalanxDialogOpen = false">{{ t('common.close') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -681,10 +830,13 @@
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import ResourceIcon from '@/components/ResourceIcon.vue'
|
||||
import { Home, Eye, Sword, Rocket, Recycle, Gift, Globe, Bomb } from 'lucide-vue-next'
|
||||
import { Home, Eye, Sword, Rocket, Recycle, Gift, Globe, Bomb, Moon, Radar } from 'lucide-vue-next'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import * as moonLogic from '@/logic/moonLogic'
|
||||
import { formatNumber, formatTime } from '@/utils/format'
|
||||
import { BuildingType, MissionType } from '@/types/game'
|
||||
import type { FleetMission } from '@/types/game'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const universeStore = useUniverseStore()
|
||||
@@ -704,6 +856,12 @@
|
||||
const missileTargetPlanet = ref<Planet | null>(null)
|
||||
const missileCount = ref(1)
|
||||
|
||||
// 传感器阵列扫描对话框状态
|
||||
const phalanxDialogOpen = ref(false)
|
||||
const phalanxTargetPlanet = ref<Planet | null>(null)
|
||||
const phalanxScanResults = ref<FleetMission[]>([])
|
||||
const phalanxScanning = ref(false)
|
||||
|
||||
const selectedGalaxy = ref(1)
|
||||
const selectedSystem = ref(1)
|
||||
const currentGalaxy = ref(1)
|
||||
@@ -718,7 +876,7 @@
|
||||
return npcStore.npcs.find(n => n.id === highlightNpcId.value) || null
|
||||
})
|
||||
|
||||
const systemSlots = ref<Array<{ position: number; planet: Planet | null }>>([])
|
||||
const systemSlots = ref<Array<{ position: number; planet: Planet | null; moon: Planet | null }>>([])
|
||||
|
||||
// 获取玩家的母星
|
||||
const homePlanet = computed(() => {
|
||||
@@ -770,18 +928,26 @@
|
||||
}
|
||||
})
|
||||
|
||||
const getSystemPlanets = (galaxy: number, system: number): Array<{ position: number; planet: Planet | null }> => {
|
||||
const getSystemPlanets = (galaxy: number, system: number): Array<{ position: number; planet: Planet | null; moon: Planet | null }> => {
|
||||
const positions = gameLogic.generateSystemPositions(galaxy, system)
|
||||
return positions.map(pos => {
|
||||
const key = gameLogic.generatePositionKey(galaxy, system, pos.position)
|
||||
// 先从玩家星球中查找,再从宇宙地图中查找
|
||||
// 先从玩家星球中查找(非月球),再从宇宙地图中查找
|
||||
const planet =
|
||||
gameStore.player.planets.find(
|
||||
p => p.position.galaxy === galaxy && p.position.system === system && p.position.position === pos.position
|
||||
p => !p.isMoon && p.position.galaxy === galaxy && p.position.system === system && p.position.position === pos.position
|
||||
) ||
|
||||
universeStore.planets[key] ||
|
||||
null
|
||||
return { position: pos.position, planet }
|
||||
|
||||
// 查找该位置的月球(如果有星球的话)
|
||||
let moon: Planet | null = null
|
||||
if (planet) {
|
||||
// 从玩家星球中查找月球
|
||||
moon = gameStore.player.planets.find(p => p.isMoon && p.parentPlanetId === planet.id) || null
|
||||
}
|
||||
|
||||
return { position: pos.position, planet, moon }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -898,6 +1064,32 @@
|
||||
return planet.name
|
||||
}
|
||||
|
||||
// 获取NPC难度等级
|
||||
const getNpcDifficultyLevel = (planet: Planet | null): number | null => {
|
||||
const npc = getPlanetNPC(planet)
|
||||
return npc?.difficultyLevel ?? null
|
||||
}
|
||||
|
||||
// 获取NPC难度等级颜色
|
||||
const getDifficultyLevelColor = (level: number | null): string => {
|
||||
if (level === null) return 'text-muted-foreground'
|
||||
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
|
||||
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
|
||||
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
|
||||
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
|
||||
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
|
||||
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
|
||||
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
|
||||
}
|
||||
|
||||
// 获取NPC难度等级Badge样式
|
||||
const getDifficultyBadgeVariant = (level: number | null): 'default' | 'secondary' | 'destructive' | 'outline' => {
|
||||
if (level === null) return 'outline'
|
||||
if (level <= 2) return 'secondary'
|
||||
if (level <= 4) return 'default'
|
||||
return 'destructive'
|
||||
}
|
||||
|
||||
// 切换到指定星球
|
||||
const switchToPlanet = (planetId: string) => {
|
||||
gameStore.currentPlanetId = planetId
|
||||
@@ -1030,4 +1222,172 @@
|
||||
const secs = seconds % 60
|
||||
return `${minutes}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// ========== 传感器阵列扫描功能 ==========
|
||||
|
||||
// 获取拥有传感器阵列的月球列表
|
||||
const moonsWithPhalanx = computed(() => {
|
||||
return gameStore.player.planets.filter(p => {
|
||||
if (!p.isMoon) return false
|
||||
const phalanxLevel = p.buildings[BuildingType.SensorPhalanx] || 0
|
||||
return phalanxLevel > 0
|
||||
})
|
||||
})
|
||||
|
||||
// 检查是否可以扫描目标(需要有传感器阵列的月球在范围内)
|
||||
const canScanPlanet = (targetPlanet: Planet | null): boolean => {
|
||||
if (!targetPlanet) return false
|
||||
if (isMyPlanet(targetPlanet)) return false
|
||||
|
||||
// 检查是否有月球的传感器阵列可以扫描目标
|
||||
return moonsWithPhalanx.value.some(moon => {
|
||||
const phalanxLevel = moon.buildings[BuildingType.SensorPhalanx] || 0
|
||||
return moonLogic.isInSensorPhalanxRange(moon.position, targetPlanet.position, phalanxLevel)
|
||||
})
|
||||
}
|
||||
|
||||
// 获取可以扫描目标的月球
|
||||
const getMoonForScan = (targetPlanet: Planet): Planet | null => {
|
||||
return (
|
||||
moonsWithPhalanx.value.find(moon => {
|
||||
const phalanxLevel = moon.buildings[BuildingType.SensorPhalanx] || 0
|
||||
return moonLogic.isInSensorPhalanxRange(moon.position, targetPlanet.position, phalanxLevel)
|
||||
}) || null
|
||||
)
|
||||
}
|
||||
|
||||
// 计算扫描消耗的氘(每次扫描消耗5000氘)
|
||||
const PHALANX_SCAN_COST = 5000
|
||||
|
||||
// 显示传感器阵列扫描对话框
|
||||
const showPhalanxScanDialog = (planet: Planet) => {
|
||||
phalanxTargetPlanet.value = planet
|
||||
phalanxScanResults.value = []
|
||||
phalanxScanning.value = false
|
||||
phalanxDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 根据星球ID获取星球坐标
|
||||
const getPlanetPositionById = (planetId: string): { galaxy: number; system: number; position: number } | null => {
|
||||
// 先从玩家星球中查找
|
||||
const playerPlanet = gameStore.player.planets.find(p => p.id === planetId)
|
||||
if (playerPlanet) return playerPlanet.position
|
||||
|
||||
// 再从NPC星球中查找
|
||||
for (const npc of npcStore.npcs) {
|
||||
const npcPlanet = npc.planets.find(p => p.id === planetId)
|
||||
if (npcPlanet) return npcPlanet.position
|
||||
}
|
||||
|
||||
// 从宇宙地图中查找
|
||||
for (const key in universeStore.planets) {
|
||||
const planet = universeStore.planets[key]
|
||||
if (planet && planet.id === planetId) return planet.position
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// 执行传感器阵列扫描
|
||||
const executePhalanxScan = () => {
|
||||
if (!phalanxTargetPlanet.value) return
|
||||
|
||||
const scanMoon = getMoonForScan(phalanxTargetPlanet.value)
|
||||
if (!scanMoon) {
|
||||
alertDialogTitle.value = t('errors.scanFailed')
|
||||
alertDialogMessage.value = t('galaxyView.phalanxNoMoon')
|
||||
alertDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 检查氘是否足够
|
||||
if (scanMoon.resources.deuterium < PHALANX_SCAN_COST) {
|
||||
alertDialogTitle.value = t('errors.scanFailed')
|
||||
alertDialogMessage.value = t('galaxyView.phalanxInsufficientDeuterium')
|
||||
alertDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 扣除氘
|
||||
scanMoon.resources.deuterium -= PHALANX_SCAN_COST
|
||||
|
||||
phalanxScanning.value = true
|
||||
|
||||
// 模拟扫描延迟
|
||||
setTimeout(() => {
|
||||
// 扫描NPC的舰队任务
|
||||
const targetPos = phalanxTargetPlanet.value!.position
|
||||
const npc = getPlanetNPC(phalanxTargetPlanet.value)
|
||||
|
||||
// 收集相关的舰队任务
|
||||
const detectedFleets: FleetMission[] = []
|
||||
|
||||
// 检查NPC的舰队任务
|
||||
if (npc) {
|
||||
npc.fleetMissions?.forEach(mission => {
|
||||
// 获取出发地坐标
|
||||
const originPos = getPlanetPositionById(mission.originPlanetId)
|
||||
|
||||
// 检查任务是否与目标星球相关(出发地或目的地)
|
||||
const isFromTarget =
|
||||
originPos &&
|
||||
originPos.galaxy === targetPos.galaxy &&
|
||||
originPos.system === targetPos.system &&
|
||||
originPos.position === targetPos.position
|
||||
const isToTarget =
|
||||
mission.targetPosition.galaxy === targetPos.galaxy &&
|
||||
mission.targetPosition.system === targetPos.system &&
|
||||
mission.targetPosition.position === targetPos.position
|
||||
|
||||
if (isFromTarget || isToTarget) {
|
||||
detectedFleets.push(mission)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 也检查玩家自己发往该星球的任务(自己的任务自己当然知道,但扫描也能看到)
|
||||
gameStore.player.fleetMissions?.forEach(mission => {
|
||||
const isToTarget =
|
||||
mission.targetPosition.galaxy === targetPos.galaxy &&
|
||||
mission.targetPosition.system === targetPos.system &&
|
||||
mission.targetPosition.position === targetPos.position
|
||||
|
||||
if (isToTarget) {
|
||||
detectedFleets.push(mission)
|
||||
}
|
||||
})
|
||||
|
||||
phalanxScanResults.value = detectedFleets
|
||||
phalanxScanning.value = false
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 获取任务类型文本
|
||||
const getMissionTypeText = (missionType: MissionType): string => {
|
||||
switch (missionType) {
|
||||
case MissionType.Attack:
|
||||
return t('fleetView.attack')
|
||||
case MissionType.Transport:
|
||||
return t('fleetView.transport')
|
||||
case MissionType.Deploy:
|
||||
return t('fleetView.deploy')
|
||||
case MissionType.Spy:
|
||||
return t('fleetView.spy')
|
||||
case MissionType.Colonize:
|
||||
return t('fleetView.colonize')
|
||||
case MissionType.Recycle:
|
||||
return t('fleetView.recycle')
|
||||
case MissionType.Destroy:
|
||||
return t('fleetView.destroy')
|
||||
case MissionType.Expedition:
|
||||
return t('fleetView.expedition')
|
||||
default:
|
||||
return missionType
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化坐标
|
||||
const formatCoords = (pos: { galaxy: number; system: number; position: number }): string => {
|
||||
return `[${pos.galaxy}:${pos.system}:${pos.position}]`
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
import { formatNumber, formatTime, formatDate, getResourceCostColor } from '@/utils/format'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
|
||||
@@ -250,6 +251,8 @@
|
||||
if (!resourceLogic.checkResourcesAvailable(gameStore.currentPlanet.resources, cost)) {
|
||||
return false
|
||||
}
|
||||
// 追踪资源消耗(在扣除前)
|
||||
gameLogic.trackResourceConsumption(gameStore.player, cost)
|
||||
resourceLogic.deductResources(gameStore.currentPlanet.resources, cost)
|
||||
gameStore.player.officers[officerType] = officerLogic.createActiveOfficer(officerType, duration)
|
||||
return true
|
||||
@@ -276,6 +279,8 @@
|
||||
if (!resourceLogic.checkResourcesAvailable(gameStore.currentPlanet.resources, cost)) {
|
||||
return false
|
||||
}
|
||||
// 追踪资源消耗(在扣除前)
|
||||
gameLogic.trackResourceConsumption(gameStore.player, cost)
|
||||
resourceLogic.deductResources(gameStore.currentPlanet.resources, cost)
|
||||
const now = Date.now()
|
||||
gameStore.player.officers[officerType] = officerLogic.renewOfficerExpiration(gameStore.player.officers[officerType], duration, now)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<!-- 月球信息 -->
|
||||
<div v-if="!planet.isMoon && moon" class="mt-2">
|
||||
<Button @click="switchToMoon" variant="outline" size="sm">
|
||||
<span class="mr-2">🌙</span>
|
||||
{{ t('planet.switchToMoon') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-if="planet" class="container mx-auto p-4 sm:p-6">
|
||||
<!-- 未解锁遮罩 -->
|
||||
<!-- <UnlockRequirement :required-building="BuildingType.ResearchLab" :required-level="1" /> -->
|
||||
<UnlockRequirement :required-building="BuildingType.ResearchLab" :required-level="1" />
|
||||
|
||||
<h1 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6">{{ t('researchView.title') }}</h1>
|
||||
|
||||
@@ -107,12 +107,14 @@
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import UnlockRequirement from '@/components/UnlockRequirement.vue'
|
||||
import CardUnlockOverlay from '@/components/CardUnlockOverlay.vue'
|
||||
import { Check, X } from 'lucide-vue-next'
|
||||
import { formatNumber, getResourceCostColor } from '@/utils/format'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as researchLogic from '@/logic/researchLogic'
|
||||
import * as researchValidation from '@/logic/researchValidation'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const detailDialog = useDetailDialogStore()
|
||||
@@ -146,6 +148,11 @@
|
||||
)
|
||||
if (!validation.valid) return false
|
||||
const currentLevel = gameStore.player.technologies[techType] || 0
|
||||
|
||||
// 追踪资源消耗(在扣除前计算成本)
|
||||
const cost = researchLogic.calculateTechnologyCost(techType, currentLevel + 1)
|
||||
gameLogic.trackResourceConsumption(gameStore.player, cost)
|
||||
|
||||
const { queueItem } = researchValidation.executeTechnologyResearch(
|
||||
gameStore.currentPlanet,
|
||||
techType,
|
||||
|
||||
@@ -31,13 +31,20 @@
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<Card v-for="shipType in Object.values(ShipType)" :key="shipType" class="relative">
|
||||
<CardUnlockOverlay :requirements="SHIPS[shipType].requirements" />
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle
|
||||
class="text-sm sm:text-base lg:text-lg cursor-pointer hover:text-primary transition-colors underline decoration-dotted underline-offset-4 mb-2"
|
||||
@click="detailDialog.openShip(shipType)"
|
||||
>
|
||||
{{ SHIPS[shipType].name }}
|
||||
</CardTitle>
|
||||
<CardHeader>
|
||||
<div class="mb-2">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-2">
|
||||
<CardTitle
|
||||
class="text-sm sm:text-base lg:text-lg cursor-pointer hover:text-primary transition-colors underline decoration-dotted underline-offset-4 order-2 sm:order-1"
|
||||
@click="detailDialog.openShip(shipType)"
|
||||
>
|
||||
{{ SHIPS[shipType].name }}
|
||||
</CardTitle>
|
||||
<Badge variant="secondary" class="text-xs whitespace-nowrap self-start order-1 sm:order-2">
|
||||
{{ formatNumber(planet.fleet[shipType] || 0) }}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription class="text-xs sm:text-sm">{{ SHIPS[shipType].description }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -146,6 +153,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { ShipType, BuildingType } from '@/types/game'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
@@ -165,6 +173,8 @@
|
||||
import * as shipValidation from '@/logic/shipValidation'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as fleetStorageLogic from '@/logic/fleetStorageLogic'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const detailDialog = useDetailDialogStore()
|
||||
@@ -220,6 +230,11 @@
|
||||
if (!gameStore.currentPlanet) return { success: false }
|
||||
const validation = shipValidation.validateShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.technologies)
|
||||
if (!validation.valid) return { success: false, reason: validation.reason }
|
||||
|
||||
// 追踪资源消耗(在扣除前计算成本)
|
||||
const totalCost = shipLogic.calculateShipCost(shipType, quantity)
|
||||
gameLogic.trackResourceConsumption(gameStore.player, totalCost)
|
||||
|
||||
const queueItem = shipValidation.executeShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.officers)
|
||||
gameStore.currentPlanet.buildQueue.push(queueItem)
|
||||
return { success: true }
|
||||
|
||||