mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 16:05:12 +08:00
feat: 新增Android平台支持及构建流程
集成Android平台相关目录与配置文件,包含Gradle构建脚本、资源文件、启动图标、Java入口、Proguard规则等,完善.gitignore以排除Android构建产物。更新CI流程,支持自动构建并发布Android APK。移除README中项目结构说明,简化文档。
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user