diff --git a/.gitignore b/.gitignore index 3dcd1e4..115e0d2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,12 @@ research/ .playwright-mcp/ cache/ star-raid/.env + +# Star Raid Android App +starraid-android/build/ +starraid-android/app/build/ +starraid-android/.gradle/ +starraid-android/local.properties +starraid-android/app/release/ +starraid-android/.keystore-pass +*.apk diff --git a/AGENTS.md b/AGENTS.md index 14ad23c..405eb51 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,8 +31,9 @@ git add -A && git commit -m "提交信息" ## 验证规则(最高优先级,违反会被用户骂) 1. **游戏视觉/手感禁止 Agent 验收**——用户亲自验收。但**账号/大厅/排行榜/结算上报等功能流允许 playwright 测试**(用户 2026-08-06 明确授权):注册/登陆/数据断言可跑,只是不许用截图评判游戏画面效果。 -2. 交付时必须诚实标注验证状态。 -3. 完成了操作 ≠ 任务完成,未经验证不得说“修复了/完成了”。 +2. **游戏玩法改动(武装配置/Boss机制/手感/数值等)默认由用户亲自测试**(用户 2026-08-06 补充):Agent 不主动跑 playwright 验收这类改动,只做语法检查等静态校验,交付时标注“未做功能验证,待用户验收”;用户明确要求时才跑功能流测试。 +3. 交付时必须诚实标注验证状态。 +4. 完成了操作 ≠ 任务完成,未经验证不得说“修复了/完成了”。 ## 项目结构 diff --git a/deploy_starraid.sh b/deploy_starraid.sh new file mode 100755 index 0000000..343e47e --- /dev/null +++ b/deploy_starraid.sh @@ -0,0 +1,106 @@ +#!/bin/bash +set -euo pipefail + +# ============================================================================ +# deploy_starraid.sh — Star Raid 一键部署(2026-08-06) +# ---------------------------------------------------------------------------- +# 参考项目本体 agents 的 deploy_agents.sh / refresh_agents.sh 双脚本模式: +# 本地:git push → rsync 上传 → SSH 触发服务器 refresh_starraid.sh +# 服务器:/opt/starraid/refresh_starraid.sh(重启 + 健康检查 + nginx reload) +# +# 用法: +# bash deploy_starraid.sh # 默认:push main + rsync + 触发刷新 +# bash deploy_starraid.sh --no-push # 跳过 git push(例如已手动 push) +# REMOTE_HOST=xxx bash deploy_starraid.sh # 覆盖远程主机 +# +# 说明: +# - rsync 排除 .env(生产密钥)、demo/、.DS_Store +# - Star Raid 是纯静态 + node 服务,无前端构建步骤 +# - 游戏服务器目录 /opt/starraid 无 git,采用 rsync 直传(与项目本体 git pull 模式不同) +# ============================================================================ + +# ── 配置(可通过环境变量覆盖) ── +REMOTE_HOST="${REMOTE_HOST:-59.110.19.30}" +REMOTE_USER="${REMOTE_USER:-root}" +REMOTE_PORT="${REMOTE_PORT:-22}" +REMOTE_DIR="${REMOTE_DIR:-/opt/starraid}" +GIT_BRANCH="${GIT_BRANCH:-main}" + +LOCAL_ROOT="$(cd "$(dirname "$0")" && pwd)" +GAME_DIR="$LOCAL_ROOT/star-raid" +SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + +log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + log "缺少命令 $1,请先安装后重试" + exit 1 + fi +} + +ssh_exec() { ssh -p "$REMOTE_PORT" $SSH_OPTS "$REMOTE_USER@$REMOTE_HOST" "$@"; } + +# ── 参数解析 ── +SKIP_PUSH=false +for arg in "$@"; do + case "$arg" in + --no-push) SKIP_PUSH=true ;; + -h|--help) + echo "用法: bash deploy_starraid.sh [--no-push]" + echo " --no-push 跳过 git push(仅 rsync 上传 + 服务器刷新)" + exit 0 + ;; + *) log "未知参数: $arg(支持 --no-push)"; exit 1 ;; + esac +done + +main() { + need_cmd ssh + need_cmd rsync + + # ── 0. 前置检查 ── + if [[ ! -d "$GAME_DIR" ]]; then + log "错误: 未找到游戏目录 $GAME_DIR" + exit 1 + fi + + # ── 1. Git Push ── + if $SKIP_PUSH; then + log "1/3 跳过 git push(--no-push)" + else + log "1/3 git push → origin/$GIT_BRANCH" + cd "$LOCAL_ROOT" + if ! git diff --quiet --exit-code 2>/dev/null || ! git diff --cached --quiet --exit-code 2>/dev/null; then + log "警告:有未提交的改动,请先 commit 或 stash" + log " - 查看: git status" + log " - 提交: git add -A && git commit -m '...'" + log " - 暂存: git stash push -m 'WIP'" + log "若仍要部署,请使用 --no-push 跳过此步骤" + exit 1 + fi + git push origin "$GIT_BRANCH" + log "push 完成" + fi + + # ── 2. rsync 上传 ── + log "2/3 rsync 上传 → $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR" + # 注意:不用 --delete——服务器端 refresh_starraid.sh 等本地没有的文件必须保留; + # 且避免本地临时缺文件时连带删除服务器文件(与手动 rsync 命令保持一致) + rsync -az \ + --exclude='.env' \ + --exclude='.env.example' \ + --exclude='.DS_Store' \ + --exclude='demo/' \ + --exclude='*.log' \ + --exclude='refresh_starraid.sh' \ + "$GAME_DIR/" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/" + log "上传完成" + + # ── 3. 触发服务器刷新 ── + log "3/3 触发服务器 refresh_starraid.sh" + ssh_exec "bash $REMOTE_DIR/refresh_starraid.sh" + log "全部完成 🚀 https://starraid.cyjai.com" +} + +main "$@" diff --git a/star-raid/css/style.css b/star-raid/css/style.css index 8eb6d58..c3aea78 100644 --- a/star-raid/css/style.css +++ b/star-raid/css/style.css @@ -134,6 +134,11 @@ h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-heig .cx-item .nm{font-size:clamp(6px,1.1vw,8px);margin-top:4px;letter-spacing:1px} .cx-item.lock{opacity:.35;cursor:default} .cx-item.lock:hover{transform:none;border-color:var(--line)} +/* 自选武装(Boss挑战):选中高亮 + 槽位计数 */ +.cx-item.sel{border-color:var(--cyan);box-shadow:0 0 12px rgba(65,230,255,.4);background:rgba(65,230,255,.08)} +.cnt{color:var(--gold);font-size:clamp(8px,1.4vw,11px)} +.cx-grid.full .cx-item:not(.sel){opacity:.55} +.cx-grid.full .cx-item:not(.sel):hover{border-color:var(--line);transform:none} .cx-sec{font-size:clamp(8px,1.5vw,10px);color:var(--cyan);margin:10px 0 4px;letter-spacing:3px} /* ---------- 登陆/注册 ---------- */ diff --git a/star-raid/index.html b/star-raid/index.html index d409855..808b6f2 100644 --- a/star-raid/index.html +++ b/star-raid/index.html @@ -102,7 +102,7 @@
— BOSS 挑战 —
-
随机武装:3主武Lv3 · 3副武Lv3 · 2僚机Lv2 · 击破计时
+
随机武装:3主武Lv4 · 3副武Lv4 · 2僚机Lv3 · 阶段回血50% · 击破计时
@@ -115,6 +115,35 @@
 
