天赋系统
This commit is contained in:
@@ -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 '🌟'})
|
||||
|
||||
# ============================================================
|
||||
# 游戏化系统:角色 / 生物 / 订单 / 建筑交互(前端-后端打通)
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user