Add config, database and user interface to change network region as well as per-arcade region. Also unified IIDX prefecture setting.
This commit is contained in:
parent
beb818f42b
commit
d05c3f907d
@ -1,7 +1,7 @@
|
||||
from bemani.common.model import Model
|
||||
from bemani.common.validateddict import ValidatedDict, Profile, PlayStatistics, intish
|
||||
from bemani.common.http import HTTP
|
||||
from bemani.common.constants import APIConstants, GameConstants, VersionConstants, DBConstants, BroadcastConstants
|
||||
from bemani.common.constants import APIConstants, GameConstants, VersionConstants, DBConstants, BroadcastConstants, RegionConstants
|
||||
from bemani.common.card import CardCipher, CardCipherException
|
||||
from bemani.common.id import ID
|
||||
from bemani.common.aes import AESCipher
|
||||
@ -21,6 +21,7 @@ __all__ = [
|
||||
"VersionConstants",
|
||||
"DBConstants",
|
||||
"BroadcastConstants",
|
||||
"RegionConstants",
|
||||
"CardCipher",
|
||||
"CardCipherException",
|
||||
"ID",
|
||||
|
@ -1,4 +1,5 @@
|
||||
from enum import Enum
|
||||
from typing import Dict
|
||||
from typing_extensions import Final
|
||||
|
||||
|
||||
@ -322,3 +323,206 @@ class BroadcastConstants(Enum):
|
||||
COOLS: Final[str] = 'Cools'
|
||||
COMBO: Final[str] = 'Combo'
|
||||
MEDAL: Final[str] = 'Medal'
|
||||
|
||||
|
||||
class _RegionConstants:
|
||||
"""
|
||||
Class representing the various region IDs found in all games.
|
||||
"""
|
||||
|
||||
# The following are the original enumerations, that still are correct
|
||||
# for new games today.
|
||||
HOKKAIDO: Final[int] = 1
|
||||
AOMORI: Final[int] = 2
|
||||
IWATE: Final[int] = 3
|
||||
MIYAGI: Final[int] = 4
|
||||
AKITA: Final[int] = 5
|
||||
YAMAGATA: Final[int] = 6
|
||||
FUKUSHIMA: Final[int] = 7
|
||||
IBARAKI: Final[int] = 8
|
||||
TOCHIGI: Final[int] = 9
|
||||
GUNMA: Final[int] = 10
|
||||
SAITAMA: Final[int] = 11
|
||||
CHIBA: Final[int] = 12
|
||||
TOKYO: Final[int] = 13
|
||||
KANAGAWA: Final[int] = 14
|
||||
NIIGATA: Final[int] = 15
|
||||
TOYAMA: Final[int] = 16
|
||||
ISHIKAWA: Final[int] = 17
|
||||
FUKUI: Final[int] = 18
|
||||
YAMANASHI: Final[int] = 19
|
||||
NAGANO: Final[int] = 20
|
||||
GIFU: Final[int] = 21
|
||||
SHIZUOKA: Final[int] = 22
|
||||
AICHI: Final[int] = 23
|
||||
MIE: Final[int] = 24
|
||||
SHIGA: Final[int] = 25
|
||||
KYOTO: Final[int] = 26
|
||||
OSAKA: Final[int] = 27
|
||||
HYOGO: Final[int] = 28
|
||||
NARA: Final[int] = 29
|
||||
WAKAYAMA: Final[int] = 30
|
||||
TOTTORI: Final[int] = 31
|
||||
SHIMANE: Final[int] = 32
|
||||
OKAYAMA: Final[int] = 33
|
||||
HIROSHIMA: Final[int] = 34
|
||||
YAMAGUCHI: Final[int] = 35
|
||||
TOKUSHIMA: Final[int] = 36
|
||||
KAGAWA: Final[int] = 37
|
||||
EHIME: Final[int] = 38
|
||||
KOUCHI: Final[int] = 39
|
||||
FUKUOKA: Final[int] = 40
|
||||
SAGA: Final[int] = 41
|
||||
NAGASAKI: Final[int] = 42
|
||||
KUMAMOTO: Final[int] = 43
|
||||
OITA: Final[int] = 44
|
||||
MIYAZAKI: Final[int] = 45
|
||||
KAGOSHIMA: Final[int] = 46
|
||||
OKINAWA: Final[int] = 47
|
||||
HONG_KONG: Final[int] = 48
|
||||
KOREA: Final[int] = 49
|
||||
TAIWAN: Final[int] = 50
|
||||
|
||||
# The following are new additions, replacing the "OLD" values below.
|
||||
THAILAND: Final[int] = 51
|
||||
INDONESIA: Final[int] = 52
|
||||
SINGAPORE: Final[int] = 53
|
||||
PHILLIPINES: Final[int] = 54
|
||||
MACAO: Final[int] = 55
|
||||
USA: Final[int] = 56
|
||||
OTHER: Final[int] = 57
|
||||
|
||||
# Bogus value for europe.
|
||||
EUROPE: Final[int] = 1000
|
||||
NO_MAPPING: Final[int] = 2000
|
||||
|
||||
# Old constant values.
|
||||
OLD_USA: Final[int] = 51
|
||||
OLD_EUROPE: Final[int] = 52
|
||||
OLD_OTHER: Final[int] = 53
|
||||
|
||||
# Min/max valid values for server.
|
||||
MIN: Final[int] = 1
|
||||
MAX: Final[int] = 56
|
||||
|
||||
# This is a really nasty LUT to attempt to make the frontend display
|
||||
# the same regardless of the game in question. This is mostly because
|
||||
# the prefecture/region stored in the profile is editable by IIDX and
|
||||
# I didn't anticipate this ever changing.
|
||||
def db_to_game_region(self, use_new_table: bool, region: int) -> int:
|
||||
if use_new_table:
|
||||
# The new lookup table does not have Europe as an option.
|
||||
if region in {RegionConstants.EUROPE, RegionConstants.NO_MAPPING}:
|
||||
return RegionConstants.OTHER
|
||||
|
||||
# The rest matches what we have already.
|
||||
return region
|
||||
else:
|
||||
# The old lookup table supports most of the values.
|
||||
if region <= RegionConstants.TAIWAN:
|
||||
return region
|
||||
|
||||
# Map the two values that still exist back to their old values.
|
||||
if region == RegionConstants.USA:
|
||||
return RegionConstants.OLD_USA
|
||||
if region == RegionConstants.EUROPE:
|
||||
return RegionConstants.OLD_EUROPE
|
||||
|
||||
# The rest get mapped to other.
|
||||
return RegionConstants.OLD_OTHER
|
||||
|
||||
# This performs the equivalent inverse of the above function. Note that
|
||||
# depending on the game and selection, this is lossy (as in, Europe could
|
||||
# get converted to Other, etc).
|
||||
def game_to_db_region(self, use_new_table: bool, region: int) -> int:
|
||||
if use_new_table:
|
||||
if region == RegionConstants.OTHER:
|
||||
return RegionConstants.NO_MAPPING
|
||||
|
||||
# The new lookup table is correct aside from the above correction.
|
||||
return region
|
||||
else:
|
||||
# The old lookup table supports most of the values.
|
||||
if region <= RegionConstants.TAIWAN:
|
||||
return region
|
||||
|
||||
# Map the three values that might be seen to new DB values.
|
||||
if region == RegionConstants.OLD_USA:
|
||||
return RegionConstants.USA
|
||||
if region == RegionConstants.OLD_EUROPE:
|
||||
return RegionConstants.EUROPE
|
||||
if region == RegionConstants.OLD_OTHER:
|
||||
return RegionConstants.NO_MAPPING
|
||||
|
||||
raise Exception(f"Unexpected value {region} for game region!")
|
||||
|
||||
@property
|
||||
def LUT(cls) -> Dict[int, str]:
|
||||
return {
|
||||
cls.HOKKAIDO: '北海道 (Hokkaido)',
|
||||
cls.AOMORI: '青森県 (Aomori)',
|
||||
cls.IWATE: '岩手県 (Iwate)',
|
||||
cls.MIYAGI: '宮城県 (Miyagi)',
|
||||
cls.AKITA: '秋田県 (Akita)',
|
||||
cls.YAMAGATA: '山形県 (Yamagata)',
|
||||
cls.FUKUSHIMA: '福島県 (Fukushima)',
|
||||
cls.IBARAKI: '茨城県 (Ibaraki)',
|
||||
cls.TOCHIGI: '栃木県 (Tochigi)',
|
||||
cls.GUNMA: '群馬県 (Gunma)',
|
||||
cls.SAITAMA: '埼玉県 (Saitama)',
|
||||
cls.CHIBA: '千葉県 (Chiba)',
|
||||
cls.TOKYO: '東京都 (Tokyo)',
|
||||
cls.KANAGAWA: '神奈川県 (Kanagawa)',
|
||||
cls.NIIGATA: '新潟県 (Niigata)',
|
||||
cls.TOYAMA: '富山県 (Toyama)',
|
||||
cls.ISHIKAWA: '石川県 (Ishikawa)',
|
||||
cls.FUKUI: '福井県 (Fukui)',
|
||||
cls.YAMANASHI: '山梨県 (Yamanashi)',
|
||||
cls.NAGANO: '長野県 (Nagano)',
|
||||
cls.GIFU: '岐阜県 (Gifu)',
|
||||
cls.SHIZUOKA: '静岡県 (Shizuoka)',
|
||||
cls.AICHI: '愛知県 (Aichi)',
|
||||
cls.MIE: '三重県 (Mie)',
|
||||
cls.SHIGA: '滋賀県 (Shiga)',
|
||||
cls.KYOTO: '京都府 (Kyoto)',
|
||||
cls.OSAKA: '大阪府 (Osaka)',
|
||||
cls.HYOGO: '兵庫県 (Hyogo)',
|
||||
cls.NARA: '奈良県 (Nara)',
|
||||
cls.WAKAYAMA: '和歌山県 (Wakayama)',
|
||||
cls.TOTTORI: '鳥取県 (Tottori)',
|
||||
cls.SHIMANE: '島根県 (Shimane)',
|
||||
cls.OKAYAMA: '岡山県 (Okayama)',
|
||||
cls.HIROSHIMA: '広島県 (Hiroshima)',
|
||||
cls.YAMAGUCHI: '山口県 (Yamaguchi)',
|
||||
cls.TOKUSHIMA: '徳島県 (Tokushima)',
|
||||
cls.KAGAWA: '香川県 (Kagawa)',
|
||||
cls.EHIME: '愛媛県 (Ehime)',
|
||||
cls.KOUCHI: '高知県 (Kochi)',
|
||||
cls.FUKUOKA: '福岡県 (Fukuoka)',
|
||||
cls.SAGA: '佐賀県 (Saga)',
|
||||
cls.NAGASAKI: '長崎県 (Nagasaki)',
|
||||
cls.KUMAMOTO: '熊本県 (Kumamoto)',
|
||||
cls.OITA: '大分県 (Oita)',
|
||||
cls.MIYAZAKI: '宮崎県 (Miyazaki)',
|
||||
cls.KAGOSHIMA: '鹿児島県 (Kagoshima)',
|
||||
cls.OKINAWA: '沖縄県 (Okinawa)',
|
||||
cls.HONG_KONG: '香港 (Hong Kong)',
|
||||
cls.KOREA: '韓国 (Korea)',
|
||||
cls.TAIWAN: '台湾 (Taiwan)',
|
||||
|
||||
# The following are different depending on the version of the game,
|
||||
# so we choose the new value.
|
||||
cls.THAILAND: "タイ (Thailand)",
|
||||
cls.INDONESIA: "インドネシア (Indonesia)",
|
||||
cls.SINGAPORE: "シンガポール (Singapore)",
|
||||
cls.PHILLIPINES: "フィリピン (Phillipines)",
|
||||
cls.MACAO: "マカオ (Macao)",
|
||||
cls.USA: "アメリカ (USA)",
|
||||
cls.EUROPE: '欧州 (Europe)',
|
||||
cls.NO_MAPPING: "海外 (Other)",
|
||||
}
|
||||
|
||||
|
||||
# This is just so I can use the defined constants inside a LUT
|
||||
# without having the LUT itself outside the class.
|
||||
RegionConstants = _RegionConstants()
|
||||
|
@ -3,7 +3,7 @@ import os
|
||||
from sqlalchemy.engine import Engine # type: ignore
|
||||
from typing import Any, Dict, Optional, Set
|
||||
|
||||
from bemani.common.constants import GameConstants
|
||||
from bemani.common import GameConstants, RegionConstants
|
||||
from bemani.data.types import ArcadeID
|
||||
|
||||
|
||||
@ -79,6 +79,18 @@ class Server:
|
||||
def pcbid_self_grant_limit(self) -> int:
|
||||
return int(self.__config.get('server', {}).get('pcbid_self_grant_limit', 0))
|
||||
|
||||
@property
|
||||
def region(self) -> int:
|
||||
region = int(self.__config.get('server', {}).get('region', RegionConstants.USA))
|
||||
if region in {RegionConstants.EUROPE, RegionConstants.NO_MAPPING}:
|
||||
# Bogus values we support.
|
||||
return region
|
||||
if region < RegionConstants.MIN or region > RegionConstants.MAX:
|
||||
# Pick the original default for the network (USA).
|
||||
return RegionConstants.USA
|
||||
# Region was fine.
|
||||
return region
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, parent_config: "Config") -> None:
|
||||
|
@ -0,0 +1,35 @@
|
||||
"""Add region column to arcade settings.
|
||||
|
||||
Revision ID: a1f4a09a1f90
|
||||
Revises: 36dff3ac15a3
|
||||
Create Date: 2021-09-06 22:01:29.905538
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a1f4a09a1f90'
|
||||
down_revision = '36dff3ac15a3'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('arcade', sa.Column('pref', sa.Integer(), nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
conn = op.get_bind()
|
||||
|
||||
# Set a sane default for all current arcades.
|
||||
sql = "UPDATE arcade SET pref = 56"
|
||||
conn.execute(text(sql), {})
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('arcade', 'pref')
|
||||
# ### end Alembic commands ###
|
@ -39,6 +39,7 @@ arcade = Table(
|
||||
Column('name', String(255), nullable=False),
|
||||
Column('description', String(255), nullable=False),
|
||||
Column('pin', String(8), nullable=False),
|
||||
Column('pref', Integer, nullable=False),
|
||||
Column('data', JSON),
|
||||
mysql_charset='utf8mb4',
|
||||
)
|
||||
@ -288,7 +289,7 @@ class MachineData(BaseData):
|
||||
sql = "DELETE FROM `machine` WHERE pcbid = :pcbid LIMIT 1"
|
||||
self.execute(sql, {'pcbid': pcbid})
|
||||
|
||||
def create_arcade(self, name: str, description: str, data: Dict[str, Any], owners: List[UserID]) -> Arcade:
|
||||
def create_arcade(self, name: str, description: str, region: int, data: Dict[str, Any], owners: List[UserID]) -> Arcade:
|
||||
"""
|
||||
Given a set of values, create a new arcade and return the ID of that arcade.
|
||||
|
||||
@ -296,14 +297,15 @@ class MachineData(BaseData):
|
||||
An Arcade object representing this arcade
|
||||
"""
|
||||
sql = (
|
||||
"INSERT INTO arcade (name, description, data, pin) " +
|
||||
"VALUES (:name, :desc, :data, '00000000')"
|
||||
"INSERT INTO arcade (name, description, pref, data, pin) " +
|
||||
"VALUES (:name, :desc, :pref, :data, '00000000')"
|
||||
)
|
||||
cursor = self.execute(
|
||||
sql,
|
||||
{
|
||||
'name': name,
|
||||
'desc': description,
|
||||
'pref': region,
|
||||
'data': self.serialize(data),
|
||||
},
|
||||
)
|
||||
@ -329,7 +331,7 @@ class MachineData(BaseData):
|
||||
An Arcade object if this arcade was found, or None otherwise.
|
||||
"""
|
||||
sql = (
|
||||
"SELECT name, description, pin, data FROM arcade WHERE id = :id"
|
||||
"SELECT name, description, pin, pref, data FROM arcade WHERE id = :id"
|
||||
)
|
||||
cursor = self.execute(sql, {'id': arcadeid})
|
||||
if cursor.rowcount != 1:
|
||||
@ -346,6 +348,7 @@ class MachineData(BaseData):
|
||||
result['name'],
|
||||
result['description'],
|
||||
result['pin'],
|
||||
result['pref'],
|
||||
self.deserialize(result['data']),
|
||||
[owner['userid'] for owner in cursor.fetchall()],
|
||||
)
|
||||
@ -360,7 +363,7 @@ class MachineData(BaseData):
|
||||
# Update machine name based on game
|
||||
sql = (
|
||||
"UPDATE `arcade` " +
|
||||
"SET name = :name, description = :desc, pin = :pin, data = :data " +
|
||||
"SET name = :name, description = :desc, pin = :pin, pref = :pref, data = :data " +
|
||||
"WHERE id = :arcadeid"
|
||||
)
|
||||
self.execute(
|
||||
@ -369,6 +372,7 @@ class MachineData(BaseData):
|
||||
'name': arcade.name,
|
||||
'desc': arcade.description,
|
||||
'pin': arcade.pin,
|
||||
'pref': arcade.region,
|
||||
'data': self.serialize(arcade.data),
|
||||
'arcadeid': arcade.id,
|
||||
},
|
||||
@ -411,7 +415,7 @@ class MachineData(BaseData):
|
||||
arcade_to_owners[arcade] = []
|
||||
arcade_to_owners[arcade].append(owner)
|
||||
|
||||
sql = "SELECT id, name, description, pin, data FROM arcade"
|
||||
sql = "SELECT id, name, description, pin, pref, data FROM arcade"
|
||||
cursor = self.execute(sql)
|
||||
return [
|
||||
Arcade(
|
||||
@ -419,6 +423,7 @@ class MachineData(BaseData):
|
||||
result['name'],
|
||||
result['description'],
|
||||
result['pin'],
|
||||
result['pref'],
|
||||
self.deserialize(result['data']),
|
||||
arcade_to_owners.get(result['id'], []),
|
||||
) for result in cursor.fetchall()
|
||||
|
@ -144,7 +144,7 @@ class Arcade:
|
||||
crediting accounts. Machines belong to either no arcade or a single arcase.
|
||||
"""
|
||||
|
||||
def __init__(self, arcadeid: ArcadeID, name: str, description: str, pin: str, data: Dict[str, Any], owners: List[UserID]) -> None:
|
||||
def __init__(self, arcadeid: ArcadeID, name: str, description: str, pin: str, region: int, data: Dict[str, Any], owners: List[UserID]) -> None:
|
||||
"""
|
||||
Initialize the arcade instance.
|
||||
|
||||
@ -153,6 +153,7 @@ class Arcade:
|
||||
name - The name of the arcade.
|
||||
description - The description of the arcade.
|
||||
pin - An eight digit string representing the PIN used to pull up PASELI info.
|
||||
region - An integer representing the region this arcade is in.
|
||||
data - A dictionary of settings for this arcade.
|
||||
owners - An list of integers specifying the user IDs of owners for this arcade.
|
||||
"""
|
||||
@ -160,11 +161,12 @@ class Arcade:
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.pin = pin
|
||||
self.region = region
|
||||
self.data = ValidatedDict(data)
|
||||
self.owners = owners
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Arcade(arcadeid={self.id}, name={self.name}, description={self.description}, pin={self.pin}, data={self.data}, owners={self.owners})"
|
||||
return f"Arcade(arcadeid={self.id}, name={self.name}, description={self.description}, pin={self.pin}, region={self.region}, data={self.data}, owners={self.owners})"
|
||||
|
||||
|
||||
class Song:
|
||||
|
@ -3,7 +3,7 @@ from typing import Dict, Tuple, Any, Optional
|
||||
from flask import Blueprint, request, Response, render_template, url_for
|
||||
|
||||
from bemani.backend.base import Base
|
||||
from bemani.common import CardCipher, CardCipherException, GameConstants
|
||||
from bemani.common import CardCipher, CardCipherException, GameConstants, RegionConstants
|
||||
from bemani.data import Arcade, Machine, User, UserID, News, Event, Server, Client
|
||||
from bemani.data.api.client import APIClient, NotAuthorizedAPIException, APIException
|
||||
from bemani.frontend.app import adminrequired, jsonify, valid_email, valid_username, valid_pin, render_react
|
||||
@ -33,6 +33,7 @@ def format_arcade(arcade: Arcade) -> Dict[str, Any]:
|
||||
'id': arcade.id,
|
||||
'name': arcade.name,
|
||||
'description': arcade.description,
|
||||
'region': arcade.region,
|
||||
'paseli_enabled': arcade.data.get_bool('paseli_enabled'),
|
||||
'paseli_infinite': arcade.data.get_bool('paseli_infinite'),
|
||||
'mask_services_url': arcade.data.get_bool('mask_services_url'),
|
||||
@ -128,6 +129,7 @@ def viewsettings() -> Response:
|
||||
**{
|
||||
'title': 'Network Settings',
|
||||
'config': g.config,
|
||||
'region': RegionConstants.LUT,
|
||||
},
|
||||
))
|
||||
|
||||
@ -213,9 +215,11 @@ def viewarcades() -> Response:
|
||||
'admin/arcades.react.js',
|
||||
{
|
||||
'arcades': [format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades()],
|
||||
'regions': RegionConstants.LUT,
|
||||
'usernames': g.data.local.user.get_all_usernames(),
|
||||
'paseli_enabled': g.config.paseli.enabled,
|
||||
'paseli_infinite': g.config.paseli.infinite,
|
||||
'default_region': g.config.server.region,
|
||||
'mask_services_url': False,
|
||||
},
|
||||
{
|
||||
@ -402,6 +406,7 @@ def updatearcade() -> Dict[str, Any]:
|
||||
|
||||
arcade.name = new_values['name']
|
||||
arcade.description = new_values['description']
|
||||
arcade.region = new_values['region']
|
||||
arcade.data.replace_bool('paseli_enabled', new_values['paseli_enabled'])
|
||||
arcade.data.replace_bool('paseli_infinite', new_values['paseli_infinite'])
|
||||
arcade.data.replace_bool('mask_services_url', new_values['mask_services_url'])
|
||||
@ -441,6 +446,7 @@ def addarcade() -> Dict[str, Any]:
|
||||
g.data.local.machine.create_arcade(
|
||||
new_values['name'],
|
||||
new_values['description'],
|
||||
new_values['region'],
|
||||
{
|
||||
'paseli_enabled': new_values['paseli_enabled'],
|
||||
'paseli_infinite': new_values['paseli_infinite'],
|
||||
|
@ -3,7 +3,7 @@ from typing import Any, Dict, List, Optional
|
||||
from flask import Blueprint, request, Response, abort, url_for
|
||||
|
||||
from bemani.backend.base import Base
|
||||
from bemani.common import CardCipher, CardCipherException, ValidatedDict, GameConstants
|
||||
from bemani.common import CardCipher, CardCipherException, ValidatedDict, GameConstants, RegionConstants
|
||||
from bemani.data import Arcade, ArcadeID, Event, Machine
|
||||
from bemani.frontend.app import loginrequired, jsonify, render_react, valid_pin
|
||||
from bemani.frontend.templates import templates_location
|
||||
@ -66,6 +66,7 @@ def format_arcade(arcade: Arcade) -> Dict[str, Any]:
|
||||
'name': arcade.name,
|
||||
'description': arcade.description,
|
||||
'pin': arcade.pin,
|
||||
'region': arcade.region,
|
||||
'paseli_enabled': arcade.data.get_bool('paseli_enabled'),
|
||||
'paseli_infinite': arcade.data.get_bool('paseli_infinite'),
|
||||
'mask_services_url': arcade.data.get_bool('mask_services_url'),
|
||||
@ -155,6 +156,7 @@ def viewarcade(arcadeid: int) -> Response:
|
||||
'arcade/arcade.react.js',
|
||||
{
|
||||
'arcade': format_arcade(arcade),
|
||||
'regions': RegionConstants.LUT,
|
||||
'machines': machines,
|
||||
'game_settings': get_game_settings(arcade),
|
||||
'balances': {balance[0]: balance[1] for balance in g.data.local.machine.get_balances(arcadeid)},
|
||||
@ -172,6 +174,7 @@ def viewarcade(arcadeid: int) -> Response:
|
||||
'add_balance': url_for('arcade_pages.addbalance', arcadeid=arcadeid),
|
||||
'update_balance': url_for('arcade_pages.updatebalance', arcadeid=arcadeid),
|
||||
'update_pin': url_for('arcade_pages.updatepin', arcadeid=arcadeid),
|
||||
'update_region': url_for('arcade_pages.updateregion', arcadeid=arcadeid),
|
||||
'generatepcbid': url_for('arcade_pages.generatepcbid', arcadeid=arcadeid),
|
||||
'updatepcbid': url_for('arcade_pages.updatepcbid', arcadeid=arcadeid),
|
||||
'removepcbid': url_for('arcade_pages.removepcbid', arcadeid=arcadeid),
|
||||
@ -306,6 +309,34 @@ def updatepin(arcadeid: int) -> Dict[str, Any]:
|
||||
return {'pin': pin}
|
||||
|
||||
|
||||
@arcade_pages.route('/<int:arcadeid>/region/update', methods=['POST'])
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def updateregion(arcadeid: int) -> Dict[str, Any]:
|
||||
# Cast the ID for type safety.
|
||||
arcadeid = ArcadeID(arcadeid)
|
||||
|
||||
try:
|
||||
region = int(request.get_json()['region'])
|
||||
except Exception:
|
||||
region = 0
|
||||
|
||||
# Make sure the arcade is valid
|
||||
arcade = g.data.local.machine.get_arcade(arcadeid)
|
||||
if arcade is None or g.userID not in arcade.owners:
|
||||
raise Exception('You don\'t own this arcade, refusing to update!')
|
||||
|
||||
if region not in {RegionConstants.EUROPE, RegionConstants.NO_MAPPING} and (region < RegionConstants.MIN or region > RegionConstants.MAX):
|
||||
raise Exception('Invalid region!')
|
||||
|
||||
# Update and save
|
||||
arcade.region = region
|
||||
g.data.local.machine.put_arcade(arcade)
|
||||
|
||||
# Return nothing
|
||||
return {'region': region}
|
||||
|
||||
|
||||
@arcade_pages.route('/<int:arcadeid>/pcbids/generate', methods=['POST'])
|
||||
@jsonify
|
||||
@loginrequired
|
||||
|
@ -6,7 +6,7 @@ from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
|
||||
from flask_caching import Cache # type: ignore
|
||||
|
||||
from bemani.common import GameConstants, Profile, ValidatedDict, ID
|
||||
from bemani.data import Data, Score, Attempt, Link, Song, UserID, RemoteUser
|
||||
from bemani.data import Data, Config, Score, Attempt, Link, Song, UserID, RemoteUser
|
||||
|
||||
|
||||
class FrontendBase(ABC):
|
||||
@ -34,7 +34,7 @@ class FrontendBase(ABC):
|
||||
"""
|
||||
valid_rival_types: List[str] = []
|
||||
|
||||
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||
def __init__(self, data: Data, config: Config, cache: Cache) -> None:
|
||||
self.data = data
|
||||
self.config = config
|
||||
self.cache = cache
|
||||
|
@ -6,7 +6,7 @@ from flask_caching import Cache # type: ignore
|
||||
|
||||
from bemani.backend.bishi import BishiBashiFactory
|
||||
from bemani.common import Profile, ValidatedDict, ID, GameConstants
|
||||
from bemani.data import Data
|
||||
from bemani.data import Data, Config
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ class BishiBashiFrontend(FrontendBase):
|
||||
|
||||
game = GameConstants.BISHI_BASHI
|
||||
|
||||
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||
def __init__(self, data: Data, config: Config, cache: Cache) -> None:
|
||||
super().__init__(data, config, cache)
|
||||
self.machines: Dict[int, str] = {}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import re
|
||||
from typing import Any, Dict
|
||||
from flask import Blueprint, request, Response, url_for, abort
|
||||
|
||||
from bemani.common import ID, GameConstants
|
||||
from bemani.common import ID, GameConstants, RegionConstants
|
||||
from bemani.data import UserID
|
||||
from bemani.frontend.app import loginrequired, jsonify, render_react
|
||||
from bemani.frontend.iidx.iidx import IIDXFrontend
|
||||
@ -326,6 +326,7 @@ def viewsettings() -> Response:
|
||||
'iidx/settings.react.js',
|
||||
{
|
||||
'player': djinfo,
|
||||
'regions': RegionConstants.LUT,
|
||||
'versions': {version: name for (game, version, name) in frontend.all_games()},
|
||||
'qpros': frontend.get_all_items(versions),
|
||||
},
|
||||
@ -515,7 +516,7 @@ def updateprefecture() -> Dict[str, Any]:
|
||||
profile = g.data.local.user.get_profile(GameConstants.IIDX, version, user.id)
|
||||
if profile is None:
|
||||
raise Exception('Unable to find profile to update!')
|
||||
profile.replace_int('pid', prefecture)
|
||||
profile.replace_int('pid', RegionConstants.db_to_game_region(version >= 25, prefecture))
|
||||
g.data.local.user.put_profile(GameConstants.IIDX, version, user.id, profile)
|
||||
|
||||
# Return that we updated
|
||||
|
@ -4,8 +4,8 @@ from typing import Any, Dict, Iterator, Optional, Tuple, List
|
||||
from flask_caching import Cache # type: ignore
|
||||
|
||||
from bemani.backend.iidx import IIDXFactory, IIDXBase
|
||||
from bemani.common import Profile, ValidatedDict, GameConstants
|
||||
from bemani.data import Attempt, Data, Score, Song, UserID
|
||||
from bemani.common import Profile, ValidatedDict, GameConstants, RegionConstants
|
||||
from bemani.data import Attempt, Data, Config, Score, Song, UserID
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ class IIDXFrontend(FrontendBase):
|
||||
'dp_rival',
|
||||
]
|
||||
|
||||
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||
def __init__(self, data: Data, config: Config, cache: Cache) -> None:
|
||||
super().__init__(data, config, cache)
|
||||
self.machines: Dict[int, str] = {}
|
||||
|
||||
@ -189,7 +189,7 @@ class IIDXFrontend(FrontendBase):
|
||||
formatted_profile = super().format_profile(profile, playstats)
|
||||
formatted_profile.update({
|
||||
'arcade': "",
|
||||
'prefecture': profile.get_int('pid', 51),
|
||||
'prefecture': RegionConstants.game_to_db_region(profile.version >= 25, profile.get_int('pid', self.config.server.region)),
|
||||
'settings': self.format_settings(profile.get_dict('settings')),
|
||||
'flags': self.format_flags(profile.get_dict('settings')),
|
||||
'sdjp': playstats.get_int('single_dj_points'),
|
||||
|
@ -6,7 +6,7 @@ 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.data import Data, Config
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ class MetalGearArcadeFrontend(FrontendBase):
|
||||
|
||||
game = GameConstants.MGA
|
||||
|
||||
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||
def __init__(self, data: Data, config: Config, cache: Cache) -> None:
|
||||
super().__init__(data, config, cache)
|
||||
self.machines: Dict[int, str] = {}
|
||||
|
||||
|
@ -5,7 +5,7 @@ from flask_caching import Cache # type: ignore
|
||||
|
||||
from bemani.backend.museca import MusecaFactory, MusecaBase
|
||||
from bemani.common import GameConstants, VersionConstants, DBConstants, Profile, ValidatedDict
|
||||
from bemani.data import Attempt, Data, Score, Song, UserID
|
||||
from bemani.data import Attempt, Data, Config, Score, Song, UserID
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ class MusecaFrontend(FrontendBase):
|
||||
MusecaBase.CHART_TYPE_RED,
|
||||
]
|
||||
|
||||
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||
def __init__(self, data: Data, config: Config, cache: Cache) -> None:
|
||||
super().__init__(data, config, cache)
|
||||
|
||||
def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
|
||||
|
@ -5,7 +5,7 @@ from flask_caching import Cache # type: ignore
|
||||
|
||||
from bemani.backend.reflec import ReflecBeatFactory, ReflecBeatBase
|
||||
from bemani.common import GameConstants, Profile, ValidatedDict
|
||||
from bemani.data import Attempt, Data, Score, Song, UserID
|
||||
from bemani.data import Attempt, Data, Config, Score, Song, UserID
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ class ReflecBeatFrontend(FrontendBase):
|
||||
'rival',
|
||||
]
|
||||
|
||||
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||
def __init__(self, data: Data, config: Config, cache: Cache) -> None:
|
||||
super().__init__(data, config, cache)
|
||||
|
||||
def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
|
||||
|
@ -5,7 +5,7 @@ from flask_caching import Cache # type: ignore
|
||||
|
||||
from bemani.backend.sdvx import SoundVoltexFactory, SoundVoltexBase
|
||||
from bemani.common import GameConstants, Profile, ValidatedDict
|
||||
from bemani.data import Attempt, Data, Score, Song, UserID
|
||||
from bemani.data import Attempt, Data, Config, Score, Song, UserID
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ class SoundVoltexFrontend(FrontendBase):
|
||||
'rival',
|
||||
]
|
||||
|
||||
def __init__(self, data: Data, config: Dict[str, Any], cache: Cache) -> None:
|
||||
def __init__(self, data: Data, config: Config, cache: Cache) -> None:
|
||||
super().__init__(data, config, cache)
|
||||
|
||||
def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
|
||||
|
@ -6,6 +6,7 @@ var card_management = React.createClass({
|
||||
new_arcade: {
|
||||
name: '',
|
||||
description: '',
|
||||
region: window.default_region,
|
||||
paseli_enabled: window.paseli_enabled,
|
||||
paseli_infinite: window.paseli_infinite,
|
||||
mask_services_url: window.mask_services_url,
|
||||
@ -34,6 +35,7 @@ var card_management = React.createClass({
|
||||
new_arcade: {
|
||||
name: '',
|
||||
description: '',
|
||||
region: window.default_region,
|
||||
paseli_enabled: window.paseli_enabled,
|
||||
paseli_infinite: window.paseli_infinite,
|
||||
mask_services_url: window.mask_services_url,
|
||||
@ -182,6 +184,23 @@ var card_management = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
renderRegion: function(arcade) {
|
||||
if (this.state.editing_arcade && arcade.id == this.state.editing_arcade.id) {
|
||||
return <SelectInt
|
||||
name="region"
|
||||
value={ this.state.editing_arcade.region }
|
||||
choices={ window.regions }
|
||||
onChange={function(choice) {
|
||||
var arcade = this.state.editing_arcade;
|
||||
arcade.region = event.target.value;
|
||||
this.setState({editing_arcade: arcade});
|
||||
}.bind(this)}
|
||||
/>;
|
||||
} else {
|
||||
return <span>{ window.regions[arcade.region] }</span>;
|
||||
}
|
||||
},
|
||||
|
||||
sortDescription: function(a, b) {
|
||||
return a.description.localeCompare(b.description);
|
||||
},
|
||||
@ -309,6 +328,10 @@ var card_management = React.createClass({
|
||||
render: this.renderDescription,
|
||||
sort: this.sortDescription,
|
||||
},
|
||||
{
|
||||
name: "Region",
|
||||
render: this.renderRegion,
|
||||
},
|
||||
{
|
||||
name: 'Owners',
|
||||
render: this.renderOwners,
|
||||
@ -344,6 +367,7 @@ var card_management = React.createClass({
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Region</th>
|
||||
<th>Owners</th>
|
||||
<th>PASELI Enabled</th>
|
||||
<th>PASELI Infinite</th>
|
||||
@ -377,6 +401,18 @@ var card_management = React.createClass({
|
||||
}.bind(this)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SelectInt
|
||||
name="region"
|
||||
value={ this.state.new_arcade.region }
|
||||
choices={ window.regions }
|
||||
onChange={function(choice) {
|
||||
var arcade = this.state.new_arcade;
|
||||
arcade.region = event.target.value;
|
||||
this.setState({new_arcade: arcade});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</td>
|
||||
<td>{
|
||||
this.state.new_arcade.owners.map(function(owner, index) {
|
||||
return (
|
||||
|
@ -32,6 +32,9 @@ var arcade_management = React.createClass({
|
||||
pin: window.arcade.pin,
|
||||
editing_pin: false,
|
||||
new_pin: '',
|
||||
region: window.arcade.region,
|
||||
editing_region: false,
|
||||
new_region: '',
|
||||
paseli_enabled_saving: false,
|
||||
paseli_infinite_saving: false,
|
||||
mask_services_url_saving: false,
|
||||
@ -103,6 +106,21 @@ var arcade_management = React.createClass({
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
saveRegion: function(event) {
|
||||
AJAX.post(
|
||||
Link.get('update_region'),
|
||||
{region: this.state.new_region},
|
||||
function(response) {
|
||||
this.setState({
|
||||
region: response.region,
|
||||
new_region: '',
|
||||
editing_region: false,
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
getSettingIndex: function(setting_name) {
|
||||
var real_index = -1;
|
||||
this.state.settings.map(function(game_settings, index) {
|
||||
@ -296,6 +314,46 @@ var arcade_management = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
renderRegion: function() {
|
||||
return (
|
||||
<LabelledSection vertical={true} label="Region">{
|
||||
!this.state.editing_region ?
|
||||
<span>
|
||||
<span>{ window.regions[this.state.region] }</span>
|
||||
<Edit
|
||||
onClick={function(event) {
|
||||
this.setState({editing_region: true, new_region: this.state.region});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</span> :
|
||||
<form className="inline" onSubmit={this.saveRegion}>
|
||||
<SelectInt
|
||||
name="region"
|
||||
value={ this.state.new_region }
|
||||
choices={ window.regions }
|
||||
onChange={function(choice) {
|
||||
this.setState({new_region: event.target.value});
|
||||
}.bind(this)}
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
value="save"
|
||||
/>
|
||||
<input
|
||||
type="button"
|
||||
value="cancel"
|
||||
onClick={function(event) {
|
||||
this.setState({
|
||||
new_region: '',
|
||||
editing_region: false,
|
||||
});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</form>
|
||||
}</LabelledSection>
|
||||
);
|
||||
},
|
||||
|
||||
generateNewMachine: function(event) {
|
||||
AJAX.post(
|
||||
Link.get('generatepcbid'),
|
||||
@ -478,6 +536,7 @@ var arcade_management = React.createClass({
|
||||
<span className="placeholder">no description</span>
|
||||
}</LabelledSection>
|
||||
{this.renderPIN()}
|
||||
{this.renderRegion()}
|
||||
<LabelledSection vertical={true} label="PASELI Enabled">
|
||||
<span>{ this.state.paseli_enabled ? 'yes' : 'no' }</span>
|
||||
<Toggle onClick={this.togglePaseliEnabled.bind(this)} />
|
||||
|
@ -314,12 +314,11 @@ var settings_view = React.createClass({
|
||||
},
|
||||
|
||||
renderPrefecture: function(player) {
|
||||
regions = this.state.version >= 25 ? Regions2 : Regions;
|
||||
return (
|
||||
<LabelledSection vertical={true} label="Prefecture">{
|
||||
!this.state.editing_prefecture ?
|
||||
<span>
|
||||
<span>{regions[player.prefecture]}</span>
|
||||
<span>{window.regions[player.prefecture]}</span>
|
||||
<Edit
|
||||
onClick={function(event) {
|
||||
this.setState({editing_prefecture: true});
|
||||
@ -330,7 +329,7 @@ var settings_view = React.createClass({
|
||||
<SelectInt
|
||||
name="prefecture"
|
||||
value={this.state.new_prefecture}
|
||||
choices={regions}
|
||||
choices={window.regions}
|
||||
onChange={function(choice) {
|
||||
this.setState({new_prefecture: choice});
|
||||
}.bind(this)}
|
||||
|
@ -1,117 +0,0 @@
|
||||
var Regions = [
|
||||
null,
|
||||
"北海道",
|
||||
"青森県",
|
||||
"岩手県",
|
||||
"宮城県",
|
||||
"秋田県",
|
||||
"山形県",
|
||||
"福島県",
|
||||
"茨城県",
|
||||
"栃木県",
|
||||
"群馬県",
|
||||
"埼玉県",
|
||||
"千葉県",
|
||||
"東京都",
|
||||
"神奈川県",
|
||||
"新潟県",
|
||||
"富山県",
|
||||
"石川県",
|
||||
"福井県",
|
||||
"山梨県",
|
||||
"長野県",
|
||||
"岐阜県",
|
||||
"静岡県",
|
||||
"愛知県",
|
||||
"三重県",
|
||||
"滋賀県",
|
||||
"京都府",
|
||||
"大阪府",
|
||||
"兵庫県",
|
||||
"奈良県",
|
||||
"和歌山県",
|
||||
"鳥取県",
|
||||
"島根県",
|
||||
"岡山県",
|
||||
"広島県",
|
||||
"山口県",
|
||||
"徳島県",
|
||||
"香川県",
|
||||
"愛媛県",
|
||||
"高知県",
|
||||
"福岡県",
|
||||
"佐賀県",
|
||||
"長崎県",
|
||||
"熊本県",
|
||||
"大分県",
|
||||
"宮崎県",
|
||||
"鹿児島県",
|
||||
"沖縄県",
|
||||
"香港",
|
||||
"韓国",
|
||||
"台湾",
|
||||
"米国 (USA)",
|
||||
"欧州 (Europe)",
|
||||
"海外 (Other)",
|
||||
];
|
||||
|
||||
Regions2 = [
|
||||
null,
|
||||
"北海道",
|
||||
"青森県",
|
||||
"岩手県",
|
||||
"宮城県",
|
||||
"秋田県",
|
||||
"山形県",
|
||||
"福島県",
|
||||
"茨城県",
|
||||
"栃木県",
|
||||
"群馬県",
|
||||
"埼玉県",
|
||||
"千葉県",
|
||||
"東京都",
|
||||
"神奈川県",
|
||||
"新潟県",
|
||||
"富山県",
|
||||
"石川県",
|
||||
"福井県",
|
||||
"山梨県",
|
||||
"長野県",
|
||||
"岐阜県",
|
||||
"静岡県",
|
||||
"愛知県",
|
||||
"三重県",
|
||||
"滋賀県",
|
||||
"京都府",
|
||||
"大阪府",
|
||||
"兵庫県",
|
||||
"奈良県",
|
||||
"和歌山県",
|
||||
"鳥取県",
|
||||
"島根県",
|
||||
"岡山県",
|
||||
"広島県",
|
||||
"山口県",
|
||||
"徳島県",
|
||||
"香川県",
|
||||
"愛媛県",
|
||||
"高知県",
|
||||
"福岡県",
|
||||
"佐賀県",
|
||||
"長崎県",
|
||||
"熊本県",
|
||||
"大分県",
|
||||
"宮崎県",
|
||||
"鹿児島県",
|
||||
"沖縄県",
|
||||
"香港",
|
||||
"韓国",
|
||||
"台湾",
|
||||
"タイ",
|
||||
"インドネシア",
|
||||
"シンガポール",
|
||||
"フィリピン",
|
||||
"マカオ",
|
||||
"アメリカ",
|
||||
"海外 (Other)",
|
||||
];
|
@ -65,6 +65,8 @@
|
||||
<dd>{{ 'yes' if config.paseli.enabled else 'no' }} (can be overridden by arcade settings)</dd>
|
||||
<dt>Infinite PASELI Enabled</dt>
|
||||
<dd>{{ 'yes' if config.paseli.infinite else 'no' }} (can be overridden by arcade settings)</dd>
|
||||
<dt>Default Region</dt>
|
||||
<dd>{{ region[config.server.region] }} (can be overridden by arcade settings)</dd>
|
||||
<dt>Event Log Preservation Duration</dt>
|
||||
<dd>{{ (config.event_log_duration|string + ' seconds') if config.event_log_duration else 'infinite' }}</dd>
|
||||
</dl>
|
||||
|
@ -33,6 +33,11 @@ server:
|
||||
# page. Note that this setting is irrelevant if PCBID enforcing is off.
|
||||
# Set to 0 or delete this setting to disable self-granting PCBIDs.
|
||||
pcbid_self_grant_limit: 0
|
||||
# Default region for this network (set to USA by default). See RegionConstants
|
||||
# for details on acceptible values. The range of accepted values is 1-56 matching
|
||||
# the 56 normal regions found in RegionConstants, and 1000 for "Europe" and
|
||||
# 2000 for "Other".
|
||||
region: 56
|
||||
|
||||
# Webhook URLs. These allow for game scores from games with scorecard support to be broadcasted to outside services.
|
||||
# Delete this to disable this support.
|
||||
|
Loading…
Reference in New Issue
Block a user