Add ability to specify a custom area that gets picked up by some games.
This commit is contained in:
parent
f11fa7de1d
commit
85a3f08c78
@ -129,9 +129,11 @@ class CoreHandler(Base):
|
||||
machine = self.get_machine()
|
||||
if machine.arcade is None:
|
||||
region = self.config.server.region
|
||||
area = self.config.server.area
|
||||
else:
|
||||
arcade = self.data.local.machine.get_arcade(machine.arcade)
|
||||
region = arcade.region
|
||||
area = arcade.area
|
||||
|
||||
if region == RegionConstants.HONG_KONG:
|
||||
country = "HK"
|
||||
@ -182,6 +184,9 @@ class CoreHandler(Base):
|
||||
location.add_child(Node.string("region", regionstr))
|
||||
location.add_child(Node.string("name", machine.name))
|
||||
location.add_child(Node.u8("type", 0))
|
||||
if area is not None:
|
||||
location.add_child(Node.string("regionname", area))
|
||||
location.add_child(Node.string("regionjname", area))
|
||||
|
||||
line = Node.void("line")
|
||||
line.add_child(Node.string("id", "."))
|
||||
|
@ -45,7 +45,19 @@ class ID:
|
||||
"""
|
||||
Take a machine ID as an integer, format it as a string.
|
||||
"""
|
||||
if region not in {"JP", "KR", "TW", "HK", "US", "GB", "IT", "ES", "FR", "PT"}:
|
||||
if region not in {
|
||||
"JP",
|
||||
"KR",
|
||||
"TW",
|
||||
"HK",
|
||||
"US",
|
||||
"GB",
|
||||
"IT",
|
||||
"ES",
|
||||
"FR",
|
||||
"PT",
|
||||
"XX",
|
||||
}:
|
||||
raise Exception(f"Invalid region {region}!")
|
||||
return f"{region}-{machine_id}"
|
||||
|
||||
@ -57,7 +69,7 @@ class ID:
|
||||
try:
|
||||
if (
|
||||
machine_id[:2]
|
||||
in {"JP", "KR", "TW", "HK", "US", "GB", "IT", "ES", "FR", "PT"}
|
||||
in {"JP", "KR", "TW", "HK", "US", "GB", "IT", "ES", "FR", "PT", "XX"}
|
||||
and machine_id[2] == "-"
|
||||
):
|
||||
return int(machine_id[3:])
|
||||
|
@ -95,6 +95,11 @@ class Server:
|
||||
# Region was fine.
|
||||
return region
|
||||
|
||||
@property
|
||||
def area(self) -> Optional[str]:
|
||||
area = self.__config.get("server", {}).get("area")
|
||||
return str(area) if area else None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, parent_config: "Config") -> None:
|
||||
|
@ -0,0 +1,28 @@
|
||||
"""Add custom area string to arcade configuration.
|
||||
|
||||
Revision ID: f64d138962e0
|
||||
Revises: a1f4a09a1f90
|
||||
Create Date: 2022-10-15 21:23:02.461661
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f64d138962e0'
|
||||
down_revision = 'a1f4a09a1f90'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('arcade', sa.Column('area', sa.String(length=63), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('arcade', 'area')
|
||||
# ### end Alembic commands ###
|
@ -41,6 +41,7 @@ arcade = Table(
|
||||
Column("description", String(255), nullable=False),
|
||||
Column("pin", String(8), nullable=False),
|
||||
Column("pref", Integer, nullable=False),
|
||||
Column("area", String(63)),
|
||||
Column("data", JSON),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
@ -317,6 +318,7 @@ class MachineData(BaseData):
|
||||
name: str,
|
||||
description: str,
|
||||
region: int,
|
||||
area: Optional[str],
|
||||
data: Dict[str, Any],
|
||||
owners: List[UserID],
|
||||
) -> Arcade:
|
||||
@ -327,8 +329,8 @@ class MachineData(BaseData):
|
||||
An Arcade object representing this arcade
|
||||
"""
|
||||
sql = (
|
||||
"INSERT INTO arcade (name, description, pref, data, pin) "
|
||||
+ "VALUES (:name, :desc, :pref, :data, '00000000')"
|
||||
"INSERT INTO arcade (name, description, pref, area, data, pin) "
|
||||
+ "VALUES (:name, :desc, :pref, :area, :data, '00000000')"
|
||||
)
|
||||
cursor = self.execute(
|
||||
sql,
|
||||
@ -336,6 +338,7 @@ class MachineData(BaseData):
|
||||
"name": name,
|
||||
"desc": description,
|
||||
"pref": region,
|
||||
"area": area,
|
||||
"data": self.serialize(data),
|
||||
},
|
||||
)
|
||||
@ -362,7 +365,9 @@ class MachineData(BaseData):
|
||||
Returns:
|
||||
An Arcade object if this arcade was found, or None otherwise.
|
||||
"""
|
||||
sql = "SELECT name, description, pin, pref, data FROM arcade WHERE id = :id"
|
||||
sql = (
|
||||
"SELECT name, description, pin, pref, area, data FROM arcade WHERE id = :id"
|
||||
)
|
||||
cursor = self.execute(sql, {"id": arcadeid})
|
||||
if cursor.rowcount != 1:
|
||||
# Arcade doesn't exist
|
||||
@ -379,6 +384,7 @@ class MachineData(BaseData):
|
||||
result["description"],
|
||||
result["pin"],
|
||||
result["pref"],
|
||||
result["area"] or None,
|
||||
self.deserialize(result["data"]),
|
||||
[owner["userid"] for owner in cursor.fetchall()],
|
||||
)
|
||||
@ -393,7 +399,7 @@ class MachineData(BaseData):
|
||||
# Update machine name based on game
|
||||
sql = (
|
||||
"UPDATE `arcade` "
|
||||
+ "SET name = :name, description = :desc, pin = :pin, pref = :pref, data = :data "
|
||||
+ "SET name = :name, description = :desc, pin = :pin, pref = :pref, area = :area, data = :data "
|
||||
+ "WHERE id = :arcadeid"
|
||||
)
|
||||
self.execute(
|
||||
@ -403,6 +409,7 @@ class MachineData(BaseData):
|
||||
"desc": arcade.description,
|
||||
"pin": arcade.pin,
|
||||
"pref": arcade.region,
|
||||
"area": arcade.area,
|
||||
"data": self.serialize(arcade.data),
|
||||
"arcadeid": arcade.id,
|
||||
},
|
||||
@ -447,7 +454,7 @@ class MachineData(BaseData):
|
||||
arcade_to_owners[arcade] = []
|
||||
arcade_to_owners[arcade].append(owner)
|
||||
|
||||
sql = "SELECT id, name, description, pin, pref, data FROM arcade"
|
||||
sql = "SELECT id, name, description, pin, pref, area, data FROM arcade"
|
||||
cursor = self.execute(sql)
|
||||
return [
|
||||
Arcade(
|
||||
@ -456,6 +463,7 @@ class MachineData(BaseData):
|
||||
result["description"],
|
||||
result["pin"],
|
||||
result["pref"],
|
||||
result["area"] or None,
|
||||
self.deserialize(result["data"]),
|
||||
arcade_to_owners.get(result["id"], []),
|
||||
)
|
||||
|
@ -161,6 +161,7 @@ class Arcade:
|
||||
description: str,
|
||||
pin: str,
|
||||
region: int,
|
||||
area: Optional[str],
|
||||
data: Dict[str, Any],
|
||||
owners: List[UserID],
|
||||
) -> None:
|
||||
@ -173,6 +174,7 @@ class 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.
|
||||
area - A string representing the custom area this arcade is in, or None if default.
|
||||
data - A dictionary of settings for this arcade.
|
||||
owners - An list of integers specifying the user IDs of owners for this arcade.
|
||||
"""
|
||||
@ -181,11 +183,12 @@ class Arcade:
|
||||
self.description = description
|
||||
self.pin = pin
|
||||
self.region = region
|
||||
self.area = area
|
||||
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}, region={self.region}, data={self.data}, owners={self.owners})"
|
||||
return f"Arcade(arcadeid={self.id}, name={self.name}, description={self.description}, pin={self.pin}, region={self.region}, area={self.area}, data={self.data}, owners={self.owners})"
|
||||
|
||||
|
||||
class Song:
|
||||
|
@ -48,6 +48,7 @@ def format_arcade(arcade: Arcade) -> Dict[str, Any]:
|
||||
"name": arcade.name,
|
||||
"description": arcade.description,
|
||||
"region": arcade.region,
|
||||
"area": arcade.area or "",
|
||||
"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"),
|
||||
@ -269,6 +270,7 @@ def viewarcades() -> Response:
|
||||
"paseli_enabled": g.config.paseli.enabled,
|
||||
"paseli_infinite": g.config.paseli.infinite,
|
||||
"default_region": g.config.server.region,
|
||||
"default_area": g.config.server.area,
|
||||
"mask_services_url": False,
|
||||
},
|
||||
{
|
||||
@ -500,6 +502,7 @@ def updatearcade() -> Dict[str, Any]:
|
||||
arcade.name = new_values["name"]
|
||||
arcade.description = new_values["description"]
|
||||
arcade.region = new_values["region"]
|
||||
arcade.area = new_values["area"] or None
|
||||
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"])
|
||||
@ -542,6 +545,7 @@ def addarcade() -> Dict[str, Any]:
|
||||
new_values["name"],
|
||||
new_values["description"],
|
||||
new_values["region"],
|
||||
new_values["area"] or None,
|
||||
{
|
||||
"paseli_enabled": new_values["paseli_enabled"],
|
||||
"paseli_infinite": new_values["paseli_infinite"],
|
||||
|
@ -76,6 +76,7 @@ def format_arcade(arcade: Arcade) -> Dict[str, Any]:
|
||||
"description": arcade.description,
|
||||
"pin": arcade.pin,
|
||||
"region": arcade.region,
|
||||
"area": arcade.area,
|
||||
"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:
|
||||
"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),
|
||||
"update_area": url_for("arcade_pages.updatearea", 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),
|
||||
@ -344,6 +346,31 @@ def updateregion(arcadeid: int) -> Dict[str, Any]:
|
||||
return {"region": region}
|
||||
|
||||
|
||||
@arcade_pages.route("/<int:arcadeid>/area/update", methods=["POST"])
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def updatearea(arcadeid: int) -> Dict[str, Any]:
|
||||
# Cast the ID for type safety.
|
||||
arcadeid = ArcadeID(arcadeid)
|
||||
|
||||
try:
|
||||
area = request.get_json()["area"] or None
|
||||
except Exception:
|
||||
area = None
|
||||
|
||||
# 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!")
|
||||
|
||||
# Update and save
|
||||
arcade.area = area
|
||||
g.data.local.machine.put_arcade(arcade)
|
||||
|
||||
# Return nothing
|
||||
return {"area": area}
|
||||
|
||||
|
||||
@arcade_pages.route("/<int:arcadeid>/pcbids/generate", methods=["POST"])
|
||||
@jsonify
|
||||
@loginrequired
|
||||
|
@ -7,6 +7,7 @@ var card_management = createReactClass({
|
||||
name: '',
|
||||
description: '',
|
||||
region: window.default_region,
|
||||
area: window.default_area,
|
||||
paseli_enabled: window.paseli_enabled,
|
||||
paseli_infinite: window.paseli_infinite,
|
||||
mask_services_url: window.mask_services_url,
|
||||
@ -36,6 +37,7 @@ var card_management = createReactClass({
|
||||
name: '',
|
||||
description: '',
|
||||
region: window.default_region,
|
||||
area: window.default_area,
|
||||
paseli_enabled: window.paseli_enabled,
|
||||
paseli_infinite: window.paseli_infinite,
|
||||
mask_services_url: window.mask_services_url,
|
||||
@ -201,10 +203,37 @@ var card_management = createReactClass({
|
||||
}
|
||||
},
|
||||
|
||||
renderArea: function(arcade) {
|
||||
if (this.state.editing_arcade && arcade.id == this.state.editing_arcade.id) {
|
||||
return <input
|
||||
name="area"
|
||||
type="text"
|
||||
value={ this.state.editing_arcade.area }
|
||||
onChange={function(event) {
|
||||
var arcade = this.state.editing_arcade;
|
||||
arcade.area = event.target.value;
|
||||
this.setState({
|
||||
editing_arcade: arcade,
|
||||
});
|
||||
}.bind(this)}
|
||||
/>;
|
||||
} else {
|
||||
return <span>{ arcade.area }</span>;
|
||||
}
|
||||
},
|
||||
|
||||
sortDescription: function(a, b) {
|
||||
return a.description.localeCompare(b.description);
|
||||
},
|
||||
|
||||
sortArea: function(a, b) {
|
||||
return a.area.localeCompare(b.area);
|
||||
},
|
||||
|
||||
sortRegion: function(a, b) {
|
||||
return window.regions[a.region].localeCompare(window.regions[b.region]);
|
||||
},
|
||||
|
||||
renderOwners: function(arcade) {
|
||||
if (this.state.editing_arcade && arcade.id == this.state.editing_arcade.id) {
|
||||
return this.state.editing_arcade.owners.map(function(owner, index) {
|
||||
@ -331,6 +360,12 @@ var card_management = createReactClass({
|
||||
{
|
||||
name: "Region",
|
||||
render: this.renderRegion,
|
||||
sort: this.sortRegion,
|
||||
},
|
||||
{
|
||||
name: "Custom Area",
|
||||
render: this.renderArea,
|
||||
sort: this.sortArea,
|
||||
},
|
||||
{
|
||||
name: 'Owners',
|
||||
@ -368,6 +403,7 @@ var card_management = createReactClass({
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Region</th>
|
||||
<th>Custom Area</th>
|
||||
<th>Owners</th>
|
||||
<th>PASELI Enabled</th>
|
||||
<th>PASELI Infinite</th>
|
||||
@ -413,6 +449,18 @@ var card_management = createReactClass({
|
||||
}.bind(this)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
name="area"
|
||||
type="text"
|
||||
value={ this.state.new_arcade.area }
|
||||
onChange={function(event) {
|
||||
var arcade = this.state.new_arcade;
|
||||
arcade.area = event.target.value;
|
||||
this.setState({new_arcade: arcade});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</td>
|
||||
<td>{
|
||||
this.state.new_arcade.owners.map(function(owner, index) {
|
||||
return (
|
||||
|
@ -27,6 +27,9 @@ var arcade_management = createReactClass({
|
||||
region: window.arcade.region,
|
||||
editing_region: false,
|
||||
new_region: '',
|
||||
area: window.arcade.area,
|
||||
editing_area: false,
|
||||
new_area: '',
|
||||
paseli_enabled_saving: false,
|
||||
paseli_infinite_saving: false,
|
||||
mask_services_url_saving: false,
|
||||
@ -105,6 +108,21 @@ var arcade_management = createReactClass({
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
saveArea: function(event) {
|
||||
AJAX.post(
|
||||
Link.get('update_area'),
|
||||
{area: this.state.new_area},
|
||||
function(response) {
|
||||
this.setState({
|
||||
area: response.area,
|
||||
new_area: '',
|
||||
editing_area: false,
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
togglePaseliEnabled: function() {
|
||||
this.setState({paseli_enabled_saving: true})
|
||||
AJAX.post(
|
||||
@ -291,6 +309,51 @@ var arcade_management = createReactClass({
|
||||
);
|
||||
},
|
||||
|
||||
renderArea: function() {
|
||||
return (
|
||||
<LabelledSection vertical={true} label="Custom Area">{
|
||||
!this.state.editing_area ?
|
||||
<>
|
||||
<span>{ this.state.area ? this.state.area : <i>unset</i> }</span>
|
||||
<Edit
|
||||
onClick={function(event) {
|
||||
this.setState({editing_area: true, new_area: this.state.area});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</> :
|
||||
<form className="inline" onSubmit={this.saveArea}>
|
||||
<input
|
||||
type="text"
|
||||
className="inline"
|
||||
autofocus="true"
|
||||
ref={c => (this.focus_element = c)}
|
||||
value={this.state.new_area}
|
||||
onChange={function(event) {
|
||||
if (event.target.value.length <= 63) {
|
||||
this.setState({new_area: event.target.value});
|
||||
}
|
||||
}.bind(this)}
|
||||
name="area"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
value="save"
|
||||
/>
|
||||
<input
|
||||
type="button"
|
||||
value="cancel"
|
||||
onClick={function(event) {
|
||||
this.setState({
|
||||
new_area: '',
|
||||
editing_area: false,
|
||||
});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</form>
|
||||
}</LabelledSection>
|
||||
);
|
||||
},
|
||||
|
||||
generateNewMachine: function(event) {
|
||||
AJAX.post(
|
||||
Link.get('generatepcbid'),
|
||||
@ -474,6 +537,7 @@ var arcade_management = createReactClass({
|
||||
}</LabelledSection>
|
||||
{this.renderPIN()}
|
||||
{this.renderRegion()}
|
||||
{this.renderArea()}
|
||||
<LabelledSection vertical={true} label={
|
||||
<>
|
||||
PASELI Enabled
|
||||
|
@ -67,6 +67,12 @@
|
||||
<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>Default Custom Area</dt>
|
||||
{% if config.server.area %}
|
||||
<dd>{{ config.server.area or "<i>unset</i>" }} (can be overridden by arcade settings)</dd>
|
||||
{% else %}
|
||||
<dd><i>unset</i> (can be overridden by arcade settings)</dd>
|
||||
{% endif %}
|
||||
<dt>Event Log Preservation Duration</dt>
|
||||
<dd>{{ (config.event_log_duration|string + ' seconds') if config.event_log_duration else 'infinite' }}</dd>
|
||||
</dl>
|
||||
|
@ -1,3 +1,4 @@
|
||||
# Core database settings, required so that the system can persist scores and accounts.
|
||||
database:
|
||||
# IP or DNS entry for MySQL instance.
|
||||
address: "localhost"
|
||||
@ -12,6 +13,8 @@ database:
|
||||
# Set this to False or delete this to run in production mode.
|
||||
read_only: False
|
||||
|
||||
# Core server settings, required so that the backend knows what to tell games for core
|
||||
# routing and server URLs.
|
||||
server:
|
||||
# Advertised server IP or DNS entry games will connect to.
|
||||
address: "192.168.0.1"
|
||||
@ -38,6 +41,9 @@ server:
|
||||
# the 56 normal regions found in RegionConstants, and 1000 for "Europe" and
|
||||
# 2000 for "Other".
|
||||
region: 56
|
||||
# The custom area code displayed in the system settings menu on various games.
|
||||
# Delete this setting to force games to display "Unobtained" instead.
|
||||
area: "USA"
|
||||
|
||||
# Webhook URLs. These allow for game scores from games with scorecard support to be broadcasted to outside services.
|
||||
# Delete this to disable this support.
|
||||
@ -47,12 +53,16 @@ webhooks:
|
||||
- "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||
pnm:
|
||||
- "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi"
|
||||
|
||||
# Global PASESLI settings, which can be overridden on a per-arcade basis. These form the default settings.
|
||||
paseli:
|
||||
# Whether PASELI is enabled on the network.
|
||||
enabled: True
|
||||
# Whether infinite PASELI balance is enabled on the network.
|
||||
infinite: True
|
||||
|
||||
# Game series to provide support for. Disabling something here hides it from the frontend and makes the backend
|
||||
# ignore games coming from that series.
|
||||
support:
|
||||
# Bishi Bashi frontend/backend enabled.
|
||||
bishi: True
|
||||
|
Loading…
Reference in New Issue
Block a user