feat: 新增多项功能并添加新工具

- 私人剪贴板新增限时直通URL生成功能
- doc_cloud_keeper新增标题复制按钮
- 新增贪蛇出动游戏工具
- 新增OSS中转直链工具
- 添加批量抽帧测试脚本
This commit is contained in:
yangxiangyuan
2026-08-11 10:22:31 +08:00
parent 91148fe064
commit 6dc79a2c12
28 changed files with 3169 additions and 6 deletions
+146
View File
@@ -0,0 +1,146 @@
/* 贪蛇出动 - 世界:金币生成/拾取/磁铁/边界/相机/死亡掉币 */
(function () {
'use strict';
window.GS = window.GS || {};
const U = GS.U;
class World {
constructor() {
this.size = GS.CFG.worldSize;
this.coins = []; // {x,y,r,value,kind:0普通 1星 2大, phase}
this.grid = new U.Grid(220);
this.cam = { x: this.size / 2, y: this.size / 2, scale: 1 };
this.seed();
}
seed() {
this.coins.length = 0;
for (let i = 0; i < GS.CFG.coinCount; i++) this.coins.push(this._makeCoin());
this._rebuild();
}
_makeCoin(x, y, kind) {
const k = kind != null ? kind : (Math.random() < 0.08 ? 1 : 0);
const base = {
0: { r: 7, value: 2 },
1: { r: 12, value: 10 }, // 星币:+幸运星
2: { r: 14, value: 20 } // 大金币:尸体掉落
}[k];
return {
x: x != null ? x : U.rand(60, this.size - 60),
y: y != null ? y : U.rand(60, this.size - 60),
r: base.r, value: base.value, kind: k,
phase: U.rand(0, Math.PI * 2),
vx: 0, vy: 0
};
}
_rebuild() {
this.grid.clear();
for (const c of this.coins) this.grid.insert(c);
}
// 尸体掉币:沿身体间隔掉落
dropFromSegments(segs) {
for (let i = 0; i < segs.length; i += 2) {
const s = segs[i];
this.coins.push(this._makeCoin(
U.clamp(s.x + U.rand(-14, 14), 40, this.size - 40),
U.clamp(s.y + U.rand(-14, 14), 40, this.size - 40), 2));
}
this._rebuild();
}
// 外部新增单枚金币(加速掉落等),并重建网格索引
addCoin(x, y, kind) {
const c = this._makeCoin(
U.clamp(x, 40, this.size - 40),
U.clamp(y, 40, this.size - 40), kind || 0);
c.r = kind === 2 ? 14 : (kind === 1 ? 12 : 5);
c.value = kind === 2 ? 20 : (kind === 1 ? 10 : 1);
this.coins.push(c);
this.grid.insert(c);
return c;
}
// 宝藏屋奖励:玩家附近撒一大簇金币
treasureBurst(x, y) {
for (let i = 0; i < 70; i++) {
const a = U.rand(0, Math.PI * 2), d = U.rand(30, 320);
this.coins.push(this._makeCoin(
U.clamp(x + Math.cos(a) * d, 40, this.size - 40),
U.clamp(y + Math.sin(a) * d, 40, this.size - 40),
Math.random() < 0.25 ? 1 : (Math.random() < 0.4 ? 2 : 0)));
}
this._rebuild();
}
update(dt, snakes, now) {
let moved = false;
for (const s of snakes) {
if (s.dead) continue;
const h = s.head;
const magnet = now < s.magnetUntil;
const eatR = s.radius + 16;
const pullR = magnet ? 260 : eatR;
const near = this.grid.query(h.x, h.y, Math.max(pullR, 60));
for (const c of near) {
if (c._eaten) continue;
const d2 = U.dist2(h.x, h.y, c.x, c.y);
if (magnet && d2 < pullR * pullR) {
const d = Math.sqrt(d2) || 1;
const pull = 420 * dt;
c.x += (h.x - c.x) / d * pull;
c.y += (h.y - c.y) / d * pull;
moved = true;
}
if (d2 < (eatR + c.r) * (eatR + c.r)) {
c._eaten = true;
s.grow(c.value);
s.coins += c.value;
if (c.kind === 1) s.lucky += 1;
}
}
}
if (this.coins.some(c => c._eaten)) {
this.coins = this.coins.filter(c => !c._eaten);
// 补充普通金币保持总量
while (this.coins.length < GS.CFG.coinCount) this.coins.push(this._makeCoin());
moved = true;
}
if (moved) this._rebuild();
}
// 相机跟随
updateCam(target, viewW, viewH, dt) {
const k = 1 - Math.pow(0.001, dt); // 平滑跟随
this.cam.x += (target.x - this.cam.x) * k;
this.cam.y += (target.y - this.cam.y) * k;
const base = Math.min(viewW, viewH) / 720;
this.cam.scale = U.clamp(base, 0.7, 1.6);
}
inBounds(x, y, margin) {
return x > margin && y > margin && x < this.size - margin && y < this.size - margin;
}
// 碰撞判定:返回 {type:'body', snake} 或 {type:'wall'} 或 null
collide(s, snakes) {
const h = s.head;
if (!this.inBounds(h.x, h.y, s.radius * 0.5)) return { type: 'wall' };
for (const o of snakes) {
if (o === s || o.dead) continue;
const segs = o.segments();
const rr = (s.radius * 0.8 + o.radius * 0.9);
// 跳过对方头部附近(允许并排)
for (let i = 2; i < segs.length; i++) {
const g = segs[i];
if (U.dist2(h.x, h.y, g.x, g.y) < rr * rr) return { type: 'body', snake: o };
}
}
return null;
}
}
GS.World = World;
})();