184 lines
6.8 KiB
JavaScript
184 lines
6.8 KiB
JavaScript
|
|
"use strict";
|
||
|
|
|
||
|
|
(function (global) {
|
||
|
|
const World = global.World;
|
||
|
|
if (!World) throw new Error("World is not available for mixin: world_spatial_budget.js");
|
||
|
|
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
|
||
|
|
performanceLevel() {
|
||
|
|
if (window.TarinaiPerformance) return window.TarinaiPerformance.level || 0;
|
||
|
|
const fps = typeof window !== "undefined" ? Number(window.__tarinaiFps || 0) : 60;
|
||
|
|
if (!fps || !Number.isFinite(fps)) return 0;
|
||
|
|
if (fps < 22) return 3;
|
||
|
|
if (fps < 34) return 2;
|
||
|
|
if (fps < 48) return 1;
|
||
|
|
return 0;
|
||
|
|
},
|
||
|
|
|
||
|
|
performanceQuality() {
|
||
|
|
return window.TarinaiPerformance?.quality?.(this) || { level: this.performanceLevel(), antStride: 1, effectLimit: CONFIG.effectLimit ?? 96 };
|
||
|
|
},
|
||
|
|
|
||
|
|
grassLoadLevel() {
|
||
|
|
const grass = this.itemCounts?.grass || 0;
|
||
|
|
const perf = this.performanceLevel();
|
||
|
|
if (perf >= 3 || grass >= 180) return 3;
|
||
|
|
if (perf >= 2 || grass >= 120) return 2;
|
||
|
|
if (perf >= 1 || grass >= 72) return 1;
|
||
|
|
return 0;
|
||
|
|
},
|
||
|
|
|
||
|
|
grassUpdateFactor() {
|
||
|
|
return [1.0, 1.35, 1.85, 2.45][this.grassLoadLevel()] || 1.0;
|
||
|
|
},
|
||
|
|
|
||
|
|
sortedDrawList() {
|
||
|
|
if (!this.drawList) this.drawList = [];
|
||
|
|
const antCount = (this.ants || []).filter(a => a && !a.dead).length;
|
||
|
|
const tarinaiCount = (this.tarinai || []).filter(t => t && !t.dead).length;
|
||
|
|
if (this.drawListDirty || this.drawList.length !== tarinaiCount + antCount) {
|
||
|
|
this.drawList.length = 0;
|
||
|
|
for (const t of this.tarinai || []) if (t && !t.dead) this.drawList.push(t);
|
||
|
|
for (const a of this.ants || []) if (a && !a.dead) this.drawList.push(a);
|
||
|
|
this.drawList.sort((a, b) => (a.y || 0) - (b.y || 0));
|
||
|
|
this.drawListDirty = false;
|
||
|
|
}
|
||
|
|
return this.drawList;
|
||
|
|
},
|
||
|
|
|
||
|
|
nearbyItems(x, y, radius, precise = false) {
|
||
|
|
return this.spatial.nearby(this.spatial.itemCells, x, y, radius, this.spatialItemScratch, precise);
|
||
|
|
},
|
||
|
|
|
||
|
|
nearbyTarinai(x, y, radius, precise = false) {
|
||
|
|
return this.spatial.nearby(this.spatial.tarinaiCells, x, y, radius, this.spatialTarinaiScratch, precise);
|
||
|
|
},
|
||
|
|
|
||
|
|
nearbyAnts(x, y, radius, precise = false) {
|
||
|
|
return this.spatial.nearby(this.spatial.antCells, x, y, radius, this.spatialAntScratch, precise);
|
||
|
|
},
|
||
|
|
|
||
|
|
nearbyObstacles(x, y, radius, precise = false) {
|
||
|
|
return this.spatial.nearby(this.spatial.obstacleCells, x, y, radius, this.spatialObstacleScratch, precise);
|
||
|
|
},
|
||
|
|
|
||
|
|
nearbyFood(x, y, radius, precise = false) {
|
||
|
|
return this.spatial.nearby(this.spatial.foodCells, x, y, radius, this.spatialFoodScratch, precise);
|
||
|
|
},
|
||
|
|
|
||
|
|
nearbyHazards(x, y, radius, precise = false) {
|
||
|
|
return this.spatial.nearby(this.spatial.hazardCells, x, y, radius, this.spatialHazardScratch, precise);
|
||
|
|
},
|
||
|
|
|
||
|
|
rebuildSpatial(force = false) {
|
||
|
|
this.spatial.rebuild(this.items, this.tarinai, this.ants || []);
|
||
|
|
this.spatialDirty = false;
|
||
|
|
this.spatialVersion = (this.spatialVersion || 0) + 1;
|
||
|
|
},
|
||
|
|
|
||
|
|
updateItemCounts() {
|
||
|
|
const counts = this.itemCounts;
|
||
|
|
for (const key of Object.keys(counts)) counts[key] = 0;
|
||
|
|
for (const it of this.items) counts[it.type] = (counts[it.type] || 0) + 1;
|
||
|
|
},
|
||
|
|
|
||
|
|
updateEffectCounts() {
|
||
|
|
const counts = this.effectCounts;
|
||
|
|
for (const key of Object.keys(counts)) counts[key] = 0;
|
||
|
|
for (const ef of this.effects) counts[ef.type] = (counts[ef.type] || 0) + 1;
|
||
|
|
},
|
||
|
|
|
||
|
|
compactItems() {
|
||
|
|
this.enforceItemBudget();
|
||
|
|
const before = this.items.length;
|
||
|
|
let write = 0;
|
||
|
|
for (let read = 0; read < this.items.length; read++) {
|
||
|
|
const it = this.items[read];
|
||
|
|
if (it.type === "mirror") continue;
|
||
|
|
if (!it.dead) this.items[write++] = it;
|
||
|
|
}
|
||
|
|
this.items.length = write;
|
||
|
|
const changed = before !== this.items.length;
|
||
|
|
if (changed) {
|
||
|
|
this.markSpatialDirty?.("compact-items");
|
||
|
|
this.markTerrainDirty?.("compact-items");
|
||
|
|
}
|
||
|
|
return changed;
|
||
|
|
},
|
||
|
|
|
||
|
|
enforceItemBudget() {
|
||
|
|
const pruneOldest = (type, limit) => {
|
||
|
|
if (!Number.isFinite(limit) || limit < 0) return;
|
||
|
|
let count = 0;
|
||
|
|
for (const it of this.items) if (it.type === type && !it.dead) count += 1;
|
||
|
|
if (count <= limit) return;
|
||
|
|
const victims = this.items
|
||
|
|
.filter(it => it.type === type && !it.dead)
|
||
|
|
.sort((a, b) => (b.age || 0) - (a.age || 0))
|
||
|
|
.slice(0, count - limit);
|
||
|
|
for (const it of victims) it.amount = 0;
|
||
|
|
};
|
||
|
|
pruneOldest("zunchi", CONFIG.zunchiLimit ?? 56);
|
||
|
|
pruneOldest("trace", CONFIG.traceLimit ?? 64);
|
||
|
|
pruneOldest("splat", CONFIG.splatLimit ?? 48);
|
||
|
|
pruneOldest("grass", this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 96));
|
||
|
|
const fenceLimit = this.fenceLimit ? this.fenceLimit() : (CONFIG.fenceLimit ?? 72);
|
||
|
|
const liveFences = this.liveFenceCount ? this.liveFenceCount() : 0;
|
||
|
|
if (liveFences > fenceLimit) {
|
||
|
|
const victims = this.items
|
||
|
|
.filter(it => this.isFenceType?.(it.type) && !it.dead)
|
||
|
|
.sort((a, b) => (b.age || 0) - (a.age || 0))
|
||
|
|
.slice(0, liveFences - fenceLimit);
|
||
|
|
for (const it of victims) it.amount = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
const limit = CONFIG.itemLimit ?? Infinity;
|
||
|
|
if (!Number.isFinite(limit) || this.items.length <= limit) return;
|
||
|
|
const priority = { trace: 1, splat: 2, zunchi: 3, water: 4, grass: 5, sweet: 6, firecracker: 7, fence_v: 8, fence_h: 8 };
|
||
|
|
const removable = this.items
|
||
|
|
.filter(it => !it.dead && (priority[it.type] || 99) < 99)
|
||
|
|
.sort((a, b) => (priority[a.type] || 99) - (priority[b.type] || 99) || (b.age || 0) - (a.age || 0));
|
||
|
|
let extra = this.items.length - limit;
|
||
|
|
for (const it of removable) {
|
||
|
|
if (extra <= 0) break;
|
||
|
|
it.amount = 0;
|
||
|
|
extra -= 1;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
compactEffects() {
|
||
|
|
const limit = this.performanceQuality?.().effectLimit ?? CONFIG.effectLimit ?? 96;
|
||
|
|
if (Number.isFinite(limit) && this.effects.length > limit) {
|
||
|
|
this.effects.sort((a, b) => (a.life / Math.max(a.maxLife || 1, 0.001)) - (b.life / Math.max(b.maxLife || 1, 0.001)));
|
||
|
|
const remove = this.effects.length - limit;
|
||
|
|
for (let i = 0; i < remove; i++) this.effects[i].life = 0;
|
||
|
|
}
|
||
|
|
let write = 0;
|
||
|
|
for (let read = 0; read < this.effects.length; read++) {
|
||
|
|
const ef = this.effects[read];
|
||
|
|
if (!ef.dead) this.effects[write++] = ef;
|
||
|
|
}
|
||
|
|
this.effects.length = write;
|
||
|
|
},
|
||
|
|
|
||
|
|
compactTarinai() {
|
||
|
|
let write = 0;
|
||
|
|
for (let read = 0; read < this.tarinai.length; read++) {
|
||
|
|
const t = this.tarinai[read];
|
||
|
|
if (!t.dead) this.tarinai[write++] = t;
|
||
|
|
}
|
||
|
|
if (this.tarinai.length !== write) this.drawListDirty = true;
|
||
|
|
this.tarinai.length = write;
|
||
|
|
},
|
||
|
|
|
||
|
|
compactAnts() {
|
||
|
|
if (!this.ants) this.ants = [];
|
||
|
|
let write = 0;
|
||
|
|
for (let read = 0; read < this.ants.length; read++) {
|
||
|
|
const a = this.ants[read];
|
||
|
|
if (a && !a.dead) this.ants[write++] = a;
|
||
|
|
}
|
||
|
|
if (this.ants.length !== write) this.drawListDirty = true;
|
||
|
|
this.ants.length = write;
|
||
|
|
}
|
||
|
|
}));
|
||
|
|
})(typeof window !== "undefined" ? window : globalThis);
|