not bad
This commit is contained in:
parent
2741b93dea
commit
434f07cc4e
103 changed files with 8387 additions and 8152 deletions
|
|
@ -4,71 +4,6 @@
|
|||
const World = global.World;
|
||||
if (!World) throw new Error("World is not available for mixin: world_update.js");
|
||||
|
||||
const REALTIME_ITEM_TYPES = new Set(["ball", "genkotsu", "firecracker"]);
|
||||
const PIN_ITEM_TYPES = new Set(["pushpin", "oshibyo"]);
|
||||
|
||||
function itemUpdateInterval(item) {
|
||||
if (!item || item.dead) return Infinity;
|
||||
if (REALTIME_ITEM_TYPES.has(item.type)) return 0;
|
||||
if (PIN_ITEM_TYPES.has(item.type)) return 0;
|
||||
if ((item.dropTimer || 0) > 0 || Math.hypot(item.vx || 0, item.vy || 0) > 0.08) return 0;
|
||||
if (item.type === "zunchi") return 2.4;
|
||||
if (item.type === "grass") return 3.0 + stableUnit(item.id || item.seed || "grass", "grass-update") * 2.2;
|
||||
if (item.type === "duplicator") return 2.0;
|
||||
if (item.type === "water") return 1.8;
|
||||
if (item.type === "trace" || item.type === "splat") return 1.2;
|
||||
if (item.type === "ant_corpse") return 3.5;
|
||||
if (typeof isServingFoodType === "function" && isServingFoodType(item.type)) return 5.0;
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
function updateItemsScheduled(worldRef, dt) {
|
||||
let ran = 0;
|
||||
for (const it of worldRef.items || []) {
|
||||
if (!it || it.dead) continue;
|
||||
const interval = itemUpdateInterval(it);
|
||||
if (!Number.isFinite(interval)) continue;
|
||||
if (interval <= 0) {
|
||||
it.update(dt, worldRef);
|
||||
ran += 1;
|
||||
continue;
|
||||
}
|
||||
it._scheduledUpdateDt = Math.min(10, (it._scheduledUpdateDt || 0) + dt);
|
||||
if (it._scheduledUpdateDt < interval) continue;
|
||||
const scheduledDt = it._scheduledUpdateDt;
|
||||
it._scheduledUpdateDt = 0;
|
||||
it.update(scheduledDt, worldRef);
|
||||
ran += 1;
|
||||
}
|
||||
return ran;
|
||||
}
|
||||
|
||||
function markSpatialDirtyIfMoved(worldRef, list, reason = "moved", threshold = 0.35) {
|
||||
if (!worldRef || !Array.isArray(list) || !list.length) return false;
|
||||
const thresholdSq = threshold * threshold;
|
||||
let moved = false;
|
||||
for (const entity of list) {
|
||||
if (!entity || entity.dead) continue;
|
||||
const x = Number.isFinite(entity.x) ? entity.x : 0;
|
||||
const y = Number.isFinite(entity.y) ? entity.y : 0;
|
||||
const oldX = entity._spatialTrackX;
|
||||
const oldY = entity._spatialTrackY;
|
||||
if (!Number.isFinite(oldX) || !Number.isFinite(oldY)) {
|
||||
entity._spatialTrackX = x;
|
||||
entity._spatialTrackY = y;
|
||||
moved = true;
|
||||
continue;
|
||||
}
|
||||
const dx = x - oldX;
|
||||
const dy = y - oldY;
|
||||
if (dx * dx + dy * dy <= thresholdSq) continue;
|
||||
entity._spatialTrackX = x;
|
||||
entity._spatialTrackY = y;
|
||||
moved = true;
|
||||
}
|
||||
if (moved) worldRef.markSpatialDirty?.(reason);
|
||||
return moved;
|
||||
}
|
||||
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
|
||||
environmentScores(force = false) {
|
||||
const nowScore = this.time || 0;
|
||||
|
|
@ -113,43 +48,18 @@
|
|||
|
||||
colonyMoodDefinition(id = "relaxed") {
|
||||
const defs = {
|
||||
calm: { id: "calm", label: "\u304a\u3060\u3084\u304b", description: "\u793e\u4ea4\u6027\u30d7\u30e9\u30b9", effects: { personality: { sociability: 0.42 } } },
|
||||
prickly: { id: "prickly", label: "\u30d4\u30ea\u30d4\u30ea", description: "\u653b\u6483\u6027\u30d7\u30e9\u30b9", effects: { personality: { aggression: 0.46 } } },
|
||||
stinky: { id: "stinky", label: "\u304f\u3055\u3044", description: "\u958b\u653e\u6027\u30de\u30a4\u30ca\u30b9", effects: { personality: { openness: -0.44 } } },
|
||||
weaklings: { id: "weaklings", label: "\u3056\u3053\u3060\u3089\u3051", description: "\u305a\u3093\u3061\u3069\u308c\u3044\u4ee5\u5916\u306e\u30b9\u30c8\u30ec\u30b9\u6e1b\u5c11", effects: { personality: {} } },
|
||||
zunda_party: { id: "zunda_party", label: "\u305a\u3093\u3060\u3071\u30fc\u3066\u3043\u30fc", description: "\u793e\u4ea4\u6027\u3068\u958b\u653e\u6027\u30d7\u30e9\u30b9\u3002\u30b9\u30c8\u30ec\u30b9\u6e1b\u5c11", effects: { personality: { sociability: 0.38, openness: 0.38 } } },
|
||||
ball_party: { id: "ball_party", label: "\u307c\u30fc\u308b\u3071\u30fc\u3066\u3043\u30fc", description: "\u793e\u4ea4\u6027\u3068\u958b\u653e\u6027\u30d7\u30e9\u30b9\u3002\u30b9\u30c8\u30ec\u30b9\u6e1b\u5c11", effects: { personality: { sociability: 0.38, openness: 0.38 } } },
|
||||
sun_party: { id: "sun_party", label: "\u304a\u3072\u3055\u307e\u3071\u30fc\u3066\u3043\u30fc", description: "\u65e5\u5149\u6d74\u983b\u5ea6\u5897\u52a0", effects: { personality: {} } },
|
||||
ant_overrun: { id: "ant_overrun", label: "\u3042\u308a\u3060\u3089\u3051", description: "\u30a2\u30ea\u3092\u898b\u308b\u3068\u30d1\u30cb\u30c3\u30af", effects: { personality: {} } },
|
||||
relaxed: { id: "relaxed", label: "\u306e\u3093\u3073\u308a", description: "\u4f55\u3082\u7121\u3057", effects: { personality: {} } },
|
||||
calm: { id: "calm", label: "穏やか", description: "社交性が上がる", effects: { personality: { sociability: 0.42 } } },
|
||||
prickly: { id: "prickly", label: "緊張", description: "攻撃性が上がる", effects: { personality: { aggression: 0.46 } } },
|
||||
stinky: { id: "stinky", label: "不衛生", description: "開放性が下がる", effects: { personality: { openness: -0.44 } } },
|
||||
weaklings: { id: "weaklings", label: "弱体化", description: "ずんちどれい以外のストレスが下がる", effects: { personality: {} } },
|
||||
zunda_party: { id: "zunda_party", label: "ずんだ集中", description: "社交性と開放性が上がり、ストレスが下がる", effects: { personality: { sociability: 0.38, openness: 0.38 } } },
|
||||
ball_party: { id: "ball_party", label: "ボール集中", description: "社交性と開放性が上がり、ストレスが下がる", effects: { personality: { sociability: 0.38, openness: 0.38 } } },
|
||||
sun_party: { id: "sun_party", label: "日光浴傾向", description: "日光浴の頻度が上がる", effects: { personality: {} } },
|
||||
ant_overrun: { id: "ant_overrun", label: "アリ増加", description: "アリを見るとパニックになりやすい", effects: { personality: {} } },
|
||||
relaxed: { id: "relaxed", label: "安定", description: "標準状態", effects: { personality: {} } },
|
||||
};
|
||||
return defs[id] || defs.relaxed;
|
||||
},
|
||||
|
||||
evaluateColonyMood() {
|
||||
const alive = (this.tarinai || []).filter(t => t && !t.dead);
|
||||
const avgStress = alive.length ? alive.reduce((s, t) => s + (t.stress || 0), 0) / alive.length : 0;
|
||||
const avgHunger = alive.length ? alive.reduce((s, t) => s + (t.hunger || 0), 0) / alive.length : 0;
|
||||
const environment = this.environmentScores ? this.environmentScores() : { hygiene: 100 };
|
||||
const fights = alive.filter(t => t.state === "fight" || (t.fightTimer || 0) > 0.04 || t.state === "intimidate" || (t.intimidateTimer || 0) > 0.04).length;
|
||||
const corpseCount = (this.itemCounts?.ant_corpse || 0) + Math.max(0, Math.round((this.deadCount || 0) * 0.5));
|
||||
const slaveRatio = alive.length ? alive.filter(t => t.isZunchiSlave).length / alive.length : 0;
|
||||
const candidates = [];
|
||||
if (alive.length && avgStress <= 28 && avgHunger <= 34) candidates.push("calm");
|
||||
if (fights >= 2 || corpseCount >= 4) candidates.push("prickly");
|
||||
if ((environment.hygiene || 0) <= 42) candidates.push("stinky");
|
||||
if (alive.length && slaveRatio >= 0.45) candidates.push("weaklings");
|
||||
if ((this.itemCounts?.sweet || 0) >= 30) candidates.push("zunda_party");
|
||||
if ((this.itemCounts?.ball || 0) >= 10) candidates.push("ball_party");
|
||||
if (alive.some(t => t.state === "sunbath" || (t.sunbathTimer || 0) > 0.1)) candidates.push("sun_party");
|
||||
if ((this.itemCounts?.ant_nest || 0) >= 1) candidates.push("ant_overrun");
|
||||
const nextId = candidates.length ? candidates[Math.floor(Math.random() * candidates.length)] : "relaxed";
|
||||
this.colonyMood = this.colonyMoodDefinition(nextId);
|
||||
this.lastColonyMoodDay = this.day || 1;
|
||||
this.log?.(`\u4eca\u65e5\u306e\u30b3\u30ed\u30cb\u30fc\u306e\u96f0\u56f2\u6c17\u306f\u300c${this.colonyMood.label}\u300d\u3002`, "event");
|
||||
return this.colonyMood;
|
||||
},
|
||||
|
||||
observeHighlightKind(t) {
|
||||
const s = this.selected;
|
||||
if (!s || !t || t === s || t.dead || this.tool !== "observe") return "";
|
||||
|
|
@ -197,56 +107,6 @@
|
|||
return conflicts;
|
||||
},
|
||||
|
||||
nestBoxOccupants(box, limit = 5) {
|
||||
if (!box || box.dead || box.type !== "nest_box") return [];
|
||||
const occupants = this.tarinai.filter(t => t && !t.dead && t.insideNestBoxId === box.id);
|
||||
return Number.isFinite(limit) ? occupants.slice(0, Math.max(0, limit)) : occupants;
|
||||
},
|
||||
|
||||
pointerNestBoxInfo() {
|
||||
const p = this.pointer;
|
||||
if (!p?.inside) return null;
|
||||
let best = null, bestD = Infinity;
|
||||
for (const it of this.nearbyItems(p.x, p.y, 140)) {
|
||||
if (!it || it.dead || it.type !== "nest_box") continue;
|
||||
const d = distXY(p.x, p.y, it.x, it.y);
|
||||
if (d < bestD && d <= Math.max(80, it.r * 2.1)) { best = it; bestD = d; }
|
||||
}
|
||||
if (!best) return null;
|
||||
const occupants = this.nestBoxOccupants(best, Infinity);
|
||||
const capacity = this.nestBoxCapacity(best);
|
||||
return { box: best, occupants, capacity };
|
||||
},
|
||||
|
||||
|
||||
|
||||
pointerOwnedBedInfo() {
|
||||
const p = this.pointer;
|
||||
if (!p?.inside) return null;
|
||||
let best = null, bestD = Infinity;
|
||||
for (const it of this.nearbyItems(p.x, p.y, 140)) {
|
||||
if (!it || it.dead || it.type !== "grass_bed") continue;
|
||||
const d = distXY(p.x, p.y, it.x, it.y);
|
||||
if (d < bestD && d <= Math.max(60, (it.r || 24) * 2.0)) { best = it; bestD = d; }
|
||||
}
|
||||
if (!best) return null;
|
||||
const owner = this.liveTarinaiById?.(best.ownerId) || null;
|
||||
return { bed: best, owner };
|
||||
},
|
||||
|
||||
nestBoxToolTipCount() {
|
||||
const pointed = this.pointerNestBoxInfo?.();
|
||||
if (pointed?.box) return { current: pointed.occupants.length, max: pointed.capacity || this.nestBoxCapacity(pointed.box) };
|
||||
const boxes = (this.items || []).filter(it => it && !it.dead && it.type === "nest_box");
|
||||
if (!boxes.length) return { current: 0, max: this.nestBoxCapacity() };
|
||||
let current = 0, max = 0;
|
||||
for (const box of boxes) {
|
||||
current += this.nestBoxOccupants(box, Infinity).length;
|
||||
max += this.nestBoxCapacity(box);
|
||||
}
|
||||
return { current, max };
|
||||
},
|
||||
|
||||
blastZunchiFrom(x, y, radius, strength = 1) {
|
||||
let moved = 0;
|
||||
for (const it of this.nearbyItems(x, y, radius + 60)) {
|
||||
|
|
@ -272,146 +132,9 @@
|
|||
},
|
||||
|
||||
update(dt) {
|
||||
if (this.paused) return;
|
||||
dt *= this.speed;
|
||||
dt = Math.min(dt, 0.12 * this.speed);
|
||||
|
||||
const prevTime = this.time;
|
||||
this.time += dt;
|
||||
const previousDay = this.day || 1;
|
||||
this.day = Math.floor(this.time / CONFIG.dayLength) + 1;
|
||||
const prevHour = this.hourOfDay ? this.hourOfDay(prevTime) : 0;
|
||||
const nowHour = this.hourOfDay ? this.hourOfDay(this.time) : 0;
|
||||
const crossedColonyEvalHour = (this.day !== previousDay) ? (nowHour >= 6) : (prevHour < 6 && nowHour >= 6);
|
||||
const pendingColonyMoodEvaluation = crossedColonyEvalHour && this.lastColonyMoodDay !== this.day;
|
||||
if (this.day !== previousDay) {
|
||||
for (const t of this.tarinai || []) if (t) t.personalityDaily = { day: this.day, total: 0, byKey: {}, byCause: {} };
|
||||
}
|
||||
if (this.pointer) this.pointer.motion = Math.max(0, (this.pointer.motion || 0) - dt * 2.4);
|
||||
this.shakeTimer = Math.max(0, (this.shakeTimer || 0) - dt);
|
||||
if ((this.shakeTimer || 0) <= 0) { this.shakeDuration = 0; this.shakeStrength = 0; }
|
||||
this.updateWeather(dt);
|
||||
this.foodSpoilagePenalty = Math.max(0, (this.foodSpoilagePenalty || 0) - dt * 0.010);
|
||||
this.updateTerrainDirtyState?.(dt);
|
||||
this.spatialRebuildsThisFrame = 0;
|
||||
this.spatialDirtyMarksThisFrame = 0;
|
||||
this.ensureSpatial?.("update-start");
|
||||
|
||||
this.beginFramePerformanceBudgets?.();
|
||||
const itemCountBefore = this.items.length;
|
||||
updateItemsScheduled(this, dt);
|
||||
const itemsMoved = markSpatialDirtyIfMoved(this, this.items, "items-moved", 0.25);
|
||||
if (this.itemCounts?.ball) {
|
||||
this.ensureSpatial?.("ball-ball-collisions");
|
||||
this.resolveBallBallCollisions(dt);
|
||||
this.markSpatialDirty?.("ball-ball-collisions");
|
||||
}
|
||||
this.updateAnts?.(dt);
|
||||
const antsMoved = markSpatialDirtyIfMoved(this, this.ants || [], "ants-moved", 0.25);
|
||||
if (itemsMoved || antsMoved || this.spatialDirty) this.ensureSpatial?.("post-mobile-item-update");
|
||||
|
||||
for (const ef of this.effects) ef.update(dt);
|
||||
|
||||
for (const t of this.tarinai) {
|
||||
t.update(dt);
|
||||
}
|
||||
const now = this.time || 0;
|
||||
const bedConflictInterval = 3.2;
|
||||
if (now >= (this.nextBedConflictCheckAt || 0)) {
|
||||
this.nextBedConflictCheckAt = now + bedConflictInterval;
|
||||
this.workStats && (this.workStats.bedConflictRuns = (this.workStats.bedConflictRuns || 0) + 1);
|
||||
this.resolveOwnedBedSleepConflicts?.();
|
||||
} else {
|
||||
this.workStats && (this.workStats.bedConflictSkips = (this.workStats.bedConflictSkips || 0) + 1);
|
||||
}
|
||||
markSpatialDirtyIfMoved(this, this.tarinai, "tarinai-moved", 0.25);
|
||||
this.ensureSpatial?.("post-tarinai-update");
|
||||
this.limitBallChasers();
|
||||
this.resolveBallInteractions(dt);
|
||||
const collisionInterval = 1.8;
|
||||
if (now >= (this.nextTarinaiCollisionCheckAt || 0)) {
|
||||
this.nextTarinaiCollisionCheckAt = now + collisionInterval;
|
||||
this.workStats && (this.workStats.collisionRuns = (this.workStats.collisionRuns || 0) + 1);
|
||||
this.resolveTarinaiHighSpeedCollisions(dt);
|
||||
} else {
|
||||
this.workStats && (this.workStats.collisionSkips = (this.workStats.collisionSkips || 0) + 1);
|
||||
}
|
||||
|
||||
this.compactTimer += dt;
|
||||
const compactInterval = 2.5;
|
||||
let itemsCompacted = false;
|
||||
if (this.compactTimer >= compactInterval) {
|
||||
itemsCompacted = this.compactItems();
|
||||
this.compactEffects();
|
||||
this.compactTarinai();
|
||||
this.compactAnts?.();
|
||||
this.compactTimer = 0;
|
||||
}
|
||||
|
||||
this.countsTimer += dt;
|
||||
const countsInterval = 2.0;
|
||||
if (this.countsTimer >= countsInterval || itemsCompacted || this.items.length !== itemCountBefore) {
|
||||
this.updateItemCounts();
|
||||
this.updateEffectCounts();
|
||||
this.countsTimer = 0;
|
||||
}
|
||||
|
||||
if ((this.outOfBoundsCheckAt || -999) + 5.0 <= this.time) {
|
||||
this.outOfBoundsCheckAt = this.time;
|
||||
this.sanitizeOutOfBounds();
|
||||
}
|
||||
|
||||
this.drawSortTimer += dt;
|
||||
const sortInterval = 0.25;
|
||||
if (this.drawSortTimer >= sortInterval) {
|
||||
this.drawListDirty = true;
|
||||
this.drawSortTimer = 0;
|
||||
}
|
||||
|
||||
const phase = this.phaseName();
|
||||
if (phase !== this.lastPhase) {
|
||||
this.lastPhase = phase;
|
||||
this.emit("world:phase", { phase });
|
||||
audio.phase();
|
||||
}
|
||||
|
||||
if (this.tarinai.length > 0 && this.tarinai.length <= 10 && this.time - (this.lastBirthAt || -999) > 12 && Math.random() < dt * 0.022) {
|
||||
const spot = this.edgeSpawnPoint(null, null, 42, true);
|
||||
const t = this.addTarinai({ x: spot.x, y: spot.y, vx: spot.vx, vy: spot.vy, type: baseSpriteId(), entryTimer: 2.0 });
|
||||
t.wanderAngle = spot.angle;
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.35);
|
||||
audio.birth?.();
|
||||
this.log(`${t.name}\u304c\u753b\u9762\u5916\u304b\u3089\u8ff7\u3044\u8fbc\u3093\u3067\u304d\u305f\u3002`);
|
||||
}
|
||||
|
||||
|
||||
if (Math.random() < dt * 0.109) {
|
||||
const spot = this.findGrassPlantingSpot(rand(60, this.w - 60), rand(60, this.h - 60), {
|
||||
allowOriginal: true,
|
||||
minRadius: 28,
|
||||
maxRadius: 140,
|
||||
attempts: 36,
|
||||
});
|
||||
if (spot) {
|
||||
const sprout = new Item("grass", spot.x, spot.y);
|
||||
sprout.amount = rand(30, 62);
|
||||
sprout.growth = rand(0.18, 0.38);
|
||||
if (this.addItem) this.addItem(sprout, "grass-spawn");
|
||||
else this.items.push(sprout);
|
||||
}
|
||||
}
|
||||
|
||||
// Rain visuals are diagonal line streaks in render.js. This branch creates
|
||||
// horizontal oval water items spawned by rain; it intentionally does not
|
||||
// share the rain overlay renderer.
|
||||
if (window.TarinaiWeatherSystem?.shouldDropRainWater?.(this, dt)) {
|
||||
const drop = window.TarinaiWeatherSystem.createRainWaterItem(this);
|
||||
this.addItem?.(drop, "rain-water") || this.items.push(drop);
|
||||
this.emit?.("rain:itemDropped", { item: drop, type: "water" });
|
||||
}
|
||||
|
||||
this.lastRuntimeDiagnostics = this.runtimeDiagnostics?.() || null;
|
||||
|
||||
const phases = window.TarinaiWorldUpdatePhases;
|
||||
if (!phases?.update) throw new Error("TarinaiWorldUpdatePhases is not available");
|
||||
phases.update(this, dt);
|
||||
},
|
||||
|
||||
sanitizeOutOfBounds() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue