任务系统,新手引导

This commit is contained in:
李鹏宇
2026-08-18 19:36:52 +08:00
parent c777db0657
commit efbf69580a
18 changed files with 802 additions and 29 deletions
+151 -3
View File
@@ -340,10 +340,157 @@ class YtWorldApi(http.Controller):
char = request.env['yt.world.character'].sudo().search(
[('world_id', '=', world.id)], limit=1)
if not char:
char = request.env['yt.world.character'].sudo().create(
{'world_id': world.id})
# 认领当前登录人的玩家档案(hr.employee),把注册昵称同步为角色名
vals = {'world_id': world.id}
uid = request.session.uid
if uid:
emp = request.env['hr.employee'].sudo().search(
[('user_id', '=', uid)], limit=1)
if emp:
vals['employee_id'] = emp.id
vals['name'] = emp.name or char.name
char = request.env['yt.world.character'].sudo().create(vals)
else:
# 兼容老数据:已存在角色但没关联 employee 时补上
if not char.employee_id:
uid = request.session.uid
if uid:
emp = request.env['hr.employee'].sudo().search(
[('user_id', '=', uid)], limit=1)
if emp:
char.sudo().write({'employee_id': emp.id,
'name': emp.name or char.name})
return char
# ========== 任务系统序列化(get_tasks / task_progress 共用) ==========
def _serialize_tasks(self, char):
"""把任务定义 + 角色进度汇总成前端面板可直接渲染的结构。"""
done_codes = [t['code'] for t in (char.tasks or []) if t.get('state') == 'done']
tasks = request.env['yt.world.task'].sudo().search(
[('active', '=', True)], order='chapter, sequence, id')
out = []
for t in tasks:
prereq = t.prereq or []
entry = None
for x in (char.tasks or []):
if x.get('code') == t.code:
entry = x
break
if entry and entry.get('state') == 'done':
state = 'done'
elif any(p not in done_codes for p in prereq):
state = 'locked'
elif entry and entry.get('state') == 'claimable':
state = 'claimable'
else:
state = 'in_progress'
progress = (entry or {}).get('progress') or {}
objs = []
for o in (t.objectives or []):
oid = o.get('id') or ('%s:%s' % (o.get('type'), o.get('key')))
need = o.get('count', 1) or 1
have = min(progress.get(oid, 0), need)
objs.append({
'type': o.get('type'), 'key': o.get('key'),
'label': o.get('label'), 'hint': o.get('hint'),
'have': have, 'need': need,
})
out.append({
'code': t.code, 'chapter': t.chapter, 'title': t.title,
'desc': t.desc or '', 'objectives': objs,
'reward_exp': t.reward_exp or 0,
'reward_res': t.reward_res or {},
'prereq': prereq, 'state': state,
})
chapters = sorted({t['chapter'] for t in out})
return chapters, out
@http.route('/yt_world/api/tasks', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def get_tasks(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)
# 回溯校验:角色等级可能已满足 level_up 类型任务,补发事件
char.on_game_event('level_up', {'level': char.level})
chapters, out = self._serialize_tasks(char)
return _json({
'ok': True,
'chapters': chapters,
'tasks': out,
'char': {
'name': char.name, 'level': char.level, 'exp': char.exp,
'exp_max': char.exp_max, 'gold': char.gold,
},
})
@http.route('/yt_world/api/task/progress', type='http', auth='public',
methods=['POST', 'OPTIONS'], csrf=False)
def task_progress(self, **kw):
"""接收前端游戏事件并推进任务进度。body: {"event":"build_placed","payload":{"code":"farmland"}}
返回更新后的任务列表(与 /tasks 同结构),claimable 列出本次新可领取的任务。"""
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()
event = (body.get('event') or '').strip()
payload = body.get('payload') or {}
if not event:
return _json({'ok': False, 'error': 'bad_params'})
char = self._get_char(world)
res = char.on_game_event(event, payload)
# 回溯校验:角色等级可能已满足 level_up 类型任务,补发事件
char.on_game_event('level_up', {'level': char.level})
chapters, out = self._serialize_tasks(char)
return _json({
'ok': True,
'chapters': chapters,
'tasks': out,
'char': {
'name': char.name, 'level': char.level, 'exp': char.exp,
'exp_max': char.exp_max, 'gold': char.gold,
},
'claimable': res.get('claimable', []),
})
@http.route('/yt_world/api/task/complete', type='http', auth='public',
methods=['POST', 'OPTIONS'], csrf=False)
def complete_task(self, **kw):
"""领取/完成任务。body: {"code": "ch1_welcome"}
校验前置→发奖→写进度,返回最新角色状态。"""
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)
res = char.complete_task(code)
if not res.get('ok'):
return _json(res)
return _json({
'ok': True,
'already': res.get('already', False),
'char': {
'name': char.name, 'level': char.level, 'exp': char.exp,
'exp_max': char.exp_max, 'gold': char.gold,
},
'reward_exp': res.get('reward_exp', 0),
'reward_res': res.get('reward_res', {}),
})
@http.route('/yt_world/api/char', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def get_char(self, **kw):
@@ -358,7 +505,8 @@ class YtWorldApi(http.Controller):
return _json({
'ok': True,
'name': char.name, 'level': char.level, 'exp': char.exp,
'gold': char.gold, 'inventory': char.inventory or {},
'exp_max': char.exp_max or 100, 'gold': char.gold,
'inventory': char.inventory or {},
'equip': char.equip or {'weapon': None, 'armor': None, 'accessory': None},
})