This commit is contained in:
33333-33333 2026-06-21 14:09:35 +09:00
commit 37087d08a7
189 changed files with 12864 additions and 9589 deletions

View file

@ -1,7 +1,11 @@
// tarinai_colony_game build 15.6.2: food servings, nest box access, and editable tarinai data
"use strict";
const FOOD_SERVING_TYPES = new Set(["food", "sweet", "love_mochi", "fight_mochi", "sleep_drug"]);
const FOOD_SERVING_TYPES = new Set(["food", "sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice"]);
const PARAM_EFFECT_ITEM_TYPES = new Set(["laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice"]);
function isParamEffectItemType(type = "") {
return PARAM_EFFECT_ITEM_TYPES.has(type);
}
const FOOD_SERVINGS_BY_SIZE = { small: 1, medium: 5, large: 15 };
function foodServingsForSize(size = "medium") {
return FOOD_SERVINGS_BY_SIZE[size] || FOOD_SERVINGS_BY_SIZE.medium;
@ -9,6 +13,19 @@ function foodServingsForSize(size = "medium") {
function isServingFoodType(type = "") {
return FOOD_SERVING_TYPES.has(type);
}
const PASSIVE_FOOD_DECAY_PER_SECOND = 0.004;
const PASSIVE_FOOD_HYGIENE_PENALTY_PER_SERVING = 0.055;
function passiveFoodDecayInterval(worldRef = null) {
// Two decay ticks per half-day: dayLength/4. Default day is 120s -> 30s interval.
const day = Number(worldRef?.config?.dayLength || CONFIG?.dayLength || 120) || 120;
return Math.max(12, day / 4);
}
function passiveFoodDecayRate(item) {
const size = item?.toolSize || "medium";
const sizeMul = size === "small" ? 0.74 : (size === "large" ? 1.32 : 1);
const typeMul = item?.type === "sweet" ? 0.82 : (item?.type === "zunda_juice" ? 1.12 : 1);
return PASSIVE_FOOD_DECAY_PER_SECOND * sizeMul * typeMul;
}
class Item {
constructor(type, x, y) {
@ -81,12 +98,15 @@ class Item {
this.dropMax = 0;
this.dropImpactDone = false;
this.impactFlash = 0;
this.lingerTimer = 0;
this.lingerDuration = 2.0;
}
if (isServingFoodType(type)) {
this.toolSize = "medium";
this.foodServingScale = 1;
this.foodServingsMax = foodServingsForSize(this.toolSize);
this.foodServingsRemaining = this.foodServingsMax;
this.passiveFoodDecayTimer = rand(0, passiveFoodDecayInterval());
this.amount = this.foodServingsRemaining;
}
}
@ -101,7 +121,34 @@ class Item {
}
}
if (this.type === "water") this.amount -= dt * 0.9;
if (["sweet", "love_mochi", "fight_mochi", "sleep_drug"].includes(this.type) && !Number.isFinite(this.foodServingsRemaining)) this.amount -= dt * 0.12;
if (isServingFoodType(this.type)) {
const interval = passiveFoodDecayInterval(worldRef);
this.passiveFoodDecayTimer = (this.passiveFoodDecayTimer || 0) + dt;
let ticks = 0;
while (this.passiveFoodDecayTimer >= interval && ticks < 3) {
this.passiveFoodDecayTimer -= interval;
ticks++;
if (Number.isFinite(this.foodServingsRemaining)) {
const before = Math.max(0, this.foodServingsRemaining || 0);
const lost = Math.min(before, interval * passiveFoodDecayRate(this));
if (lost > 0) {
this.foodServingsRemaining = Math.max(0, before - lost);
this.amount = this.foodServingsRemaining;
worldRef?.registerFoodSpoilage?.(lost * PASSIVE_FOOD_HYGIENE_PENALTY_PER_SERVING, this);
worldRef?.markTerrainDirty?.("food-passive-decay");
if (this.foodServingsRemaining <= 0.015) this.amount = 0;
}
} else if (["sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice"].includes(this.type)) {
const before = this.amount || 0;
const lost = Math.min(before, interval * 0.12);
this.amount -= lost;
if (lost > 0) {
worldRef?.registerFoodSpoilage?.(lost * 0.002, this);
worldRef?.markTerrainDirty?.("food-passive-decay");
}
}
}
}
if (this.type === "trace") this.amount -= dt * 1.35;
if (this.type === "splat") this.amount -= dt * 1.05;
if (this.type === "ant_corpse") this.amount -= dt * 0.018;
@ -114,8 +161,12 @@ class Item {
}
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.dropImpactDone) {
this.lingerTimer = Math.max(0, (this.lingerTimer || 0) - dt);
if ((this.lingerTimer || 0) <= 0) this.amount = 0;
} 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);
@ -571,6 +622,104 @@ class Item {
ctx.fill();
}
}
} else if (typeof isParamEffectItemType === "function" && isParamEffectItemType(this.type)) {
const meta = {
laxative: { fill: "#eef0b8", stroke: "#879046", accent: "#dfe770", kind: "pill" },
protein: { fill: "#f4a24f", stroke: "#b45b27", accent: "#ffe2b9", kind: "powder" },
niteropu: { fill: "#8a79d0", stroke: "#4c3f91", accent: "#d8d1ff", kind: "powder" },
ammo: { fill: "#ffe8b6", stroke: "#b37a26", accent: "#d0942f", kind: "box" },
mystery_drug: { fill: "#e9ffe8", stroke: "#4a9b65", accent: "#9de7ad", kind: "pill" },
mercury: { fill: "rgba(216,219,220,0.98)", stroke: "rgba(198,202,204,0.98)", accent: "rgba(162,169,174,0.98)", kind: "droplet" },
giant_drug: { fill: "#ffe3cf", stroke: "#c9632f", accent: "#ffb47b", kind: "pill" },
dwarf_drug: { fill: "#e9e9ff", stroke: "#6967ac", accent: "#b8b5f4", kind: "pill" },
zunda_juice: { fill: "rgba(134,224,91,0.98)", stroke: "rgba(76,168,57,0.98)", accent: "rgba(202,255,186,0.98)", kind: "droplet" },
}[this.type] || { fill: "#fffaf1", stroke: "#8d6f48", accent: "#ddd", kind: "pill" };
const localR = this.r * (this.type === "giant_drug" ? 1.08 : (this.type === "dwarf_drug" ? 0.92 : 1));
ctx.rotate(Math.sin(this.seed) * 0.10);
if (meta.kind === "powder") {
ctx.save();
ctx.fillStyle = "rgba(68,46,28,0.18)";
ctx.beginPath();
ctx.ellipse(0, localR * 0.50, localR * 1.22, localR * 0.34, 0, 0, Math.PI * 2);
ctx.fill();
for (let i = 0; i < 18; i++) {
const px = randSeed(this.seed + i * 7, -localR * 0.85, localR * 0.85);
const layer = 1 - Math.abs(px) / Math.max(1, localR * 0.90);
const py = randSeed(this.seed + i * 11, -localR * 0.15, localR * 0.48) - layer * localR * 0.48;
const rr = randSeed(this.seed + i * 13, localR * 0.14, localR * 0.32) * (0.75 + layer * 0.45);
ctx.fillStyle = i % 3 === 0 ? meta.accent : meta.fill;
ctx.strokeStyle = meta.stroke;
ctx.globalAlpha = 0.86 + layer * 0.12;
ctx.lineWidth = 1.0;
ctx.beginPath();
ctx.ellipse(px, py, rr * 1.18, rr * 0.72, randSeed(this.seed + i, -0.45, 0.45), 0, Math.PI * 2);
ctx.fill(); ctx.stroke();
}
ctx.globalAlpha = 1;
ctx.fillStyle = "rgba(255,255,255,0.62)";
ctx.beginPath();
ctx.ellipse(-localR * 0.30, -localR * 0.42, localR * 0.28, localR * 0.10, -0.35, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
} else if (meta.kind === "droplet") {
ctx.save();
ctx.lineCap = "round";
ctx.strokeStyle = meta.stroke;
ctx.lineWidth = Math.max(2.2, localR * 0.16);
ctx.globalAlpha = 0.92;
const lean = -localR * 0.34;
ctx.beginPath();
ctx.moveTo(localR * 0.18, -localR * 0.90);
ctx.lineTo(localR * 0.18 + lean, localR * 0.42);
ctx.stroke();
ctx.globalAlpha = 0.70;
ctx.fillStyle = meta.fill;
ctx.beginPath();
ctx.ellipse(localR * 0.18 + lean, localR * 0.54, localR * 0.24, localR * 0.38, -0.18, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 0.50;
ctx.strokeStyle = meta.accent;
ctx.lineWidth = Math.max(1.0, localR * 0.07);
ctx.beginPath();
ctx.moveTo(localR * 0.10, -localR * 0.72);
ctx.lineTo(localR * 0.10 + lean * 0.72, localR * 0.18);
ctx.stroke();
ctx.restore();
} else if (meta.kind === "box") {
ctx.fillStyle = meta.fill;
ctx.strokeStyle = meta.stroke;
ctx.lineWidth = 2;
roundedRect(ctx, -localR * 0.96, -localR * 0.78, localR * 1.92, localR * 1.56, localR * 0.24);
ctx.fill(); ctx.stroke();
ctx.fillStyle = meta.accent;
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.ellipse(randSeed(this.seed + i, -localR * 0.50, localR * 0.50), randSeed(this.seed + i + 5, -localR * 0.30, localR * 0.34), localR * 0.18, localR * 0.34, 0.28, 0, Math.PI * 2);
ctx.fill();
}
ctx.strokeStyle = "rgba(110,76,30,0.52)";
ctx.beginPath();
ctx.moveTo(-localR * 0.70, localR * 0.50);
ctx.lineTo(localR * 0.70, -localR * 0.50);
ctx.stroke();
} else {
ctx.save();
ctx.fillStyle = meta.fill;
ctx.strokeStyle = meta.stroke;
ctx.lineWidth = 2.2;
ctx.rotate(-0.38);
roundedRect(ctx, -localR * 1.15, -localR * 0.46, localR * 2.30, localR * 0.92, localR * 0.46);
ctx.fill(); ctx.stroke();
ctx.fillStyle = meta.accent;
ctx.globalAlpha = 0.78;
ctx.fillRect(-1.5, -localR * 0.42, 3.0, localR * 0.84);
ctx.globalAlpha = 0.36;
ctx.fillStyle = "rgba(255,255,255,0.92)";
ctx.beginPath();
ctx.ellipse(-localR * 0.48, -localR * 0.16, localR * 0.32, localR * 0.10, -0.10, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
} else if (this.type === "sleep_drug") {
ctx.fillStyle = "#f5f0ff";
ctx.strokeStyle = "rgba(112, 83, 170, 0.55)";
@ -581,11 +730,10 @@ class Item {
ctx.fillStyle = "rgba(152, 116, 218, 0.58)";
ctx.fillRect(-1, -this.r * 0.42, 2, this.r * 0.84);
ctx.rotate(0.45);
ctx.fillStyle = "rgba(74,58,104,0.72)";
ctx.font = `${Math.round((this.r || 13) * 0.78)}px sans-serif`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Z", 0, this.r * 1.18);
ctx.fillStyle = "rgba(255,255,255,0.58)";
ctx.beginPath();
ctx.ellipse(-this.r * 0.36, -this.r * 0.16, this.r * 0.25, this.r * 0.09, -0.20, 0, Math.PI * 2);
ctx.fill();
} else if (this.type === "genkotsu") {
const img = typeof getRenderableImage === "function" ? getRenderableImage("genkotsu", "genkotsu") : images.get("genkotsu");
const impact = clamp(this.impactFlash || 0, 0, 1);
@ -638,14 +786,22 @@ class Item {
ctx.fillStyle = "rgba(255,255,255,0.54)";
ctx.beginPath(); ctx.arc(-this.r * 0.32, -this.r * 0.18, 2.8, 0, Math.PI * 2); ctx.fill();
} else if (this.type === "water") {
ctx.fillStyle = "rgba(86, 157, 224, 0.50)";
ctx.strokeStyle = "rgba(62, 113, 178, 0.42)";
ctx.lineWidth = 2;
ctx.save();
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(198, 202, 204, 0.98)";
ctx.lineWidth = Math.max(2.0, this.r * 0.16);
const lean = -this.r * 0.34;
ctx.globalAlpha = 0.86;
ctx.beginPath();
ctx.ellipse(0, Math.sin(t * 1.7 + this.seed) * 1.5, this.r * 1.05, this.r * 0.8, 0, 0, Math.PI * 2);
ctx.fill(); ctx.stroke();
ctx.fillStyle = "rgba(255,255,255,0.55)";
ctx.beginPath(); ctx.arc(-4, -4, 3, 0, Math.PI * 2); ctx.fill();
ctx.moveTo(this.r * 0.18, -this.r * 0.90);
ctx.lineTo(this.r * 0.18 + lean, this.r * 0.42);
ctx.stroke();
ctx.globalAlpha = 0.58;
ctx.fillStyle = "rgba(216, 219, 220, 0.98)";
ctx.beginPath();
ctx.ellipse(this.r * 0.18 + lean, this.r * 0.54, this.r * 0.24, this.r * 0.38, -0.18, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
} else if (this.type === "grass") {
const growth = this.growth ?? clamp(this.amount / 120, 0, 1);
const stage = growth < 0.22 ? "sprout" : growth < 0.48 ? "young" : "mature";