From 847c9c76a44a06b5a1fe40f30069838a98e03c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Tue, 22 Sep 2026 19:35:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=89=E7=BA=A7=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/yt_world/__manifest__.py | 1 + addons/yt_world/controllers/world_api.py | 24 ++++++++++++- addons/yt_world/data/yt_world_build_item.xml | 1 + addons/yt_world/models/yt_world_build_item.py | 17 ++++++++- assets/game-api.js | 10 ++++++ world.html | 35 +++++++++++++++---- 6 files changed, 80 insertions(+), 8 deletions(-) diff --git a/addons/yt_world/__manifest__.py b/addons/yt_world/__manifest__.py index f172669..3cc80ad 100644 --- a/addons/yt_world/__manifest__.py +++ b/addons/yt_world/__manifest__.py @@ -13,6 +13,7 @@ 'data/codex_race.xml', 'data/codex_talent.xml', 'data/yt_world_build_item.xml', + 'data/yt_world_build_item_updates.xml', 'data/yt_world_equip_item.xml', 'data/yt_world_race.xml', 'data/yt_world_task.xml', diff --git a/addons/yt_world/controllers/world_api.py b/addons/yt_world/controllers/world_api.py index 495c5f3..b6ca1b3 100644 --- a/addons/yt_world/controllers/world_api.py +++ b/addons/yt_world/controllers/world_api.py @@ -201,7 +201,7 @@ class YtWorldApi(http.Controller): out = [] for r in items.read(['code', 'name', 'category', 'icon', 'footprint_w', 'footprint_h', 'cost_gold', 'terrains', 'desc', 'flat', - 'stat_level', 'stat_hp', 'stat_yield', 'stat_yield_instant', + 'stat_level', 'max_level', 'stat_hp', 'stat_yield', 'stat_yield_instant', 'stat_yield_tick', 'actions']): if r['category'] not in seen: seen.append(r['category']) @@ -227,6 +227,7 @@ class YtWorldApi(http.Controller): 'desc': r['desc'] or '', 'flat': bool(r['flat']), 'statLevel': r['stat_level'] or 1, + 'maxLevel': r['max_level'] or 1, 'statHp': r['stat_hp'] or 100, 'statYield': r['stat_yield'] or {}, 'statYieldInstant': instant, @@ -605,6 +606,19 @@ class YtWorldApi(http.Controller): ('market', 'upgrade'): {'cost': {'gold': 25}, 'msg': '集市升级申请已提交'}, } + @http.route('/yt_world/api/build/effects', type='http', auth='public', + methods=['GET', 'OPTIONS'], csrf=False) + def build_effects(self, **kw): + """建筑动作效果表(费用/产出真源),供前端弹窗标注费用。 + 返回 {"cottage:upgrade": {"cost": {"gold": 50}, "gain": {}}, ...}""" + effects = {} + for (code, action), eff in self.BUILD_EFFECTS.items(): + effects['%s:%s' % (code, action)] = { + 'cost': eff.get('cost') or {}, + 'gain': eff.get('gain') or {}, + } + return _json({'ok': True, 'effects': effects}) + def _require_user(self): """auth='public' 时手动校验登录态;未登录返回统一错误字典。 Odoo auth='public' 不会把 session uid 同步到 request.env.user, @@ -961,6 +975,12 @@ class YtWorldApi(http.Controller): items = list(tile.micro_world_id.build_items or []) for b in items: if b.get('type') == code and b.get('i') == i and b.get('j') == j: + # 满级守卫:最大等级真源 = yt.world.build.item.max_level + item = request.env['yt.world.build.item'].sudo().search( + [('code', '=', code)], limit=1) + max_lv = (item.max_level or 1) if item else 1 + if (b.get('level') or 1) >= max_lv: + return {'max_reached': True} b['level'] = (b.get('level') or 1) + 1 b['exp'] = (b.get('exp') or 0) + 50 tile.micro_world_id.sudo().write({'build_items': items}) @@ -1022,6 +1042,8 @@ class YtWorldApi(http.Controller): if q is None or r is None or i is None or j is None: return _json({'ok': False, 'error': 'bad_pos'}) upgrade_info = self._upgrade_building_instance(world, code, q, r, i, j) + if upgrade_info and upgrade_info.get('max_reached'): + return _json({'ok': False, 'error': 'max_level'}) if not upgrade_info: return _json({'ok': False, 'error': 'building_not_found'}) diff --git a/addons/yt_world/data/yt_world_build_item.xml b/addons/yt_world/data/yt_world_build_item.xml index d932d7d..cf161eb 100644 --- a/addons/yt_world/data/yt_world_build_item.xml +++ b/addons/yt_world/data/yt_world_build_item.xml @@ -68,6 +68,7 @@ 20 村民起居的基本居所,提供人口上限与夜间休息。 1120 + 2 ` : ''; + // 面板大图跟随实际等级:有等级贴图用等级贴图,否则回退目录图标 + const panelIcon = (it.levels && it.levels[b.level]) || it.icon; + const img = (typeof panelIcon === 'string') + ? `` : ''; const yld = (it.statYield && typeof it.statYield === 'object' && Object.keys(it.statYield).length) ? Object.entries(it.statYield) .map(([k, v]) => `
产出·${k}+${v}
`).join('') : ''; @@ -6488,9 +6501,19 @@ function showBuildModal(b) { const expPct = Math.max(0, Math.min(100, Math.round((b.exp % need) / need * 100))); const hpPct = Math.max(0, Math.min(100, Math.round(b.hp / (it.statHp || 100) * 100))); const hpColor = hpPct > 50 ? '#8bc34a' : hpPct > 25 ? '#ff9800' : '#f44336'; - const acts = (cfg.actions || []).map(a => - `
${a.icon} ${a.label}
${a.desc}
` - ).join(''); + const acts = (cfg.actions || []).filter(a => + // 满级隐藏升级按钮:b.level 已达 maxLevel(后端真源)时不再渲染 + !(a.action === 'upgrade' && b.level >= (it.maxLevel || 1)) + ).map(a => { + // 费用标注:优先用后端 catalog actions 自带的 cost;否则查效果表(key = "code:action") + const eff = MICRO_BUILD_EFFECTS[b.type + ':' + a.action] || {}; + const costMap = (a.cost && Object.keys(a.cost).length) ? a.cost : (eff.cost || {}); + const costEntries = Object.entries(costMap); + const costHtml = costEntries.length + ? `
费用:${costEntries.map(([k, v]) => (MICRO_RES_NAMES[k] || k) + ' ×' + v).join(' ')}
` + : ''; + return `
${a.icon} ${a.label}${costHtml}
${a.desc}
`; + }).join(''); document.getElementById('modalTitle').textContent = `${name} · Lv.${b.level}`; const body = document.getElementById('modalBody'); body.innerHTML = ` -- 2.34.1