1
0
mirror of synced 2024-11-24 06:20:12 +01:00

Add default game settings for PCBIDs not in any arcade.

This commit is contained in:
Jennifer Taylor 2021-09-10 02:23:20 +00:00
parent 1e581f0867
commit a226c39eb9
9 changed files with 376 additions and 251 deletions

View File

@ -447,14 +447,22 @@ class Base(ABC):
def get_game_config(self) -> ValidatedDict:
machine = self.data.local.machine.get_machine(self.config.machine.pcbid)
# If this machine belongs to an arcade, use its settings. If the settings aren't present,
# default to the game's defaults.
if machine.arcade is not None:
settings = self.data.local.machine.get_settings(machine.arcade, self.game, self.version, 'game_config')
else:
settings = None
if settings is None:
settings = ValidatedDict()
return settings
if settings is None:
settings = ValidatedDict()
return settings
# If this machine does not belong to an arcade, use the server-wide settings. If the settings
# aren't present, default ot the game's default.
else:
settings = self.data.local.machine.get_settings(self.data.local.machine.DEFAULT_SETTINGS_ARCADE, self.game, self.version, 'game_config')
if settings is None:
settings = ValidatedDict()
return settings
def get_play_statistics(self, userid: UserID) -> PlayStatistics:
"""

View File

@ -2,6 +2,7 @@ from sqlalchemy import Table, Column, UniqueConstraint # type: ignore
from sqlalchemy.types import String, Integer, JSON # type: ignore
from sqlalchemy.dialects.mysql import BIGINT as BigInteger # type: ignore
from typing import Optional, Dict, List, Tuple, Any
from typing_extensions import Final
from bemani.common import GameConstants, ValidatedDict
from bemani.data.mysql.base import BaseData, metadata
@ -80,6 +81,10 @@ class ArcadeCreationException(Exception):
class MachineData(BaseData):
# This relies on the fact that arcadeid in the arcade_settings table is auto-increment
# and thus will start at 1.
DEFAULT_SETTINGS_ARCADE: Final[ArcadeID] = ArcadeID(-1)
def from_port(self, port: int) -> Optional[str]:
"""
Given a port, look up the PCBID attached to that port.

View File

@ -3,10 +3,11 @@ 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, RegionConstants
from bemani.common import CardCipher, CardCipherException, GameConstants, RegionConstants, ValidatedDict
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
from bemani.frontend.gamesettings import get_game_settings
from bemani.frontend.iidx.iidx import IIDXFrontend
from bemani.frontend.jubeat.jubeat import JubeatFrontend
from bemani.frontend.popn.popn import PopnMusicFrontend
@ -320,6 +321,21 @@ def viewnews() -> Response:
)
@admin_pages.route('/gamesettings')
@adminrequired
def viewgamesettings() -> Response:
return render_react(
'Game Settings',
'admin/gamesettings.react.js',
{
'game_settings': get_game_settings(g.data, g.data.local.machine.DEFAULT_SETTINGS_ARCADE),
},
{
'update_settings': url_for('admin_pages.updatesettings'),
},
)
@admin_pages.route('/users/<int:userid>')
@adminrequired
def viewuser(userid: int) -> Response:
@ -1081,3 +1097,44 @@ def updatenews() -> Dict[str, Any]:
return {
'news': [format_news(news) for news in g.data.local.network.get_all_news()],
}
@admin_pages.route('/gamesettings/update', methods=['POST'])
@jsonify
@adminrequired
def updatesettings() -> Dict[str, Any]:
# Cast the ID for type safety.
arcadeid = g.data.local.machine.DEFAULT_SETTINGS_ARCADE
game = GameConstants(request.get_json()['game'])
version = request.get_json()['version']
for setting_type, update_function in [
('bools', 'replace_bool'),
('ints', 'replace_int'),
('strs', 'replace_str'),
('longstrs', 'replace_str'),
]:
for game_setting in request.get_json()[setting_type]:
# Grab the value to update
category = game_setting['category']
setting = game_setting['setting']
new_value = game_setting['value']
# Update the value
current_settings = g.data.local.machine.get_settings(arcadeid, game, version, category)
if current_settings is None:
current_settings = ValidatedDict()
getattr(current_settings, update_function)(setting, new_value)
# Save it back
g.data.local.machine.put_settings(arcadeid, game, version, category, current_settings)
# Return the updated value
return {
'game_settings': [
gs for gs in get_game_settings(g.data, arcadeid)
if gs['game'] == game.value and gs['version'] == version
][0],
}

View File

