Files
game/addons/yt_world/controllers/npc_api.py
T
李鹏宇 106d49f6de feat(game): 游戏前后端整合与素材预置备份
后端(Odoo addons):
- game_base: 迁入 关于 功能(game.page/game.page.item/yusen.asset 模型、page_views/asset_views、6条权限、menu_website_about 左侧应用入口),新增 chat 模块(AI世界聊天: chat.py/chat_api.py/chat_data.xml/chat_views.xml)
- yt_world: 合并 game_ai_npc(npc 模型/数据/controller),修复菜单 NotNullViolation
- yusen_website: 移除已迁出的 page/asset,背景菜单改挂 game_base.action_game_page
- 新增 game_asset_backup 纯数据模块: 备份前端素材到 addons(game_assets/),预置 图鉴(game.codex.entry)9条(过场CG/动物) + 素材管理(yusen.asset)120条(地形/建筑/瓦片/幻灯片/背景/路/UI),按类分 tags

前端(assets + HTML):
- 新增 chat.js / game-api.js、chat.html / buildings_default_demo.html
- 清理旧 slides/website 冗余文件,更新 GENERATION_RULES.md 与动物九宫格素材
2026-08-11 10:02:19 +08:00

125 lines
5.1 KiB
Python

# -*- coding: utf-8 -*-
import json
from odoo import http
from odoo.http import request
from datetime import datetime
def _json(data):
return request.make_response(
json.dumps(data, ensure_ascii=False, default=str),
headers=[('Content-Type', 'application/json; charset=utf-8')]
)
class GameNpcApi(http.Controller):
# ── NPC 列表 ──
@http.route('/game/api/npcs', type='http', auth='public', methods=['GET'], csrf=False)
def npc_list(self, **kw):
"""返回所有活跃 NPC(含当前活动和关键状态)。"""
npcs = request.env['game.npc'].sudo().search_read(
[('active', '=', True)],
['id', 'name', 'title', 'code', 'archetype', 'personality',
'current_activity', 'energy', 'hunger', 'safety', 'mood',
'mood_label', 'activity_label', 'goal_progress', 'current_goal',
'current_area', 'relationship_count', 'recent_log', 'tick_count',
'affiliation'],
order='sequence, id'
)
for n in npcs:
n['archetype_label'] = dict(
request.env['game.npc']._fields['archetype'].selection
).get(n['archetype'], n['archetype'])
return _json({'npcs': npcs})
# ── NPC 详情 ──
@http.route('/game/api/npc/<int:npc_id>', type='http', auth='public', methods=['GET'], csrf=False)
def npc_detail(self, npc_id, **kw):
"""NPC 详情(含关系网 + 最近行为 + 知识)。"""
Npc = request.env['game.npc'].sudo()
npc = Npc.browse(npc_id)
if not npc.exists():
return _json({'error': 'NPC 不存在'})
data = npc.read([
'id', 'name', 'title', 'code', 'archetype', 'personality',
'origin', 'bio', 'current_activity', 'energy', 'hunger', 'safety',
'mood', 'mood_label', 'activity_label', 'goal_progress', 'current_goal',
'goal_detail', 'current_area', 'tick_count', 'last_tick',
'affiliation',
])
if not data:
return _json({'error': 'NPC 不存在'})
data = data[0]
# 关系网
rels = request.env['game.npc.relationship'].sudo().search_read(
[('npc_id', '=', npc_id)],
['target_npc_id', 'affinity', 'status', 'last_interaction'],
order='affinity desc'
)
data['relationships'] = [{
'target_id': r['target_npc_id'][0] if r['target_npc_id'] else None,
'target_name': r['target_npc_id'][1] if r['target_npc_id'] else '',
'affinity': r['affinity'],
'status': r['status'],
} for r in rels]
# 最近行为(20 条)
logs = request.env['game.npc.behavior.log'].sudo().search_read(
[('npc_id', '=', npc_id)],
['tick_time', 'activity', 'activity_label', 'location', 'summary', 'target_npc_id'],
limit=20, order='tick_time desc'
)
data['recent_behaviors'] = [{
'time': str(l['tick_time']),
'activity': l['activity'],
'label': l['activity_label'],
'location': l['location'] or '',
'summary': l['summary'],
} for l in logs]
# 知识
knowledge = request.env['game.npc.knowledge'].sudo().search_read(
[('npc_id', '=', npc_id)],
['topic', 'content', 'accuracy', 'source'],
order='sequence, id'
)
data['knowledge'] = knowledge
return _json(data)
# ── NPC 关系网 ──
@http.route('/game/api/npc/<int:npc_id>/relationships', type='http', auth='public', methods=['GET'], csrf=False)
def npc_relationships(self, npc_id, **kw):
rels = request.env['game.npc.relationship'].sudo().search_read(
[('npc_id', '=', npc_id)],
['target_npc_id', 'affinity', 'status', 'last_interaction', 'interaction_count'],
order='affinity desc'
)
return _json({'relationships': [{
'target_id': r['target_npc_id'][0] if r['target_npc_id'] else None,
'target_name': r['target_npc_id'][1] if r['target_npc_id'] else '',
'affinity': r['affinity'],
'status': r['status'],
'last_interaction': str(r['last_interaction']),
'interaction_count': r['interaction_count'],
} for r in rels]})
# ── 主动触发单个 NPC Tick ──
@http.route('/game/api/npc/<int:npc_id>/tick', type='http', auth='user', methods=['POST'], csrf=False)
def npc_tick(self, npc_id, **kw):
npc = request.env['game.npc'].sudo().browse(npc_id)
if not npc.exists():
return _json({'error': 'NPC 不存在'})
npc.action_tick()
return _json({'ok': True, 'name': npc.name, 'tick_count': npc.tick_count,
'activity': npc.activity_label, 'mood': npc.mood_label})
# ── 批量 Tick ──
@http.route('/game/api/npc/tick-all', type='http', auth='user', methods=['POST'], csrf=False)
def npc_tick_all(self, **kw):
count = request.env['game.npc'].sudo().cron_batch_tick(batch_size=100)
return _json({'ok': True, 'ticked': count})