0.2.5-dev: 驭械术师 normal attacks keep the operator's own hit plus one drone hit, the drone multiplier and per-target map reset on redeploy, 洛洛 S2 keeps its lock until a kill or the skill ends (PRs #537 / #536 / #535 by @Xinglan233, ported); hit tiles use the official round-half-to-even with an edge inset (PR #520 by @Cloudnyco, ported); bots softly avoid living teammates' opening factions until they are eliminated (PR #518 by @IceCodeNew, ported with changes); golden: 43 more scenarios move, each named in docs/history/0.2.5.md

This commit is contained in:
sganggs committed 2026-10-12 14:45:01 +08:00
1 parent 7fd0478dc0
commit e0f3771821
41 files changed
+1939 -779

No files matched your search

+21 -8
View File
@@ -138,16 +138,26 @@ const DEFAULT_MELEE_RANGE = [[0, 0], [0, 1]];
* preferred). Alone, a band that withholds the first rounds' funds (老鲤 "资金暂存": no operator in R1–R2, every enemy
* leaks) is avoided — only 联防 teammates cover that. A band whose mechanic rides on a bond the mode switches off
* (gd.bandBondIds ∩ gd.modeInactiveBonds — 标准: 潘格尼尼 <拉特兰>, 克莱门莎 <阿戈尔>, 玛恩纳 <卡西米尔>) weighs 0, never taken
* (DESIGN §21.26); with every band excluded, the default band. One rng draw per call (deterministic per seed); modes
* without inactive bonds keep exactly the earlier picks.
* (DESIGN §21.26). [ASSUMED] A living teammate's opening strategy soft-reserves those bonds before anyone is deployed:
* the weight stays positive, at a quarter of the unreserved weight, so the strategy can still be drawn, and the
* reservation ends when that teammate is eliminated (alivePlayers). With every band excluded, the default band. One
* rng draw per call (deterministic per seed); a solo match has no teammate to reserve against.
*/
const SOFT_BAND_RESERVE = 0.25;
export function botPickBand(m, ps) {
const gd = ps?.gd || m.gd;
const ids = gd.bandIds();
if (!ids.length) return gd.defaultBandId;
const lateFunds = (id) => /暂存/.test(String(gd.band(id)?.desc || ''));
const offBond = (id) => gd.bandBondIds(id).some((b) => gd.modeInactiveBonds.has(b));
const pairs = ids.map((id) => [id, offBond(id) ? 0 : Math.max(1, (gd.startLp(id) - 18) ** 2) * (m.isSolo && lateFunds(id) ? 0.02 : 1)]);
const reserved = new Set(ps && typeof m.alivePlayers === 'function'
? m.alivePlayers().filter((p) => p !== ps).flatMap((p) => gd.bandBondIds(p.bandId))
: []);
const inactive = (id) => gd.bandBondIds(id).some((b) => gd.modeInactiveBonds.has(b));
const pairs = ids.map((id) => {
if (inactive(id)) return [id, 0];
const w = Math.max(1, (gd.startLp(id) - 18) ** 2) * (m.isSolo && lateFunds(id) ? 0.02 : 1);
return [id, gd.bandBondIds(id).some((b) => reserved.has(b)) ? w * SOFT_BAND_RESERVE : w];
});
let total = 0;
for (const [, w] of pairs) total += w;
let r = m.rngBots() * total;
@@ -348,12 +358,14 @@ function mainCoreBond(m, p) {
* The bonds the bot builds around (cached per round and owned set): `focus` = the core bond with the best
* owned members × 10 + reachable members (bondPoolStats, ≤ 4) + layers already banked (≤ 6) + 6 for last round's focus
* (commit instead of flip-flopping between equal bonds) − 6 when a teammate already builds it (its main core bond on
* its public bond strip, mainCoreBond — the shop pool is shared); `second` = the other live bond (core or add-on) with
* the most owned members whose next threshold is within reach (≥ 1 owned member). `ps._botFocusId` keeps the focus
* across rounds.
* its public bond strip, mainCoreBond — the shop pool is shared) or has selected that bond's opening strategy, even
* before anyone is deployed. The strategy reservation ends when that teammate is eliminated. `second` = the other live
* bond (core or add-on) with the most owned members whose next threshold is within reach (≥ 1 owned member).
* `ps._botFocusId` keeps the focus across rounds.
*/
export function bondPlan(m, ps, owned = ownedBonds(m, ps)) {
const key = `${m.round}|${ps.shop.level}|${[...owned.counts.entries()].map(([k, v]) => k + v).join()}`;
const livingBands = m.alivePlayers().filter((p) => p !== ps).flatMap((p) => m.gd.bandBondIds(p.bandId)).sort().join();
const key = `${m.round}|${ps.shop.level}|${[...owned.counts.entries()].map(([k, v]) => k + v).join()}|${livingBands}`;
if (ps._botFocus && ps._botFocus.key === key) return ps._botFocus;
const prev = ps._botFocusId ?? null;
const mates = new Set();
@@ -361,6 +373,7 @@ export function bondPlan(m, ps, owned = ownedBonds(m, ps)) {
if (p === ps) continue;
const b = mainCoreBond(m, p);
if (b) mates.add(b);
for (const id of m.gd.bandBondIds(p.bandId)) mates.add(id);
}
let focus = null;
let bestS = 0;