年糕生存游戏
This commit is contained in:
@@ -33,16 +33,16 @@ const msgBadge = computed(() => {
|
||||
return n > 99 ? '99+' : n
|
||||
})
|
||||
|
||||
// 仅饥荒游玩页:导航变悬浮小条,默认隐藏,鼠标靠近顶部再展示
|
||||
// 仅饥荒游玩页:导航变悬浮小条,默认隐藏;由 GamePlay 的吸顶按钮统一展开/收起
|
||||
const navFloat = computed(() => route.path.startsWith('/play/starve'))
|
||||
const navShow = ref(true)
|
||||
watch(navFloat, (v) => { navShow.value = !v }, { immediate: true })
|
||||
function onNavMouseMove(e) {
|
||||
function onStarveNavToggle() {
|
||||
if (!navFloat.value) return
|
||||
navShow.value = e.clientY <= 60
|
||||
navShow.value = !navShow.value
|
||||
}
|
||||
onMounted(() => window.addEventListener('mousemove', onNavMouseMove))
|
||||
onBeforeUnmount(() => window.removeEventListener('mousemove', onNavMouseMove))
|
||||
onMounted(() => window.addEventListener('nlg:starve-nav-toggle', onStarveNavToggle))
|
||||
onBeforeUnmount(() => window.removeEventListener('nlg:starve-nav-toggle', onStarveNavToggle))
|
||||
|
||||
// 当前路由是否命中某导航
|
||||
function isActive(path) {
|
||||
|
||||
@@ -35,6 +35,8 @@ const emit = defineEmits(['score', 'end', 'save', 'progress'])
|
||||
const canvas = ref(null)
|
||||
const clockCv = ref(null)
|
||||
const mapCv = ref(null)
|
||||
const fullMapCv = ref(null)
|
||||
const showFullMap = ref(false)
|
||||
|
||||
// ---- HUD 响应式状态 ----
|
||||
const playing = ref(false)
|
||||
@@ -46,6 +48,7 @@ const hunger = ref(CAP.hunger)
|
||||
const san = ref(CAP.san)
|
||||
const temp = ref(20)
|
||||
const day = ref(1)
|
||||
const cursorMode = ref('normal')
|
||||
const seasonTag = ref({ name: '秋', dayIn: 1, len: 20, tint: '#c98548', code: 'autumn' })
|
||||
const tips = ref([])
|
||||
const hint = ref('')
|
||||
@@ -661,6 +664,48 @@ function onCanvasClick(ev) {
|
||||
if (ent) return setMyTarget({ kind: 'ent', id: ent.id })
|
||||
setMyTarget({ kind: 'walk', x: wxp, y: wyp })
|
||||
}
|
||||
function updateCursor(ev) {
|
||||
if (!playing.value || !canvas.value) {
|
||||
cursorMode.value = 'normal'
|
||||
return
|
||||
}
|
||||
const rect = canvas.value.getBoundingClientRect()
|
||||
const wxp = ((ev.clientX - rect.left) / rect.width) * canvas.value.width + camX
|
||||
const wyp = ((ev.clientY - rect.top) / rect.height) * canvas.value.height + camY
|
||||
let mon = null
|
||||
let md = Infinity
|
||||
monsters.forEach((m) => {
|
||||
const r = m.kind === 'deerclops' ? 70 : m.kind === 'treeguard' ? 50 : m.kind === 'beefalo' ? 36 : 28
|
||||
const d = Math.hypot(m.x - wxp, m.y - wyp)
|
||||
if (d < r && d < md) { md = d; mon = m }
|
||||
})
|
||||
if (mon) {
|
||||
cursorMode.value = (mon.kind === 'pig' || mon.kind === 'beefalo' || modRt.mobKinds[mon.kind]?.friendly) ? 'hand' : 'attack'
|
||||
return
|
||||
}
|
||||
const f = fires.find((f2) => Math.hypot(f2.x - wxp, f2.y - wyp) < 36)
|
||||
if (f) {
|
||||
cursorMode.value = 'hand'
|
||||
return
|
||||
}
|
||||
let ent = null
|
||||
let ed = Infinity
|
||||
entities.forEach((e) => {
|
||||
if (e.deco && e.type !== 'stump') return
|
||||
if (e.stub || e.solid) return
|
||||
const d = Math.hypot(e.x - wxp, e.y - wyp)
|
||||
if (d < Math.max(26, e.size * 0.9) && d < ed) { ed = d; ent = e }
|
||||
})
|
||||
if (ent) {
|
||||
const attackable = ent.hp > 0 && (ent.mobile || ent.nest || ent.lurk)
|
||||
cursorMode.value = attackable ? 'attack' : 'hand'
|
||||
return
|
||||
}
|
||||
cursorMode.value = 'normal'
|
||||
}
|
||||
function onCanvasLeave() {
|
||||
cursorMode.value = 'normal'
|
||||
}
|
||||
// 设置本地目标:客机同时上报主机
|
||||
function setMyTarget(t) {
|
||||
player.target = resolveTarget(t)
|
||||
@@ -1388,6 +1433,9 @@ function setPaused(v) {
|
||||
function openManualFromGame() {
|
||||
window.dispatchEvent(new CustomEvent('nlg:open-manual'))
|
||||
}
|
||||
function openSettingsFromGame() {
|
||||
window.dispatchEvent(new CustomEvent('nlg:open-settings'))
|
||||
}
|
||||
function togglePause() {
|
||||
setPaused(!paused.value)
|
||||
}
|
||||
@@ -2361,7 +2409,8 @@ function draw() {
|
||||
}
|
||||
}
|
||||
ctx.restore()
|
||||
drawClock()
|
||||
drawClockV2()
|
||||
if (showFullMap.value) drawFullMap()
|
||||
}
|
||||
function drawEntWrap(e, env) {
|
||||
const sx = e.x - camX
|
||||
@@ -2535,8 +2584,242 @@ function drawNight(W, H) {
|
||||
})
|
||||
ctx.drawImage(layer, 0, 0)
|
||||
}
|
||||
// 无限接近原版:外木圈 + 三段昼夜盘 + 中心羊皮纸日数盘 + 黑针从中心指向边缘
|
||||
function drawClockV2() {
|
||||
const c = clockCv.value?.getContext('2d')
|
||||
if (!c) return
|
||||
const S = 112
|
||||
c.clearRect(0, 0, S, S)
|
||||
const cx2 = S / 2
|
||||
const cy2 = S / 2
|
||||
const R = S / 2 - 5
|
||||
const [duskAt, nightAt] = daySplit(seasonNow.code)
|
||||
|
||||
// 外层木圈
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, R, 0, 7)
|
||||
c.fillStyle = '#6b4a2c'
|
||||
c.fill()
|
||||
c.strokeStyle = '#1d1409'
|
||||
c.lineWidth = 3
|
||||
c.stroke()
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, R * 0.94, 0, 7)
|
||||
c.strokeStyle = '#8a6238'
|
||||
c.lineWidth = 2
|
||||
c.stroke()
|
||||
|
||||
// 昼夜三段扇区
|
||||
const segs = [
|
||||
[0, duskAt / DAY_LEN, '#f0c94a'],
|
||||
[duskAt / DAY_LEN, nightAt / DAY_LEN, '#d88a3e'],
|
||||
[nightAt / DAY_LEN, 1, '#2a2742'],
|
||||
]
|
||||
segs.forEach(([a, b, col]) => {
|
||||
c.beginPath()
|
||||
c.moveTo(cx2, cy2)
|
||||
c.arc(cx2, cy2, R * 0.82, -Math.PI / 2 + a * Math.PI * 2, -Math.PI / 2 + b * Math.PI * 2)
|
||||
c.closePath()
|
||||
c.fillStyle = col
|
||||
c.fill()
|
||||
c.strokeStyle = 'rgba(20,14,8,0.75)'
|
||||
c.lineWidth = 1.4
|
||||
c.stroke()
|
||||
})
|
||||
|
||||
// 外圈 16 段刻度
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const a = -Math.PI / 2 + (i / 16) * Math.PI * 2
|
||||
const long = i % 4 === 0
|
||||
const r1 = R * 0.94
|
||||
const r2 = long ? R * 0.82 : R * 0.88
|
||||
c.beginPath()
|
||||
c.moveTo(cx2 + Math.cos(a) * r1, cy2 + Math.sin(a) * r1)
|
||||
c.lineTo(cx2 + Math.cos(a) * r2, cy2 + Math.sin(a) * r2)
|
||||
c.strokeStyle = 'rgba(20,14,8,0.9)'
|
||||
c.lineWidth = long ? 2.2 : 1.1
|
||||
c.stroke()
|
||||
}
|
||||
|
||||
// 中心羊皮纸盘
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, R * 0.46, 0, 7)
|
||||
c.fillStyle = '#eadfc4'
|
||||
c.fill()
|
||||
c.strokeStyle = '#3a2a17'
|
||||
c.lineWidth = 2
|
||||
c.stroke()
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, R * 0.46, 0, 7)
|
||||
c.strokeStyle = seasonTag.value.tint
|
||||
c.lineWidth = 2
|
||||
c.stroke()
|
||||
c.fillStyle = '#2b1d10'
|
||||
c.font = 'bold 16px "Ma Shan Zheng", serif'
|
||||
c.textAlign = 'center'
|
||||
c.textBaseline = 'middle'
|
||||
c.fillText(`${day.value}`, cx2, cy2 - 6)
|
||||
c.font = 'bold 11px "Ma Shan Zheng", serif'
|
||||
c.fillStyle = seasonTag.value.tint
|
||||
c.fillText(`${seasonTag.value.name} ${seasonTag.value.dayIn}/${seasonTag.value.len}`, cx2, cy2 + 10)
|
||||
|
||||
// 原版黑针:从中心向外,尖端略过中心盘与外圈之间
|
||||
const ang = -Math.PI / 2 + (dayTime / DAY_LEN) * Math.PI * 2
|
||||
c.save()
|
||||
c.translate(cx2, cy2)
|
||||
c.rotate(ang)
|
||||
c.beginPath()
|
||||
c.moveTo(0, -2.6)
|
||||
c.lineTo(R * 0.92, -0.8)
|
||||
c.lineTo(R * 0.92, 0.8)
|
||||
c.lineTo(0, 2.6)
|
||||
c.closePath()
|
||||
c.fillStyle = '#17120b'
|
||||
c.fill()
|
||||
c.strokeStyle = '#f4ead2'
|
||||
c.lineWidth = 1.4
|
||||
c.stroke()
|
||||
// 指针尖端圆点
|
||||
const tipIcon = dayTime >= nightAt || dayTime < 0 ? '#c9d4e8' : '#f0c94a'
|
||||
c.beginPath()
|
||||
c.arc(R * 0.92, 0, 2.8, 0, 7)
|
||||
c.fillStyle = tipIcon
|
||||
c.fill()
|
||||
c.strokeStyle = '#17120b'
|
||||
c.lineWidth = 1.2
|
||||
c.stroke()
|
||||
c.restore()
|
||||
|
||||
// 中心铜轴
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, 4, 0, 7)
|
||||
c.fillStyle = '#b89a5c'
|
||||
c.fill()
|
||||
c.strokeStyle = '#1d1409'
|
||||
c.lineWidth = 1.4
|
||||
c.stroke()
|
||||
}
|
||||
// 原版风格罗盘时钟:外圈木纹时刻环 + 昼夜扇区 + 中心天数季节盘 + 日月指针
|
||||
function drawClockV2Legacy() {
|
||||
const c = clockCv.value?.getContext('2d')
|
||||
if (!c) return
|
||||
const S = 104
|
||||
c.clearRect(0, 0, S, S)
|
||||
const cx2 = S / 2
|
||||
const cy2 = S / 2
|
||||
const R = S / 2 - 6
|
||||
const [duskAt, nightAt] = daySplit(seasonNow.code)
|
||||
|
||||
// 外圈木纹底
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, R, 0, 7)
|
||||
c.fillStyle = '#4a341f'
|
||||
c.fill()
|
||||
c.strokeStyle = '#1d1409'
|
||||
c.lineWidth = 3
|
||||
c.stroke()
|
||||
// 木纹环线
|
||||
c.strokeStyle = 'rgba(31,20,9,0.55)'
|
||||
c.lineWidth = 1.4
|
||||
;[0.82, 0.9, 0.96].forEach((k) => {
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, R * k, 0, 7)
|
||||
c.stroke()
|
||||
})
|
||||
|
||||
// 昼夜扇区(黄 / 橙 / 深蓝)
|
||||
const segs = [
|
||||
[0, duskAt / DAY_LEN, '#e8bc5a'],
|
||||
[duskAt / DAY_LEN, nightAt / DAY_LEN, '#c97b3a'],
|
||||
[nightAt / DAY_LEN, 1, '#25223d'],
|
||||
]
|
||||
segs.forEach(([a, b, col]) => {
|
||||
c.beginPath()
|
||||
c.moveTo(cx2, cy2)
|
||||
c.arc(cx2, cy2, R * 0.78, -Math.PI / 2 + a * Math.PI * 2, -Math.PI / 2 + b * Math.PI * 2)
|
||||
c.closePath()
|
||||
c.fillStyle = col
|
||||
c.fill()
|
||||
c.strokeStyle = 'rgba(20,14,8,0.65)'
|
||||
c.lineWidth = 1.2
|
||||
c.stroke()
|
||||
})
|
||||
|
||||
// 16 段时刻刻度:整点长刻度
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const a = -Math.PI / 2 + (i / 16) * Math.PI * 2
|
||||
const long = i % 4 === 0
|
||||
const r1 = R * 0.96
|
||||
const r2 = long ? R * 0.82 : R * 0.88
|
||||
c.beginPath()
|
||||
c.moveTo(cx2 + Math.cos(a) * r1, cy2 + Math.sin(a) * r1)
|
||||
c.lineTo(cx2 + Math.cos(a) * r2, cy2 + Math.sin(a) * r2)
|
||||
c.strokeStyle = 'rgba(20,14,8,0.85)'
|
||||
c.lineWidth = long ? 2 : 1
|
||||
c.stroke()
|
||||
}
|
||||
|
||||
// 指针:当前时刻,针头挂太阳/月亮
|
||||
const ang = -Math.PI / 2 + (dayTime / DAY_LEN) * Math.PI * 2
|
||||
c.save()
|
||||
c.translate(cx2, cy2)
|
||||
c.rotate(ang)
|
||||
c.strokeStyle = '#f4ead2'
|
||||
c.lineWidth = 3
|
||||
c.beginPath()
|
||||
c.moveTo(0, 0)
|
||||
c.lineTo(R * 0.86, 0)
|
||||
c.stroke()
|
||||
const icon = dayTime >= nightAt || dayTime < 0 ? 'moon' : 'sun'
|
||||
c.fillStyle = icon === 'sun' ? '#f0c04a' : '#dfe8f0'
|
||||
c.strokeStyle = '#1d1409'
|
||||
c.lineWidth = 1.4
|
||||
if (icon === 'sun') {
|
||||
c.beginPath(); c.arc(R * 0.74, 0, 5, 0, 7); c.fill(); c.stroke()
|
||||
} else {
|
||||
c.beginPath(); c.arc(R * 0.74, 0, 5, 0, 7); c.fill(); c.stroke()
|
||||
c.fillStyle = '#25223d'
|
||||
c.beginPath(); c.arc(R * 0.76, 1, 3.4, 0, 7); c.fill()
|
||||
}
|
||||
c.restore()
|
||||
// 中心铜轴,原版罗盘指针的固定点
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, 4.4, 0, 7)
|
||||
c.fillStyle = '#b89a5c'
|
||||
c.fill()
|
||||
c.strokeStyle = '#1d1409'
|
||||
c.lineWidth = 1.4
|
||||
c.stroke()
|
||||
|
||||
// 中心季节盘
|
||||
c.beginPath()
|
||||
c.arc(cx2, cy2, R * 0.5, 0, 7)
|
||||
c.fillStyle = '#f4ead2'
|
||||
c.fill()
|
||||
c.strokeStyle = seasonTag.value.tint
|
||||
c.lineWidth = 3
|
||||
c.stroke()
|
||||
c.fillStyle = '#3a2d18'
|
||||
c.font = 'bold 16px "Ma Shan Zheng", serif'
|
||||
c.textAlign = 'center'
|
||||
c.textBaseline = 'middle'
|
||||
c.fillText(`${day.value}`, cx2, cy2 - 7)
|
||||
c.font = 'bold 11px "Ma Shan Zheng", serif'
|
||||
c.fillStyle = seasonTag.value.tint
|
||||
c.fillText(`${seasonTag.value.name} ${seasonTag.value.dayIn}/${seasonTag.value.len}`, cx2, cy2 + 10)
|
||||
|
||||
// 顶部小提环
|
||||
c.strokeStyle = '#3a2a17'
|
||||
c.lineWidth = 3
|
||||
c.beginPath()
|
||||
c.moveTo(cx2 - 6, cy2 - R)
|
||||
c.lineTo(cx2 - 4, cy2 - R - 7)
|
||||
c.lineTo(cx2 + 4, cy2 - R - 7)
|
||||
c.lineTo(cx2 + 6, cy2 - R)
|
||||
c.stroke()
|
||||
}
|
||||
// 右上角昼夜时钟(16 段刻度 + 季节配色与标签)
|
||||
function drawClock() {
|
||||
function drawClockLegacy() {
|
||||
const c = clockCv.value?.getContext('2d')
|
||||
if (!c) return
|
||||
const S = 92
|
||||
@@ -2600,10 +2883,11 @@ function drawClock() {
|
||||
c.stroke()
|
||||
}
|
||||
// 小地图(2Hz 更新):群系底色 + 战争迷雾 + 标记
|
||||
function drawMinimap() {
|
||||
const c = mapCv.value?.getContext('2d')
|
||||
if (!c || !tiles || !explored) return
|
||||
const s = 5
|
||||
function drawMapTo(cv, s) {
|
||||
if (!cv || !tiles || !explored) return
|
||||
const c = cv.getContext('2d')
|
||||
// s 由调用方传入
|
||||
// s 为像素缩放
|
||||
c.fillStyle = '#0b0906'
|
||||
c.fillRect(0, 0, GW * s, GH * s)
|
||||
for (let ty = 0; ty < GH; ty++) {
|
||||
@@ -2627,7 +2911,33 @@ function drawMinimap() {
|
||||
if (e.struct && explored[tileIdx(e.x, e.y)]) dot(e.x, e.y, e.type === 'pighouse' ? '#e8a8a0' : '#7fd8f0', 2)
|
||||
})
|
||||
monsters.forEach((m) => { if (m.kind === 'deerclops') dot(m.x, m.y, '#e04840', 3.4) })
|
||||
players.forEach((p) => { if (!p.dead || coop) dot(p.x, p.y, p === player ? '#fff' : '#ffe89a', p === player ? 3 : 2.4) })
|
||||
players.forEach((p) => {
|
||||
if (!p.dead || coop) {
|
||||
const mx = (p.x / TILE) * s
|
||||
const my = (p.y / TILE) * s
|
||||
const r = p === player ? (s >= 8 ? 7 : 4.5) : (s >= 8 ? 5.5 : 3.2)
|
||||
c.fillStyle = p === player ? '#ffffff' : '#ffe89a'
|
||||
c.strokeStyle = '#1d1409'
|
||||
c.lineWidth = s >= 8 ? 3 : 1.5
|
||||
c.beginPath()
|
||||
c.arc(mx, my, r, 0, 7)
|
||||
c.fill()
|
||||
c.stroke()
|
||||
if (p === player && s >= 8) {
|
||||
c.strokeStyle = 'rgba(255,255,255,0.7)'
|
||||
c.lineWidth = 2
|
||||
c.beginPath()
|
||||
c.arc(mx, my, r + 5 + Math.sin(time * 4) * 2, 0, 7)
|
||||
c.stroke()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
function drawMinimap() {
|
||||
drawMapTo(mapCv.value, 3)
|
||||
}
|
||||
function drawFullMap() {
|
||||
drawMapTo(fullMapCv.value, 12)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
@@ -2911,8 +3221,8 @@ function freshClass(f) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="starve-wrap">
|
||||
<canvas ref="canvas" class="starve-canvas" @click="onCanvasClick"></canvas>
|
||||
<div class="starve-wrap" :class="'cur-' + cursorMode">
|
||||
<canvas ref="canvas" class="starve-canvas" @click="onCanvasClick" @mousemove="updateCursor" @mouseleave="onCanvasLeave"></canvas>
|
||||
|
||||
<!-- 手机摇杆(设置里可切换) -->
|
||||
<div
|
||||
@@ -2941,7 +3251,7 @@ function freshClass(f) {
|
||||
|
||||
<!-- 右上:时钟 + 三围 + 体温 -->
|
||||
<div v-show="playing || dead" class="hud-right">
|
||||
<canvas ref="clockCv" width="92" height="92" class="clock"></canvas>
|
||||
<canvas ref="clockCv" width="112" height="112" class="clock"></canvas>
|
||||
<div class="badges">
|
||||
<div class="badge" :class="{ low: hp < 40 }" title="生命">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 21C7 16.5 3 13 3 8.8 3 5.9 5.2 4 7.7 4c1.7 0 3.3.9 4.3 2.4C13 4.9 14.6 4 16.3 4 18.8 4 21 5.9 21 8.8c0 4.2-4 7.7-9 12.2z" fill="#d8524e" stroke="#1d1409" stroke-width="1.6"/></svg>
|
||||
@@ -2964,9 +3274,10 @@ function freshClass(f) {
|
||||
|
||||
<!-- 左上:存档 / 地图开关 / mod 标签 -->
|
||||
<div v-show="playing" class="hud-left">
|
||||
<button v-if="!isCoop" class="ds-btn" :disabled="saving" @click="doSave(true)">{{ saving ? '已保存' : '保存' }}</button>
|
||||
<button class="ds-btn" @click="togglePause">{{ paused ? '继续' : '暂停 P' }}</button>
|
||||
<button class="ds-btn" @click="showMap = !showMap; showMap && drawMinimap()">地图 M</button>
|
||||
<button v-if="!isCoop" class="ds-btn icon-btn" :disabled="saving" title="保存" @click="doSave(true)">{{ saving ? '✓' : '💾' }}</button>
|
||||
<button class="ds-btn icon-btn" :title="paused ? '继续' : '暂停'" @click="togglePause">{{ paused ? '▶' : 'Ⅱ' }}</button>
|
||||
<button class="ds-btn icon-btn" title="设置" @click="openSettingsFromGame">⚙</button>
|
||||
<button class="ds-btn icon-btn" title="地图 M" @click="showMap = !showMap; showMap && drawMinimap()">🗺</button>
|
||||
<span v-if="uiMods.length" class="mod-tags" :title="uiMods.map((m) => m.name).join('、')">
|
||||
Mod×{{ uiMods.length }}
|
||||
</span>
|
||||
@@ -2974,10 +3285,23 @@ function freshClass(f) {
|
||||
|
||||
<!-- 小地图 -->
|
||||
<div v-show="playing && showMap" class="ds-map">
|
||||
<canvas ref="mapCv" width="200" height="150"></canvas>
|
||||
<canvas ref="mapCv" width="192" height="144"></canvas>
|
||||
<button class="ds-btn" @click="showFullMap = true; drawFullMap()">全屏地图</button>
|
||||
<span class="map-note">战争迷雾随探索揭开</span>
|
||||
</div>
|
||||
|
||||
<!-- 全屏地图 -->
|
||||
<div v-if="showFullMap" class="full-map-overlay">
|
||||
<div class="full-map-card">
|
||||
<div class="full-map-head">
|
||||
<b>世界地图</b>
|
||||
<button class="ds-btn" @click="showFullMap = false">关闭</button>
|
||||
</div>
|
||||
<canvas ref="fullMapCv" width="768" height="576"></canvas>
|
||||
<span class="map-note">玩家为白色亮点 · Boss 为红色</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 幽灵横幅 -->
|
||||
<div v-if="ghostMode && playing" class="ghost-banner">你变成了幽灵……在队友全灭前一直旁观</div>
|
||||
|
||||
@@ -3231,8 +3555,17 @@ function freshClass(f) {
|
||||
}
|
||||
.ds-map canvas {
|
||||
border-radius: 6px;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: pixelated;
|
||||
width: 192px;
|
||||
height: 144px;
|
||||
max-width: 80vw;
|
||||
width: 360px;
|
||||
height: 270px;
|
||||
max-width: 80vw;
|
||||
}
|
||||
width: 360px;
|
||||
height: 270px;
|
||||
max-width: 80vw;
|
||||
.map-note {
|
||||
color: #b8a888;
|
||||
font-size: 11px;
|
||||
@@ -3668,4 +4001,145 @@ function freshClass(f) {
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ---- 饥荒主题控件强化 ---- */
|
||||
.ds-btn {
|
||||
font-family: "Ma Shan Zheng", "KaiTi", serif;
|
||||
letter-spacing: 1px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255,255,255,0.09), transparent 42%),
|
||||
linear-gradient(180deg, #5a4327, #2b1d10);
|
||||
border: 2px solid #b89a5c;
|
||||
box-shadow: inset 0 0 0 2px #1d1409, 0 3px 0 #0d0906;
|
||||
color: #f4ead2;
|
||||
text-shadow: 1px 1px 0 #0d0906;
|
||||
}
|
||||
.ds-btn:hover {
|
||||
border-color: #f0c04a;
|
||||
color: #fff2c0;
|
||||
}
|
||||
.pause-card,
|
||||
.craft-panel,
|
||||
.pot-panel,
|
||||
.ds-map {
|
||||
background:
|
||||
radial-gradient(circle at 12% 10%, rgba(232, 188, 90, 0.14), transparent 34%),
|
||||
repeating-linear-gradient(90deg, rgba(0,0,0,0.06) 0 2px, transparent 2px 14px),
|
||||
linear-gradient(180deg, #33261a, #17120b);
|
||||
border: 2px solid #b89a5c;
|
||||
box-shadow: inset 0 0 0 4px #241a10, 0 12px 36px rgba(0, 0, 0, 0.55);
|
||||
color: #f4ead2;
|
||||
}
|
||||
.pause-card h3 {
|
||||
font-family: "Ma Shan Zheng", "KaiTi", serif;
|
||||
letter-spacing: 2px;
|
||||
color: #f4d68a;
|
||||
text-shadow: 2px 2px 0 #17120b;
|
||||
}
|
||||
.badge {
|
||||
background: linear-gradient(180deg, #33261a, #17120b);
|
||||
border-color: #6f5b38;
|
||||
box-shadow: inset 0 0 0 2px #1d1409;
|
||||
}
|
||||
.hud-left .ds-btn {
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.starve-wrap .ds-map canvas {
|
||||
width: 192px !important;
|
||||
height: 144px !important;
|
||||
}
|
||||
.full-map-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(8, 5, 4, 0.78);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
.full-map-card {
|
||||
background: linear-gradient(180deg, #33261a, #17120b);
|
||||
border: 2px solid #b89a5c;
|
||||
box-shadow: inset 0 0 0 4px #241a10, 0 14px 44px rgba(0, 0, 0, 0.6);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.full-map-head {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
color: #f4ead2;
|
||||
}
|
||||
.full-map-card canvas {
|
||||
image-rendering: pixelated;
|
||||
max-width: min(768px, 92vw);
|
||||
max-height: 72vh;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.icon-btn {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
min-width: 34px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0;
|
||||
font-size: 16px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.craft-tab {
|
||||
border-radius: 0;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.craft-tab span {
|
||||
display: none;
|
||||
}
|
||||
.craft-tab:hover span,
|
||||
.craft-tab.on span {
|
||||
display: inline;
|
||||
}
|
||||
.craft-item {
|
||||
border-radius: 0;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
/* 原版风格鼠标:米白色手套指针 */
|
||||
.starve-wrap,
|
||||
.starve-wrap .starve-canvas,
|
||||
.starve-wrap .ds-btn,
|
||||
.starve-wrap .slot,
|
||||
.starve-wrap .pause-card button {
|
||||
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cpath d='M5 2v10l-3 2v6c0 4.2 3.3 7 7.6 7h2.6c5 0 8.2-3.3 8.2-7.4V9l-3.6-2-2.6-2.4-3.2-3z' fill='%23f4ead2' stroke='%231d1409' stroke-width='2'/%3E%3Cpath d='M11 8v9M15 9v9' stroke='%231d1409' stroke-width='1.6'/%3E%3C/svg%3E") 4 4, auto;
|
||||
}
|
||||
|
||||
/* 攻击光标:短剑 */
|
||||
.starve-wrap.cur-attack .starve-canvas {
|
||||
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cpath d='M4 3L22 21l-2 2L2 5z' fill='%23b0b4bc' stroke='%231d1409' stroke-width='1.8'/%3E%3Cpath d='M5 21l-3-3 6-6 3 3z' fill='%237a5b33' stroke='%231d1409' stroke-width='1.4'/%3E%3C/svg%3E") 4 4, crosshair;
|
||||
}
|
||||
/* 采集/交互光标:张开的手 */
|
||||
.starve-wrap.cur-hand .starve-canvas {
|
||||
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cpath d='M4 10v4c0 7 4 11 10 11 5 0 8-3 8-7V9l-2 1-2-2-2-1-2-2-2-2v7z' fill='%23f4ead2' stroke='%231d1409' stroke-width='1.8'/%3E%3Cpath d='M8 12v7M12 10v9M16 11v9M20 13v6' stroke='%231d1409' stroke-width='1.4'/%3E%3C/svg%3E") 4 4, pointer;
|
||||
}
|
||||
|
||||
/* 更精细的原版手套指针 */
|
||||
.starve-wrap.cur-normal .starve-canvas {
|
||||
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg%3E%3Cpath d='M11 2C8.8 2 7 3.8 7 6v9l-3.6 2v7.2c0 4.6 3.2 7.8 7.8 7.8h3.2c4.8 0 7.6-2.9 7.6-7.4V10.2l-3.8-2-3-2.2-2.6-2.4L11 2z' fill='%23f4ead2' stroke='%231d1409' stroke-width='2.2' stroke-linejoin='round'/%3E%3Cpath d='M8 8v8M12 7v9M15 8v9M18 10v8' stroke='%231d1409' stroke-width='1.6' stroke-linecap='round'/%3E%3Cpath d='M10 4c1.4.4 2.4 1.4 2.8 3l-3.2 1.6C8.6 7 9 5 10 4z' fill='%23ffffff' opacity='.45'/%3E%3Cpath d='M7.5 16.5c1.5.7 3 .8 4.4.3' stroke='%23ffffff' stroke-width='1.4' opacity='.4' fill='none' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E") 4 4, auto;
|
||||
}
|
||||
/* 更精细的攻击短剑指针 */
|
||||
.starve-wrap.cur-attack .starve-canvas {
|
||||
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg%3E%3Cpath d='M4 3L23 22l-2 2L2 5z' fill='%23b8c0cc' stroke='%231d1409' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M4 3L23 22' stroke='%23ffffff' stroke-width='1.4' opacity='.6'/%3E%3Cpath d='M20 18l-3-3 4-4 4 4z' fill='%238a6238' stroke='%231d1409' stroke-width='1.8'/%3E%3Cpath d='M22 14l4 4-2 2-4-4z' fill='%236b4a28'/%3E%3Cpath d='M21 21l-2-2 2-2 3 3-2 2' fill='%23f0c04a' stroke='%231d1409' stroke-width='1.4'/%3E%3C/g%3E%3C/svg%3E") 4 4, crosshair;
|
||||
}
|
||||
/* 更精细的采集手掌指针 */
|
||||
.starve-wrap.cur-hand .starve-canvas {
|
||||
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg%3E%3Cpath d='M5 12v4c0 8 4 13 11 13 6 0 9-4 9-8V12l-2.4 1.2L19 11l-2.2-2-2-1.8-2-2.4-2-2.6v6l-2.6 1.4L5 12z' fill='%23f4ead2' stroke='%231d1409' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M9 14v8M13 12v10M17 13v10M21 16v7' stroke='%231d1409' stroke-width='1.6' stroke-linecap='round'/%3E%3Cpath d='M6 15c1.6.6 3 .5 4.4-.2' stroke='%23ffffff' stroke-width='1.4' opacity='.5' fill='none' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E") 4 4, pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,5 +10,6 @@ import magic from './magic.js'
|
||||
import chill from './chill.js'
|
||||
import traveler from './traveler.js'
|
||||
import lucky from './lucky.js'
|
||||
import loyaldog from './loyaldog.js'
|
||||
|
||||
export const MODS = [qol, farm, chest, chester, revive, peaceful, nightmare, magic, chill, traveler, lucky]
|
||||
export const MODS = [qol, farm, chest, chester, revive, peaceful, nightmare, magic, chill, traveler, lucky, loyaldog]
|
||||
|
||||
150
src/games/starve/mods/loyaldog.js
Normal file
150
src/games/starve/mods/loyaldog.js
Normal file
@@ -0,0 +1,150 @@
|
||||
// 最忠诚的伙伴:地图随机散落骨肉/肉,找到小狗并喂食即可结为永久伙伴
|
||||
const TAU = Math.PI * 2
|
||||
|
||||
function findFood(p) {
|
||||
const areas = [['main', p.slots], ['pack', p.pack]].filter(([, area]) => area)
|
||||
for (const [name, area] of areas) {
|
||||
for (let i = 0; i < area.length; i++) {
|
||||
const s = area[i]
|
||||
if (s && (s.code === 'bonemeat' || s.code === 'meat')) return { area: name, i }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export default {
|
||||
id: 'loyaldog',
|
||||
name: '最忠诚的伙伴',
|
||||
desc: '地图散落骨肉与肉,找到小狗喂食后,它会攻击你攻击的目标和保护你',
|
||||
factor: 1,
|
||||
tag: 'content',
|
||||
items: {
|
||||
bonemeat: { name: '骨肉', stack: 10, food: { hunger: 12.5, hp: 2, san: 0 }, meatv: 0.5 },
|
||||
},
|
||||
mobs: {
|
||||
dog: {
|
||||
name: '小狗',
|
||||
conf: { hp: 500, speed: 175, dmg: 26, score: 0, friendly: true },
|
||||
update(m, dt, g) {
|
||||
const conf = g.mon[m.kind] || this.conf
|
||||
if (m.hp <= 0) return
|
||||
const owner = m.owner && !m.owner.dead ? m.owner : null
|
||||
if (!owner) {
|
||||
// 流浪:原地短距游荡
|
||||
m.wanderT = (m.wanderT || 0) - dt
|
||||
if (m.wanderT <= 0) {
|
||||
m.wanderT = 2 + Math.random() * 3
|
||||
const a = Math.random() * TAU
|
||||
m.tx = m.x + Math.cos(a) * 90
|
||||
m.ty = m.y + Math.sin(a) * 90
|
||||
}
|
||||
if (m.tx !== undefined && Math.hypot(m.tx - m.x, m.ty - m.y) > 8) {
|
||||
g.moveToward(m, m.tx, m.ty, conf.speed * 0.45, dt)
|
||||
}
|
||||
return
|
||||
}
|
||||
// 目标:主人当前攻击目标,或靠近主人的敌对生物
|
||||
let target = null
|
||||
if (owner.target?.kind === 'mon' && g.monsters.includes(owner.target.ref)) {
|
||||
target = owner.target.ref
|
||||
}
|
||||
if (!target) {
|
||||
let bd = 190
|
||||
g.monsters.forEach((q) => {
|
||||
if (!g.mon[q.kind]?.hostile) return
|
||||
const d = Math.hypot(q.x - owner.x, q.y - owner.y)
|
||||
if (d < bd) { bd = d; target = q }
|
||||
})
|
||||
}
|
||||
if (target && !g.monsters.includes(target)) target = null
|
||||
if (target) {
|
||||
const d = Math.hypot(target.x - m.x, target.y - m.y)
|
||||
if (d > 26) {
|
||||
g.moveToward(m, target.x, target.y, conf.speed, dt)
|
||||
} else if ((m.cd || 0) <= 0) {
|
||||
m.cd = 1.1
|
||||
target.hp -= conf.dmg
|
||||
target.shake = 0.22
|
||||
if (target.hp <= 0) g.killMob(target, owner)
|
||||
}
|
||||
} else {
|
||||
const d = Math.hypot(owner.x - m.x, owner.y - m.y)
|
||||
if (d > 66) g.moveToward(m, owner.x, owner.y, conf.speed, dt)
|
||||
}
|
||||
},
|
||||
interact(p, m, g) {
|
||||
if (m.owner) {
|
||||
g.ptip(p, m.owner === p ? '小狗忠诚地跟在你身边' : '小狗已经有主人了')
|
||||
return
|
||||
}
|
||||
const food = findFood(p)
|
||||
if (!food) {
|
||||
g.ptip(p, '小狗闻了闻你,似乎在找骨肉或肉')
|
||||
return
|
||||
}
|
||||
g.consumeAt(p, food.area, food.i)
|
||||
m.owner = p
|
||||
m.loyalT = 999999
|
||||
m.state = 'loyal'
|
||||
m.hp = Math.max(m.hp, 500)
|
||||
g.swing(p, 'eat')
|
||||
g.ptip(p, '小狗吃下骨肉,成为了你最忠诚的伙伴!')
|
||||
},
|
||||
draw(ctx, m, time) {
|
||||
ctx.save()
|
||||
ctx.scale(m.dir || 1, 1)
|
||||
const hop = Math.sin(time * 8 + (m.bob || 0)) * (m.moving ? 2 : 0.6)
|
||||
// 身体
|
||||
ctx.fillStyle = '#6b4a2a'
|
||||
ctx.strokeStyle = '#1d1409'
|
||||
ctx.lineWidth = 2
|
||||
ctx.beginPath()
|
||||
ctx.ellipse(0, -13 + hop * 0.4, 11, 7, 0, 0, TAU)
|
||||
ctx.fill(); ctx.stroke()
|
||||
// 头
|
||||
ctx.beginPath()
|
||||
ctx.arc(11, -20 + hop * 0.6, 6, 0, TAU)
|
||||
ctx.fill(); ctx.stroke()
|
||||
// 耳
|
||||
ctx.fillStyle = '#4a2e16'
|
||||
ctx.beginPath(); ctx.moveTo(7, -24); ctx.lineTo(4, -32); ctx.lineTo(12, -25); ctx.closePath(); ctx.fill(); ctx.stroke()
|
||||
// 眼鼻
|
||||
ctx.fillStyle = '#f4ead2'
|
||||
ctx.beginPath(); ctx.arc(13, -21, 1.6, 0, TAU); ctx.fill()
|
||||
ctx.fillStyle = '#1d1409'
|
||||
ctx.beginPath(); ctx.arc(15.6, -19.5, 1.2, 0, TAU); ctx.fill()
|
||||
// 腿
|
||||
ctx.strokeStyle = '#4a2e16'
|
||||
ctx.lineWidth = 3
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(-5, -7); ctx.lineTo(-6 + Math.sin(time * 10) * 2, 1)
|
||||
ctx.moveTo(5, -7); ctx.lineTo(6 + Math.cos(time * 10) * 2, 1)
|
||||
ctx.stroke()
|
||||
// 忠诚标记
|
||||
if (m.owner) {
|
||||
ctx.fillStyle = '#e0485a'
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, -32 + hop)
|
||||
ctx.bezierCurveTo(-4, -36 + hop, -1, -39 + hop, 0, -36 + hop)
|
||||
ctx.bezierCurveTo(1, -39 + hop, 4, -36 + hop, 0, -32 + hop)
|
||||
ctx.fill()
|
||||
}
|
||||
ctx.restore()
|
||||
},
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
onWorldGen(g) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const pos = g.findLand(80 + Math.random() * (g.worldW - 160), 80 + Math.random() * (g.worldH - 160), 0, 20)
|
||||
g.addEntity('loot', pos.x, pos.y, { code: 'bonemeat', n: 1 })
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const pos = g.findLand(80 + Math.random() * (g.worldW - 160), 80 + Math.random() * (g.worldH - 160), 0, 20)
|
||||
g.addEntity('loot', pos.x, pos.y, { code: 'meat', n: 1 })
|
||||
}
|
||||
const dogPos = g.findLand(g.worldW / 2, g.worldH / 2, 260, 520)
|
||||
g.spawnMob('dog', dogPos.x, dogPos.y, {})
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -6,8 +6,8 @@ function randInt(min, max) {
|
||||
}
|
||||
|
||||
export const TILE = 80
|
||||
export const GW = 40
|
||||
export const GH = 30
|
||||
export const GW = 64
|
||||
export const GH = 48
|
||||
export const WORLD = { w: GW * TILE, h: GH * TILE } // 3200 x 2400
|
||||
|
||||
export const BIOMES = {
|
||||
|
||||
@@ -70,10 +70,30 @@ function saveTouchMode(mode) {
|
||||
localStorage.setItem('starve_touch_mode', mode)
|
||||
window.dispatchEvent(new CustomEvent('starve-touch-mode', { detail: mode }))
|
||||
}
|
||||
const isMobile = computed(() => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0))
|
||||
const isPortrait = ref(false)
|
||||
let portraitMq = null
|
||||
function updateOrientation() {
|
||||
isPortrait.value = isMobile.value && !!portraitMq?.matches
|
||||
}
|
||||
async function enterLandscape() {
|
||||
try {
|
||||
if (document.fullscreenElement) await document.exitFullscreen()
|
||||
await document.documentElement.requestFullscreen?.()
|
||||
} catch (e) {}
|
||||
try {
|
||||
await screen.orientation?.lock?.('landscape')
|
||||
} catch (e) {}
|
||||
updateOrientation()
|
||||
}
|
||||
function openManual() {
|
||||
showManual.value = true
|
||||
if (phase.value === 'playing') gameRef.value?.setPaused?.(true)
|
||||
}
|
||||
function toggleHeadNav() {
|
||||
headShow.value = !headShow.value
|
||||
window.dispatchEvent(new CustomEvent('nlg:starve-nav-toggle'))
|
||||
}
|
||||
function closeManual() {
|
||||
showManual.value = false
|
||||
if (phase.value === 'playing') gameRef.value?.setPaused?.(false)
|
||||
@@ -581,11 +601,17 @@ onMounted(() => {
|
||||
// 宽屏游戏(如饥荒):给 body 打标解除全局 1200px 限宽
|
||||
if (meta?.wide) document.body.classList.add('wide-page')
|
||||
load()
|
||||
portraitMq = window.matchMedia('(orientation: portrait)')
|
||||
window.addEventListener('nlg:open-settings', openSettings)
|
||||
portraitMq.addEventListener('change', updateOrientation)
|
||||
updateOrientation()
|
||||
window.addEventListener('nlg:open-manual', openManual)
|
||||
if (coopable) {
|
||||
window.addEventListener('nlg:pending-join', tryPendingJoin)
|
||||
tryPendingJoin()
|
||||
}
|
||||
portraitMq?.removeEventListener('change', updateOrientation)
|
||||
window.removeEventListener('nlg:open-settings', openSettings)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
document.body.classList.remove('wide-page')
|
||||
@@ -600,8 +626,8 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="detail" class="play-wrap" :class="{ 'play-fill': meta?.wide }">
|
||||
<button v-if="floatHead" class="head-toggle" @click="headShow = !headShow">
|
||||
<div v-if="detail" class="play-wrap" :class="{ 'play-fill': meta?.wide, 'starve-ui': code === 'starve' }">
|
||||
<button v-if="floatHead" class="head-toggle" @click="toggleHeadNav">
|
||||
{{ headShow ? '▲ 收起' : '☰ 年糕求生' }}
|
||||
</button>
|
||||
<!-- 顶部信息条 -->
|
||||
@@ -985,6 +1011,16 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="floatHead && isPortrait" class="landscape-tip">
|
||||
<div class="landscape-card">
|
||||
<div class="landscape-icon">📱↻</div>
|
||||
<h3>请横屏游玩</h3>
|
||||
<p class="text-dim">年糕求生建议横屏操作,点击按钮自动进入横屏。</p>
|
||||
<button class="btn" @click="enterLandscape">进入横屏</button>
|
||||
<button class="btn btn-ghost" @click="isPortrait = false">暂不</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未拥有提示 -->
|
||||
@@ -1800,4 +1836,172 @@ onBeforeUnmount(() => {
|
||||
color: var(--primary-2);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ---- 移动端横屏提示 ---- */
|
||||
.landscape-tip {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 400;
|
||||
background: rgba(8, 5, 4, 0.82);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
.landscape-card {
|
||||
background: linear-gradient(180deg, #2b2115, #17120b);
|
||||
border: 2px solid #b89a5c;
|
||||
border-radius: 18px;
|
||||
padding: 26px 30px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.landscape-icon {
|
||||
font-size: 54px;
|
||||
}
|
||||
/* ---- 年糕求生 UI:游戏内弹窗/面板统一手绘木纸风格 ---- */
|
||||
.play-wrap.starve-ui .modal-mask {
|
||||
background: rgba(10, 7, 4, 0.62);
|
||||
}
|
||||
.play-wrap.starve-ui .mod-modal,
|
||||
.play-wrap.starve-ui .modal-mask .mod-modal {
|
||||
background: linear-gradient(180deg, #2b2115, #17120b);
|
||||
border: 2px solid #b89a5c;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
|
||||
color: #f4ead2;
|
||||
}
|
||||
.play-wrap.starve-ui .btn-ghost {
|
||||
background: rgba(42, 32, 18, 0.9);
|
||||
border-color: #6f5b38;
|
||||
color: #f4ead2;
|
||||
}
|
||||
.play-wrap.starve-ui .play-head {
|
||||
background: linear-gradient(180deg, #2b2115, #17120b);
|
||||
border: 2px solid #b89a5c;
|
||||
}
|
||||
.play-wrap.starve-ui .stat-chip {
|
||||
background: rgba(42, 32, 18, 0.9);
|
||||
border-color: #6f5b38;
|
||||
}
|
||||
.play-wrap.starve-ui .head-toggle {
|
||||
background: #2b2115;
|
||||
border-color: #b89a5c;
|
||||
color: #f4ead2;
|
||||
}
|
||||
|
||||
.play-wrap.starve-ui .mod-card {
|
||||
background: rgba(42, 32, 18, 0.9);
|
||||
border-color: #6f5b38;
|
||||
}
|
||||
.play-wrap.starve-ui .mod-card:hover {
|
||||
border-color: #e8bc5a;
|
||||
}
|
||||
.play-wrap.starve-ui .btn {
|
||||
border-radius: 10px;
|
||||
border-color: #b89a5c;
|
||||
background: linear-gradient(180deg, #5a4327, #3a2b17);
|
||||
color: #f4ead2;
|
||||
}
|
||||
.play-wrap.starve-ui .mode-card {
|
||||
background: rgba(42, 32, 18, 0.92);
|
||||
border-color: #6f5b38;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.play-wrap.starve-ui .mode-card:hover {
|
||||
border-color: #e8bc5a;
|
||||
}
|
||||
.play-wrap.starve-ui .join-row,
|
||||
.play-wrap.starve-ui .overlay.room-entry .ov-btns {
|
||||
background: rgba(42, 32, 18, 0.9);
|
||||
border-color: #6f5b38;
|
||||
}
|
||||
.play-wrap.starve-ui .join-input {
|
||||
background: #17120b;
|
||||
border-color: #b89a5c;
|
||||
color: #f4ead2;
|
||||
}
|
||||
.play-wrap.starve-ui .mod-modal h3 {
|
||||
color: #f4ead2;
|
||||
}
|
||||
|
||||
|
||||
/* 更强的饥荒主题:羊皮纸/木箱/铜扣 */
|
||||
.play-wrap.starve-ui .mod-modal,
|
||||
.play-wrap.starve-ui .manual-modal,
|
||||
.play-wrap.starve-ui .settings-modal {
|
||||
background:
|
||||
radial-gradient(circle at 12% 10%, rgba(232, 188, 90, 0.16), transparent 32%),
|
||||
repeating-linear-gradient(90deg, rgba(0,0,0,0.06) 0 2px, transparent 2px 14px),
|
||||
linear-gradient(180deg, #33261a, #17120b);
|
||||
border: 2px solid #b89a5c;
|
||||
box-shadow:
|
||||
inset 0 0 0 4px #241a10,
|
||||
0 12px 40px rgba(0, 0, 0, 0.6);
|
||||
color: #f4ead2;
|
||||
}
|
||||
.play-wrap.starve-ui .mod-modal h3,
|
||||
.play-wrap.starve-ui .manual-modal h3,
|
||||
.play-wrap.starve-ui .settings-modal h3 {
|
||||
font-family: "Ma Shan Zheng", "KaiTi", serif;
|
||||
letter-spacing: 2px;
|
||||
color: #f4d68a;
|
||||
text-shadow: 2px 2px 0 #17120b;
|
||||
}
|
||||
.play-wrap.starve-ui .btn,
|
||||
.play-wrap.starve-ui .btn-ghost {
|
||||
position: relative;
|
||||
font-family: "Ma Shan Zheng", "KaiTi", serif;
|
||||
letter-spacing: 1px;
|
||||
border: 1.6px solid #b89a5c;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255,255,255,0.08), transparent 45%),
|
||||
linear-gradient(180deg, #5a4327, #2b1d10);
|
||||
box-shadow: inset 0 0 0 2px #1d1409, 0 3px 0 #0d0906;
|
||||
text-shadow: 1px 1px 0 #0d0906;
|
||||
}
|
||||
.play-wrap.starve-ui .btn:hover,
|
||||
.play-wrap.starve-ui .btn-ghost:hover {
|
||||
border-color: #f0c04a;
|
||||
color: #fff2c0;
|
||||
}
|
||||
.play-wrap.starve-ui .mode-card {
|
||||
background:
|
||||
radial-gradient(circle at 50% 0%, rgba(232, 188, 90, 0.18), transparent 45%),
|
||||
linear-gradient(180deg, #302213, #17120b);
|
||||
box-shadow: inset 0 0 0 3px #1d1409, 0 6px 16px rgba(0,0,0,0.35);
|
||||
font-family: "Ma Shan Zheng", "KaiTi", serif;
|
||||
}
|
||||
.play-wrap.starve-ui .overlay.room-entry .ov-btns,
|
||||
.play-wrap.starve-ui .join-row {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(232,188,90,0.08), transparent),
|
||||
linear-gradient(180deg, #302213, #17120b);
|
||||
box-shadow: inset 0 0 0 2px #1d1409;
|
||||
}
|
||||
.play-wrap.starve-ui .mod-modal,
|
||||
.play-wrap.starve-ui .manual-modal,
|
||||
.play-wrap.starve-ui .settings-modal,
|
||||
.play-wrap.starve-ui .mode-card,
|
||||
.play-wrap.starve-ui .mod-card,
|
||||
.play-wrap.starve-ui .join-row,
|
||||
.play-wrap.starve-ui .join-input {
|
||||
border-radius: 0;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.play-wrap.starve-ui .bm-close {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: 2px solid #b89a5c;
|
||||
background: #2b2115;
|
||||
color: #f4ead2;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
.play-wrap.starve-ui .mod-card-check {
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user