@ -726,6 +726,10 @@ def navigation() -> Dict[str, Any]:
'label': 'PCBIDs',
'uri': url_for('admin_pages.viewmachines'),
},
{
'label': 'Game Settings',
'uri': url_for('admin_pages.viewgamesettings'),
},
{
'label': 'Cards',
'uri': url_for('admin_pages.viewcards'),

View File

@ -6,6 +6,7 @@ from bemani.backend.base import Base
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.gamesettings import get_game_settings
from bemani.frontend.templates import templates_location
from bemani.frontend.static import static_location
from bemani.frontend.types import g
@ -85,60 +86,6 @@ def format_event(event: Event) -> Dict[str, Any]:
}
def get_game_settings(arcade: Arcade) -> List[Dict[str, Any]]:
game_lut: Dict[GameConstants, Dict[int, str]] = {}
settings_lut: Dict[GameConstants, Dict[int, Dict[str, Any]]] = {}
all_settings = []
for (game, version, name) in Base.all_games():
if game not in game_lut:
game_lut[game] = {}
settings_lut[game] = {}
game_lut[game][version] = name
settings_lut[game][version] = {}
for (game, version, settings) in Base.all_settings():
if not settings:
continue
# First, set up the basics
game_settings: Dict[str, Any] = {
'game': game.value,
'version': version,
'name': game_lut[game][version],
'bools': [],
'ints': [],
'strs': [],
'longstrs': [],
}
# Now, look up the current setting for each returned setting
for setting_type, setting_unpacker in [
('bools', "get_bool"),
('ints', "get_int"),
('strs', "get_str"),
('longstrs', "get_str"),
]:
for setting in settings.get(setting_type, []):
if setting['category'] not in settings_lut[game][version]:
cached_setting = g.data.local.machine.get_settings(arcade.id, game, version, setting['category'])
if cached_setting is None:
cached_setting = ValidatedDict()
settings_lut[game][version][setting['category']] = cached_setting
current_settings = settings_lut[game][version][setting['category']]
setting['value'] = getattr(current_settings, setting_unpacker)(setting['setting'])
game_settings[setting_type].append(setting)
# Now, include it!
all_settings.append(game_settings)
return sorted(
all_settings,
key=lambda setting: (setting['game'], setting['version']),
)
@arcade_pages.route('/<int:arcadeid>')
@loginrequired
def viewarcade(arcadeid: int) -> Response:
@ -158,7 +105,7 @@ def viewarcade(arcadeid: int) -> Response:
'arcade': format_arcade(arcade),
'regions': RegionConstants.LUT,
'machines': machines,
'game_settings': get_game_settings(arcade),
'game_settings': get_game_settings(g.data, arcadeid),
'balances': {balance[0]: balance[1] for balance in g.data.local.machine.get_balances(arcadeid)},
'users': {user.id: user.username for user in g.data.local.user.get_all_users()},
'events': [format_event(event) for event in g.data.local.network.get_events(arcadeid=arcadeid, event='paseli_transaction')],
@ -526,7 +473,7 @@ def updatesettings(arcadeid: int) -> Dict[str, Any]:
new_value = game_setting['value']
# Update the value
current_settings = g.data.local.machine.get_settings(arcade.id, game, version, category)
current_settings = g.data.local.machine.get_settings(arcadeid, game, version, category)
if current_settings is None:
current_settings = ValidatedDict()
@ -538,7 +485,7 @@ def updatesettings(arcadeid: int) -> Dict[str, Any]:
# Return the updated value
return {
'game_settings': [
gs for gs in get_game_settings(arcade)
gs for gs in get_game_settings(g.data, arcadeid)
if gs['game'] == game.value and gs['version'] == version
][0],
}

View File

@ -0,0 +1,59 @@
from typing import Any, Dict, List
from bemani.backend.base import Base
from bemani.common import ValidatedDict, GameConstants
from bemani.data import Data, ArcadeID
def get_game_settings(data: Data, arcadeid: ArcadeID) -> List[Dict[str, Any]]:
game_lut: Dict[GameConstants, Dict[int, str]] = {}
settings_lut: Dict[GameConstants, Dict[int, Dict[str, Any]]] = {}
all_settings = []
for (game, version, name) in Base.all_games():
if game not in game_lut:
game_lut[game] = {}
settings_lut[game] = {}
game_lut[game][version] = name
settings_lut[game][version] = {}
for (game, version, settings) in Base.all_settings():
if not settings:
continue
# First, set up the basics
game_settings: Dict[str, Any] = {
'game': game.value,
'version': version,
'name': game_lut[game][version],
'bools': [],
'ints': [],
'strs': [],
'longstrs': [],
}
# Now, look up the current setting for each returned setting
for setting_type, setting_unpacker in [
('bools', "get_bool"),
('ints', "get_int"),
('strs', "get_str"),
('longstrs', "get_str"),
]:
for setting in settings.get(setting_type, []):
if setting['category'] not in settings_lut[game][version]:
cached_setting = data.local.machine.get_settings(arcadeid, game, version, setting['category'])
if cached_setting is None:
cached_setting = ValidatedDict()
settings_lut[game][version][setting['category']] = cached_setting
current_settings = settings_lut[game][version][setting['category']]
setting['value'] = getattr(current_settings, setting_unpacker)(setting['setting'])
game_settings[setting_type].append(setting)
# Now, include it!
all_settings.append(game_settings)
return sorted(
all_settings,
key=lambda setting: (setting['game'], setting['version']),
)

