diff --git a/addons/yt_world/models/yt_world_build_item.py b/addons/yt_world/models/yt_world_build_item.py index 4024e75..26481ba 100644 --- a/addons/yt_world/models/yt_world_build_item.py +++ b/addons/yt_world/models/yt_world_build_item.py @@ -63,7 +63,7 @@ class YtWorldBuildItem(models.Model): # 设计最大等级(随素材制作进度迭代;由 data/yt_world_build_item_updates.xml 的 # 在每次模块升级时刷新——noupdate 记录无法用普通 record 覆盖) DESIGN_MAX_LEVELS = { - 'cottage': 2, # L2 素材已上线 + 'cottage': 4, # L4 素材已上线(米白灰泥墙+青灰石板瓦+金饰升级外观) } @api.model diff --git a/assets/buildings/spring/cottage/cottage_l3_l.png b/assets/buildings/spring/cottage/cottage_l3_l.png new file mode 100644 index 0000000..2bc6204 Binary files /dev/null and b/assets/buildings/spring/cottage/cottage_l3_l.png differ diff --git a/assets/buildings/spring/cottage/cottage_l3_r.png b/assets/buildings/spring/cottage/cottage_l3_r.png new file mode 100644 index 0000000..481c546 Binary files /dev/null and b/assets/buildings/spring/cottage/cottage_l3_r.png differ diff --git a/assets/buildings/spring/cottage/cottage_l4_l.png b/assets/buildings/spring/cottage/cottage_l4_l.png new file mode 100644 index 0000000..2a8199d Binary files /dev/null and b/assets/buildings/spring/cottage/cottage_l4_l.png differ diff --git a/assets/buildings/spring/cottage/cottage_l4_r.png b/assets/buildings/spring/cottage/cottage_l4_r.png new file mode 100644 index 0000000..e472fc9 Binary files /dev/null and b/assets/buildings/spring/cottage/cottage_l4_r.png differ diff --git a/world.html b/world.html index 6f44414..6af0223 100644 --- a/world.html +++ b/world.html @@ -2688,7 +2688,7 @@ const MICRO_BUILD_ITEMS = [ // 建筑 / 设施 目录(兜底:未登录或 Odoo 未提供时直接用此;登录后由 /yt_world/api/micro/catalog 覆盖)。 // flat=true 表示平面俯视,岛上压成菱形贴地;否则为 3/4 立绘,竖直站立。 const MICRO_BUILD_EXTRA = [ - { id: 'cottage', cat: 'build', icon: 'assets/buildings/spring/cottage/cottage_l.png?v=6', levels: { 2: 'assets/buildings/spring/cottage/cottage_l2_l.png?v=1' }, name: '农舍', desc: '村民起居的基本居所,提供人口上限与夜间休息。', flat: false, w: 2, h: 2, statLevel: 1, statHp: 120, statYield: { population: 4 }, costGold: 20 }, + { id: 'cottage', cat: 'build', icon: 'assets/buildings/spring/cottage/cottage_l.png?v=6', levels: { 2: 'assets/buildings/spring/cottage/cottage_l2_l.png?v=1', 3: 'assets/buildings/spring/cottage/cottage_l3_l.png?v=1', 4: 'assets/buildings/spring/cottage/cottage_l4_l.png?v=1' }, name: '农舍', desc: '村民起居的基本居所,提供人口上限与夜间休息。', flat: false, w: 2, h: 2, statLevel: 1, statHp: 120, statYield: { population: 4 }, costGold: 20 }, { id: 'windmill', cat: 'build', icon: 'assets/buildings/spring/windmill/windmill_l.png?v=5', name: '风车', desc: '利用风力磨粉与提水,提升粮食加工效率。', flat: false, w: 3, h: 3, statLevel: 1, statHp: 110, statYield: { flour: 2 }, costGold: 60 }, { id: 'blacksmith', cat: 'build', icon: 'assets/buildings/spring/blacksmith/blacksmith_l.png?v=5',name: '铁匠铺', desc: '锻造工具与武器,产出金属制品。', flat: false, w: 2, h: 2, statLevel: 1, statHp: 130, statYield: { metal: 2 }, costGold: 80 }, { id: 'grocery', cat: 'build', icon: 'assets/buildings/spring/grocery/grocery_l.png?v=5', name: '杂货铺', desc: '交易日用物资的小型商铺,促进经济流通。', flat: false, w: 2, h: 2, statLevel: 1, statHp: 90, statYield: { coin: 2 }, costGold: 50 }, @@ -3667,7 +3667,14 @@ function microDrawPlaced(ctx, b, it, cell, ew, eh) { const isRoad = it && it.cat === 'road'; const flat = !!(it && it.flat); const rot = (b && b.rot) || 0; - const lvl = (b && b.level) ? b.level : 1; + // 升级外观切换时序:数据等级(b.level)落库后立即更新,但画布"视觉等级"延迟到 + // 升级特效进入 complete 阶段才切换(UF.phase==='complete' 时按 UF.toLevel), + // 实现"先播升级动画、动画完成外观才变"。未就绪(visualLevel 为 null)时保持旧外观, + // 无特效(UF 为空)/已结束则直接用真实等级——避免异常分支卡在旧图。 + let lvl; + if (b && b.visualLevel != null) lvl = b.visualLevel; + else if (UF && UF.building === b && UF.phase === 'complete' && UF.toLevel) lvl = UF.toLevel; + else lvl = (b && b.level) ? b.level : 1; const lvlIcon = (it && it.levels && it.levels[lvl]) ? it.levels[lvl] : null; const baseIcon = (b && b.sub) || lvlIcon || (it && it.icon); const icon = (!isRoad && !flat) ? microIconForRot(baseIcon, rot) : baseIcon; @@ -6547,6 +6554,8 @@ function _ufPushRings(uf, n) { } function _ufEnterComplete(uf) { uf.phase = 'complete'; uf.t0 = _ufNow(); + // 升级动画进入"完成"这一刻:才把建筑视觉等级切到目标等级 → 外观此时才变 + if (uf.building) uf.building.visualLevel = uf.toLevel; uf.floats.push({ kind: 'level', text: 'Lv.' + uf.toLevel, color: '#ffffff', t0: _ufNow(), life: UPGRADE_FX.durations.complete, vy: -0.022 }); if (uf.reward) uf.floats.push({ kind: 'reward', text: uf.reward, color: UPGRADE_FX.colors.reward, t0: _ufNow(), life: UPGRADE_FX.durations.complete + 220, vy: -0.04 }); if (!_ufLowEnd()) _ufSpawnParticles(uf, UPGRADE_FX.particleCount, false); @@ -6802,6 +6811,10 @@ async function microBuildingAct(type, action, label) { const _was = microOpenBuilding.level; microOpenBuilding.level = res.level; microOpenBuilding.exp = res.exp; + // 视觉等级延迟切换:数据已落库升级,但画布外观先保持旧等级, + // 等升级特效进入 complete 阶段(microUpgradeFxDraw 自动推进)才换上新外观。 + // 特效被关闭(UF 为空)时无法承载动画,直接切到新等级降级为旧行为(不卡旧图)。 + microOpenBuilding.visualLevel = (UF ? _was : res.level); // 升级即时换肤:主动确保新等级贴图(含左右视角)已加载,不依赖进入微观时的预加载时机 const itU = microBuildItemById(microOpenBuilding.type); if (itU && itU.levels && itU.levels[res.level]) { @@ -8463,7 +8476,10 @@ async function flushBuildSave() { if (!microState) return; const t = microState.tile; const items = microBuildings[t.q + ',' + t.r] || []; - const res = await API.saveMicroBuild(t.q, t.r, items); + // 剥离运行时视觉等级字段(visualLevel):它只在升级动画期间临时存在, + // 不能持久化进存档,否则动画未播完就存档、重开后会卡在旧外观。后端只认真实 level。 + const clean = items.map(b => { const c = Object.assign({}, b); delete c.visualLevel; return c; }); + const res = await API.saveMicroBuild(t.q, t.r, clean); if (!res || !res.ok) { if (res && res.error === 'auth') { microToast('建造未保存:请先登录游戏(建造需登录 Odoo)');