+ +
+
— BOSS 挑战 —
+
 
+
选择出击方式
+
+ + + +
+
+ + +
+
— 自选武装 —
+
 
+
主武器 0/3
+
+
副武器 0/3
+
+
僚机 0/2
+
+
未选满出击时自动随机补齐(仅已解锁)
+
+ + +
+
+
— 机库 · 强化 —
diff --git a/star-raid/js/enemies.js b/star-raid/js/enemies.js index 1fb8ebf..3b74b51 100644 --- a/star-raid/js/enemies.js +++ b/star-raid/js/enemies.js @@ -282,7 +282,13 @@ function updateBoss(){ if(np>b.phase){b.phase=np;b.transT=70;b.r=(def.rr&&def.rr[np])||b.r;b.ry=(def.ryy&&def.ryy[np])||undefined; G.eb.length=0;G.bbeams.length=0;b.keys=null;SFX.warn();G.shake=Math.max(G.shake,5); if(b.idx===6){G.funnels.length=0;b.form=null;if(np===1){spawnFunnels(b,4);b.plT=Math.round(300/b.fireM);}} // 夺权者X二阶段:增至4架僚机 - banner(b.name+" — 第"+["一","二","三"][np]+"形态","#ff5566",90);} + banner(b.name+" — 第"+["一","二","三"][np]+"形态","#ff5566",90); + if(G.mode==="rush"){ // Boss挑战:通过一个阶段恢复50%血量 + const heal=Math.ceil(G.maxHp/2); + G.hp=Math.min(G.maxHp,G.hp+heal); + G.texts.push({x:G.p.x,y:G.p.y-16,t:"+50% 生命恢复",col:"#5cff9d",life:70}); + SFX.graze(); + }} if(b.transT>0){b.transT--;b.dashTrail=null; // 变形期间清冲撞残影 if(b.transT===0){explodeFx(b.x,b.y,"dark",1.3);G.shake=Math.max(G.shake,6); for(let i=0;i<10;i++)ebul(b.x,b.y,i/10*TAU,1.5,"orbP",12);} // 变形完成爆发环弹 diff --git a/star-raid/js/ui.js b/star-raid/js/ui.js index a33da7f..f92b9c0 100644 --- a/star-raid/js/ui.js +++ b/star-raid/js/ui.js @@ -1,6 +1,6 @@ "use strict"; /* ================= 界面流转 ================= */ -const OVLS=["o-auth","o-home","o-title","o-setup","o-hangar","o-cards","o-pause","o-over","o-codex","o-demo","o-rush","o-slot","o-unsaved"]; +const OVLS=["o-auth","o-home","o-title","o-setup","o-hangar","o-cards","o-pause","o-over","o-codex","o-demo","o-rush","o-slot","o-unsaved","o-rushmode","o-rushpick"]; function show(id){for(const o of OVLS)$(o).classList.toggle("hide",o!==id);} const SETUP={mode:"endless",ship:"falcon",diff:0}; // 出击默认无尽模式 function bestLine(){ @@ -359,21 +359,96 @@ function buildRush(){ if(row.children.length){grp.appendChild(row);el.appendChild(grp);} } } -function rollRushBuild(){ // 随机武装:3主武Lv3 + 3副武Lv3 + 2僚机Lv2 +function rollRushBuild(){ // 随机武装:3主武Lv4 + 3副武Lv4 + 2僚机Lv3 const sh=a=>{const x=a.slice();for(let i=x.length-1;i>0;i--){const j=rndi(0,i),t2=x[i];x[i]=x[j];x[j]=t2;}return x;}; return {m:sh(Object.keys(WEAPONS)).slice(0,3),s:sh(Object.keys(SUBS)).slice(0,3),d:sh(Object.keys(DRONES)).slice(0,2)}; } -function startRush(i){ +function startRush(i){ // 点Boss卡片:弹窗选择 随机出击/自选武器 AU.init();if(AU.ctx&&AU.ctx.state==="suspended")AU.ctx.resume(); - const build=rollRushBuild(); - if(SAVE.rushAnim)playSlot(build,()=>startRushGo(i,build)); // 老虎机动画抽取 - else startRushGo(i,build); // 直接随机 + openRushMode(i); +} +/* ---- 出击方式弹窗:随机 / 自选 ---- */ +let rushPickIdx=0; +function openRushMode(i){ + rushPickIdx=i; + const b=BOSSES[i],tierName=["简单","中等","困难","地狱"][b.tier]||""; + const nm=$("rushmode-name");nm.textContent=b.name+(tierName?" · "+tierName:"");nm.style.color=b.col; + $("b-rushmode-random").onclick=()=>{ + show("o-rush"); + const build=rollRushBuild(); + if(SAVE.rushAnim)playSlot(build,()=>startRushGo(i,build)); // 老虎机动画抽取 + else startRushGo(i,build); // 直接随机 + }; + $("b-rushmode-pick").onclick=()=>openRushPick(i); + $("b-rushmode-cancel").onclick=()=>show("o-rush"); + show("o-rushmode"); +} +/* ---- 自选武装面板:从图鉴已解锁中挑 3主+3副+2僚(等级与随机同级 Lv4/4/3) ---- */ +const rushPick={m:[null,null,null],s:[null,null,null],d:[null,null,null]}; // 槽位:null=未选 +function openRushPick(i){ + rushPickIdx=i; + const b=BOSSES[i]; + $("rushpick-title").textContent=b.name+" — 从已解锁图鉴中选择"; + for(const k of ["m","s","d"])rushPick[k].fill(null); + const mk=(label,keys,dict)=>{ + const box=$("rushpick-"+label);box.innerHTML=""; + for(const k of keys){ + const def=dict[k],owned=!!SAVE.codex[k]||codexTestAll; // 测试模式:全部可选 + const d=document.createElement("div"); + d.className="cx-item"+(owned?"":" lock"); + const img=def.icon?ICONS[def.icon]:SPR[def.spr]; + d.appendChild(iconCanvas(img,28,!owned)); + d.insertAdjacentHTML("beforeend",`
${owned?def.name:"???"}
`); + d.dataset.key=k; + if(owned)d.onclick=()=>toggleRushPick(d,label); + box.appendChild(d); + } + }; + mk("m",MAINS,WEAPONS); + mk("s",SUB_KEYS,SUBS); + mk("d",DRONE_KEYS,DRONES); + updRushPickCaps(); + show("o-rushpick"); +} +function toggleRushPick(el,label){ + const cap=label==="d"?2:3,slots=rushPick[label]; + const i=slots.indexOf(el.dataset.key); + if(i>=0){slots[i]=null;el.classList.remove("sel");} // 取消已选 + else{ + if(slots.filter(Boolean).length>=cap)return; // 槽位已满:忽略点击 + const empty=slots.indexOf(null);if(empty<0)return; + slots[empty]=el.dataset.key;el.classList.add("sel"); + SFX.pick(); + } + updRushPickCaps(); +} +function updRushPickCaps(){ + const cap={m:3,s:3,d:2}; + for(const k of ["m","s","d"]){ + const n=rushPick[k].filter(Boolean).length; + const c=$("rushpick-cnt-"+k);if(c)c.textContent=n+"/"+cap[k]; + $("rushpick-"+k).classList.toggle("full",n>=cap[k]); + } +} +function rushPickGo(){ // 未选满的槽位:从已解锁池随机补齐(图鉴全空时兜底全池) + const owned=kk=>!!SAVE.codex[kk]||codexTestAll; + const pools={m:MAINS,s:SUB_KEYS,d:DRONE_KEYS}; + const build={m:[],s:[],d:[]}; + for(const k of ["m","s","d"]){ + const cap=k==="d"?2:3; + const ownedPool=pools[k].filter(owned); + const src=ownedPool.length?ownedPool:pools[k]; // 兜底 + build[k]=rushPick[k].filter(Boolean).slice(0,cap); + const rest=src.filter(x=>!build[k].includes(x)); + while(build[k].lengthG.weapons[k]=3);G.loadout=build.m.slice(); - G.subs={};build.s.forEach(k=>G.subs[k]=3);G.subLoadout=build.s.slice(); - G.drones={};build.d.forEach(k=>G.drones[k]=2);G.droneLoadout=build.d.slice(); + G.weapons={};build.m.forEach(k=>G.weapons[k]=4);G.loadout=build.m.slice(); + G.subs={};build.s.forEach(k=>G.subs[k]=4);G.subLoadout=build.s.slice(); + G.drones={};build.d.forEach(k=>G.drones[k]=3);G.droneLoadout=build.d.slice(); G.xpNeed=1e9; // 挑战模式无升级打断 G.rushIdx=i;G.rushT0=0; G.stage=i; // 排行榜统计:rush 局 stage 记录 Boss 索引(0~10) @@ -384,6 +459,8 @@ function startRushGo(i,build){ G.rushT0=G.time; startBoss(); } +$("b-rushpick-go").onclick=()=>{show("o-rush");rushPickGo();}; +$("b-rushpick-back").onclick=()=>show("o-rush"); /* ---- 老虎机抽奖动画 ---- */ let slotTimers=[]; function playSlot(build,cb){ diff --git a/starraid-android/.gitignore b/starraid-android/.gitignore new file mode 100644 index 0000000..1741fc1 --- /dev/null +++ b/starraid-android/.gitignore @@ -0,0 +1,25 @@ +# macOS +.DS_Store + +# Local Android/IDE state +local.properties +.idea/ +.gradle/ + +# Build outputs +build/ +**/build/ + +# Signing keys and secrets +*.jks +*.keystore + +# Logs +*.log + +# Release artifacts +*.apk +*.aab +*.apks +*.dm +app/release/ diff --git a/starraid-android/APP_CHANGELOG.md b/starraid-android/APP_CHANGELOG.md new file mode 100644 index 0000000..e8fabaa --- /dev/null +++ b/starraid-android/APP_CHANGELOG.md @@ -0,0 +1,7 @@ +# Star Raid Android 更新说明 + +## 1.0.0(2026-08-06) +- 首个版本:WebView 壳加载 https://starraid.cyjai.com +- 锁定竖屏 + 沉浸式全屏 + 游戏期间屏幕常亮 +- 像素风启动画面与 App 图标(初始飞机「猎鹰」) +- 账号 / 排行榜 / 云存档与 Web 版完全互通 diff --git a/starraid-android/README.md b/starraid-android/README.md new file mode 100644 index 0000000..1c050c3 --- /dev/null +++ b/starraid-android/README.md @@ -0,0 +1,34 @@ +# Star Raid Android 客户端 + +星尘突击队(Star Raid)安卓客户端,WebView 壳工程。 + +- 游戏线上地址:https://starraid.cyjai.com +- 客户端仅承载 WebView,游戏/账号/排行榜/云存档全部走线上服务 +- 锁定竖屏、沉浸式全屏、游戏期间屏幕常亮 + +## 构建 + +```bash +# 需要 Android SDK(local.properties 或 ANDROID_HOME)与 JDK 17 + +# 签名参数(也可用同名环境变量) +./gradlew assembleRelease \ + -PANDROID_KEYSTORE_PATH=/path/to/starraid-release.jks \ + -PANDROID_KEYSTORE_PASSWORD=xxx \ + -PANDROID_KEY_ALIAS=starraid \ + -PANDROID_KEY_PASSWORD=xxx +``` + +产物:`app/build/outputs/apk/release/app-release.apk` + +## 可改项 + +- 包名:`app/build.gradle.kts`(namespace/applicationId)与 `MainActivity.kt` 的 `package` +- 应用名:`app/src/main/res/values/strings.xml` +- 首页地址:`MainActivity.kt` 中 `HOME_URL` +- 图标:`tools/gen_icons.py`(像素数据取自游戏 `js/sprites.js` 的 ship0,重新生成后需在 Android Studio 或 gradle 里 rebuild) + +## 版本发布 + +- 递增 `app/build.gradle.kts` 的 `versionCode` / `versionName` +- 顶部更新 `APP_CHANGELOG.md` diff --git a/starraid-android/app/build.gradle.kts b/starraid-android/app/build.gradle.kts new file mode 100644 index 0000000..e4b4b54 --- /dev/null +++ b/starraid-android/app/build.gradle.kts @@ -0,0 +1,66 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +fun resolveConfig(name: String): String? { + return (project.findProperty(name) as String?)?.takeIf { it.isNotBlank() } + ?: System.getenv(name)?.takeIf { it.isNotBlank() } +} + +android { + namespace = "com.cyjai.starraid" + compileSdk = 35 + + defaultConfig { + applicationId = "com.cyjai.starraid" + minSdk = 24 + targetSdk = 35 + versionCode = 1 + versionName = "1.0.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + create("release") { + val storeFilePath = resolveConfig("ANDROID_KEYSTORE_PATH") + val storePass = resolveConfig("ANDROID_KEYSTORE_PASSWORD") + val keyAliasValue = resolveConfig("ANDROID_KEY_ALIAS") + val keyPass = resolveConfig("ANDROID_KEY_PASSWORD") + + if (!storeFilePath.isNullOrBlank()) { + storeFile = file(storeFilePath) + } + storePassword = storePass + keyAlias = keyAliasValue + keyPassword = keyPass + } + } + + buildTypes { + release { + isMinifyEnabled = false + signingConfig = signingConfigs.getByName("release") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.13.1") + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("com.google.android.material:material:1.12.0") + implementation("androidx.activity:activity-ktx:1.9.2") +} diff --git a/starraid-android/app/proguard-rules.pro b/starraid-android/app/proguard-rules.pro new file mode 100644 index 0000000..2cb8d49 --- /dev/null +++ b/starraid-android/app/proguard-rules.pro @@ -0,0 +1 @@ +# Intentionally minimal for WebView shell app diff --git a/starraid-android/app/src/main/AndroidManifest.xml b/starraid-android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..626935c --- /dev/null +++ b/starraid-android/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + diff --git a/starraid-android/app/src/main/java/com/cyjai/starraid/MainActivity.kt b/starraid-android/app/src/main/java/com/cyjai/starraid/MainActivity.kt new file mode 100644 index 0000000..4aa5282 --- /dev/null +++ b/starraid-android/app/src/main/java/com/cyjai/starraid/MainActivity.kt @@ -0,0 +1,120 @@ +package com.cyjai.starraid + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.view.View +import android.view.WindowManager +import android.webkit.CookieManager +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.Toast +import androidx.activity.OnBackPressedCallback +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat + +/** + * Star Raid(星尘突击队)安卓客户端 + * WebView 壳加载线上游戏,账号/排行榜/云存档与 Web 版完全互通。 + */ +class MainActivity : AppCompatActivity() { + + private lateinit var webView: WebView + private var lastBackPressedAt = 0L + + @SuppressLint("SetJavaScriptEnabled") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // 沉浸式全屏:隐藏状态栏/导航栏,滑动手势可临时唤出 + WindowCompat.setDecorFitsSystemWindows(window, false) + WindowInsetsControllerCompat(window, window.decorView).apply { + hide(WindowInsetsCompat.Type.systemBars()) + systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + // 游戏期间屏幕常亮 + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + + webView = WebView(this) + webView.isVerticalScrollBarEnabled = false + webView.isHorizontalScrollBarEnabled = false + webView.overScrollMode = View.OVER_SCROLL_NEVER + setContentView(webView) + + CookieManager.getInstance().apply { + setAcceptCookie(true) + setAcceptThirdPartyCookies(webView, true) + } + + webView.settings.apply { + javaScriptEnabled = true + domStorageEnabled = true + databaseEnabled = true + mediaPlaybackRequiresUserGesture = false + // 服务器静态资源为 immutable 长缓存;App 侧强制走网络,确保游戏更新即时生效 + cacheMode = WebSettings.LOAD_NO_CACHE + useWideViewPort = true + loadWithOverviewMode = true + setSupportZoom(false) + builtInZoomControls = false + displayZoomControls = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + safeBrowsingEnabled = true + } + } + webView.clearCache(true) + + webView.webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + val url = request?.url?.toString() ?: return false + if (url.startsWith(HOME_URL)) return false + // 站外链接交给系统浏览器 + try { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + } catch (_: Exception) { + // 忽略无浏览器可用的极端情况 + } + return true + } + } + + // 返回键:2 秒内再按一次退出,防误触 + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val now = System.currentTimeMillis() + if (now - lastBackPressedAt < 2000) { + finish() + } else { + lastBackPressedAt = now + Toast.makeText(this@MainActivity, "再按一次退出游戏", Toast.LENGTH_SHORT).show() + } + } + }) + + if (savedInstanceState == null) { + webView.loadUrl(HOME_URL) + } else { + webView.restoreState(savedInstanceState) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + webView.saveState(outState) + super.onSaveInstanceState(outState) + } + + override fun onDestroy() { + webView.destroy() + super.onDestroy() + } + + companion object { + private const val HOME_URL = "https://starraid.cyjai.com" + } +} diff --git a/starraid-android/app/src/main/res/drawable/ic_launcher_fg.png b/starraid-android/app/src/main/res/drawable/ic_launcher_fg.png new file mode 100644 index 0000000..553f0cd Binary files /dev/null and b/starraid-android/app/src/main/res/drawable/ic_launcher_fg.png differ diff --git a/starraid-android/app/src/main/res/drawable/ic_launcher_full.png b/starraid-android/app/src/main/res/drawable/ic_launcher_full.png new file mode 100644 index 0000000..46d3af0 Binary files /dev/null and b/starraid-android/app/src/main/res/drawable/ic_launcher_full.png differ diff --git a/starraid-android/app/src/main/res/drawable/ic_launcher_mono.png b/starraid-android/app/src/main/res/drawable/ic_launcher_mono.png new file mode 100644 index 0000000..c756048 Binary files /dev/null and b/starraid-android/app/src/main/res/drawable/ic_launcher_mono.png differ diff --git a/starraid-android/app/src/main/res/drawable/splash_bg.xml b/starraid-android/app/src/main/res/drawable/splash_bg.xml new file mode 100644 index 0000000..619ef81 --- /dev/null +++ b/starraid-android/app/src/main/res/drawable/splash_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/starraid-android/app/src/main/res/drawable/splash_logo.png b/starraid-android/app/src/main/res/drawable/splash_logo.png new file mode 100644 index 0000000..394ea01 Binary files /dev/null and b/starraid-android/app/src/main/res/drawable/splash_logo.png differ diff --git a/starraid-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/starraid-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..b420871 --- /dev/null +++ b/starraid-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/starraid-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/starraid-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..b420871 --- /dev/null +++ b/starraid-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/starraid-android/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml b/starraid-android/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml new file mode 100644 index 0000000..b6a0d17 --- /dev/null +++ b/starraid-android/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/starraid-android/app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml b/starraid-android/app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml new file mode 100644 index 0000000..b6a0d17 --- /dev/null +++ b/starraid-android/app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/starraid-android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/starraid-android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..b6bba39 Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/starraid-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/starraid-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..b6bba39 Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/starraid-android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/starraid-android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..513bf1b Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/starraid-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/starraid-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..513bf1b Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/starraid-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/starraid-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..cfa836e Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/starraid-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/starraid-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..cfa836e Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/starraid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/starraid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..6911b0d Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/starraid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/starraid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..6911b0d Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/starraid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/starraid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..543c821 Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/starraid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/starraid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..543c821 Binary files /dev/null and b/starraid-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/starraid-android/app/src/main/res/values/colors.xml b/starraid-android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..84f84b8 --- /dev/null +++ b/starraid-android/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #0A0A18 + #0A0A18 + diff --git a/starraid-android/app/src/main/res/values/strings.xml b/starraid-android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..0b0f291 --- /dev/null +++ b/starraid-android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + 星尘突击队 + diff --git a/starraid-android/app/src/main/res/values/themes.xml b/starraid-android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..cbed99a --- /dev/null +++ b/starraid-android/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + diff --git a/starraid-android/app/src/main/res/xml/network_security_config.xml b/starraid-android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..66ef456 --- /dev/null +++ b/starraid-android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,7 @@ + + + + + starraid.cyjai.com + + diff --git a/starraid-android/build.gradle.kts b/starraid-android/build.gradle.kts new file mode 100644 index 0000000..017d909 --- /dev/null +++ b/starraid-android/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + id("com.android.application") version "8.5.2" apply false + id("org.jetbrains.kotlin.android") version "1.9.24" apply false +} diff --git a/starraid-android/gradle.properties b/starraid-android/gradle.properties new file mode 100644 index 0000000..f0a2e55 --- /dev/null +++ b/starraid-android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +android.useAndroidX=true +kotlin.code.style=official +android.nonTransitiveRClass=true diff --git a/starraid-android/gradle/wrapper/gradle-wrapper.jar b/starraid-android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..980502d Binary files /dev/null and b/starraid-android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/starraid-android/gradle/wrapper/gradle-wrapper.properties b/starraid-android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..128196a --- /dev/null +++ b/starraid-android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-milestone-1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/starraid-android/gradlew b/starraid-android/gradlew new file mode 100755 index 0000000..faf9300 --- /dev/null +++ b/starraid-android/gradlew @@ -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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# 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" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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" "$@" diff --git a/starraid-android/gradlew.bat b/starraid-android/gradlew.bat new file mode 100644 index 0000000..9b42019 --- /dev/null +++ b/starraid-android/gradlew.bat @@ -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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +: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 diff --git a/starraid-android/settings.gradle.kts b/starraid-android/settings.gradle.kts new file mode 100644 index 0000000..0287b35 --- /dev/null +++ b/starraid-android/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "StarRaidApp" +include(":app") diff --git a/starraid-android/tools/gen_icons.py b/starraid-android/tools/gen_icons.py new file mode 100644 index 0000000..ec5e402 --- /dev/null +++ b/starraid-android/tools/gen_icons.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Star Raid Android 图标生成:使用游戏初始飞机 falcon(ship0) 像素数据""" +import os +import random +from PIL import Image, ImageDraw + +BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +RES = os.path.join(BASE, "app", "src", "main", "res") + +# —— 初始飞机 falcon (ship0) 像素数据(来自 star-raid/js/sprites.js)—— +PAL = {"K": "#14142b", "W": "#f2f0e6", "C": "#43e8ff", "R": "#e0393e", "D": "#8f1d2c", "O": "#ff9a3d"} +SHIP = [ + "......K......", ".....KWK.....", ".....KWK.....", ".....KCK.....", + "....KWWWK....", "....KRCRK....", "...KWRRRWK...", "..KWKRDRKWK..", + "..KWRRRRRWK..", ".KWKRRRRRKWK.", ".KRRKWWWKRRK.", "KKRDKWKWKDRKK", + "KD..KWKWK..DK", "K...KOKOK...K", "....KOKOK....", +] +BG_COLOR = (10, 10, 24) # 深空蓝黑 + +def hex2rgb(h): + h = h.lstrip("#") + return tuple(int(h[i:i + 2], 16) for i in (0, 2, 4)) + +def render_ship(scale, mono=False): + w, h = len(SHIP[0]), len(SHIP) + img = Image.new("RGBA", (w * scale, h * scale), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + for y, row in enumerate(SHIP): + for x, ch in enumerate(row): + if ch != ".": + c = (255, 255, 255) if mono else hex2rgb(PAL[ch]) + d.rectangle([x * scale, y * scale, (x + 1) * scale - 1, (y + 1) * scale - 1], fill=c + (255,)) + return img + +def stars_bg(size, seed=7, n=120): + rnd = random.Random(seed) + img = Image.new("RGB", (size, size), BG_COLOR) + d = ImageDraw.Draw(img) + for _ in range(n): + x, y = rnd.randint(0, size - 1), rnd.randint(0, size - 1) + r = rnd.choice([1, 1, 1, 2]) + col = rnd.choice([(242, 240, 230), (67, 232, 255), (255, 255, 255), (150, 170, 220)]) + d.rectangle([x, y, x + r, y + r], fill=col) + for _ in range(6): + x, y = rnd.randint(20, size - 20), rnd.randint(20, size - 20) + d.rectangle([x - 1, y, x + 1, y], fill=(242, 240, 230)) + d.rectangle([x, y - 1, x, y + 1], fill=(242, 240, 230)) + return img + +def paste_center(canvas, layer, scale): + fg = render_ship(scale) + x = (canvas.width - fg.width) // 2 + y = (canvas.height - fg.height) // 2 + canvas.alpha_composite(fg, (x, y)) + +def save_png(img, path): + os.makedirs(os.path.dirname(path), exist_ok=True) + img.save(path, "PNG") + print("saved", path, img.size) + +# 1) adaptive icon foreground:512 透明底 + 飞机 16x(176x240,居中,安全区内) +fg = Image.new("RGBA", (512, 512), (0, 0, 0, 0)) +paste_center(fg, render_ship, 16) +save_png(fg, os.path.join(RES, "drawable", "ic_launcher_fg.png")) + +# 2) mono(Android 13 主题图标):白色飞机 +mono = Image.new("RGBA", (512, 512), (0, 0, 0, 0)) +mono_ship = render_ship(16, mono=True) +mono.alpha_composite(mono_ship, ((512 - mono_ship.width) // 2, (512 - mono_ship.height) // 2)) +save_png(mono, os.path.join(RES, "drawable", "ic_launcher_mono.png")) + +# 3) 完整图标(背景 + 飞机):用于 legacy mipmap +full = stars_bg(512).convert("RGBA") +paste_center(full, render_ship, 16) +save_png(full, os.path.join(RES, "drawable", "ic_launcher_full.png")) + +# 4) legacy mipmap 各尺寸 +mipmap_sizes = {"mdpi": 48, "hdpi": 72, "xhdpi": 96, "xxhdpi": 144, "xxxhdpi": 192} +for dpi, size in mipmap_sizes.items(): + icon = full.resize((size, size), Image.NEAREST) + for name in ("ic_launcher", "ic_launcher_round"): + save_png(icon, os.path.join(RES, "mipmap-" + dpi, name + ".png")) + +# 5) splash logo:透明底飞机大图(windowBackground layer-list 用) +splash = Image.new("RGBA", (512, 512), (0, 0, 0, 0)) +paste_center(splash, render_ship, 24) +save_png(splash, os.path.join(RES, "drawable", "splash_logo.png")) + +print("DONE")