Merge pull request 'feat: 角色增加铜币/银币/钻石字段并与金币在底部HUD横向展示' (#26) from why_ga into main
Reviewed-on: #26
This commit is contained in:
@@ -388,7 +388,7 @@ class YtWorldApi(http.Controller):
|
||||
return _json({
|
||||
'ok': True,
|
||||
'talent_points': char.talent_points or 0,
|
||||
'gold': char.gold or 0,
|
||||
**self._char_currency(char),
|
||||
'talents': self._serialize_char_talents(char),
|
||||
})
|
||||
|
||||
@@ -458,7 +458,7 @@ class YtWorldApi(http.Controller):
|
||||
'code': code,
|
||||
'level': 1,
|
||||
'talent_points': char.talent_points or 0,
|
||||
'gold': char.gold or 0,
|
||||
**self._char_currency(char),
|
||||
})
|
||||
|
||||
@http.route('/yt_world/api/char/talent/upgrade', type='http', auth='public',
|
||||
@@ -498,7 +498,7 @@ class YtWorldApi(http.Controller):
|
||||
'code': code,
|
||||
'level': res['level'],
|
||||
'talent_points': char.talent_points or 0,
|
||||
'gold': char.gold or 0,
|
||||
**self._char_currency(char),
|
||||
})
|
||||
|
||||
@http.route('/yt_world/api/char/talent/draw', type='http', auth='public',
|
||||
@@ -656,6 +656,15 @@ class YtWorldApi(http.Controller):
|
||||
'name': emp.name or char.name})
|
||||
return char
|
||||
|
||||
def _char_currency(self, char):
|
||||
"""角色货币字段统一序列化(金币/铜币/银币/钻石)。"""
|
||||
return {
|
||||
'gold': char.gold or 0,
|
||||
'copper': char.copper or 0,
|
||||
'silver': char.silver or 0,
|
||||
'diamond': char.diamond or 0,
|
||||
}
|
||||
|
||||
# ========== 任务系统序列化(get_tasks / task_progress 共用) ==========
|
||||
def _serialize_tasks(self, char):
|
||||
"""把任务定义 + 角色进度汇总成前端面板可直接渲染的结构。"""
|
||||
@@ -719,7 +728,7 @@ class YtWorldApi(http.Controller):
|
||||
'tasks': out,
|
||||
'char': {
|
||||
'name': char.name, 'level': char.level, 'exp': char.exp,
|
||||
'exp_max': char.exp_max, 'gold': char.gold,
|
||||
'exp_max': char.exp_max, **self._char_currency(char),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -750,7 +759,7 @@ class YtWorldApi(http.Controller):
|
||||
'tasks': out,
|
||||
'char': {
|
||||
'name': char.name, 'level': char.level, 'exp': char.exp,
|
||||
'exp_max': char.exp_max, 'gold': char.gold,
|
||||
'exp_max': char.exp_max, **self._char_currency(char),
|
||||
},
|
||||
'claimable': res.get('claimable', []),
|
||||
})
|
||||
@@ -779,7 +788,7 @@ class YtWorldApi(http.Controller):
|
||||
'already': res.get('already', False),
|
||||
'char': {
|
||||
'name': char.name, 'level': char.level, 'exp': char.exp,
|
||||
'exp_max': char.exp_max, 'gold': char.gold,
|
||||
'exp_max': char.exp_max, **self._char_currency(char),
|
||||
},
|
||||
'reward_exp': res.get('reward_exp', 0),
|
||||
'reward_res': res.get('reward_res', {}),
|
||||
@@ -799,7 +808,7 @@ class YtWorldApi(http.Controller):
|
||||
return _json({
|
||||
'ok': True,
|
||||
'name': char.name, 'level': char.level, 'exp': char.exp,
|
||||
'exp_max': char.exp_max or 100, 'gold': char.gold,
|
||||
'exp_max': char.exp_max or 100, **self._char_currency(char),
|
||||
'inventory': char.inventory or {},
|
||||
'equip': char.equip or {'weapon': None, 'armor': None, 'accessory': None},
|
||||
})
|
||||
@@ -817,7 +826,7 @@ class YtWorldApi(http.Controller):
|
||||
char = self._get_char(world)
|
||||
body = _post_body()
|
||||
vals = {}
|
||||
for f in ('name', 'level', 'exp', 'gold'):
|
||||
for f in ('name', 'level', 'exp', 'gold', 'copper', 'silver', 'diamond'):
|
||||
if f in body and body[f] is not None:
|
||||
vals[f] = body[f]
|
||||
if 'inventory' in body and body['inventory'] is not None:
|
||||
@@ -827,7 +836,8 @@ class YtWorldApi(http.Controller):
|
||||
char.sudo().write(vals)
|
||||
return _json({'ok': True, 'char': {
|
||||
'name': char.name, 'level': char.level, 'exp': char.exp,
|
||||
'gold': char.gold, 'inventory': char.inventory or {},
|
||||
**self._char_currency(char),
|
||||
'inventory': char.inventory or {},
|
||||
'equip': char.equip or {},
|
||||
}})
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ class YtWorldCharacter(models.Model):
|
||||
exp = fields.Integer(string='经验', default=0)
|
||||
exp_max = fields.Integer(string='升级所需经验', default=100)
|
||||
gold = fields.Integer(string='金币', default=100)
|
||||
copper = fields.Integer(string='铜币', default=0)
|
||||
silver = fields.Integer(string='银币', default=0)
|
||||
diamond = fields.Integer(string='钻石', default=0)
|
||||
talent_points = fields.Integer(string='天赋点', default=0,
|
||||
help='用于激活/升级天赋的专用点数')
|
||||
inventory = fields.Json(string='背包', default=dict)
|
||||
|
||||
@@ -83,6 +83,9 @@
|
||||
<field name="level"/>
|
||||
<field name="exp"/>
|
||||
<field name="gold"/>
|
||||
<field name="copper"/>
|
||||
<field name="silver"/>
|
||||
<field name="diamond"/>
|
||||
<field name="inventory_display"/>
|
||||
<field name="equip_display"/>
|
||||
</list>
|
||||
@@ -256,6 +259,9 @@
|
||||
<field name="level"/>
|
||||
<field name="exp"/>
|
||||
<field name="gold"/>
|
||||
<field name="copper"/>
|
||||
<field name="silver"/>
|
||||
<field name="diamond"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
@@ -283,6 +289,9 @@
|
||||
<field name="level"/>
|
||||
<field name="exp"/>
|
||||
<field name="gold"/>
|
||||
<field name="copper"/>
|
||||
<field name="silver"/>
|
||||
<field name="diamond"/>
|
||||
</group>
|
||||
<group string="背包">
|
||||
<field name="inventory_display" colspan="4" nolabel="1"
|
||||
|
||||
+7
-5
@@ -17,14 +17,16 @@ const _isDevPort = (port) =>
|
||||
(port === 80 && (location.port === '' || location.port === '80'));
|
||||
|
||||
const API_CONFIG = {
|
||||
// 本地开发一律跨域直连 Odoo 8069(python http.server 无代理能力);
|
||||
// 仅 8080 dev-server(自带反向代理到 Odoo)才走同源空 base。
|
||||
base: _isLocalDev()
|
||||
? ((_isDevPort(8080) || _isDevPort(8888)) ? '' : `http://${location.hostname}:8080`)
|
||||
: '',
|
||||
db: _isLocalDev() ? 'study' : 'game',
|
||||
? (_isDevPort(8080) ? '' : `http://${location.hostname}:8069`)
|
||||
: '',
|
||||
db: _isLocalDev() ? 'game_a' : 'game',
|
||||
};
|
||||
|
||||
// 世界(yt_world)所在库:本地 Odoo 钉 yt_game;线上游戏库名为 game。按域名自适应,git 同步不冲突。
|
||||
const WORLD_DB = _isLocalDev() ? 'yt_game' : 'game';
|
||||
// 世界(yt_world)所在库:与 API_CONFIG.db 保持一致(本地均为 game_a),避免跨库导致 auth 失败。
|
||||
const WORLD_DB = API_CONFIG.db;
|
||||
|
||||
// Odoo 后端基础 URL(世界 API 专用,兼容本地跨域 / 生产同域)
|
||||
const WORLD_BASE = API_CONFIG.base || '';
|
||||
|
||||
+72
-18
@@ -243,7 +243,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
/* 世界等级经验条(位于 bottomBar 内最左侧,加大加清晰) */
|
||||
#worldExpBarWrap{
|
||||
flex:0 0 auto; width:520px; height:100%;
|
||||
display:flex; flex-direction:column; justify-content:center; gap:7px;
|
||||
display:flex; flex-direction:column; justify-content:center; gap:6px;
|
||||
padding:0 16px 0 0; background:transparent; border:none;
|
||||
}
|
||||
.we-info{display:flex; align-items:center; gap:12px; flex-shrink:0; color:#e6f0fb;}
|
||||
@@ -253,6 +253,16 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
.we-pct{margin-left:2px; font-size:13px; font-weight:700; color:#c4b5fd; min-width:34px; text-align:right;}
|
||||
.we-bar{width:100%; height:14px; background:rgba(255,255,255,.10); border-radius:7px; overflow:hidden;}
|
||||
.we-fill{height:100%; width:0%; background:linear-gradient(90deg,#8b5cf6,#4af); border-radius:7px; transition:width .3s ease; box-shadow:0 0 12px rgba(139,92,246,.45);}
|
||||
.we-currencies{display:flex; align-items:center; gap:8px; margin-left:auto; flex-shrink:0;}
|
||||
.wc-item{display:flex; align-items:center; gap:4px; font-size:12px; font-weight:700; color:#e6f0fb; background:rgba(255,255,255,.06); padding:3px 8px; border-radius:8px; border:1px solid rgba(255,255,255,.08); white-space:nowrap;}
|
||||
.wc-item b{font-variant-numeric:tabular-nums;}
|
||||
@media (max-width:768px){
|
||||
.we-currencies{gap:5px;}
|
||||
.wc-item{font-size:11px; padding:2px 6px;}
|
||||
}
|
||||
@media (max-width:420px){
|
||||
.we-currencies{display:none;}
|
||||
}
|
||||
|
||||
/* 主沙盘底部功能框架(预留区:放功能按钮 / 素材槽,比微观边框更高) */
|
||||
#bottomBar {
|
||||
@@ -1254,6 +1264,12 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
<div class="we-info">
|
||||
<span class="we-lv">世界 Lv.<b id="weLv">--</b></span>
|
||||
<span class="we-title" id="weTitle">--</span>
|
||||
<div class="we-currencies">
|
||||
<span class="wc-item" title="金币">🪙 <b id="wcGold">0</b></span>
|
||||
<span class="wc-item" title="铜币">🥉 <b id="wcCopper">0</b></span>
|
||||
<span class="wc-item" title="银币">🥈 <b id="wcSilver">0</b></span>
|
||||
<span class="wc-item" title="钻石">💎 <b id="wcDiamond">0</b></span>
|
||||
</div>
|
||||
<span class="we-exp-txt" id="weExpTxt">EXP -- / --</span>
|
||||
<span class="we-pct" id="weExpPct">--%</span>
|
||||
</div>
|
||||
@@ -2399,8 +2415,11 @@ async function microLoadGame() {
|
||||
try {
|
||||
const [c, cr] = await Promise.all([API.char(), API.creatures(), microLoadEquipCatalog()]);
|
||||
microChar = (c && c.ok !== false) ? c : (c || null);
|
||||
if (microChar && !microChar.equip) microChar.equip = { weapon: null, armor: null, accessory: null };
|
||||
if (microChar && !microChar.inventory) microChar.inventory = {};
|
||||
if (microChar) {
|
||||
if (!microChar.equip) microChar.equip = { weapon: null, armor: null, accessory: null };
|
||||
if (!microChar.inventory) microChar.inventory = {};
|
||||
['gold','copper','silver','diamond'].forEach(k => { if (microChar[k] == null) microChar[k] = 0; });
|
||||
}
|
||||
microCreatureOverlay = {};
|
||||
if (cr && cr.ok && Array.isArray(cr.creatures)) {
|
||||
cr.creatures.forEach(x => { if (x.cid) microCreatureOverlay[x.cid] = x; });
|
||||
@@ -2476,14 +2495,21 @@ function microRenderCharHud() {
|
||||
updateGoldEditorDisplay();
|
||||
}
|
||||
|
||||
// 顶部金币编辑器:测试阶段调试角色金币
|
||||
// 顶部金币编辑器 + 底部世界经验条货币展示(共用角色货币数据)
|
||||
function updateGoldEditorDisplay() {
|
||||
const editor = document.getElementById('goldEditor');
|
||||
const current = document.getElementById('goldCurrent');
|
||||
if (!editor || !current) return;
|
||||
if (!microChar) { editor.style.display = 'none'; return; }
|
||||
editor.style.display = 'inline-flex';
|
||||
current.textContent = Math.floor(microChar.gold || 0);
|
||||
if (editor) editor.style.display = microChar ? 'inline-flex' : 'none';
|
||||
if (current) current.textContent = Math.floor((microChar && microChar.gold) || 0);
|
||||
// 底部世界经验条:金币/铜币/银币/钻石
|
||||
const setWc = (id, val) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = Math.floor(val || 0);
|
||||
};
|
||||
setWc('wcGold', microChar ? microChar.gold : 0);
|
||||
setWc('wcCopper', microChar ? microChar.copper : 0);
|
||||
setWc('wcSilver', microChar ? microChar.silver : 0);
|
||||
setWc('wcDiamond', microChar ? microChar.diamond : 0);
|
||||
}
|
||||
async function changeGoldEditor(delta) {
|
||||
if (!microChar) { microToast('角色未加载,无法修改金币'); return; }
|
||||
@@ -2494,7 +2520,10 @@ async function changeGoldEditor(delta) {
|
||||
try {
|
||||
const r = await API.saveChar({ gold: next });
|
||||
if (r && r.ok && r.char) {
|
||||
microChar.gold = r.char.gold;
|
||||
microChar.gold = r.char.gold || 0;
|
||||
microChar.copper = r.char.copper || 0;
|
||||
microChar.silver = r.char.silver || 0;
|
||||
microChar.diamond = r.char.diamond || 0;
|
||||
updateGoldEditorDisplay();
|
||||
microRenderCharHud();
|
||||
microToast(`金币已调整为 🪙${microChar.gold}`);
|
||||
@@ -2536,8 +2565,11 @@ async function emitTaskEvent(event, payload) {
|
||||
taskData = { chapters: r.chapters, tasks: r.tasks, char: r.char };
|
||||
if (microChar && r.char) {
|
||||
microChar.level = r.char.level; microChar.exp = r.char.exp;
|
||||
microChar.exp_max = r.char.exp_max; microChar.gold = r.char.gold;
|
||||
microChar.exp_max = r.char.exp_max; microChar.gold = r.char.gold || 0;
|
||||
microChar.copper = r.char.copper || 0; microChar.silver = r.char.silver || 0;
|
||||
microChar.diamond = r.char.diamond || 0;
|
||||
microRenderCharHud();
|
||||
updateGoldEditorDisplay();
|
||||
}
|
||||
const body = document.getElementById('taskPanelBody');
|
||||
if (body) {
|
||||
@@ -2564,7 +2596,9 @@ async function fetchTasks() {
|
||||
if (!microChar) {
|
||||
microChar = {
|
||||
name: r.char.name, level: r.char.level, exp: r.char.exp,
|
||||
exp_max: r.char.exp_max, gold: r.char.gold,
|
||||
exp_max: r.char.exp_max, gold: r.char.gold || 0,
|
||||
copper: r.char.copper || 0, silver: r.char.silver || 0,
|
||||
diamond: r.char.diamond || 0,
|
||||
equip: { weapon: null, armor: null, accessory: null },
|
||||
inventory: {}
|
||||
};
|
||||
@@ -2573,7 +2607,10 @@ async function fetchTasks() {
|
||||
microChar.level = r.char.level;
|
||||
microChar.exp = r.char.exp;
|
||||
microChar.exp_max = r.char.exp_max;
|
||||
microChar.gold = r.char.gold;
|
||||
microChar.gold = r.char.gold || 0;
|
||||
microChar.copper = r.char.copper || 0;
|
||||
microChar.silver = r.char.silver || 0;
|
||||
microChar.diamond = r.char.diamond || 0;
|
||||
}
|
||||
microRenderCharHud();
|
||||
updateGoldEditorDisplay();
|
||||
@@ -2668,8 +2705,12 @@ async function claimTask(code, btn) {
|
||||
microChar.level = r.char.level;
|
||||
microChar.exp = r.char.exp;
|
||||
microChar.exp_max = r.char.exp_max;
|
||||
microChar.gold = r.char.gold;
|
||||
microChar.gold = r.char.gold || 0;
|
||||
microChar.copper = r.char.copper || 0;
|
||||
microChar.silver = r.char.silver || 0;
|
||||
microChar.diamond = r.char.diamond || 0;
|
||||
microRenderCharHud();
|
||||
updateGoldEditorDisplay();
|
||||
}
|
||||
const gained = [];
|
||||
if (r.reward_exp) gained.push(`⭐${r.reward_exp} 经验`);
|
||||
@@ -4610,7 +4651,7 @@ async function onMicroUp(e) {
|
||||
return;
|
||||
}
|
||||
if (microChar) microChar.gold = res.gold;
|
||||
microRenderCharHud();
|
||||
microRenderCharHud(); updateGoldEditorDisplay();
|
||||
}
|
||||
microRoadPaintBefore = 0;
|
||||
scheduleBuildSave(); microToast('道路已铺设');
|
||||
@@ -5307,8 +5348,16 @@ async function microUnitRest(unitId) {
|
||||
// 保存角色(金币/背包/装备)到 Odoo
|
||||
async function updateChar() {
|
||||
if (!microChar) return;
|
||||
const r = await API.saveChar({ gold: microChar.gold, inventory: microChar.inventory, equip: microChar.equip });
|
||||
if (r && r.ok && r.char) microChar = r.char;
|
||||
const r = await API.saveChar({
|
||||
gold: microChar.gold, copper: microChar.copper, silver: microChar.silver,
|
||||
diamond: microChar.diamond, inventory: microChar.inventory, equip: microChar.equip
|
||||
});
|
||||
if (r && r.ok && r.char) {
|
||||
microChar = r.char;
|
||||
if (!microChar.equip) microChar.equip = { weapon: null, armor: null, accessory: null };
|
||||
if (!microChar.inventory) microChar.inventory = {};
|
||||
['gold','copper','silver','diamond'].forEach(k => { if (microChar[k] == null) microChar[k] = 0; });
|
||||
}
|
||||
}
|
||||
|
||||
function closeUnitModal() { microOpenUnitId = null; document.getElementById('unitModal').classList.remove('active'); }
|
||||
@@ -5413,8 +5462,11 @@ async function microBuildingAct(type, action, label) {
|
||||
}
|
||||
// 同步角色金币 / 背包
|
||||
if (res.gold != null) { microChar = microChar || {}; microChar.gold = res.gold; }
|
||||
if (res.copper != null && microChar) microChar.copper = res.copper;
|
||||
if (res.silver != null && microChar) microChar.silver = res.silver;
|
||||
if (res.diamond != null && microChar) microChar.diamond = res.diamond;
|
||||
if (res.inventory && microChar) microChar.inventory = res.inventory;
|
||||
microRenderCharHud();
|
||||
microRenderCharHud(); updateGoldEditorDisplay();
|
||||
// 发射任务事件:建筑交互 + 资源产出
|
||||
const _bkey = type + '_' + action;
|
||||
emitTaskEvent('building_action', { key: _bkey });
|
||||
@@ -6611,7 +6663,7 @@ async function microCommitPlace() {
|
||||
return;
|
||||
}
|
||||
if (microChar) microChar.gold = res.gold;
|
||||
microRenderCharHud();
|
||||
microRenderCharHud(); updateGoldEditorDisplay();
|
||||
}
|
||||
microBuildList().push({ i: g.i, j: g.j, type: microBuildSel, rot: microBuildRot, w: it.w || 1, h: it.h || 1, level: 1, exp: 0, hp: (it.statHp || 100) });
|
||||
emitTaskEvent('build_placed', { code: microBuildSel });
|
||||
@@ -8277,6 +8329,8 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
microChar = {
|
||||
name: fresh.name, level: fresh.level, exp: fresh.exp,
|
||||
exp_max: fresh.exp_max || 100, gold: fresh.gold || 0,
|
||||
copper: fresh.copper || 0, silver: fresh.silver || 0,
|
||||
diamond: fresh.diamond || 0,
|
||||
equip: fresh.equip || { weapon: null, armor: null, accessory: null },
|
||||
inventory: fresh.inventory || {}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user