View File

@ -0,0 +1,210 @@
/*** @jsx React.DOM */
function makeGameSettingName(game_settings) {
return game_settings.game + '-' + game_settings.version;
}
var GameSettings = React.createClass({
getInitialState: function() {
console.log(this.props);
var valid_settings = this.props.game_settings.map(function(setting) {
return makeGameSettingName(setting);
});
var pagenav = new History(valid_settings);
return {
pagenav: pagenav,
settings: this.props.game_settings,
current_setting: pagenav.getInitialState(makeGameSettingName(this.props.game_settings[0])),
settings_changed: {},
settings_saving: {},
settings_saved: {},
};
},
componentDidMount: function() {
this.state.pagenav.onChange(function(setting) {
this.setState({current_setting: setting});
}.bind(this));
},
getSettingIndex: function(setting_name) {
var real_index = -1;
this.state.settings.map(function(game_settings, index) {
var current = makeGameSettingName(game_settings);
if (current == setting_name) { real_index = index; }
}.bind(this));
return real_index;
},
setChanged: function(val) {
this.state.settings_changed[this.state.current_setting] = val;
return this.state.settings_changed;
},
setSaving: function(val) {
this.state.settings_saving[this.state.current_setting] = val;
return this.state.settings_saving;
},
setSaved: function(val) {
this.state.settings_saved[this.state.current_setting] = val;
return this.state.settings_saved;
},
saveSettings: function(event) {
var index = this.getSettingIndex(this.state.current_setting);
this.setState({settings_saving: this.setSaving(true), settings_saved: this.setSaved(false)});
AJAX.post(
Link.get('update_settings'),
this.state.settings[index],
function(response) {
this.state.settings[index] = response.game_settings;
this.setState({
settings: this.state.settings,
settings_saving: this.setSaving(false),
settings_saved: this.setSaved(true),
settings_changed: this.setChanged(false),
});
}.bind(this)
);
event.preventDefault();
},
render: function() {
return (
<div>
<div className="section settings-nav">
{ this.state.settings.map(function(game_settings) {
var current = makeGameSettingName(game_settings);
return (
<Nav
title={game_settings.name}
active={this.state.current_setting == current}
showAlert={this.state.settings_changed[current]}
onClick={function(event) {
if (this.state.current_setting == current) { return; }
this.setState({current_setting: current});
this.state.pagenav.navigate(current);
}.bind(this)}
/>
);
}.bind(this))}
</div>
<div className="section">
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].ints.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<SelectInt
name={setting.setting}
id={setting.setting}
value={setting.value}
choices={setting.values}
onChange={function(value) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].ints[index].value = value;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].bools.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<input
name={setting.setting}
id={setting.setting}
type="checkbox"
checked={setting.value}
onChange={function(event) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].bools[index].value = event.target.checked;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].strs.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<input
name={setting.setting}
id={setting.setting}
type="text"
value={setting.value}
onChange={function(event) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].strs[index].value = event.target.value;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].longstrs.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<textarea
name={setting.setting}
id={setting.setting}
value={setting.value}
onChange={function(event) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].longstrs[index].value = event.target.value;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
<input
type="submit"
disabled={!this.state.settings_changed[this.state.current_setting]}
value="save"
onClick={function(event) {
this.saveSettings(event);
}.bind(this)}
/>
{ this.state.settings_saving[this.state.current_setting] ?
<img className="loading" src={Link.get('static', 'loading-16.gif')} /> :
null
}
{ this.state.settings_saved[this.state.current_setting] ?
<span>{ "\u2713" }</span> :
null
}
</div>
</div>
);
},
});

View File

@ -0,0 +1,21 @@
/*** @jsx React.DOM */
var gamesettings = React.createClass({
render: function() {
return (
<div>
<div className="section">
Game settings that will be used for any PCBID that does not belong to an arcade.
</div>
<div className="section">
<GameSettings game_settings={window.game_settings} />
</div>
</div>
);
},
});
ReactDOM.render(
React.createElement(gamesettings, null),
document.getElementById('content')
);

View File

