diff --git a/bemani/common/__init__.py b/bemani/common/__init__.py index 0ba9eb6..3ade580 100644 --- a/bemani/common/__init__.py +++ b/bemani/common/__init__.py @@ -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", diff --git a/bemani/common/constants.py b/bemani/common/constants.py index 53362fc..9cfa1f7 100644 --- a/bemani/common/constants.py +++ b/bemani/common/constants.py @@ -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() diff --git a/bemani/data/config.py b/bemani/data/config.py index fca86ce..f3b2929 100644 --- a/bemani/data/config.py +++ b/bemani/data/config.py @@ -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: diff --git a/bemani/data/migrations/versions/a1f4a09a1f90_add_region_column_to_arcade_settings.py b/bemani/data/migrations/versions/a1f4a09a1f90_add_region_column_to_arcade_settings.py new file mode 100644 index 0000000..5aba225 --- /dev/null +++ b/bemani/data/migrations/versions/a1f4a09a1f90_add_region_column_to_arcade_settings.py @@ -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 ### diff --git a/bemani/data/mysql/machine.py b/bemani/data/mysql/machine.py index 3b9a7b1..b8e5931 100644 --- a/bemani/data/mysql/machine.py +++ b/bemani/data/mysql/machine.py @@ -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() diff --git a/bemani/data/types.py b/bemani/data/types.py index c38950b..9540c43 100644 --- a/bemani/data/types.py +++ b/bemani/data/types.py @@ -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: diff --git a/bemani/frontend/admin/admin.py b/bemani/frontend/admin/admin.py index d4597b0..1fa2316 100644 --- a/bemani/frontend/admin/admin.py +++ b/bemani/frontend/admin/admin.py @@ -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'], diff --git a/bemani/frontend/arcade/arcade.py b/bemani/frontend/arcade/arcade.py index 77f7d4f..df111a7 100644 --- a/bemani/frontend/arcade/arcade.py +++ b/bemani/frontend/arcade/arcade.py @@ -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('//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('//pcbids/generate', methods=['POST']) @jsonify @loginrequired diff --git a/bemani/frontend/base.py b/bemani/frontend/base.py index a2296e8..f14dfe6 100644 --- a/bemani/frontend/base.py +++ b/bemani/frontend/base.py @@ -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 diff --git a/bemani/frontend/bishi/bishi.py b/bemani/frontend/bishi/bishi.py index 238a2eb..eeeb7c1 100644 --- a/bemani/frontend/bishi/bishi.py +++ b/bemani/frontend/bishi/bishi.py @@ -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] = {} diff --git a/bemani/frontend/iidx/endpoints.py b/bemani/frontend/iidx/endpoints.py index 784e6ac..b9f2aa1 100644 --- a/bemani/frontend/iidx/endpoints.py +++ b/bemani/frontend/iidx/endpoints.py @@ -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 diff --git a/bemani/frontend/iidx/iidx.py b/bemani/frontend/iidx/iidx.py index f6c6b1d..ba38847 100644 --- a/bemani/frontend/iidx/iidx.py +++ b/bemani/frontend/iidx/iidx.py @@ -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'), diff --git a/bemani/frontend/mga/mga.py b/bemani/frontend/mga/mga.py index 31bc163..39f1096 100644 --- a/bemani/frontend/mga/mga.py +++ b/bemani/frontend/mga/mga.py @@ -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] = {} diff --git a/bemani/frontend/museca/museca.py b/bemani/frontend/museca/museca.py index aff0f19..915b47e 100644 --- a/bemani/frontend/museca/museca.py +++ b/bemani/frontend/museca/museca.py @@ -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]]: diff --git a/bemani/frontend/reflec/reflec.py b/bemani/frontend/reflec/reflec.py index 7499c51..b6e6f97 100644 --- a/bemani/frontend/reflec/reflec.py +++ b/bemani/frontend/reflec/reflec.py @@ -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]]: diff --git a/bemani/frontend/sdvx/sdvx.py b/bemani/frontend/sdvx/sdvx.py index 6c4ebbc..9ce0df7 100644 --- a/bemani/frontend/sdvx/sdvx.py +++ b/bemani/frontend/sdvx/sdvx.py @@ -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]]: diff --git a/bemani/frontend/static/controllers/admin/arcades.react.js b/bemani/frontend/static/controllers/admin/arcades.react.js index 254d971..66696f9 100644 --- a/bemani/frontend/static/controllers/admin/arcades.react.js +++ b/bemani/frontend/static/controllers/admin/arcades.react.js @@ -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 ; + } else { + return { window.regions[arcade.region] }; + } + }, + 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({ Name Description + Region Owners PASELI Enabled PASELI Infinite @@ -377,6 +401,18 @@ var card_management = React.createClass({ }.bind(this)} /> + + + { this.state.new_arcade.owners.map(function(owner, index) { return ( diff --git a/bemani/frontend/static/controllers/arcade/arcade.react.js b/bemani/frontend/static/controllers/arcade/arcade.react.js index db59d99..6120fec 100644 --- a/bemani/frontend/static/controllers/arcade/arcade.react.js +++ b/bemani/frontend/static/controllers/arcade/arcade.react.js @@ -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 ( + { + !this.state.editing_region ? + + { window.regions[this.state.region] } + + : +
+ + + + + }
+ ); + }, + generateNewMachine: function(event) { AJAX.post( Link.get('generatepcbid'), @@ -478,6 +536,7 @@ var arcade_management = React.createClass({ no description } {this.renderPIN()} + {this.renderRegion()} { this.state.paseli_enabled ? 'yes' : 'no' } diff --git a/bemani/frontend/static/controllers/iidx/settings.react.js b/bemani/frontend/static/controllers/iidx/settings.react.js index 26dc88c..4c2dbd0 100644 --- a/bemani/frontend/static/controllers/iidx/settings.react.js +++ b/bemani/frontend/static/controllers/iidx/settings.react.js @@ -314,12 +314,11 @@ var settings_view = React.createClass({ }, renderPrefecture: function(player) { - regions = this.state.version >= 25 ? Regions2 : Regions; return ( { !this.state.editing_prefecture ? - {regions[player.prefecture]} + {window.regions[player.prefecture]} {{ 'yes' if config.paseli.enabled else 'no' }} (can be overridden by arcade settings)
Infinite PASELI Enabled
{{ 'yes' if config.paseli.infinite else 'no' }} (can be overridden by arcade settings)
+
Default Region
+
{{ region[config.server.region] }} (can be overridden by arcade settings)
Event Log Preservation Duration
{{ (config.event_log_duration|string + ' seconds') if config.event_log_duration else 'infinite' }}
diff --git a/config/server.yaml b/config/server.yaml index dbc05b9..70d9483 100644 --- a/config/server.yaml +++ b/config/server.yaml @@ -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.