game/addons/yt_world/controllers/world_api.py

143 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
import json
import logging
import re
from odoo import http
from odoo.http import request, Response
_logger = logging.getLogger(__name__)
def _json(data):
"""返回 UTF-8 JSON 响应(与 game_base 一致),并附带 CORS 头以支持跨域8080→8069"""
headers = {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
}
try:
origin = request.httprequest.headers.get('Origin')
if origin:
headers['Access-Control-Allow-Origin'] = origin
headers['Access-Control-Allow-Credentials'] = 'true'
else:
headers['Access-Control-Allow-Origin'] = '*'
except Exception:
headers['Access-Control-Allow-Origin'] = '*'
return Response(json.dumps(data, ensure_ascii=False), headers=headers)
def _post_body():
try:
raw = request.httprequest.get_data(as_text=True) or '{}'
return json.loads(raw)
except Exception:
return {}
class YtWorldApi(http.Controller):
"""前端沙盘记忆接口JSON over HTTP。auth=user需已登录 Odoo。
前端统一带 ?db=game 且 credentials:'same-origin',故路由须为 type='http'
并以 _json 返回原始 JSON与 game-api.js 的 fetch().json() 对齐)。"""
@http.route('/yt_world/api/world', type='http', auth='user',
methods=['GET', 'OPTIONS'], csrf=False)
def world(self, **kw):
"""取或建当前用户世界,导出 seed + 地块覆盖。"""
user = request.env.user
rec = request.env['yt.world'].sudo().get_or_create_for_user(user)
return _json(rec.export_state())
@http.route('/yt_world/api/tiles/save', type='http', auth='user',
methods=['POST', 'OPTIONS'], csrf=False)
def save_tiles(self, **kw):
"""增量覆盖保存地块。body: {"tiles": [...]}"""
user = request.env.user
world = request.env['yt.world'].sudo().search(
[('user_id', '=', user.id)], limit=1)
if not world:
return _json({'ok': False, 'error': 'no_world'})
body = _post_body()
tiles = body.get('tiles') or []
world.import_tiles(tiles)
return _json({'ok': True, 'saved': len(tiles)})
@http.route('/yt_world/api/settings', type='http', auth='user',
methods=['POST', 'OPTIONS'], csrf=False)
def save_settings(self, **kw):
"""保存相机/UI 偏好。body: {"settings": {...}}"""
user = request.env.user
world = request.env['yt.world'].sudo().search(
[('user_id', '=', user.id)], limit=1)
body = _post_body()
settings = body.get('settings')
if world and settings is not None:
world.settings = settings
return _json({'ok': True})
@http.route('/yt_world/api/world/rename', type='http', auth='user',
methods=['POST', 'OPTIONS'], csrf=False)
def rename_world(self, **kw):
"""修改当前用户世界名称。body: {"name": "..."}
校验:trim 后 1-24 字符,禁控制字符,空白正常允许。"""
user = request.env.user
world = request.env['yt.world'].sudo().search(
[('user_id', '=', user.id)], limit=1)
if not world:
return _json({'ok': False, 'error': 'no_world'})
body = _post_body()
name = (body.get('name') or '').strip()
if not name:
return _json({'ok': False, 'error': 'empty'})
if len(name) > 24:
return _json({'ok': False, 'error': 'too_long'})
if re.search(r'[\x00-\x1f\x7f]', name):
return _json({'ok': False, 'error': 'invalid_char'})
old = world.name
world.name = name
_logger.info('yt.world rename uid=%s %r -> %r', user.id, old, name)
return _json({'ok': True, 'name': name})
# ========== 图鉴 API ==========
def _records_json(self, model, fields):
"""将 model 的全部记录转为 [{field: value}, ...] 格式Json 字段解析为对象)。"""
recs = request.env[model].sudo().search([], order='sequence, id')
out = []
for r in recs:
item = {}
for f in fields:
val = getattr(r, f)
if isinstance(val, str):
try:
val = json.loads(val)
except (ValueError, TypeError):
pass
item[f] = val
out.append(item)
return _json(out)
@http.route('/yt_world/api/codex/resources', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def codex_resources(self, **kw):
return self._records_json('yt.codex.resource',
['code','name','icon','category','quality','source','uses','recipe','description'])
@http.route('/yt_world/api/codex/creatures', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def codex_creatures(self, **kw):
return self._records_json('yt.codex.creature',
['code','name','icon','habitat','tameable','diet','products','description'])
@http.route('/yt_world/api/codex/monsters', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def codex_monsters(self, **kw):
return self._records_json('yt.codex.monster',
['code','name','icon','tier','level_min','level_max','habitat','drops','description'])
@http.route('/yt_world/api/codex/races', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False)
def codex_races(self, **kw):
return self._records_json('yt.codex.race',
['code','name','icon','layer','traits','description'])