This commit is contained in:
33333-33333 2026-06-20 23:27:39 +09:00
commit c6ba3e38a2
21 changed files with 1565 additions and 898 deletions

View file

@ -76,6 +76,12 @@ class Item {
this.fuseTimer = 5;
this.fuseMax = 5;
}
if (type === "genkotsu") {
this.amount = 100;
this.dropMax = 0;
this.dropImpactDone = false;
this.impactFlash = 0;
}
if (isServingFoodType(type)) {
this.toolSize = "medium";
this.foodServingScale = 1;
@ -106,6 +112,11 @@ class Item {
else this.amount = 0;
}
}
if (this.type === "genkotsu") {
this.impactFlash = Math.max(0, (this.impactFlash || 0) - dt);
if (this.dropImpactDone) this.amount -= dt * 115;
else this.amount = Math.max(this.amount || 0, 100);
}
if (this.type === "ball") this.updateBall(dt, worldRef);
if (this.type === "zunchi") this.updateZunchiMotion(dt, worldRef);
if (this.type === "grass") {
@ -176,8 +187,6 @@ class Item {
const speed = Math.hypot(this.vx || 0, this.vy || 0);
const searchRadius = Math.max(150, (this.r || 18) + speed * Math.max(dt || 0.016, 0.016) + 120);
const items = worldRef.nearbyItems(this.x, this.y, searchRadius) || [];
let solidChecks = 0;
const maxSolidChecks = CONFIG.maxFenceCollisionChecks ?? 24;
for (const it of items) {
if (!it || it === this || it.dead) continue;
if (it.type === "bed") {
@ -187,8 +196,6 @@ class Item {
}
const rects = worldRef.solidObstacleRects ? worldRef.solidObstacleRects(it) : [];
if (!rects.length) continue;
if (solidChecks >= maxSolidChecks) continue;
solidChecks += 1;
for (const rect of rects) this.resolveBallRectBounce(rect, worldRef, it);
}
}
@ -467,148 +474,6 @@ class Item {
get dead() {
return this.amount <= 0;
}
serialize() {
return {
type: this.type, x: this.x, y: this.y, amount: this.amount, age: this.age, seed: this.seed,
stage: this.stage, stageTimer: this.stageTimer, fertility: this.fertility, freshness: this.freshness,
growth: this.growth, health: this.health, seedTimer: this.seedTimer, eatenAmount: this.eatenAmount, fertilityBoost: this.fertilityBoost, lifeSpan: this.lifeSpan, wither: this.wither,
comfort: this.comfort, wear: this.wear, crawlOpen: this.crawlOpen, zunchiVariant: this.zunchiVariant, toolSize: this.toolSize, foodServingScale: this.foodServingScale, foodServingsMax: this.foodServingsMax, foodServingsRemaining: this.foodServingsRemaining, blastScale: this.blastScale, fuseTimer: this.fuseTimer, fuseMax: this.fuseMax, dropTimer: this.dropTimer, dropMax: this.dropMax,
vx: this.vx, vy: this.vy, prevX: this.prevX, prevY: this.prevY,
spin: this.spin, spinVelocity: this.spinVelocity, lastKickedAt: this.lastKickedAt, lastKickerId: this.lastKickerId,
lastPokedAt: this.lastPokedAt, pokeCombo: this.pokeCombo, lastPokeAngle: this.lastPokeAngle,
antCount: this.antCount, antSpawnTimer: this.antSpawnTimer, antSpawnCooldown: this.antSpawnCooldown, antWorkers: Array.isArray(this.antWorkers) ? this.antWorkers.slice() : undefined, queenSpawnAt: this.queenSpawnAt, workerSprite: this.workerSprite, decayTimer: this.decayTimer
};
}
static from(o) {
const type = o?.type || "grass";
const it = Object.create(Item.prototype);
Item._loadId = (Item._loadId || 0) + 1;
it.id = o.id || `loaded-${Item._loadId}`;
it.type = type;
it.x = o.x;
it.y = o.y;
it.r = itemRadiusFor(type, 12);
it.amount = o.amount ?? itemAmountFor(type, 80);
it.age = o.age || 0;
it.seed = o.seed || Math.random() * 1000;
it.dropTimer = 0;
it.dropMax = 0;
it.dropImpactDone = false;
it.lifecycleTimer = o.lifecycleTimer ?? rand(0, type === "grass" ? 1.15 : 0.55);
it.lifecycleInterval = o.lifecycleInterval ?? (type === "grass" ? (1.75 + stableUnit(it.id, "grass-life") * 0.90) : (type === "zunchi" ? (0.95 + stableUnit(it.id, "zunchi-life") * 0.45) : 0));
if (type === "grass") {
it.growth = 0.25;
it.health = 1;
it.seedTimer = rand(18, 38);
it.eatenAmount = 0;
it.fertilityBoost = 0;
}
if (type === "bed") {
it.comfort = 1;
it.wear = 0;
}
if (type === "ball") {
it.vx = o.vx || 0;
it.vy = o.vy || 0;
it.prevX = o.prevX ?? o.x;
it.prevY = o.prevY ?? o.y;
it.spin = o.spin || 0;
it.spinVelocity = o.spinVelocity || 0;
it.lastKickedAt = o.lastKickedAt ?? -999;
it.lastKickerId = o.lastKickerId || "";
it.lastPokedAt = o.lastPokedAt ?? -999;
it.pokeCombo = o.pokeCombo || 0;
it.lastPokeAngle = o.lastPokeAngle || rand(0, Math.PI * 2);
}
if (type === "zunchi") {
it.stage = "fresh";
it.stageTimer = 0;
it.fertility = 0;
it.freshness = 1;
it.zunchiVariant = "zunchi";
}
if (type === "ant_nest") {
it.antCount = Number.isFinite(o.antCount) ? clamp(Math.round(o.antCount), 0, ANT_NEST_MAX_COUNT || 10) : (ANT_NEST_START_COUNT || 6);
it.antSpawnTimer = o.antSpawnTimer ?? rand(0.6, 2.2);
it.antSpawnCooldown = o.antSpawnCooldown ?? rand(0.8, 2.4);
const legacyPool = Array.isArray(o.antWorkers) ? o.antWorkers : (Array.isArray(o.workerHpPool) ? o.workerHpPool : null);
it.antWorkers = Array.isArray(legacyPool)
? legacyPool.map(v => clamp(Number(v) || ANT_WORKER_HP || 32, 0.1, ANT_WORKER_HP || 32)).slice(0, ANT_NEST_MAX_COUNT || 10)
: (typeof buildAntWorkerPool === "function"
? buildAntWorkerPool(it.antCount)
: Array.from({ length: it.antCount }, () => ANT_WORKER_HP || 32));
it.queenSpawnAt = Number.isFinite(o.queenSpawnAt) ? o.queenSpawnAt : -999;
}
if (type === "ant_corpse") {
it.workerSprite = o.workerSprite || "ant_worker";
it.decayTimer = o.decayTimer || 0;
}
if (type === "firecracker") {
it.fuseTimer = 5;
it.fuseMax = 5;
}
it.stage = o.stage || it.stage;
it.stageTimer = o.stageTimer || 0;
it.fertility = o.fertility || 0;
it.freshness = o.freshness ?? it.freshness;
it.growth = o.growth ?? it.growth;
it.health = o.health ?? it.health;
it.seedTimer = o.seedTimer ?? it.seedTimer;
it.eatenAmount = o.eatenAmount || 0;
it.fertilityBoost = o.fertilityBoost || 0;
it.lifeSpan = o.lifeSpan ?? it.lifeSpan;
it.wither = o.wither ?? it.wither ?? 0;
it.comfort = o.comfort ?? it.comfort;
it.wear = o.wear ?? it.wear;
it.crawlOpen = o.crawlOpen ?? it.crawlOpen;
it.vx = o.vx ?? it.vx ?? 0;
it.vy = o.vy ?? it.vy ?? 0;
it.prevX = o.prevX ?? it.prevX ?? it.x;
it.prevY = o.prevY ?? it.prevY ?? it.y;
it.spin = o.spin ?? it.spin ?? 0;
it.spinVelocity = o.spinVelocity ?? it.spinVelocity ?? 0;
it.lastKickedAt = o.lastKickedAt ?? it.lastKickedAt ?? -999;
it.lastKickerId = o.lastKickerId || it.lastKickerId || "";
it.lastPokedAt = o.lastPokedAt ?? it.lastPokedAt ?? -999;
it.pokeCombo = o.pokeCombo ?? it.pokeCombo ?? 0;
it.lastPokeAngle = o.lastPokeAngle ?? it.lastPokeAngle ?? rand(0, Math.PI * 2);
it.zunchiVariant = o.zunchiVariant || it.zunchiVariant || "zunchi";
it.antCount = Number.isFinite(o.antCount) ? clamp(Math.round(o.antCount), 0, ANT_NEST_MAX_COUNT || 10) : it.antCount;
it.antSpawnTimer = o.antSpawnTimer ?? it.antSpawnTimer;
it.antSpawnCooldown = o.antSpawnCooldown ?? it.antSpawnCooldown;
if (type === "ant_nest") {
const legacyPool = Array.isArray(o.antWorkers) ? o.antWorkers : (Array.isArray(o.workerHpPool) ? o.workerHpPool : null);
if (Array.isArray(legacyPool)) {
it.antWorkers = legacyPool.map(v => clamp(Number(v) || 0, 0, ANT_WORKER_HP || 32)).filter(v => v > 0).slice(0, ANT_NEST_MAX_COUNT || 10);
} else if (!Array.isArray(it.antWorkers)) {
it.antWorkers = typeof buildAntWorkerPool === "function"
? buildAntWorkerPool(it.antCount || (ANT_NEST_START_COUNT || 6))
: Array.from({ length: it.antCount || (ANT_NEST_START_COUNT || 6) }, () => ANT_WORKER_HP || 32);
}
it.queenSpawnAt = Number.isFinite(o.queenSpawnAt) ? o.queenSpawnAt : (it.queenSpawnAt ?? -999);
}
it.workerSprite = o.workerSprite || it.workerSprite;
it.decayTimer = o.decayTimer ?? it.decayTimer;
it.toolSize = o.toolSize || it.toolSize || "medium";
it.foodServingScale = o.foodServingScale || it.foodServingScale || ({ small: 0.68, medium: 1.0, large: 1.48 }[it.toolSize] || 1);
if (isServingFoodType(it.type)) {
const servingMax = foodServingsForSize(it.toolSize);
it.foodServingsMax = Number.isFinite(o.foodServingsMax) ? o.foodServingsMax : servingMax;
if (Number.isFinite(o.foodServingsRemaining)) {
it.foodServingsRemaining = clamp(o.foodServingsRemaining, 0, it.foodServingsMax);
} else {
it.foodServingsRemaining = Math.max(1, Math.min(it.foodServingsMax, Math.ceil(it.foodServingsMax * clamp((o.amount ?? it.amount) / 70, 0.05, 1))));
}
it.amount = it.foodServingsRemaining;
}
it.blastScale = o.blastScale || it.blastScale || 1;
it.fuseTimer = o.fuseTimer ?? it.fuseTimer;
it.fuseMax = o.fuseMax ?? it.fuseMax;
it.dropTimer = 0;
it.dropMax = 0;
it.dropImpactDone = true;
return it;
}
draw(ctx, t, lighting = null) {
const lightState = lighting || getLightingState(world);
const night = clamp(lightState.nightStrength * 1.35, 0, 1);
@ -618,7 +483,8 @@ class Item {
const servingRatio = isServingFoodType(this.type) ? ((this.foodServingsRemaining ?? this.amount) / Math.max(1, this.foodServingsMax || foodServingsForSize(this.toolSize || "medium"))) : null;
const visibleAlpha = isServingFoodType(this.type) ? clamp(servingRatio ?? 1, 0.24, 1) : clamp(this.amount / 40, 0.24, 1);
const dropT = this.dropMax > 0 ? clamp(this.dropTimer / this.dropMax, 0, 1) : 0;
const dropY = dropT > 0 ? -170 * Math.pow(dropT, 0.42) : 0;
const dropFallHeight = this.type === "genkotsu" ? Math.max(360, this.r * 5.8) : 170;
const dropY = dropT > 0 ? -dropFallHeight * dropT : 0;
const shadow = projectedShadowParams(lightState, Math.max(0.4, this.r / 18));
if (this.type !== "trace" && this.type !== "splat") {
const zunchiId = this.type === "zunchi" ? (this.zunchiVariant || "zunchi") : null;
@ -689,13 +555,8 @@ class Item {
ctx.ellipse(-this.r * 0.28, -this.r * 0.18, this.r * 0.23, this.r * 0.10, -0.35, 0, Math.PI * 2);
ctx.fill();
} else if (fightMochi) {
ctx.fillStyle = "#d55632";
ctx.font = `${Math.round((this.r || 13) * 1.02)}px sans-serif`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("\u2726", 0, 1);
ctx.strokeStyle = "rgba(168,76,42,0.72)";
ctx.lineWidth = 1.4;
ctx.strokeStyle = "rgba(168,76,42,0.82)";
ctx.lineWidth = 1.8;
ctx.beginPath();
ctx.moveTo(-this.r * 0.44, -this.r * 0.42);
ctx.lineTo(this.r * 0.44, this.r * 0.42);
@ -725,6 +586,42 @@ class Item {
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Z", 0, this.r * 1.18);
} else if (this.type === "genkotsu") {
const img = typeof getRenderableImage === "function" ? getRenderableImage("genkotsu", "genkotsu") : images.get("genkotsu");
const impact = clamp(this.impactFlash || 0, 0, 1);
ctx.save();
ctx.shadowColor = "rgba(54,42,31,0.26)";
ctx.shadowBlur = 5 + impact * 8;
ctx.shadowOffsetY = 4;
const squashY = 1 - impact * 0.08;
const stretchX = 1 + impact * 0.05;
ctx.scale(stretchX, squashY);
if (img) {
const metrics = typeof getImageMetrics === "function" ? getImageMetrics("genkotsu") : null;
const w = this.r * 2.55;
const h = w * (metrics?.ratio || 1.27);
ctx.drawImage(img, -w * 0.5, -h * 0.76, w, h);
} else {
ctx.fillStyle = "rgba(255,255,255,0.94)";
ctx.strokeStyle = "rgba(64,64,64,0.78)";
ctx.lineWidth = 5;
roundedRect(ctx, -this.r * 0.72, -this.r * 1.45, this.r * 1.44, this.r * 1.84, 18);
ctx.fill(); ctx.stroke();
ctx.fillStyle = "rgba(42,46,52,0.92)";
ctx.font = `bold ${Math.round(this.r * 0.45)}px sans-serif`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("\u6b63", 0, -this.r * 0.74);
ctx.fillText("\u7fa9", 0, -this.r * 0.27);
}
if (impact > 0.01) {
ctx.globalAlpha = impact * 0.20;
ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.ellipse(0, -this.r * 0.65, this.r * 0.92, this.r * 0.22, -0.10, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
} else if (this.type === "water_bowl") {
ctx.fillStyle = "rgba(171, 120, 76, 0.72)";
ctx.strokeStyle = "rgba(94, 69, 48, 0.55)";
@ -812,21 +709,26 @@ class Item {
ctx.stroke();
}
} else if (this.type === "nest_box") {
ctx.fillStyle = styleNight ? "#7d6040" : (styleWarm ? "#b98246" : "#9a6b3d");
ctx.strokeStyle = styleNight ? "#4a3a2d" : "#5d4028";
ctx.lineWidth = 2.4;
ctx.fillStyle = styleNight ? "#5f4933" : (styleWarm ? "#89613e" : "#775237");
ctx.strokeStyle = styleNight ? "#31261f" : "#3f2e24";
ctx.lineWidth = 2.6;
roundedRect(ctx, -this.r * 1.28, -this.r * 0.80, this.r * 2.56, this.r * 1.52, 9);
ctx.fill(); ctx.stroke();
ctx.fillStyle = styleNight ? "#3b2f27" : "#65432a";
ctx.beginPath();
ctx.arc(0, -this.r * 0.02, this.r * 0.42, 0, Math.PI * 2);
ctx.fillStyle = styleNight ? "#6e5439" : "#8a5f3b";
roundedRect(ctx, -this.r * 1.12, -this.r * 0.62, this.r * 2.24, this.r * 1.18, 6);
ctx.fill();
ctx.strokeStyle = styleNight ? "rgba(42,32,25,0.76)" : "rgba(65,45,32,0.72)";
ctx.lineWidth = 2.0;
ctx.beginPath();
ctx.moveTo(-this.r * 0.95, -this.r * 0.50);
ctx.lineTo(this.r * 0.95, -this.r * 0.50);
ctx.moveTo(-this.r * 0.95, this.r * 0.48);
ctx.lineTo(this.r * 0.95, this.r * 0.48);
ctx.stroke();
ctx.fillStyle = styleNight ? "#2b211c" : "#3c281f";
ctx.beginPath();
ctx.arc(0, -this.r * 0.08, this.r * 0.40, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "rgba(255,230,152,0.38)";
for (let i = 0; i < 6; i++) {
ctx.beginPath();
ctx.ellipse(randSeed(this.seed + i, -this.r * 0.84, this.r * 0.84), randSeed(this.seed + 20 + i, this.r * 0.32, this.r * 0.68), this.r * 0.26, this.r * 0.045, randSeed(this.seed + 40 + i, -0.7, 0.7), 0, Math.PI * 2);
ctx.fill();
}
} else if (this.type === "ant_nest") {
const count = clamp(Math.round(this.antCount ?? ANT_NEST_START_COUNT ?? 6), 0, ANT_NEST_MAX_COUNT ?? 10);
ctx.fillStyle = styleNight ? "#5e4634" : (styleWarm ? "#956b42" : "#77583a");