# -*- 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/', 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//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//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})