天赋系统

This commit is contained in:
李鹏宇
2026-08-27 20:15:18 +08:00
parent 5d79209ccb
commit 78aaace049
18 changed files with 1211 additions and 62 deletions
+241 -1
View File
@@ -2,7 +2,8 @@
import json
import logging
import re
from odoo import http
import random
from odoo import http, fields
from odoo.http import request, Response
_logger = logging.getLogger(__name__)
@@ -297,6 +298,245 @@ class YtWorldApi(http.Controller):
return self._records_json('yt.codex.race',
['code','name','icon','layer','traits','description'])
@http.route('/yt_world/api/codex/talents', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def codex_talents(self, **kw):
"""返回天赋图鉴配置(含前置天赋 code)。"""
recs = request.env['yt.codex.talent'].sudo().search([], order='sequence, id')
out = []
for r in recs:
out.append({
'code': r.code,
'name': r.name,
'icon': r.icon or '🌟',
'quality': r.quality or 'common',
'trigger': r.trigger or '被动',
'effect': r.effect or '',
'description': r.description or '',
'max_level': r.max_level or 1,
'cost_type': r.cost_type or 'point',
'cost_value': r.cost_value or 0,
'unlock_condition': r.unlock_condition or '',
'prerequisite_code': r.prerequisite_talent_id.code if r.prerequisite_talent_id else None,
'sequence': r.sequence or 10,
})
return _json(out)
# ---------- 角色天赋系统 ----------
def _serialize_char_talents(self, char):
"""把角色已拥有的天赋序列化成前端可用结构。"""
owned = {}
for t in char.talent_ids:
owned[t.talent_id.code] = {
'id': t.id,
'level': t.level or 1,
'active': t.active,
'acquired_at': fields.Datetime.to_string(t.acquired_at) if t.acquired_at else None,
}
all_talents = request.env['yt.codex.talent'].sudo().search([])
talents = []
for tal in all_talents:
info = owned.get(tal.code, {})
talents.append({
'code': tal.code,
'name': tal.name,
'icon': tal.icon or '🌟',
'quality': tal.quality or 'common',
'trigger': tal.trigger or '被动',
'effect': tal.effect or '',
'description': tal.description or '',
'max_level': tal.max_level or 1,
'cost_type': tal.cost_type or 'point',
'cost_value': tal.cost_value or 0,
'unlock_condition': tal.unlock_condition or '',
'prerequisite_code': tal.prerequisite_talent_id.code if tal.prerequisite_talent_id else None,
'owned': bool(info),
'level': info.get('level', 0),
'active': info.get('active', False),
})
return talents
@http.route('/yt_world/api/char/talents', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def char_talents(self, **kw):
"""取当前角色天赋状态(图鉴 + 拥有情况)。"""
auth_err = self._require_user()
if auth_err:
return _json(auth_err)
world = self._get_world()
if not world:
return _json({'ok': False, 'error': 'no_world'})
char = self._get_char(world)
return _json({
'ok': True,
'talent_points': char.talent_points or 0,
'gold': char.gold or 0,
'talents': self._serialize_char_talents(char),
})
@http.route('/yt_world/api/char/talent/activate', type='http', auth='public',
methods=['POST', 'OPTIONS'], csrf=False)
def char_talent_activate(self, **kw):
"""激活天赋 body: {"code": "talent_xxx"}。"""
auth_err = self._require_user()
if auth_err:
return _json(auth_err)
world = self._get_world()
if not world:
return _json({'ok': False, 'error': 'no_world'})
body = _post_body()
code = (body.get('code') or '').strip()
if not code:
return _json({'ok': False, 'error': 'bad_params'})
char = self._get_char(world)
Talent = request.env['yt.codex.talent'].sudo()
CharTalent = request.env['yt.world.character.talent'].sudo()
talent = Talent.search([('code', '=', code)], limit=1)
if not talent:
return _json({'ok': False, 'error': 'talent_not_found', 'code': code})
existing = CharTalent.search([
('character_id', '=', char.id),
('talent_id', '=', talent.id)
], limit=1)
if existing:
return _json({'ok': False, 'error': 'already_owned', 'code': code})
# 前置天赋校验
if talent.prerequisite_talent_id:
pre = CharTalent.search([
('character_id', '=', char.id),
('talent_id', '=', talent.prerequisite_talent_id.id),
('active', '=', True)
], limit=1)
if not pre:
return _json({'ok': False, 'error': 'prereq_unmet',
'prerequisite_code': talent.prerequisite_talent_id.code})
cost = talent.cost_value or 0
if talent.cost_type == 'point':
if (char.talent_points or 0) < cost:
return _json({'ok': False, 'error': 'no_points',
'need': cost, 'have': char.talent_points or 0})
char.talent_points = (char.talent_points or 0) - cost
elif talent.cost_type == 'gold':
if (char.gold or 0) < cost:
return _json({'ok': False, 'error': 'no_gold',
'need': cost, 'have': char.gold or 0})
char.gold = (char.gold or 0) - cost
CharTalent.create({
'character_id': char.id,
'talent_id': talent.id,
'level': 1,
'active': True,
})
_logger.info('yt.talent.activate uid=%s code=%s cost=%s',
request.session.uid, code, cost)
return _json({
'ok': True,
'code': code,
'level': 1,
'talent_points': char.talent_points or 0,
'gold': char.gold or 0,
})
@http.route('/yt_world/api/char/talent/upgrade', type='http', auth='public',
methods=['POST', 'OPTIONS'], csrf=False)
def char_talent_upgrade(self, **kw):
"""升级天赋 body: {"code": "talent_xxx"}。"""
auth_err = self._require_user()
if auth_err:
return _json(auth_err)
world = self._get_world()
if not world:
return _json({'ok': False, 'error': 'no_world'})
body = _post_body()
code = (body.get('code') or '').strip()
if not code:
return _json({'ok': False, 'error': 'bad_params'})
char = self._get_char(world)
Talent = request.env['yt.codex.talent'].sudo()
CharTalent = request.env['yt.world.character.talent'].sudo()
talent = Talent.search([('code', '=', code)], limit=1)
if not talent:
return _json({'ok': False, 'error': 'talent_not_found', 'code': code})
rec = CharTalent.search([
('character_id', '=', char.id),
('talent_id', '=', talent.id)
], limit=1)
if not rec:
return _json({'ok': False, 'error': 'not_owned', 'code': code})
res = rec.do_upgrade()
if not res.get('ok'):
return _json(res)
return _json({
'ok': True,
'code': code,
'level': res['level'],
'talent_points': char.talent_points or 0,
'gold': char.gold or 0,
})
@http.route('/yt_world/api/char/talent/draw', type='http', auth='public',
methods=['POST', 'OPTIONS'], csrf=False)
def char_talent_draw(self, **kw):
"""新手抽天赋:返回 5 张随机未拥有的天赋牌(前端先展示牌背,翻牌时再免费激活)。"""
auth_err = self._require_user()
if auth_err:
return _json(auth_err)
world = self._get_world()
if not world:
return _json({'ok': False, 'error': 'no_world'})
char = self._get_char(world)
owned_codes = char.talent_ids.mapped('talent_id.code')
pool = request.env['yt.codex.talent'].sudo().search([('code', 'not in', owned_codes)])
data = pool.read(['code', 'name', 'icon', 'quality', 'trigger',
'effect', 'description', 'max_level'])
random.shuffle(data)
return _json({'ok': True, 'deck': data[:5]})
@http.route('/yt_world/api/char/talent/draw/activate', type='http', auth='public',
methods=['POST', 'OPTIONS'], csrf=False)
def char_talent_draw_activate(self, **kw):
"""新手抽卡翻牌激活:免费、免前置、免消耗。body: {"code": "talent_xxx"}。"""
auth_err = self._require_user()
if auth_err:
return _json(auth_err)
world = self._get_world()
if not world:
return _json({'ok': False, 'error': 'no_world'})
body = _post_body()
code = (body.get('code') or '').strip()
if not code:
return _json({'ok': False, 'error': 'bad_params'})
char = self._get_char(world)
Talent = request.env['yt.codex.talent'].sudo()
CharTalent = request.env['yt.world.character.talent'].sudo()
talent = Talent.search([('code', '=', code)], limit=1)
if not talent:
return _json({'ok': False, 'error': 'talent_not_found', 'code': code})
existing = CharTalent.search([
('character_id', '=', char.id),
('talent_id', '=', talent.id)
], limit=1)
if existing:
return _json({'ok': False, 'error': 'already_owned', 'code': code})
CharTalent.create({
'character_id': char.id,
'talent_id': talent.id,
'level': 1,
'active': True,
})
return _json({'ok': True, 'code': code, 'level': 1,
'name': talent.name, 'icon': talent.icon or '🌟'})
# ============================================================
# 游戏化系统:角色 / 生物 / 订单 / 建筑交互(前端-后端打通)
# ============================================================