@ -1,13 +1,5 @@
/*** @jsx React.DOM */
function makeSettingName(game_settings) {
return game_settings.game + '-' + game_settings.version;
}
var valid_settings = window.game_settings.map(function(setting) {
return makeSettingName(setting);
});
var pagenav = new History(valid_settings);
function count_pcbids(machines) {
var count = 0;
machines.map(function(machine) {
@ -40,15 +32,10 @@ var arcade_management = React.createClass({
mask_services_url_saving: false,
editing_machine: null,
machines: window.machines,
settings: window.game_settings,
pcbcount: count_pcbids(window.machines),
random_pcbid: {
description: '',
},
current_setting: pagenav.getInitialState(makeSettingName(window.game_settings[0])),
settings_changed: {},
settings_saving: {},
settings_saved: {},
users: window.users,
balances: window.balances,
credits: credits,
@ -61,9 +48,6 @@ var arcade_management = React.createClass({
},
componentDidMount: function() {
pagenav.onChange(function(setting) {
this.setState({current_setting: setting});
}.bind(this));
this.refreshArcade();
},
@ -121,15 +105,6 @@ var arcade_management = React.createClass({
event.preventDefault();
},
getSettingIndex: function(setting_name) {
var real_index = -1;
this.state.settings.map(function(game_settings, index) {
var current = makeSettingName(game_settings);
if (current == setting_name) { real_index = index; }
}.bind(this));
return real_index;
},
togglePaseliEnabled: function(event) {
this.setState({paseli_enabled_saving: true})
AJAX.post(
@ -175,40 +150,6 @@ var arcade_management = React.createClass({
event.preventDefault();
},
setChanged: function(val) {
this.state.settings_changed[this.state.current_setting] = val;
return this.state.settings_changed;
},
setSaving: function(val) {
this.state.settings_saving[this.state.current_setting] = val;
return this.state.settings_saving;
},
setSaved: function(val) {
this.state.settings_saved[this.state.current_setting] = val;
return this.state.settings_saved;
},
saveSettings: function(event) {
var index = this.getSettingIndex(this.state.current_setting);
this.setState({settings_saving: this.setSaving(true), settings_saved: this.setSaved(false)});
AJAX.post(
Link.get('update_settings'),
this.state.settings[index],
function(response) {
this.state.settings[index] = response.game_settings;
this.setState({
settings: this.state.settings,
settings_saving: this.setSaving(false),
settings_saved: this.setSaved(true),
settings_changed: this.setChanged(false),
});
}.bind(this)
);
event.preventDefault();
},
addBalance: function(event) {
var intval = parseInt(this.state.credit_amount);
if (isNaN(intval)) {
@ -644,136 +585,9 @@ var arcade_management = React.createClass({
</div>
: null
}
<div className="section settings-nav">
<h3>Game Settings For This Arcade</h3>
{ this.state.settings.map(function(game_settings) {
var current = makeSettingName(game_settings);
return (
<Nav
title={game_settings.name}
active={this.state.current_setting == current}
showAlert={this.state.settings_changed[current]}
onClick={function(event) {
if (this.state.current_setting == current) { return; }
this.setState({current_setting: current});
pagenav.navigate(current);
}.bind(this)}
/>
);
}.bind(this))}
</div>
<div className="section">
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].ints.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<SelectInt
name={setting.setting}
id={setting.setting}
value={setting.value}
choices={setting.values}
onChange={function(value) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].ints[index].value = value;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].bools.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<input
name={setting.setting}
id={setting.setting}
type="checkbox"
checked={setting.value}
onChange={function(event) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].bools[index].value = event.target.checked;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].strs.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<input
name={setting.setting}
id={setting.setting}
type="text"
value={setting.value}
onChange={function(event) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].strs[index].value = event.target.value;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
{ this.state.settings[this.getSettingIndex(this.state.current_setting)].longstrs.map(function(setting, index) {
return (
<div className="arcade menuoption">
<Tip
text={setting.tip}
>
<label htmlFor={setting.setting}>{setting.name}:</label>
<textarea
name={setting.setting}
id={setting.setting}
value={setting.value}
onChange={function(event) {
this.state.settings[this.getSettingIndex(this.state.current_setting)].longstrs[index].value = event.target.value;
this.setState({
settings: this.state.settings,
settings_changed: this.setChanged(true),
});
}.bind(this)}
/>
</Tip>
</div>
);
}.bind(this))}
<input
type="submit"
disabled={!this.state.settings_changed[this.state.current_setting]}
value="save"
onClick={function(event) {
this.saveSettings(event);
}.bind(this)}
/>
{ this.state.settings_saving[this.state.current_setting] ?
<img className="loading" src={Link.get('static', 'loading-16.gif')} /> :
null
}
{ this.state.settings_saved[this.state.current_setting] ?
<span>{ "\u2713" }</span> :
null
}
<h3>Game Settings For This Arcade</h3>
<GameSettings game_settings={window.game_settings} />
</div>
<div className="section">
<h3>User PASELI Balances for This Arcade</h3>