- 网站 7 个页签统一内边距/宽度/滚动方式,去掉重复页头 - 公告卡片改为图文大卡(支持后台 image/title/pinned 字段) - assets/terrain/ 拆 6 个子目录并同步 manifest + TERRAIN_ASSETS - 删除 assets/art/(transition 图片已清空) - 加载界面增加随机过渡背景图 + 深色压暗层 - 底部栏建造按钮拆为地形/移动/高度/管理 4 个独立按钮 - 侧面板改为从底部向上展开(WorldBox 风格) - 删除视频背景文件(已回滚为静态 JPG) - 新增 wc1.js(六边形地图渲染)、wc2.js(导航控件)
89 lines
3.3 KiB
Python
89 lines
3.3 KiB
Python
# -*- 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 一致)。"""
|
||
return Response(
|
||
json.dumps(data, ensure_ascii=False),
|
||
content_type='application/json; charset=utf-8',
|
||
)
|
||
|
||
|
||
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})
|