更新饥荒

This commit is contained in:
2026-09-24 10:52:55 +08:00
parent 1df33337e9
commit d7ef97d46e
6 changed files with 114 additions and 45 deletions

View File

@@ -1384,7 +1384,8 @@ const gCtx = {
return f
},
// 否决型钩子调用器(供 mod 使用):任一 mod 返回 false 即返回 false
allows: (name, ...args) => callAllows(modRt, name, ...args),
// 与核心其它钩子一致,gCtx 作为钩子首参自动前置,mod 侧签名统一为 (g, ...)
allows: (name, ...args) => callAllows(modRt, name, gCtx, ...args),
me: () => player,
isHost: () => isHostRole(),
invalidate: () => { invDirty = true },

View File

@@ -338,18 +338,22 @@ export default {
name: '水井',
conf: { size: 30, struct: true },
init(e) {
if (e.cd == null) e.cd = 0
// 冷却必须放在 e.mx 里:核心只序列化 e.mx(+ e.state),放 e.cd 会存档/联机丢失
if (!e.mx) e.mx = { cd: 0 }
if (e.mx.cd == null) e.mx.cd = 0
},
update(e, dt, g) {
if (e.cd > 0) e.cd = Math.max(0, e.cd - dt)
const cd = e.mx?.cd || 0
if (cd > 0) e.mx.cd = Math.max(0, cd - dt)
},
interact(p, e, g) {
if (e.cd > 0) {
g.ptip(p, `井水还没续满……(${Math.ceil(e.cd)} 秒)`)
const cd = e.mx?.cd || 0
if (cd > 0) {
g.ptip(p, `井水还没续满……(${Math.ceil(cd)} 秒)`)
return
}
p.san += 5
e.cd = 60
e.mx = { ...(e.mx || {}), cd: 60 }
g.markDirty(e)
g.swing(p, 'eat')
g.ptip(p, '打了一瓢清水,理智 +5')

View File

@@ -94,6 +94,15 @@ function campCenter(g) {
if (house) return { x: house.x, y: house.y }
return { x: g.worldW / 2, y: g.worldH / 2 }
}
// 全部营地房屋(用于绘制每一座的安全区边界)
function campHouseList(g) {
return g.entities.filter((e) => e.type === 'camphouse')
}
// 玩家在「任意一座」营地房屋的 CAMP_R 内都算进入营地 —— 自建的分基地同样生效。
// ⚠️ 这里不能用 find 只取第一座,否则玩家再造房屋不会扩展安全区。
function inCamp(g, x, y) {
return g.entities.some((e) => e.type === 'camphouse' && Math.hypot(x - e.x, y - e.y) < CAMP_R)
}
export default {
id: 'camp',
name: '营地',
@@ -435,21 +444,21 @@ export default {
// 只对敌对生物生效(保留「怪可以去打守卫/猪人」的能力)。
if (!g.mon[m.kind]?.hostile) return
// 判断依据是「受害者在不在营地内」,怪本身不做任何位移(无跳变)。
const center = g.entities.find((e) => e.type === 'camphouse') || { x: g.worldW / 2, y: g.worldH / 2 }
if (Math.hypot(victim.x - center.x, victim.y - center.y) < CAMP_R) return false
if (inCamp(g, victim.x, victim.y)) return false
},
onDraw(g, ctx, env) {
// 虚线画出营地领地边界(世界坐标位置,画布为屏幕坐标系,故减去相机)
const c = campCenter(g)
const sx = c.x - env.camX
const sy = c.y - env.camY
// 虚线画出**每一座**营地房屋的领地边界(世界坐标 → 画布屏幕坐标,故减去相机)
const houses = campHouseList(g)
const list = houses.length ? houses : [campCenter(g)]
ctx.save()
ctx.strokeStyle = 'rgba(255,220,140,0.35)'
ctx.lineWidth = 2
ctx.setLineDash([10, 8])
ctx.beginPath()
ctx.ellipse(sx, sy, CAMP_R, CAMP_R * 0.62, 0, 0, TAU)
ctx.stroke()
for (const h of list) {
ctx.beginPath()
ctx.ellipse(h.x - env.camX, h.y - env.camY, CAMP_R, CAMP_R * 0.62, 0, 0, TAU)
ctx.stroke()
}
ctx.restore()
},
onDrawUI(g, ctx, env) {

View File

@@ -341,12 +341,15 @@ export default {
g.ptip(p, '需要剪刀才能剪羊毛')
return
}
if (m.shearDay === g.day) {
// 剪毛冷却记在 m.state 上:mob 只持久化 state / loyalT 等少数字段,
// 自定义字段(如 m.shearDay)存档与联机都会丢,会导致读档后可重复剪毛
const sheared = Number(String(m.state || '').replace('sheared:', '')) || -1
if (sheared === g.day) {
g.ptip(p, '羊毛还没长回来(需等到明天)')
return
}
g.addItem(p, 'wool', 2)
m.shearDay = g.day
m.state = `sheared:${g.day}`
g.ptip(p, '给绵羊剪下一团羊毛')
g.swing(p, 'chop')
},

View File

@@ -277,18 +277,20 @@ export default {
name: '蒸汽机',
conf: { size: 38, struct: true },
init(e) {
if (e.fuel == null) e.fuel = 0
if (e.on == null) e.on = false
// 燃料必须放在 e.mx 里:核心只序列化 e.mx(+ e.state),放 e.fuel 会存档/联机丢失
if (!e.mx) e.mx = { fuel: 0 }
if (e.mx.fuel == null) e.mx.fuel = 0
e.on = e.mx.fuel > 0
},
update(e, dt, g) {
e.on = (e.fuel || 0) > 0
e.on = (e.mx?.fuel || 0) > 0
},
interact(p, e, g) {
if (g.hasCost(p, { log: 1 })) {
g.payCost(p, { log: 1 })
e.fuel = (e.fuel || 0) + 3
e.mx = { ...(e.mx || {}), fuel: (e.mx?.fuel || 0) + 3 }
g.markDirty(e)
g.ptip(p, `投入木头,蒸汽机燃料剩余 ${e.fuel}`)
g.ptip(p, `投入木头,蒸汽机燃料剩余 ${e.mx.fuel}`)
} else {
g.ptip(p, '蒸汽机需要木头作燃料')
}
@@ -341,7 +343,7 @@ export default {
name: '工厂',
conf: { size: 40, struct: true },
interact(p, e, g) {
const powered = g.entities.some((s) => s.type === 'steamengine' && s.on && Math.hypot(s.x - e.x, s.y - e.y) < STEAM_RANGE)
const powered = g.entities.some((s) => s.type === 'steamengine' && (s.mx?.fuel || 0) > 0 && Math.hypot(s.x - e.x, s.y - e.y) < STEAM_RANGE)
if (!powered) {
g.ptip(p, '需要通蒸汽动力(在附近建蒸汽机并加燃料)')
return
@@ -402,7 +404,7 @@ export default {
name: '自动矿机',
conf: { size: 34, struct: true },
interact(p, e, g) {
const powered = g.entities.some((s) => s.type === 'steamengine' && s.on && Math.hypot(s.x - e.x, s.y - e.y) < STEAM_RANGE)
const powered = g.entities.some((s) => s.type === 'steamengine' && (s.mx?.fuel || 0) > 0 && Math.hypot(s.x - e.x, s.y - e.y) < STEAM_RANGE)
g.ptip(p, powered ? '矿机正在满速运转(每台每天自动开采)' : '缺少蒸汽动力,效率减半')
},
draw(ctx, e, env) {
@@ -450,14 +452,14 @@ export default {
onNewDay(g) {
// 自动矿机开采:附近有供能的蒸汽机则翻倍
g.entities.filter((e) => e.type === 'miner').forEach((m) => {
const powered = g.entities.some((s) => s.type === 'steamengine' && s.on && Math.hypot(s.x - m.x, s.y - m.y) < STEAM_RANGE)
const powered = g.entities.some((s) => s.type === 'steamengine' && (s.mx?.fuel || 0) > 0 && Math.hypot(s.x - m.x, s.y - m.y) < STEAM_RANGE)
g.dropLoot(m.x + g.rand(-24, 24), m.y + g.rand(-24, 24), 'rock', powered ? 4 : 2)
g.dropLoot(m.x + g.rand(-24, 24), m.y + g.rand(-24, 24), 'flint', powered ? 2 : 1)
})
// 蒸汽机每日消耗 1 份燃料
// 蒸汽机每日消耗 1 份燃料(燃料存在 e.mx.fuel,随存档与联机同步)
g.entities.filter((e) => e.type === 'steamengine').forEach((s) => {
s.fuel = Math.max(0, (s.fuel || 0) - 1)
s.on = s.fuel > 0
s.mx = { ...(s.mx || {}), fuel: Math.max(0, (s.mx?.fuel || 0) - 1) }
s.on = s.mx.fuel > 0
g.markDirty(s)
})
},