1.6
This commit is contained in:
parent
ee22e1a929
commit
88f618cadf
55 changed files with 1299 additions and 447 deletions
|
|
@ -227,13 +227,27 @@
|
|||
return ((base % twoPi) + twoPi) % twoPi;
|
||||
}
|
||||
|
||||
function fanWindAngleFor(item) {
|
||||
return normalizeAngleLocal(item?.angle, item?.fanSwingCenter ?? 0);
|
||||
}
|
||||
|
||||
function ensureFanBodyAngle(item) {
|
||||
if (!item || item.type !== "fan") return 0;
|
||||
const body = Number(item.fanBodyAngle);
|
||||
if (Number.isFinite(body)) return normalizeAngleLocal(body, item.angle || 0);
|
||||
item.fanBodyAngle = normalizeAngleLocal(item.angle || item.fanSwingCenter || 0, 0);
|
||||
return item.fanBodyAngle;
|
||||
}
|
||||
|
||||
function updateFanSwing(item, dt) {
|
||||
if (!item || item.dead || item.type !== "fan") return false;
|
||||
ensureFanBodyAngle(item);
|
||||
if (!item.fanSwingOn) {
|
||||
item.fanSwingCenter = Number.isFinite(Number(item.fanSwingCenter)) ? Number(item.fanSwingCenter) : itemAngleFor(item);
|
||||
item.fanSwingCenter = Number.isFinite(Number(item.fanSwingCenter)) ? Number(item.fanSwingCenter) : fanWindAngleFor(item);
|
||||
item.angle = normalizeAngleLocal(item.fanSwingCenter, item.angle || 0);
|
||||
return false;
|
||||
}
|
||||
const center = normalizeAngleLocal(item.fanSwingCenter, itemAngleFor(item));
|
||||
const center = normalizeAngleLocal(item.fanSwingCenter, fanWindAngleFor(item));
|
||||
const range = clamp(Number(item.fanSwingRange ?? (35 * Math.PI / 180)) || 0, 0, 150 * Math.PI / 180);
|
||||
const speed = clamp(Number(item.fanSwingSpeed ?? (48 * Math.PI / 180)) || 0, 0, 360 * Math.PI / 180);
|
||||
item.fanSwingCenter = center;
|
||||
|
|
@ -250,7 +264,7 @@
|
|||
const spread = Math.max(0.18, Number(opts.spread || 0.62) || 0.62);
|
||||
const dx = x - fan.x;
|
||||
const dy = y - fan.y;
|
||||
const angle = itemAngleFor(fan);
|
||||
const angle = fanWindAngleFor(fan);
|
||||
const ax = Math.cos(angle);
|
||||
const ay = Math.sin(angle);
|
||||
const along = dx * ax + dy * ay;
|
||||
|
|
@ -271,11 +285,12 @@
|
|||
const now = worldRef?.time || 0;
|
||||
item.prevX = item.x;
|
||||
item.prevY = item.y;
|
||||
ensureFanBodyAngle(item);
|
||||
item.fanSpin = (item.fanSpin || 0) + 11.5 * frameDt;
|
||||
item.fanPulse = (item.fanPulse || 0) + 2.2 * frameDt;
|
||||
updateFanSwing(item, frameDt);
|
||||
|
||||
const angle = itemAngleFor(item);
|
||||
const angle = fanWindAngleFor(item);
|
||||
const ax = Math.cos(angle);
|
||||
const ay = Math.sin(angle);
|
||||
const range = 320;
|
||||
|
|
@ -306,6 +321,46 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
function spreadFireByFan(fan, fire, wind, dt, worldRef) {
|
||||
if (!fan || !fire || !worldRef || fire.dead || fire.type !== "fire") return false;
|
||||
const now = worldRef.time || 0;
|
||||
const falloff = Math.max(0, Number(wind?.falloff || 0) || 0);
|
||||
if (falloff < 0.16 || (fire.amount || 0) < 18) return false;
|
||||
const interval = Math.max(0.10, 0.34 - falloff * 0.18);
|
||||
if ((fire.lastFanSpreadAt || -999) + interval > now) return false;
|
||||
fire.lastFanSpreadAt = now;
|
||||
const ax = Math.cos(wind.angle);
|
||||
const ay = Math.sin(wind.angle);
|
||||
const side = global.deterministicRange(worldRef, "fan-fire-side", -22, 22, fan, fire, Math.round(now * 10));
|
||||
const dist = global.deterministicRange(worldRef, "fan-fire-distance", 58, 118, fan, fire, Math.round(now * 10)) * (0.82 + falloff * 0.58);
|
||||
const x = clamp((fire.x || 0) + ax * dist - ay * side, CONFIG.worldPadding || 30, Math.max(CONFIG.worldPadding || 30, (worldRef.w || 0) - (CONFIG.worldPadding || 30)));
|
||||
const y = clamp((fire.y || 0) + ay * dist + ax * side, CONFIG.worldPadding || 30, Math.max(CONFIG.worldPadding || 30, (worldRef.h || 0) - (CONFIG.worldPadding || 30)));
|
||||
const spawned = global.TarinaiItemDynamicToolSystem?.spawnFireAt?.(worldRef, x, y, {
|
||||
radius: Math.max(8, (fire.r || 14) * (0.55 + falloff * 0.22)),
|
||||
amount: Math.max(18, Math.min(86, (fire.amount || 60) * (0.20 + falloff * 0.22))),
|
||||
life: 2.2 + falloff * 3.4,
|
||||
source: fan,
|
||||
mergeRadius: 24,
|
||||
});
|
||||
if (spawned) {
|
||||
// A fan should carry embers forward instead of merely dimming the source flame.
|
||||
// The source loses a little fuel, while the downwind flame handles normal fire contacts.
|
||||
fire.amount = Math.max(4, (fire.amount || 0) - (2.2 + falloff * 3.8));
|
||||
worldRef.effects?.push(new Effect("flame", fire.x + ax * Math.max(12, fire.r || 14), fire.y + ay * Math.max(12, fire.r || 14), {
|
||||
size: Math.max(7, (fire.r || 14) * 0.72),
|
||||
life: 0.22,
|
||||
color: "rgba(255,126,54,0.58)",
|
||||
vx: ax * (80 + falloff * 120),
|
||||
vy: ay * (80 + falloff * 120),
|
||||
}));
|
||||
worldRef.markItemBucketsDirty?.("fan-fire-spread");
|
||||
worldRef.markSpatialDirty?.("fan-fire-spread");
|
||||
worldRef.drawListDirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyFanToItem(fan, target, wind, dt, worldRef) {
|
||||
const type = target.type || "";
|
||||
let massFactor = 0;
|
||||
|
|
@ -314,7 +369,10 @@
|
|||
else if (type === "pushpin" || type === "oshibyo") massFactor = 0.90;
|
||||
else if (type === "water" || type === "zunda_juice" || type === "mercury") massFactor = 0.42;
|
||||
else if (type === "zunchi") massFactor = 0.30;
|
||||
else if (type === "fire") massFactor = 0.18;
|
||||
else if (type === "fire") {
|
||||
spreadFireByFan(fan, target, wind, dt, worldRef);
|
||||
return;
|
||||
}
|
||||
else return;
|
||||
|
||||
const force = 236 * wind.falloff * massFactor;
|
||||
|
|
@ -326,7 +384,6 @@
|
|||
target.vy = clamp((target.vy || 0) + ay * force * dt, -820, 820);
|
||||
if (type === "balloon") target.spinVelocity = clamp((target.spinVelocity || 0) + 3.2 * wind.falloff * dt, -12, 12);
|
||||
else if (type === "ball") target.spinVelocity = clamp((target.spinVelocity || 0) + 5.4 * wind.falloff * dt, -38, 38);
|
||||
if (type === "fire") target.amount = Math.max(0, (target.amount ?? 1) - wind.falloff * dt * 1.0);
|
||||
worldRef?.markSpatialDirty?.("fan-wind-item");
|
||||
}
|
||||
|
||||
|
|
@ -353,8 +410,9 @@
|
|||
const radius = Math.max(26, (balloon.r || 20) + 22);
|
||||
for (const it of worldRef.nearbyItems(balloon.x, balloon.y, radius) || []) {
|
||||
if (!it || it.dead || it === balloon) continue;
|
||||
if ((typeof isPinType === "function" && isPinType(it.type) && it.pinState !== "lodged") || it.type === "fire" || it.type === "flame_firecracker") {
|
||||
const hit = distXY(balloon.x, balloon.y, it.x, it.y) <= (balloon.r || 20) + (it.r || 10) + (it.type === "fire" ? 16 : 4);
|
||||
if ((typeof isPinType === "function" && isPinType(it.type) && it.pinState !== "lodged") || it.type === "fire" || it.type === "flame_firecracker" || it.type === "firecracker") {
|
||||
const extra = it.type === "fire" ? 16 : (it.type === "firecracker" || it.type === "flame_firecracker" ? 8 : 4);
|
||||
const hit = distXY(balloon.x, balloon.y, it.x, it.y) <= (balloon.r || 20) + (it.r || 10) + extra;
|
||||
if (hit) {
|
||||
popBalloon(balloon, worldRef, it);
|
||||
return;
|
||||
|
|
@ -410,22 +468,26 @@
|
|||
function spawnWaterFromBalloon(balloon, worldRef) {
|
||||
if (!balloon || !worldRef) return 0;
|
||||
const baseR = Math.max(14, Number(balloon.r || 20) || 20);
|
||||
const count = Math.max(7, Math.min(13, Math.round(baseR * 0.42)));
|
||||
const count = Math.max(12, Math.min(22, Math.round(baseR * 0.68)));
|
||||
let spawned = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (typeof global.Item !== "function") break;
|
||||
const a = deterministicAngle(worldRef || balloon, "water-balloon-splash-angle", balloon, i);
|
||||
const d = deterministicRange(worldRef || balloon, "water-balloon-splash-dist", baseR * 0.10, baseR * 0.58, balloon, i);
|
||||
const speed = deterministicRange(worldRef || balloon, "water-balloon-drop-speed", 150, 420, balloon, i);
|
||||
const drop = new global.Item("water", balloon.x + Math.cos(a) * d, balloon.y + Math.sin(a) * d);
|
||||
drop.r = deterministicRange(worldRef || balloon, "water-balloon-drop-radius", 5.5, 10.5, balloon, i);
|
||||
drop.amount = deterministicRange(worldRef || balloon, "water-balloon-drop-amount", 10, 28, balloon, i);
|
||||
drop.prevX = drop.x;
|
||||
drop.prevY = drop.y;
|
||||
drop.vx = Math.cos(a) * speed + (Number(balloon.vx || 0) || 0) * 0.18;
|
||||
drop.vy = Math.sin(a) * speed + (Number(balloon.vy || 0) || 0) * 0.18;
|
||||
drop.splashFlightTimer = deterministicRange(worldRef || balloon, "water-balloon-drop-flight", 0.30, 0.62, balloon, i);
|
||||
drop.splashSourceId = balloon.id || "";
|
||||
const d = deterministicRange(worldRef || balloon, "water-balloon-splash-dist", baseR * 0.12, baseR * 0.95, balloon, i);
|
||||
const speed = deterministicRange(worldRef || balloon, "water-balloon-drop-speed", 280, 760, balloon, i);
|
||||
const x = balloon.x + Math.cos(a) * d;
|
||||
const y = balloon.y + Math.sin(a) * d;
|
||||
const vx = Math.cos(a) * speed + (Number(balloon.vx || 0) || 0) * 0.28;
|
||||
const vy = Math.sin(a) * speed + (Number(balloon.vy || 0) || 0) * 0.28;
|
||||
const flight = deterministicRange(worldRef || balloon, "water-balloon-drop-flight", 0.42, 0.88, balloon, i);
|
||||
const amount = deterministicRange(worldRef || balloon, "water-balloon-drop-amount", 24, 58, balloon, i);
|
||||
const drop = global.TarinaiWeatherSystem?.createRainWaterItem
|
||||
? global.TarinaiWeatherSystem.createRainWaterItem(worldRef, { x, y, amount, vx, vy, splashFlightTimer: flight })
|
||||
: new global.Item("water", x, y);
|
||||
if (!Number.isFinite(Number(drop.amount))) drop.amount = amount;
|
||||
if (!Number.isFinite(Number(drop.vx))) drop.vx = vx;
|
||||
if (!Number.isFinite(Number(drop.vy))) drop.vy = vy;
|
||||
if (!Number.isFinite(Number(drop.splashFlightTimer))) drop.splashFlightTimer = flight;
|
||||
if (worldRef.addItem?.(drop, "water-balloon-splash", { terrain: false })) spawned += 1;
|
||||
else { worldRef.items?.push?.(drop); spawned += 1; }
|
||||
if (i < 8) {
|
||||
|
|
@ -538,7 +600,11 @@
|
|||
worldRef?.effects?.push(new Effect("fight", balloon.x, balloon.y, { size: deterministicRange(worldRef || balloon, "water-balloon-pop-size", 4, 10, balloon, i), life: 0.28, color: "rgba(95,204,246,0.72)", vx: Math.cos(a) * s, vy: Math.sin(a) * s }));
|
||||
}
|
||||
spawnWaterFromBalloon(balloon, worldRef);
|
||||
const sourceLabel = cause?.type === "fire" || cause?.type === "flame_firecracker" ? "火" : (cause?.type === "genkotsu" ? "げんこつ" : (cause && typeof isPinType === "function" && isPinType(cause.type) ? "鋲" : "何か"));
|
||||
const sourceLabel = cause?.type === "fire" || cause?.type === "flame_firecracker" ? "火"
|
||||
: (cause?.type === "firecracker" ? "爆竹"
|
||||
: (cause?.type === "shoot" ? "射撃"
|
||||
: (cause?.type === "genkotsu" ? "げんこつ"
|
||||
: (cause && typeof isPinType === "function" && isPinType(cause.type) ? "鋲" : "強い衝撃"))));
|
||||
if (worldRef?.relationNotice?.(balloon.id || "balloon", cause?.id || sourceLabel, "water-balloon-pop", 1.4)) {
|
||||
worldRef.log?.(`水風船が${sourceLabel}に触れて割れた。`, "accident", { participants: [] });
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue