任务系统,新手引导
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
'data/codex_monster.xml',
|
||||
'data/codex_race.xml',
|
||||
'data/yt_world_build_item.xml',
|
||||
'data/yt_world_task.xml',
|
||||
'views/yt_world_views.xml',
|
||||
# AI NPC(自 game_ai_npc 合并):先数据(建模型记录)→ 子视图(含 action) → 主视图 → 菜单(引用 action 与父菜单)
|
||||
'data/game_npc_data.xml',
|
||||
|
||||
@@ -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},
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="0">
|
||||
<!-- ===== 第1章 初入世界 ===== -->
|
||||
<record id="task_ch1_welcome" model="yt.world.task">
|
||||
<field name="chapter">1</field>
|
||||
<field name="code">ch1_welcome</field>
|
||||
<field name="title">欢迎来到宇森世界</field>
|
||||
<field name="desc">你正式成为这个世界的缔造者。打开任务面板,即可领取新手启程礼包。</field>
|
||||
<field name="objectives" eval="[{'id':'o1','type':'panel_opened','count':1,'label':'打开任务面板','hint':'点击顶部 📜 任务按钮'}]"/>
|
||||
<field name="reward_exp">50</field>
|
||||
<field name="reward_res" eval="{'gold': 100}"/>
|
||||
<field name="prereq" eval="[]"/>
|
||||
<field name="sequence">10</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
|
||||
<record id="task_ch1_name" model="yt.world.task">
|
||||
<field name="chapter">1</field>
|
||||
<field name="code">ch1_name</field>
|
||||
<field name="title">为你的世界命名</field>
|
||||
<field name="desc">每个世界都该有自己的名字。点击世界标题,为它取一个响亮的名号。</field>
|
||||
<field name="objectives" eval="[{'id':'o1','type':'world_named','count':1,'label':'为世界命名','hint':'点击顶部世界标题后回车保存'}]"/>
|
||||
<field name="reward_exp">30</field>
|
||||
<field name="reward_res" eval="{'gold': 50}"/>
|
||||
<field name="prereq" eval="['ch1_welcome']"/>
|
||||
<field name="sequence">20</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
|
||||
<!-- ===== 第2章 建造启蒙 ===== -->
|
||||
<record id="task_ch2_build" model="yt.world.task">
|
||||
<field name="chapter">2</field>
|
||||
<field name="code">ch2_build</field>
|
||||
<field name="title">奠基第一座建筑</field>
|
||||
<field name="desc">进入任意地块,从建造面板放置你的第一座建筑,让世界开始运转。</field>
|
||||
<field name="objectives" eval="[{'id':'o1','type':'build_placed','count':1,'label':'放置任意建筑','hint':'进入地块,选素材后左键落地'}]"/>
|
||||
<field name="reward_exp">40</field>
|
||||
<field name="reward_res" eval="{'gold': 80}"/>
|
||||
<field name="prereq" eval="['ch1_name']"/>
|
||||
<field name="sequence">10</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
|
||||
<record id="task_ch2_harvest" model="yt.world.task">
|
||||
<field name="chapter">2</field>
|
||||
<field name="code">ch2_harvest</field>
|
||||
<field name="title">第一次收获</field>
|
||||
<field name="desc">在农田上使用「收获」动作,体验建筑产出资源的乐趣。</field>
|
||||
<field name="objectives" eval="[{'id':'o1','type':'building_action','key':'farmland_harvest','count':1,'label':'收获一次农田','hint':'选中农田 → 收获'}]"/>
|
||||
<field name="reward_exp">40</field>
|
||||
<field name="reward_res" eval="{'gold': 60}"/>
|
||||
<field name="prereq" eval="['ch2_build']"/>
|
||||
<field name="sequence">20</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
|
||||
<!-- ===== 第3章 资源流转 ===== -->
|
||||
<record id="task_ch3_food" model="yt.world.task">
|
||||
<field name="chapter">3</field>
|
||||
<field name="code">ch3_food</field>
|
||||
<field name="title">粮仓初盈</field>
|
||||
<field name="desc">反复经营你的农田与作坊,累计收获 5 份粮食,支撑世界运转。</field>
|
||||
<field name="objectives" eval="[{'id':'o1','type':'resource_collected','key':'food','count':5,'label':'累计收获粮食 ×5','hint':'多次使用农田「收获」/风车「加工」'}]"/>
|
||||
<field name="reward_exp">60</field>
|
||||
<field name="reward_res" eval="{'gold': 100}"/>
|
||||
<field name="prereq" eval="['ch2_harvest']"/>
|
||||
<field name="sequence">10</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
|
||||
<!-- ===== 第4章 生灵入驻 ===== -->
|
||||
<record id="task_ch4_char" model="yt.world.task">
|
||||
<field name="chapter">4</field>
|
||||
<field name="code">ch4_char</field>
|
||||
<field name="title">首位居民</field>
|
||||
<field name="desc">一个世界不能只有建筑。在地块中放置你的第一名种族角色,带来生机。</field>
|
||||
<field name="objectives" eval="[{'id':'o1','type':'char_recruited','count':1,'label':'入驻一名种族角色','hint':'在地块中放置一个种族角色'}]"/>
|
||||
<field name="reward_exp">80</field>
|
||||
<field name="reward_res" eval="{'gold': 150}"/>
|
||||
<field name="prereq" eval="['ch3_food']"/>
|
||||
<field name="sequence">10</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
|
||||
<!-- ===== 第5章 文明初成 ===== -->
|
||||
<record id="task_ch5_level" model="yt.world.task">
|
||||
<field name="chapter">5</field>
|
||||
<field name="code">ch5_level</field>
|
||||
<field name="title">文明初成</field>
|
||||
<field name="desc">完成前面的历练,让你的角色积累经验、晋升至 2 级,开启更广阔的世界。</field>
|
||||
<field name="objectives" eval="[{'id':'o1','type':'level_up','key':2,'count':1,'label':'角色升至 2 级','hint':'完成更多任务积累经验'}]"/>
|
||||
<field name="reward_exp">100</field>
|
||||
<field name="reward_res" eval="{'gold': 200}"/>
|
||||
<field name="prereq" eval="['ch4_char']"/>
|
||||
<field name="sequence">10</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -4,6 +4,7 @@ from . import yt_world_tile_micro
|
||||
from . import yt_world_micro_char
|
||||
from . import yt_world_build_item
|
||||
from . import yt_world_game
|
||||
from . import yt_world_task
|
||||
from . import yt_resource
|
||||
from . import yt_codex
|
||||
from . import npc
|
||||
|
||||
@@ -29,14 +29,22 @@ class YtWorldCharacter(models.Model):
|
||||
|
||||
world_id = fields.Many2one('yt.world', string='世界', required=True,
|
||||
ondelete='cascade', index=True)
|
||||
employee_id = fields.Many2one('hr.employee', string='所属玩家',
|
||||
ondelete='cascade', index=True)
|
||||
name = fields.Char(string='角色名', default='旅者')
|
||||
level = fields.Integer(string='等级', default=1)
|
||||
exp = fields.Integer(string='经验', default=0)
|
||||
exp_max = fields.Integer(string='升级所需经验', default=100)
|
||||
gold = fields.Integer(string='金币', default=100)
|
||||
inventory = fields.Json(string='背包', default=dict)
|
||||
inventory_display = fields.Text(string='背包(可读)', compute='_compute_displays')
|
||||
equip = fields.Json(string='装备', default=dict)
|
||||
equip_display = fields.Text(string='装备(可读)', compute='_compute_displays')
|
||||
# 任务进度:[{code, progress:{obj_id:have}, state, ts}]
|
||||
# state ∈ in_progress / claimable / done(locked 为前端按前置派生,不落库)
|
||||
tasks = fields.Json(string='任务进度', default=list)
|
||||
tutorial_chapter = fields.Integer(string='当前章节', default=1)
|
||||
tutorial_step = fields.Integer(string='章节内步骤', default=0)
|
||||
|
||||
@api.depends('inventory', 'equip')
|
||||
def _compute_displays(self):
|
||||
@@ -44,6 +52,136 @@ class YtWorldCharacter(models.Model):
|
||||
r.inventory_display = _fmt_kv(r.inventory)
|
||||
r.equip_display = _fmt_kv(r.equip)
|
||||
|
||||
# ========== 任务系统 ==========
|
||||
# 前端可发射的游戏事件类型(服务端事件总线):
|
||||
# panel_opened / world_named / build_placed{code} /
|
||||
# building_action{key:'code_action'} / resource_collected{res} /
|
||||
# char_recruited{race} / creature_recruited{species} / level_up{level}
|
||||
@staticmethod
|
||||
def _obj_id(o):
|
||||
return '%s:%s' % (o.get('type'), o.get('key'))
|
||||
|
||||
@staticmethod
|
||||
def _match_event(o, event, payload):
|
||||
"""单条 objective 是否被该事件命中。"""
|
||||
if o.get('type') != event:
|
||||
return False
|
||||
key = o.get('key')
|
||||
if key is None:
|
||||
return True
|
||||
if event == 'level_up':
|
||||
try:
|
||||
return int(payload.get('level', 0)) >= int(key)
|
||||
except Exception:
|
||||
return False
|
||||
field = {
|
||||
'build_placed': 'code',
|
||||
'building_action': 'key',
|
||||
'resource_collected': 'res',
|
||||
'char_recruited': 'race',
|
||||
'creature_recruited': 'species',
|
||||
}.get(event)
|
||||
if not field:
|
||||
return True # 不带 key 的事件(panel_opened/world_named)命中即计数
|
||||
val = payload.get(field)
|
||||
if val is None:
|
||||
return True
|
||||
return str(val) == str(key)
|
||||
|
||||
def on_game_event(self, event, payload=None):
|
||||
"""服务端事件总线:遍历未完成任务的 objectives,命中则推进进度,满额置 claimable。
|
||||
返回 {ok, changed, claimable:[codes]} 供接口提示前端。"""
|
||||
self.ensure_one()
|
||||
payload = payload or {}
|
||||
Task = self.env['yt.world.task'].sudo()
|
||||
tasks = list(self.tasks or [])
|
||||
done_codes = [t['code'] for t in tasks if t.get('state') == 'done']
|
||||
newly_claimable = []
|
||||
changed = False
|
||||
for task in Task.search([('active', '=', True)], order='chapter, sequence, id'):
|
||||
code = task.code
|
||||
idx = next((i for i, t in enumerate(tasks) if t.get('code') == code), None)
|
||||
entry = tasks[idx] if idx is not None else None
|
||||
if entry and entry.get('state') in ('done', 'claimable'):
|
||||
continue # 已结算/待领取,不再推进
|
||||
prereq = task.prereq or []
|
||||
if any(p not in done_codes for p in prereq):
|
||||
continue # 前置未解锁
|
||||
objectives = task.objectives or []
|
||||
if not objectives:
|
||||
continue
|
||||
prog = dict((entry or {}).get('progress') or {})
|
||||
was_claimable = bool(entry and entry.get('state') == 'claimable')
|
||||
for o in objectives:
|
||||
oid = o.get('id') or self._obj_id(o)
|
||||
need = o.get('count', 1) or 1
|
||||
have = prog.get(oid, 0)
|
||||
if have >= need:
|
||||
continue
|
||||
if self._match_event(o, event, payload):
|
||||
prog[oid] = have + 1
|
||||
changed = True
|
||||
all_done = all(
|
||||
(prog.get(o.get('id') or self._obj_id(o), 0)) >= (o.get('count', 1) or 1)
|
||||
for o in objectives)
|
||||
new_state = 'claimable' if all_done else 'in_progress'
|
||||
rec = {'code': code, 'progress': prog, 'state': new_state}
|
||||
if idx is not None:
|
||||
tasks[idx] = rec
|
||||
else:
|
||||
tasks.append(rec)
|
||||
changed = True
|
||||
if new_state == 'claimable' and not was_claimable:
|
||||
newly_claimable.append(code)
|
||||
if changed:
|
||||
self.sudo().write({'tasks': tasks})
|
||||
return {'ok': True, 'changed': changed, 'claimable': newly_claimable}
|
||||
|
||||
def complete_task(self, code, task_model=None):
|
||||
"""领取任务奖励(仅 state==claimable 可领)。校验前置→发奖→置 done。"""
|
||||
self.ensure_one()
|
||||
Task = task_model or self.env['yt.world.task'].sudo()
|
||||
task = Task.search([('code', '=', code), ('active', '=', True)], limit=1)
|
||||
if not task:
|
||||
return {'ok': False, 'error': 'task_not_found', 'code': code}
|
||||
# 前置校验
|
||||
prereq = task.prereq or []
|
||||
done_codes = [t['code'] for t in (self.tasks or []) if t.get('state') == 'done']
|
||||
for p in prereq:
|
||||
if p not in done_codes:
|
||||
return {'ok': False, 'error': 'prereq_unmet', 'code': p}
|
||||
# 找到进度条目
|
||||
tasks = list(self.tasks or [])
|
||||
entry = next((t for t in tasks if t.get('code') == code), None)
|
||||
if entry and entry.get('state') == 'done':
|
||||
return {'ok': True, 'already': True, 'level': self.level,
|
||||
'exp': self.exp, 'gold': self.gold}
|
||||
if not entry or entry.get('state') != 'claimable':
|
||||
return {'ok': False, 'error': 'not_claimable', 'code': code}
|
||||
# 加经验(带越级)
|
||||
exp = self.exp + (task.reward_exp or 0)
|
||||
level = self.level
|
||||
exp_max = self.exp_max
|
||||
while exp >= exp_max:
|
||||
exp -= exp_max
|
||||
level += 1
|
||||
exp_max = int(exp_max * 1.3) # 每级所需经验递增
|
||||
# 加货币
|
||||
gold = self.gold + int((task.reward_res or {}).get('gold', 0))
|
||||
# 置已完成
|
||||
for i, t in enumerate(tasks):
|
||||
if t.get('code') == code:
|
||||
tasks[i] = {'code': code, 'progress': t.get('progress') or {},
|
||||
'state': 'done', 'ts': fields.Datetime.to_string(fields.Datetime.now())}
|
||||
break
|
||||
self.sudo().write({
|
||||
'level': level, 'exp': exp, 'exp_max': exp_max,
|
||||
'gold': gold, 'tasks': tasks,
|
||||
})
|
||||
return {'ok': True, 'level': level, 'exp': exp, 'exp_max': exp_max,
|
||||
'gold': gold, 'reward_exp': task.reward_exp or 0,
|
||||
'reward_res': task.reward_res or {}}
|
||||
|
||||
|
||||
class YtWorldCreature(models.Model):
|
||||
_name = 'yt.world.creature'
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""宇森世界 · 任务定义模型。
|
||||
|
||||
任务定义放后端(数据驱动),前端只渲染。与 yt.world.build.item 同一套路:
|
||||
- 一条 yt.world.task 记录 = 一个任务(含章节/目标/奖励/前置)
|
||||
- 玩家进度挂在 yt.world.character.tasks(Json 列表),不另建表
|
||||
"""
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class YtWorldTask(models.Model):
|
||||
_name = 'yt.world.task'
|
||||
_description = '世界任务(新手主线)'
|
||||
_order = 'chapter, sequence, id'
|
||||
|
||||
chapter = fields.Integer(string='章节', default=1)
|
||||
code = fields.Char(string='任务编码', required=True, index=True)
|
||||
title = fields.Char(string='标题', required=True)
|
||||
desc = fields.Text(string='描述')
|
||||
# 目标步骤:[{key, label, hint}],前端渲染用;后端仅做展示
|
||||
objectives = fields.Json(string='目标步骤', default=list)
|
||||
reward_exp = fields.Integer(string='经验奖励', default=0)
|
||||
# 货币奖励:{gold: 100} 等;gold 走 character.gold,其余走 inventory
|
||||
reward_res = fields.Json(string='货币奖励', default=dict)
|
||||
# 前置任务编码列表,全部完成后本任务才解锁
|
||||
prereq = fields.Json(string='前置任务', default=list)
|
||||
sequence = fields.Integer(string='排序', default=10)
|
||||
active = fields.Boolean(string='启用', default=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('uniq_code', 'unique(code)', '任务编码必须唯一'),
|
||||
]
|
||||
@@ -33,3 +33,6 @@ access_game_npc_knowledge,game.npc.knowledge user,model_game_npc_knowledge,base.
|
||||
access_yt_world_micro_char_user,yt.world.micro.char user,model_yt_world_micro_char,base.group_user,1,1,1,1
|
||||
access_yt_world_micro_char_public,yt.world.micro.char public,model_yt_world_micro_char,base.group_public,1,0,0,0
|
||||
access_yt_world_micro_char_portal,yt.world.micro.char portal,model_yt_world_micro_char,base.group_portal,1,0,0,0
|
||||
access_yt_world_task_user,yt.world.task user,model_yt_world_task,base.group_user,1,1,1,1
|
||||
access_yt_world_task_public,yt.world.task public,model_yt_world_task,base.group_public,1,0,0,0
|
||||
access_yt_world_task_portal,yt.world.task portal,model_yt_world_task,base.group_portal,1,0,0,0
|
||||
|
||||
|
Reference in New Issue
Block a user