Add basic Metal Gear Arcade support
This commit is contained in:
parent
ff63b35de3
commit
8dccd48faf
@ -19,6 +19,7 @@ include bemani/frontend/static/controllers/iidx/*.js
|
|||||||
include bemani/frontend/static/controllers/popn/*.js
|
include bemani/frontend/static/controllers/popn/*.js
|
||||||
include bemani/frontend/static/controllers/jubeat/*.js
|
include bemani/frontend/static/controllers/jubeat/*.js
|
||||||
include bemani/frontend/static/controllers/bishi/*.js
|
include bemani/frontend/static/controllers/bishi/*.js
|
||||||
|
include bemani/frontend/static/controllers/mga/*.js
|
||||||
include bemani/frontend/static/controllers/ddr/*.js
|
include bemani/frontend/static/controllers/ddr/*.js
|
||||||
include bemani/frontend/static/controllers/reflec/*.js
|
include bemani/frontend/static/controllers/reflec/*.js
|
||||||
include bemani/frontend/static/controllers/sdvx/*.js
|
include bemani/frontend/static/controllers/sdvx/*.js
|
||||||
|
@ -222,7 +222,7 @@ BEMANI games boot and supports full profile and events for Beatmania IIDX 20-26,
|
|||||||
Pop'n Music 19-24, Jubeat Saucer, Saucer Fulfill, Prop, Qubell and Clan, Sound Voltex
|
Pop'n Music 19-24, Jubeat Saucer, Saucer Fulfill, Prop, Qubell and Clan, Sound Voltex
|
||||||
1, 2, 3 Season 1/2 and 4, Dance Dance Revolution X2, X3, 2013, 2014 and Ace, MÚSECA 1,
|
1, 2, 3 Season 1/2 and 4, Dance Dance Revolution X2, X3, 2013, 2014 and Ace, MÚSECA 1,
|
||||||
MÚSECA 1+1/2, MÚSECA Plus, Reflec Beat, Limelight, Colette, groovin'!! Upper, Volzza
|
MÚSECA 1+1/2, MÚSECA Plus, Reflec Beat, Limelight, Colette, groovin'!! Upper, Volzza
|
||||||
1 and Volzza 2, and finally The\*BishiBashi.
|
1 and Volzza 2, Metal Gear Arcade, and finally The\*BishiBashi.
|
||||||
|
|
||||||
Do not use this utility to serve production traffic. Instead, see
|
Do not use this utility to serve production traffic. Instead, see
|
||||||
`bemani/wsgi/api.wsgi` for a ready-to-go WSGI file that can be used with a Python
|
`bemani/wsgi/api.wsgi` for a ready-to-go WSGI file that can be used with a Python
|
||||||
|
8
bemani/backend/mga/__init__.py
Normal file
8
bemani/backend/mga/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from bemani.backend.mga.factory import MetalGearArcadeFactory
|
||||||
|
from bemani.backend.mga.base import MetalGearArcadeBase
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"MetalGearArcadeFactory",
|
||||||
|
"MetalGearArcadeBase",
|
||||||
|
]
|
21
bemani/backend/mga/base.py
Normal file
21
bemani/backend/mga/base.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# vim: set fileencoding=utf-8
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from bemani.backend.base import Base
|
||||||
|
from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler
|
||||||
|
from bemani.common import GameConstants
|
||||||
|
|
||||||
|
|
||||||
|
class MetalGearArcadeBase(CoreHandler, CardManagerHandler, PASELIHandler, Base):
|
||||||
|
"""
|
||||||
|
Base game class for Metal Gear Arcade.
|
||||||
|
"""
|
||||||
|
|
||||||
|
game = GameConstants.MGA
|
||||||
|
|
||||||
|
def previous_version(self) -> Optional['MetalGearArcadeBase']:
|
||||||
|
"""
|
||||||
|
Returns the previous version of the game, based on this game. Should
|
||||||
|
be overridden.
|
||||||
|
"""
|
||||||
|
return None
|
27
bemani/backend/mga/factory.py
Normal file
27
bemani/backend/mga/factory.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from bemani.backend.base import Base, Factory
|
||||||
|
from bemani.backend.mga.mga import MetalGearArcade
|
||||||
|
from bemani.common import Model
|
||||||
|
from bemani.data import Config, Data
|
||||||
|
|
||||||
|
|
||||||
|
class MetalGearArcadeFactory(Factory):
|
||||||
|
|
||||||
|
MANAGED_CLASSES = [
|
||||||
|
MetalGearArcade,
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_all(cls) -> None:
|
||||||
|
for gamecode in ['I36']:
|
||||||
|
Base.register(gamecode, MetalGearArcadeFactory)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]:
|
||||||
|
|
||||||
|
if model.gamecode == 'I36':
|
||||||
|
return MetalGearArcade(data, config, model)
|
||||||
|
|
||||||
|
# Unknown game version
|
||||||
|
return None
|
177
bemani/backend/mga/mga.py
Normal file
177
bemani/backend/mga/mga.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
# vim: set fileencoding=utf-8
|
||||||
|
import copy
|
||||||
|
import base64
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
from bemani.backend.mga.base import MetalGearArcadeBase
|
||||||
|
from bemani.backend.ess import EventLogHandler
|
||||||
|
from bemani.common import Profile, VersionConstants, Time
|
||||||
|
from bemani.data import UserID
|
||||||
|
from bemani.protocol import Node
|
||||||
|
|
||||||
|
|
||||||
|
class MetalGearArcade(
|
||||||
|
EventLogHandler,
|
||||||
|
MetalGearArcadeBase,
|
||||||
|
):
|
||||||
|
|
||||||
|
name = "Metal Gear Arcade"
|
||||||
|
version = VersionConstants.MGA
|
||||||
|
|
||||||
|
def __update_shop_name(self, profiledata: bytes) -> None:
|
||||||
|
# Figure out the profile type
|
||||||
|
csvs = profiledata.split(b',')
|
||||||
|
if len(csvs) < 2:
|
||||||
|
# Not long enough to care about
|
||||||
|
return
|
||||||
|
datatype = csvs[1].decode('ascii')
|
||||||
|
if datatype != 'PLAYDATA':
|
||||||
|
# Not the right profile type requested
|
||||||
|
return
|
||||||
|
|
||||||
|
# Grab the shop name
|
||||||
|
try:
|
||||||
|
shopname = csvs[30].decode('shift-jis')
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
self.update_machine_name(shopname)
|
||||||
|
|
||||||
|
def handle_system_getmaster_request(self, request: Node) -> Node:
|
||||||
|
# See if we can grab the request
|
||||||
|
data = request.child('data')
|
||||||
|
if not data:
|
||||||
|
root = Node.void('system')
|
||||||
|
root.add_child(Node.s32('result', 0))
|
||||||
|
return root
|
||||||
|
|
||||||
|
# Figure out what type of messsage this is
|
||||||
|
reqtype = data.child_value('datatype')
|
||||||
|
reqkey = data.child_value('datakey')
|
||||||
|
|
||||||
|
# System message
|
||||||
|
root = Node.void('system')
|
||||||
|
|
||||||
|
if reqtype == "S_SRVMSG" and reqkey == "INFO":
|
||||||
|
# Generate system message
|
||||||
|
settings1_str = "2011081000:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1"
|
||||||
|
settings2_str = "1,1,1,1,1,1,1,1,1,1,1,1,1,1"
|
||||||
|
|
||||||
|
# Send it to the client, making sure to inform the client that it was valid.
|
||||||
|
root.add_child(Node.string('strdata1', base64.b64encode(settings1_str.encode('ascii')).decode('ascii')))
|
||||||
|
root.add_child(Node.string('strdata2', base64.b64encode(settings2_str.encode('ascii')).decode('ascii')))
|
||||||
|
root.add_child(Node.u64('updatedate', Time.now() * 1000))
|
||||||
|
root.add_child(Node.s32('result', 1))
|
||||||
|
else:
|
||||||
|
# Unknown message.
|
||||||
|
root.add_child(Node.s32('result', 0))
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node:
|
||||||
|
# Look up user by refid
|
||||||
|
refid = request.child_value('data/eaid')
|
||||||
|
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
|
||||||
|
if userid is None:
|
||||||
|
root = Node.void('playerdata')
|
||||||
|
root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here.
|
||||||
|
return root
|
||||||
|
|
||||||
|
# Extract new profile info from old profile
|
||||||
|
oldprofile = self.get_profile(userid)
|
||||||
|
is_new = False
|
||||||
|
if oldprofile is None:
|
||||||
|
oldprofile = Profile(self.game, self.version, refid, 0)
|
||||||
|
is_new = True
|
||||||
|
newprofile = self.unformat_profile(userid, request, oldprofile, is_new)
|
||||||
|
|
||||||
|
# Write new profile
|
||||||
|
self.put_profile(userid, newprofile)
|
||||||
|
|
||||||
|
# Return success!
|
||||||
|
root = Node.void('playerdata')
|
||||||
|
root.add_child(Node.s32('result', 0))
|
||||||
|
return root
|
||||||
|
|
||||||
|
def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node:
|
||||||
|
# Look up user by refid
|
||||||
|
refid = request.child_value('data/eaid')
|
||||||
|
profiletypes = request.child_value('data/recv_csv').split(',')
|
||||||
|
profile = None
|
||||||
|
userid = None
|
||||||
|
if refid is not None:
|
||||||
|
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
|
||||||
|
if userid is not None:
|
||||||
|
profile = self.get_profile(userid)
|
||||||
|
if profile is not None:
|
||||||
|
return self.format_profile(userid, profiletypes, profile)
|
||||||
|
else:
|
||||||
|
root = Node.void('playerdata')
|
||||||
|
root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here.
|
||||||
|
return root
|
||||||
|
|
||||||
|
def format_profile(self, userid: UserID, profiletypes: List[str], profile: Profile) -> Node:
|
||||||
|
root = Node.void('playerdata')
|
||||||
|
root.add_child(Node.s32('result', 0))
|
||||||
|
player = Node.void('player')
|
||||||
|
root.add_child(player)
|
||||||
|
records = 0
|
||||||
|
record = Node.void('record')
|
||||||
|
player.add_child(record)
|
||||||
|
|
||||||
|
for profiletype in profiletypes:
|
||||||
|
if profiletype == "3fffffffff":
|
||||||
|
continue
|
||||||
|
for j in range(len(profile['strdatas'])):
|
||||||
|
strdata = profile['strdatas'][j]
|
||||||
|
bindata = profile['bindatas'][j]
|
||||||
|
|
||||||
|
# Figure out the profile type
|
||||||
|
csvs = strdata.split(b',')
|
||||||
|
if len(csvs) < 2:
|
||||||
|
# Not long enough to care about
|
||||||
|
continue
|
||||||
|
datatype = csvs[1].decode('ascii')
|
||||||
|
if datatype != profiletype:
|
||||||
|
# Not the right profile type requested
|
||||||
|
continue
|
||||||
|
|
||||||
|
# This is a valid profile node for this type, lets return only the profile values
|
||||||
|
strdata = b','.join(csvs[2:])
|
||||||
|
d = Node.string('d', base64.b64encode(strdata).decode('ascii'))
|
||||||
|
record.add_child(d)
|
||||||
|
d.add_child(Node.string('bin1', base64.b64encode(bindata).decode('ascii')))
|
||||||
|
|
||||||
|
# Remember that we had this record
|
||||||
|
records = records + 1
|
||||||
|
|
||||||
|
player.add_child(Node.u32('record_num', records))
|
||||||
|
return root
|
||||||
|
|
||||||
|
def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool) -> Profile:
|
||||||
|
# Profile save request, data values are base64 encoded.
|
||||||
|
# d is a CSV, and bin1 is binary data.
|
||||||
|
newprofile = copy.deepcopy(oldprofile)
|
||||||
|
strdatas: List[bytes] = []
|
||||||
|
bindatas: List[bytes] = []
|
||||||
|
|
||||||
|
record = request.child('data/record')
|
||||||
|
for node in record.children:
|
||||||
|
if node.name != 'd':
|
||||||
|
continue
|
||||||
|
|
||||||
|
profile = base64.b64decode(node.value)
|
||||||
|
# Update the shop name if this is a new profile, since we know it came
|
||||||
|
# from this cabinet. This is the only source of truth for what the
|
||||||
|
# cabinet shop name is set to.
|
||||||
|
if is_new:
|
||||||
|
self.__update_shop_name(profile)
|
||||||
|
strdatas.append(profile)
|
||||||
|
bindatas.append(base64.b64decode(node.child_value('bin1')))
|
||||||
|
|
||||||
|
newprofile['strdatas'] = strdatas
|
||||||
|
newprofile['bindatas'] = bindatas
|
||||||
|
|
||||||
|
# Keep track of play statistics across all versions
|
||||||
|
self.update_play_statistics(userid)
|
||||||
|
|
||||||
|
return newprofile
|
@ -14,6 +14,7 @@ class GameConstants(Enum):
|
|||||||
DDR: Final[str] = 'ddr'
|
DDR: Final[str] = 'ddr'
|
||||||
IIDX: Final[str] = 'iidx'
|
IIDX: Final[str] = 'iidx'
|
||||||
JUBEAT: Final[str] = 'jubeat'
|
JUBEAT: Final[str] = 'jubeat'
|
||||||
|
MGA: Final[str] = 'mga'
|
||||||
MUSECA: Final[str] = 'museca'
|
MUSECA: Final[str] = 'museca'
|
||||||
POPN_MUSIC: Final[str] = 'pnm'
|
POPN_MUSIC: Final[str] = 'pnm'
|
||||||
REFLEC_BEAT: Final[str] = 'reflec'
|
REFLEC_BEAT: Final[str] = 'reflec'
|
||||||
@ -89,6 +90,8 @@ class VersionConstants:
|
|||||||
JUBEAT_CLAN: Final[int] = 12
|
JUBEAT_CLAN: Final[int] = 12
|
||||||
JUBEAT_FESTO: Final[int] = 13
|
JUBEAT_FESTO: Final[int] = 13
|
||||||
|
|
||||||
|
MGA: Final[int] = 1
|
||||||
|
|
||||||
MUSECA: Final[int] = 1
|
MUSECA: Final[int] = 1
|
||||||
MUSECA_1_PLUS: Final[int] = 2
|
MUSECA_1_PLUS: Final[int] = 2
|
||||||
|
|
||||||
|
@ -246,6 +246,7 @@ def viewmachines() -> Response:
|
|||||||
GameConstants.DDR.value: 'DDR',
|
GameConstants.DDR.value: 'DDR',
|
||||||
GameConstants.IIDX.value: 'IIDX',
|
GameConstants.IIDX.value: 'IIDX',
|
||||||
GameConstants.JUBEAT.value: 'Jubeat',
|
GameConstants.JUBEAT.value: 'Jubeat',
|
||||||
|
GameConstants.MGA.value: 'Metal Gear Arcade',
|
||||||
GameConstants.MUSECA.value: 'MÚSECA',
|
GameConstants.MUSECA.value: 'MÚSECA',
|
||||||
GameConstants.POPN_MUSIC.value: 'Pop\'n Music',
|
GameConstants.POPN_MUSIC.value: 'Pop\'n Music',
|
||||||
GameConstants.REFLEC_BEAT.value: 'Reflec Beat',
|
GameConstants.REFLEC_BEAT.value: 'Reflec Beat',
|
||||||
|
@ -335,6 +335,35 @@ def navigation() -> Dict[str, Any]:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if GameConstants.MGA in g.config.support:
|
||||||
|
# Metal Gear Arcade pages
|
||||||
|
mga_entries = []
|
||||||
|
if len([p for p in profiles if p[0] == GameConstants.MGA]) > 0:
|
||||||
|
mga_entries.extend([
|
||||||
|
{
|
||||||
|
'label': 'Game Options',
|
||||||
|
'uri': url_for('mga_pages.viewsettings'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': 'Personal Profile',
|
||||||
|
'uri': url_for('mga_pages.viewplayer', userid=g.userID),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
mga_entries.extend([
|
||||||
|
{
|
||||||
|
'label': 'All Players',
|
||||||
|
'uri': url_for('mga_pages.viewplayers'),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
pages.append(
|
||||||
|
{
|
||||||
|
'label': 'Metal Gear Arcade',
|
||||||
|
'entries': mga_entries,
|
||||||
|
'base_uri': app.blueprints['mga_pages'].url_prefix,
|
||||||
|
'gamecode': GameConstants.MGA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if GameConstants.DDR in g.config.support:
|
if GameConstants.DDR in g.config.support:
|
||||||
# DDR pages
|
# DDR pages
|
||||||
ddr_entries = []
|
ddr_entries = []
|
||||||
|
@ -33,6 +33,7 @@ def format_machine(machine: Machine) -> Dict[str, Any]:
|
|||||||
GameConstants.DDR: 'DDR',
|
GameConstants.DDR: 'DDR',
|
||||||
GameConstants.IIDX: 'IIDX',
|
GameConstants.IIDX: 'IIDX',
|
||||||
GameConstants.JUBEAT: 'Jubeat',
|
GameConstants.JUBEAT: 'Jubeat',
|
||||||
|
GameConstants.MGA: 'Metal Gear Arcade',
|
||||||
GameConstants.MUSECA: 'MÚSECA',
|
GameConstants.MUSECA: 'MÚSECA',
|
||||||
GameConstants.POPN_MUSIC: 'Pop\'n Music',
|
GameConstants.POPN_MUSIC: 'Pop\'n Music',
|
||||||
GameConstants.REFLEC_BEAT: 'Reflec Beat',
|
GameConstants.REFLEC_BEAT: 'Reflec Beat',
|
||||||
|
8
bemani/frontend/mga/__init__.py
Normal file
8
bemani/frontend/mga/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from bemani.frontend.mga.endpoints import mga_pages
|
||||||
|
from bemani.frontend.mga.cache import MetalGearArcadeCache
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"MetalGearArcadeCache",
|
||||||
|
"mga_pages",
|
||||||
|
]
|
8
bemani/frontend/mga/cache.py
Normal file
8
bemani/frontend/mga/cache.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from bemani.data import Config, Data
|
||||||
|
|
||||||
|
|
||||||
|
class MetalGearArcadeCache:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def preload(cls, data: Data, config: Config) -> None:
|
||||||
|
pass
|
175
bemani/frontend/mga/endpoints.py
Normal file
175
bemani/frontend/mga/endpoints.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
# vim: set fileencoding=utf-8
|
||||||
|
import re
|
||||||
|
from typing import Any, Dict
|
||||||
|
from flask import Blueprint, request, Response, url_for, abort
|
||||||
|
|
||||||
|
from bemani.common import GameConstants
|
||||||
|
from bemani.data import UserID
|
||||||
|
from bemani.frontend.app import loginrequired, jsonify, render_react
|
||||||
|
from bemani.frontend.mga.mga import MetalGearArcadeFrontend
|
||||||
|
from bemani.frontend.templates import templates_location
|
||||||
|
from bemani.frontend.static import static_location
|
||||||
|
from bemani.frontend.types import g
|
||||||
|
|
||||||
|
|
||||||
|
mga_pages = Blueprint(
|
||||||
|
'mga_pages',
|
||||||
|
__name__,
|
||||||
|
url_prefix='/mga',
|
||||||
|
template_folder=templates_location,
|
||||||
|
static_folder=static_location,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mga_pages.route('/players')
|
||||||
|
@loginrequired
|
||||||
|
def viewplayers() -> Response:
|
||||||
|
frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache)
|
||||||
|
return render_react(
|
||||||
|
'All MGA Players',
|
||||||
|
'mga/allplayers.react.js',
|
||||||
|
{
|
||||||
|
'players': frontend.get_all_players()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'refresh': url_for('mga_pages.listplayers'),
|
||||||
|
'player': url_for('mga_pages.viewplayer', userid=-1),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mga_pages.route('/players/list')
|
||||||
|
@jsonify
|
||||||
|
@loginrequired
|
||||||
|
def listplayers() -> Dict[str, Any]:
|
||||||
|
frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache)
|
||||||
|
return {
|
||||||
|
'players': frontend.get_all_players(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mga_pages.route('/players/<int:userid>')
|
||||||
|
@loginrequired
|
||||||
|
def viewplayer(userid: UserID) -> Response:
|
||||||
|
frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache)
|
||||||
|
djinfo = frontend.get_all_player_info([userid])[userid]
|
||||||
|
if not djinfo:
|
||||||
|
abort(404)
|
||||||
|
latest_version = sorted(djinfo.keys(), reverse=True)[0]
|
||||||
|
|
||||||
|
return render_react(
|
||||||
|
f'{djinfo[latest_version]["name"]}\'s MGA Profile',
|
||||||
|
'mga/player.react.js',
|
||||||
|
{
|
||||||
|
'playerid': userid,
|
||||||
|
'own_profile': userid == g.userID,
|
||||||
|
'player': djinfo,
|
||||||
|
'versions': {version: name for (game, version, name) in frontend.all_games()},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'refresh': url_for('mga_pages.listplayer', userid=userid),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mga_pages.route('/players/<int:userid>/list')
|
||||||
|
@jsonify
|
||||||
|
@loginrequired
|
||||||
|
def listplayer(userid: UserID) -> Dict[str, Any]:
|
||||||
|
frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache)
|
||||||
|
djinfo = frontend.get_all_player_info([userid])[userid]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'player': djinfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mga_pages.route('/options')
|
||||||
|
@loginrequired
|
||||||
|
def viewsettings() -> Response:
|
||||||
|
frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache)
|
||||||
|
userid = g.userID
|
||||||
|
djinfo = frontend.get_all_player_info([userid])[userid]
|
||||||
|
if not djinfo:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
return render_react(
|
||||||
|
'Metal Gear Arcade Game Settings',
|
||||||
|
'mga/settings.react.js',
|
||||||
|
{
|
||||||
|
'player': djinfo,
|
||||||
|
'versions': {version: name for (game, version, name) in frontend.all_games()},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'updatename': url_for('mga_pages.updatename'),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mga_pages.route('/options/name/update', methods=['POST'])
|
||||||
|
@jsonify
|
||||||
|
@loginrequired
|
||||||
|
def updatename() -> Dict[str, Any]:
|
||||||
|
frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache)
|
||||||
|
version = int(request.get_json()['version'])
|
||||||
|
name = request.get_json()['name']
|
||||||
|
user = g.data.local.user.get_user(g.userID)
|
||||||
|
if user is None:
|
||||||
|
raise Exception('Unable to find user to update!')
|
||||||
|
|
||||||
|
# Grab profile and update dj name
|
||||||
|
profile = g.data.local.user.get_profile(GameConstants.MGA, version, user.id)
|
||||||
|
if profile is None:
|
||||||
|
raise Exception('Unable to find profile to update!')
|
||||||
|
if len(name) == 0 or len(name) > 6:
|
||||||
|
raise Exception('Invalid profile name!')
|
||||||
|
|
||||||
|
# Convert lowercase to uppercase. We allow lowercase widetext in
|
||||||
|
# the JS frontend to allow for Windows IME input of hiragana/katakana.
|
||||||
|
def conv(char: str) -> str:
|
||||||
|
i = ord(char)
|
||||||
|
if i >= 0xFF41 and i <= 0xFF5A:
|
||||||
|
return chr(i - (0xFF41 - 0xFF21))
|
||||||
|
else:
|
||||||
|
return char
|
||||||
|
name = ''.join([conv(a) for a in name])
|
||||||
|
|
||||||
|
if re.match(
|
||||||
|
"^[" +
|
||||||
|
"\uFF20-\uFF3A" + # widetext A-Z, @
|
||||||
|
"\uFF10-\uFF19" + # widetext 0-9
|
||||||
|
"\u3041-\u308D\u308F\u3092\u3093" + # hiragana
|
||||||
|
"\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC" + # katakana
|
||||||
|
"\u3000" + # widetext blank space
|
||||||
|
"\u301C" + # widetext ~
|
||||||
|
"\u30FB" + # widetext middot
|
||||||
|
"\u30FC" + # widetext long dash
|
||||||
|
"\u2212" + # widetext short dash
|
||||||
|
"\u2605" + # widetext heavy star
|
||||||
|
"\uFF01" + # widetext !
|
||||||
|
"\uFF03" + # widetext #
|
||||||
|
"\uFF04" + # widetext $
|
||||||
|
"\uFF05" + # widetext %
|
||||||
|
"\uFF06" + # widetext &
|
||||||
|
"\uFF08" + # widetext (
|
||||||
|
"\uFF09" + # widetext )
|
||||||
|
"\uFF0A" + # widetext *
|
||||||
|
"\uFF0B" + # widetext +
|
||||||
|
"\uFF0F" + # widetext /
|
||||||
|
"\uFF1C" + # widetext <
|
||||||
|
"\uFF1D" + # widetext =
|
||||||
|
"\uFF1E" + # widetext >
|
||||||
|
"\uFF1F" + # widetext ?
|
||||||
|
"\uFFE5" + # widetext Yen symbol
|
||||||
|
"]*$",
|
||||||
|
name,
|
||||||
|
) is None:
|
||||||
|
raise Exception('Invalid profile name!')
|
||||||
|
profile = frontend.update_name(profile, name)
|
||||||
|
g.data.local.user.put_profile(GameConstants.MGA, version, user.id, profile)
|
||||||
|
|
||||||
|
# Return that we updated
|
||||||
|
return {
|
||||||
|
'version': version,
|
||||||
|
'name': frontend.sanitize_name(name),
|
||||||
|
}
|
88
bemani/frontend/mga/mga.py
Normal file
88
bemani/frontend/mga/mga.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# vim: set fileencoding=utf-8
|
||||||
|
import copy
|
||||||
|
from typing import Any, Dict, Iterator, Tuple
|
||||||
|
|
||||||
|
from flask_caching import Cache # type: ignore
|
||||||
|
|
||||||
|
from bemani.backend.mga import MetalGearArcadeFactory
|
||||||
|
from bemani.common import Profile, ValidatedDict, ID, GameConstants
|
||||||
|
from bemani.data import Data
|
||||||
|
from bemani.frontend.base import FrontendBase
|
||||||
|
|
||||||
|
|
||||||
|
class MetalGearArcadeFrontend(FrontendBase):
|
||||||
|
|
||||||
|
game = GameConstants.MGA
|
||||||
|
|
||||||
|
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||||
|
super().__init__(data, config, cache)
|
||||||
|
self.machines: Dict[int, str] = {}
|
||||||
|
|
||||||
|
def all_games(self) -> Iterator[Tuple[str, int, str]]:
|
||||||
|
yield from MetalGearArcadeFactory.all_games()
|
||||||
|
|
||||||
|
def __update_value(self, oldvalue: str, newvalue: bytes) -> str:
|
||||||
|
try:
|
||||||
|
newstr = newvalue.decode('shift-jis')
|
||||||
|
except Exception:
|
||||||
|
newstr = ''
|
||||||
|
if len(newstr) == 0:
|
||||||
|
return oldvalue
|
||||||
|
else:
|
||||||
|
return newstr
|
||||||
|
|
||||||
|
def sanitize_name(self, name: str) -> str:
|
||||||
|
if len(name) == 0:
|
||||||
|
return 'なし'
|
||||||
|
return name
|
||||||
|
|
||||||
|
def update_name(self, profile: Profile, name: str) -> Profile:
|
||||||
|
newprofile = copy.deepcopy(profile)
|
||||||
|
for i in range(len(newprofile['strdatas'])):
|
||||||
|
strdata = newprofile['strdatas'][i]
|
||||||
|
|
||||||
|
# Figure out the profile type
|
||||||
|
csvs = strdata.split(b',')
|
||||||
|
if len(csvs) < 2:
|
||||||
|
# Not long enough to care about
|
||||||
|
continue
|
||||||
|
datatype = csvs[1].decode('ascii')
|
||||||
|
if datatype != 'PLAYDATA':
|
||||||
|
# Not the right profile type requested
|
||||||
|
continue
|
||||||
|
csvs[27] = name.encode('shift-jis')
|
||||||
|
newprofile['strdatas'][i] = b','.join(csvs)
|
||||||
|
|
||||||
|
return newprofile
|
||||||
|
|
||||||
|
def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]:
|
||||||
|
name = 'なし' # Nothing
|
||||||
|
shop = '未設定' # Not set
|
||||||
|
shop_area = '未設定' # Not set
|
||||||
|
|
||||||
|
for i in range(len(profile['strdatas'])):
|
||||||
|
strdata = profile['strdatas'][i]
|
||||||
|
|
||||||
|
# Figure out the profile type
|
||||||
|
csvs = strdata.split(b',')
|
||||||
|
if len(csvs) < 2:
|
||||||
|
# Not long enough to care about
|
||||||
|
continue
|
||||||
|
datatype = csvs[1].decode('ascii')
|
||||||
|
if datatype != 'PLAYDATA':
|
||||||
|
# Not the right profile type requested
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = self.__update_value(name, csvs[27])
|
||||||
|
shop = self.__update_value(shop, csvs[30])
|
||||||
|
shop_area = self.__update_value(shop_area, csvs[31])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'extid': ID.format_extid(profile.extid),
|
||||||
|
'shop': shop,
|
||||||
|
'shop_area': shop_area,
|
||||||
|
'first_play_time': playstats.get_int('first_play_timestamp'),
|
||||||
|
'last_play_time': playstats.get_int('last_play_timestamp'),
|
||||||
|
'plays': playstats.get_int('total_plays'),
|
||||||
|
}
|
109
bemani/frontend/static/controllers/mga/allplayers.react.js
Normal file
109
bemani/frontend/static/controllers/mga/allplayers.react.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*** @jsx React.DOM */
|
||||||
|
|
||||||
|
var all_players = React.createClass({
|
||||||
|
|
||||||
|
getInitialState: function(props) {
|
||||||
|
return {
|
||||||
|
players: window.players,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.refreshPlayers();
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshPlayers: function() {
|
||||||
|
AJAX.get(
|
||||||
|
Link.get('refresh'),
|
||||||
|
function(response) {
|
||||||
|
this.setState({
|
||||||
|
players: response.players,
|
||||||
|
});
|
||||||
|
// Refresh every 30 seconds
|
||||||
|
setTimeout(this.refreshPlayers, 30000);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="section">
|
||||||
|
<Table
|
||||||
|
className="list players"
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
name: 'Name',
|
||||||
|
render: function(userid) {
|
||||||
|
var player = this.state.players[userid];
|
||||||
|
return <a href={Link.get('player', userid)}>{ player.name }</a>;
|
||||||
|
}.bind(this),
|
||||||
|
sort: function(aid, bid) {
|
||||||
|
var a = this.state.players[aid];
|
||||||
|
var b = this.state.players[bid];
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}.bind(this),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Metal Gear Arcade ID',
|
||||||
|
render: function(userid) {
|
||||||
|
var player = this.state.players[userid];
|
||||||
|
return player.extid;
|
||||||
|
}.bind(this),
|
||||||
|
sort: function(aid, bid) {
|
||||||
|
var a = this.state.players[aid];
|
||||||
|
var b = this.state.players[bid];
|
||||||
|
return a.extid.localeCompare(b.extid);
|
||||||
|
}.bind(this),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Play Count',
|
||||||
|
render: function(userid) {
|
||||||
|
var player = this.state.players[userid];
|
||||||
|
return player.plays;
|
||||||
|
}.bind(this),
|
||||||
|
sort: function(aid, bid) {
|
||||||
|
var a = this.state.players[aid];
|
||||||
|
var b = this.state.players[bid];
|
||||||
|
return a.plays - b.plays;
|
||||||
|
}.bind(this),
|
||||||
|
reverse: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Region',
|
||||||
|
render: function(userid) {
|
||||||
|
var player = this.state.players[userid];
|
||||||
|
return player.shop_area;
|
||||||
|
}.bind(this),
|
||||||
|
sort: function(aid, bid) {
|
||||||
|
var a = this.state.players[aid];
|
||||||
|
var b = this.state.players[bid];
|
||||||
|
return a.shop_area.localeCompare(b.shop_area);
|
||||||
|
}.bind(this),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Arcade',
|
||||||
|
render: function(userid) {
|
||||||
|
var player = this.state.players[userid];
|
||||||
|
return player.shop;
|
||||||
|
}.bind(this),
|
||||||
|
sort: function(aid, bid) {
|
||||||
|
var a = this.state.players[aid];
|
||||||
|
var b = this.state.players[bid];
|
||||||
|
return a.shop.localeCompare(b.shop);
|
||||||
|
}.bind(this),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rows={Object.keys(this.state.players)}
|
||||||
|
paginate={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
React.createElement(all_players, null),
|
||||||
|
document.getElementById('content')
|
||||||
|
);
|
108
bemani/frontend/static/controllers/mga/player.react.js
Normal file
108
bemani/frontend/static/controllers/mga/player.react.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*** @jsx React.DOM */
|
||||||
|
|
||||||
|
var valid_versions = Object.keys(window.versions);
|
||||||
|
var pagenav = new History(valid_versions);
|
||||||
|
|
||||||
|
var profile_view = React.createClass({
|
||||||
|
|
||||||
|
getInitialState: function(props) {
|
||||||
|
var profiles = Object.keys(window.player);
|
||||||
|
return {
|
||||||
|
player: window.player,
|
||||||
|
profiles: profiles,
|
||||||
|
version: pagenav.getInitialState(profiles[profiles.length - 1]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
pagenav.onChange(function(version) {
|
||||||
|
this.setState({version: version});
|
||||||
|
}.bind(this));
|
||||||
|
this.refreshProfile();
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshProfile: function() {
|
||||||
|
AJAX.get(
|
||||||
|
Link.get('refresh'),
|
||||||
|
function(response) {
|
||||||
|
var profiles = Object.keys(response.player);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
player: response.player,
|
||||||
|
profiles: profiles,
|
||||||
|
});
|
||||||
|
setTimeout(this.refreshProfile, 5000);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
if (this.state.player[this.state.version]) {
|
||||||
|
var player = this.state.player[this.state.version];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="section">
|
||||||
|
<h3>{player.name}'s profile</h3>
|
||||||
|
{this.state.profiles.map(function(version) {
|
||||||
|
return (
|
||||||
|
<Nav
|
||||||
|
title={window.versions[version]}
|
||||||
|
active={this.state.version == version}
|
||||||
|
onClick={function(event) {
|
||||||
|
if (this.state.version == version) { return; }
|
||||||
|
this.setState({version: version});
|
||||||
|
pagenav.navigate(version);
|
||||||
|
}.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}.bind(this))}
|
||||||
|
</div>
|
||||||
|
<div className="section">
|
||||||
|
<LabelledSection label="User ID">{player.extid}</LabelledSection>
|
||||||
|
<LabelledSection label="Register Time">
|
||||||
|
<Timestamp timestamp={player.first_play_time}/>
|
||||||
|
</LabelledSection>
|
||||||
|
<LabelledSection label="Last Play Time">
|
||||||
|
<Timestamp timestamp={player.last_play_time}/>
|
||||||
|
</LabelledSection>
|
||||||
|
<LabelledSection label="Total Plays">
|
||||||
|
{player.plays}回
|
||||||
|
</LabelledSection>
|
||||||
|
</div>
|
||||||
|
<div className="section">
|
||||||
|
<LabelledSection label="Home Shop">{player.shop}</LabelledSection>
|
||||||
|
<LabelledSection label="Home Shop Area">{player.shop_area}</LabelledSection>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="section">
|
||||||
|
{this.state.profiles.map(function(version) {
|
||||||
|
return (
|
||||||
|
<Nav
|
||||||
|
title={window.versions[version]}
|
||||||
|
active={this.state.version == version}
|
||||||
|
onClick={function(event) {
|
||||||
|
if (this.state.version == version) { return; }
|
||||||
|
this.setState({version: version});
|
||||||
|
pagenav.navigate(version);
|
||||||
|
}.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}.bind(this))}
|
||||||
|
</div>
|
||||||
|
<div className="section">
|
||||||
|
This player has no profile for {window.versions[this.state.version]}!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
React.createElement(profile_view, null),
|
||||||
|
document.getElementById('content')
|
||||||
|
);
|
242
bemani/frontend/static/controllers/mga/settings.react.js
Normal file
242
bemani/frontend/static/controllers/mga/settings.react.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/*** @jsx React.DOM */
|
||||||
|
|
||||||
|
var valid_versions = Object.keys(window.versions);
|
||||||
|
var pagenav = new History(valid_versions);
|
||||||
|
|
||||||
|
var settings_view = React.createClass({
|
||||||
|
|
||||||
|
getInitialState: function(props) {
|
||||||
|
var profiles = Object.keys(window.player);
|
||||||
|
var version = pagenav.getInitialState(profiles[profiles.length - 1]);
|
||||||
|
return {
|
||||||
|
player: window.player,
|
||||||
|
profiles: profiles,
|
||||||
|
version: version,
|
||||||
|
new_name: window.player[version].name,
|
||||||
|
editing_name: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
pagenav.onChange(function(version) {
|
||||||
|
this.setState({version: version});
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUpdate: function() {
|
||||||
|
if (this.focus_element && this.focus_element != this.already_focused) {
|
||||||
|
this.focus_element.focus();
|
||||||
|
this.already_focused = this.focus_element;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveName: function(event) {
|
||||||
|
AJAX.post(
|
||||||
|
Link.get('updatename'),
|
||||||
|
{
|
||||||
|
version: this.state.version,
|
||||||
|
name: this.state.new_name,
|
||||||
|
},
|
||||||
|
function(response) {
|
||||||
|
var player = this.state.player;
|
||||||
|
player[response.version].name = response.name;
|
||||||
|
this.setState({
|
||||||
|
player: player,
|
||||||
|
new_name: this.state.player[response.version].name,
|
||||||
|
editing_name: false,
|
||||||
|
});
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
renderName: function(player) {
|
||||||
|
return (
|
||||||
|
<LabelledSection vertical={true} label="Name">{
|
||||||
|
!this.state.editing_name ?
|
||||||
|
<span>
|
||||||
|
<span>{player.name}</span>
|
||||||
|
<Edit
|
||||||
|
onClick={function(event) {
|
||||||
|
this.setState({editing_name: true});
|
||||||
|
}.bind(this)}
|
||||||
|
/>
|
||||||
|
</span> :
|
||||||
|
<form className="inline" onSubmit={this.saveName}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="inline"
|
||||||
|
maxlength="6"
|
||||||
|
size="6"
|
||||||
|
autofocus="true"
|
||||||
|
ref={c => (this.focus_element = c)}
|
||||||
|
value={this.state.new_name}
|
||||||
|
onChange={function(event) {
|
||||||
|
var rawvalue = event.target.value;
|
||||||
|
var value = "";
|
||||||
|
// Nasty conversion to change typing into wide text
|
||||||
|
for (var i = 0; i < rawvalue.length; i++) {
|
||||||
|
var c = rawvalue.charCodeAt(i);
|
||||||
|
if (c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0)) {
|
||||||
|
c = 0xFF10 + (c - '0'.charCodeAt(0));
|
||||||
|
} else if(c >= 'A'.charCodeAt(0) && c <= 'Z'.charCodeAt(0)) {
|
||||||
|
c = 0xFF21 + (c - 'A'.charCodeAt(0));
|
||||||
|
} else if(c >= 'a'.charCodeAt(0) && c <= 'z'.charCodeAt(0)) {
|
||||||
|
c = 0xFF41 + (c - 'a'.charCodeAt(0));
|
||||||
|
} else if(c == '@'.charCodeAt(0)) {
|
||||||
|
c = 0xFF20;
|
||||||
|
} else if(c == ' '.charCodeAt(0)) {
|
||||||
|
c = 0x3000;
|
||||||
|
} else if(c == '~'.charCodeAt(0)) {
|
||||||
|
c = 0x301C;
|
||||||
|
} else if(c == '-'.charCodeAt(0)) {
|
||||||
|
c = 0x2212;
|
||||||
|
} else if(c == '!'.charCodeAt(0)) {
|
||||||
|
c = 0xFF01;
|
||||||
|
} else if(c == '#'.charCodeAt(0)) {
|
||||||
|
c = 0xFF03;
|
||||||
|
} else if(c == '$'.charCodeAt(0)) {
|
||||||
|
c = 0xFF04;
|
||||||
|
} else if(c == '%'.charCodeAt(0)) {
|
||||||
|
c = 0xFF04;
|
||||||
|
} else if(c == '&'.charCodeAt(0)) {
|
||||||
|
c = 0xFF06;
|
||||||
|
} else if(c == '('.charCodeAt(0)) {
|
||||||
|
c = 0xFF08;
|
||||||
|
} else if(c == ')'.charCodeAt(0)) {
|
||||||
|
c = 0xFF09;
|
||||||
|
} else if(c == '*'.charCodeAt(0)) {
|
||||||
|
c = 0xFF0A;
|
||||||
|
} else if(c == '+'.charCodeAt(0)) {
|
||||||
|
c = 0xFF0B;
|
||||||
|
} else if(c == '/'.charCodeAt(0)) {
|
||||||
|
c = 0xFF0F;
|
||||||
|
} else if(c == '<'.charCodeAt(0)) {
|
||||||
|
c = 0xFF1C;
|
||||||
|
} else if(c == '='.charCodeAt(0)) {
|
||||||
|
c = 0xFF1D;
|
||||||
|
} else if(c == '>'.charCodeAt(0)) {
|
||||||
|
c = 0xFF1E;
|
||||||
|
} else if(c == '?'.charCodeAt(0)) {
|
||||||
|
c = 0xFF1F;
|
||||||
|
}
|
||||||
|
value = value + String.fromCharCode(c);
|
||||||
|
}
|
||||||
|
var nameRegex = new RegExp(
|
||||||
|
"^[" +
|
||||||
|
"\uFF20-\uFF3A" + // widetext A-Z, @
|
||||||
|
"\uFF41-\uFF5A" + // widetext a-z (will be uppercased in backend)
|
||||||
|
"\uFF10-\uFF19" + // widetext 0-9
|
||||||
|
"\u3041-\u308D\u308F\u3092\u3093" + // hiragana
|
||||||
|
"\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC" + // katakana
|
||||||
|
"\u3000" + // widetext blank space
|
||||||
|
"\u301C" + // widetext ~
|
||||||
|
"\u30FB" + // widetext middot
|
||||||
|
"\u30FC" + // widetext long dash
|
||||||
|
"\u2212" + // widetext short dash
|
||||||
|
"\u2605" + // widetext heavy star
|
||||||
|
"\uFF01" + // widetext !
|
||||||
|
"\uFF03" + // widetext #
|
||||||
|
"\uFF04" + // widetext $
|
||||||
|
"\uFF05" + // widetext %
|
||||||
|
"\uFF06" + // widetext &
|
||||||
|
"\uFF08" + // widetext (
|
||||||
|
"\uFF09" + // widetext )
|
||||||
|
"\uFF0A" + // widetext *
|
||||||
|
"\uFF0B" + // widetext +
|
||||||
|
"\uFF0F" + // widetext /
|
||||||
|
"\uFF1C" + // widetext <
|
||||||
|
"\uFF1D" + // widetext =
|
||||||
|
"\uFF1E" + // widetext >
|
||||||
|
"\uFF1F" + // widetext ?
|
||||||
|
"\uFFE5" + // widetext Yen symbol
|
||||||
|
"]*$"
|
||||||
|
);
|
||||||
|
if (value.length <= 6 && nameRegex.test(value)) {
|
||||||
|
this.setState({new_name: value});
|
||||||
|
}
|
||||||
|
}.bind(this)}
|
||||||
|
name="name"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="save"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="button"
|
||||||
|
value="cancel"
|
||||||
|
onClick={function(event) {
|
||||||
|
this.setState({
|
||||||
|
new_name: this.state.player[this.state.version].name,
|
||||||
|
editing_name: false,
|
||||||
|
});
|
||||||
|
}.bind(this)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
}</LabelledSection>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
if (this.state.player[this.state.version]) {
|
||||||
|
var player = this.state.player[this.state.version];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="section">
|
||||||
|
{this.state.profiles.map(function(version) {
|
||||||
|
return (
|
||||||
|
<Nav
|
||||||
|
title={window.versions[version]}
|
||||||
|
active={this.state.version == version}
|
||||||
|
onClick={function(event) {
|
||||||
|
if (this.state.editing_name) { return; }
|
||||||
|
if (this.state.version == version) { return; }
|
||||||
|
this.setState({
|
||||||
|
version: version,
|
||||||
|
new_name: this.state.player[version].name,
|
||||||
|
});
|
||||||
|
pagenav.navigate(version);
|
||||||
|
}.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}.bind(this))}
|
||||||
|
</div>
|
||||||
|
<div className="section">
|
||||||
|
<h3>User Profile</h3>
|
||||||
|
{this.renderName(player)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="section">
|
||||||
|
You have no profile for {window.versions[this.state.version]}!
|
||||||
|
</div>
|
||||||
|
<div className="section">
|
||||||
|
{this.state.profiles.map(function(version) {
|
||||||
|
return (
|
||||||
|
<Nav
|
||||||
|
title={window.versions[version]}
|
||||||
|
active={this.state.version == version}
|
||||||
|
onClick={function(event) {
|
||||||
|
if (this.state.version == version) { return; }
|
||||||
|
this.setState({
|
||||||
|
version: version,
|
||||||
|
});
|
||||||
|
pagenav.navigate(version);
|
||||||
|
}.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}.bind(this))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
React.createElement(settings_view, null),
|
||||||
|
document.getElementById('content')
|
||||||
|
);
|
@ -2,6 +2,10 @@
|
|||||||
border-color: #EF6A32;
|
border-color: #EF6A32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mga.border {
|
||||||
|
border-color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
.ddr.border {
|
.ddr.border {
|
||||||
border-color: #017351;
|
border-color: #017351;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ from bemani.backend.ddr import DDRFactory
|
|||||||
from bemani.backend.sdvx import SoundVoltexFactory
|
from bemani.backend.sdvx import SoundVoltexFactory
|
||||||
from bemani.backend.reflec import ReflecBeatFactory
|
from bemani.backend.reflec import ReflecBeatFactory
|
||||||
from bemani.backend.museca import MusecaFactory
|
from bemani.backend.museca import MusecaFactory
|
||||||
|
from bemani.backend.mga import MetalGearArcadeFactory
|
||||||
from bemani.common import GameConstants
|
from bemani.common import GameConstants
|
||||||
from bemani.data import Config, Data
|
from bemani.data import Config, Data
|
||||||
|
|
||||||
@ -42,3 +43,5 @@ def register_games(config: Config) -> None:
|
|||||||
ReflecBeatFactory.register_all()
|
ReflecBeatFactory.register_all()
|
||||||
if GameConstants.MUSECA in config.support:
|
if GameConstants.MUSECA in config.support:
|
||||||
MusecaFactory.register_all()
|
MusecaFactory.register_all()
|
||||||
|
if GameConstants.MGA in config.support:
|
||||||
|
MetalGearArcadeFactory.register_all()
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from bemani.common import GameConstants
|
from bemani.common import GameConstants
|
||||||
from bemani.frontend import app, config # noqa: F401
|
from bemani.frontend import app, config # noqa: F401
|
||||||
from bemani.frontend.account import account_pages
|
from bemani.frontend.account import account_pages
|
||||||
@ -9,6 +8,7 @@ from bemani.frontend.home import home_pages
|
|||||||
from bemani.frontend.iidx import iidx_pages
|
from bemani.frontend.iidx import iidx_pages
|
||||||
from bemani.frontend.popn import popn_pages
|
from bemani.frontend.popn import popn_pages
|
||||||
from bemani.frontend.bishi import bishi_pages
|
from bemani.frontend.bishi import bishi_pages
|
||||||
|
from bemani.frontend.mga import mga_pages
|
||||||
from bemani.frontend.jubeat import jubeat_pages
|
from bemani.frontend.jubeat import jubeat_pages
|
||||||
from bemani.frontend.ddr import ddr_pages
|
from bemani.frontend.ddr import ddr_pages
|
||||||
from bemani.frontend.sdvx import sdvx_pages
|
from bemani.frontend.sdvx import sdvx_pages
|
||||||
@ -33,6 +33,8 @@ def register_blueprints() -> None:
|
|||||||
app.register_blueprint(jubeat_pages)
|
app.register_blueprint(jubeat_pages)
|
||||||
if GameConstants.BISHI_BASHI in config.support:
|
if GameConstants.BISHI_BASHI in config.support:
|
||||||
app.register_blueprint(bishi_pages)
|
app.register_blueprint(bishi_pages)
|
||||||
|
if GameConstants.MGA in config.support:
|
||||||
|
app.register_blueprint(mga_pages)
|
||||||
if GameConstants.DDR in config.support:
|
if GameConstants.DDR in config.support:
|
||||||
app.register_blueprint(ddr_pages)
|
app.register_blueprint(ddr_pages)
|
||||||
if GameConstants.SDVX in config.support:
|
if GameConstants.SDVX in config.support:
|
||||||
|
@ -5,6 +5,7 @@ from bemani.backend.popn import PopnMusicFactory
|
|||||||
from bemani.backend.jubeat import JubeatFactory
|
from bemani.backend.jubeat import JubeatFactory
|
||||||
from bemani.backend.iidx import IIDXFactory
|
from bemani.backend.iidx import IIDXFactory
|
||||||
from bemani.backend.bishi import BishiBashiFactory
|
from bemani.backend.bishi import BishiBashiFactory
|
||||||
|
from bemani.backend.mga import MetalGearArcadeFactory
|
||||||
from bemani.backend.ddr import DDRFactory
|
from bemani.backend.ddr import DDRFactory
|
||||||
from bemani.backend.sdvx import SoundVoltexFactory
|
from bemani.backend.sdvx import SoundVoltexFactory
|
||||||
from bemani.backend.reflec import ReflecBeatFactory
|
from bemani.backend.reflec import ReflecBeatFactory
|
||||||
@ -13,6 +14,7 @@ from bemani.frontend.popn import PopnMusicCache
|
|||||||
from bemani.frontend.iidx import IIDXCache
|
from bemani.frontend.iidx import IIDXCache
|
||||||
from bemani.frontend.jubeat import JubeatCache
|
from bemani.frontend.jubeat import JubeatCache
|
||||||
from bemani.frontend.bishi import BishiBashiCache
|
from bemani.frontend.bishi import BishiBashiCache
|
||||||
|
from bemani.frontend.mga import MetalGearArcadeCache
|
||||||
from bemani.frontend.ddr import DDRCache
|
from bemani.frontend.ddr import DDRCache
|
||||||
from bemani.frontend.sdvx import SoundVoltexCache
|
from bemani.frontend.sdvx import SoundVoltexCache
|
||||||
from bemani.frontend.reflec import ReflecBeatCache
|
from bemani.frontend.reflec import ReflecBeatCache
|
||||||
@ -40,6 +42,9 @@ def run_scheduled_work(config: Config) -> None:
|
|||||||
if GameConstants.BISHI_BASHI in config.support:
|
if GameConstants.BISHI_BASHI in config.support:
|
||||||
enabled_factories.append(BishiBashiFactory)
|
enabled_factories.append(BishiBashiFactory)
|
||||||
enabled_caches.append(BishiBashiCache)
|
enabled_caches.append(BishiBashiCache)
|
||||||
|
if GameConstants.MGA in config.support:
|
||||||
|
enabled_factories.append(MetalGearArcadeFactory)
|
||||||
|
enabled_caches.append(MetalGearArcadeCache)
|
||||||
if GameConstants.DDR in config.support:
|
if GameConstants.DDR in config.support:
|
||||||
enabled_factories.append(DDRFactory)
|
enabled_factories.append(DDRFactory)
|
||||||
enabled_caches.append(DDRCache)
|
enabled_caches.append(DDRCache)
|
||||||
|
2
setup.py
2
setup.py
@ -222,6 +222,7 @@ setup(
|
|||||||
'bemani.frontend.popn',
|
'bemani.frontend.popn',
|
||||||
'bemani.frontend.jubeat',
|
'bemani.frontend.jubeat',
|
||||||
'bemani.frontend.bishi',
|
'bemani.frontend.bishi',
|
||||||
|
'bemani.frontend.mga',
|
||||||
'bemani.frontend.ddr',
|
'bemani.frontend.ddr',
|
||||||
'bemani.frontend.sdvx',
|
'bemani.frontend.sdvx',
|
||||||
'bemani.frontend.reflec',
|
'bemani.frontend.reflec',
|
||||||
@ -235,6 +236,7 @@ setup(
|
|||||||
'bemani.backend.jubeat',
|
'bemani.backend.jubeat',
|
||||||
'bemani.backend.popn',
|
'bemani.backend.popn',
|
||||||
'bemani.backend.bishi',
|
'bemani.backend.bishi',
|
||||||
|
'bemani.backend.mga',
|
||||||
'bemani.backend.ddr',
|
'bemani.backend.ddr',
|
||||||
'bemani.backend.sdvx',
|
'bemani.backend.sdvx',
|
||||||
'bemani.backend.reflec',
|
'bemani.backend.reflec',
|
||||||
|
Loading…
Reference in New Issue
Block a user