【Arma3】スコアをウェブ公開する方法@Exileサーバ
動作サンプルはこちら(鯖味噌サーバ密告書)
ゲーム動作時でもリアルタイムのプレーヤースコア公開が可能です。
Exile ModによるArma3 Dedicated Serverでサーバ運用されてる管理者さんの参考にさればと、公開してます。
なお、こちらで公開しているのは、Arma3側もウェブサーバ側もLinuxですので、Windowsの場合は、適宜書き換えてください。
また、動けばOKッ!というスタイルで短時間で書いた代物ですので、小汚く低品質コードなのは了承ください。
(;^ω^)参考程度に・・
分からないという方は、ご連絡ください。
動作仕組み
Arma3サーバ側
Cronにて、以下を定期的に実行するようにします。当方では1時間毎。
MySQLデータベースから必要なデータを取り込み、JSONファイルを複数生成します。
SCP(秘密鍵)にて、Webサーバへコピーします。念の為、ユーザーのホームへコピーします。
ウェブサーバ側
コピーされたJSONファイルへ、ウェブ公開ホルダーにリンクを貼っておきます。
ここから先は、HTMLのお話なので、割愛しますが、当方では、eisenbraun-columnsで、そのままJSONデータを表示しています。とっても楽です。
SQLコード
まずは、欲しいデータの要求が正常に行えるようSQLコードを作ります。
使い慣れたDBユーティリティで、SQL文を用意します。Exileのデータベースはとてもシンプルなので、見るだけで分かるはずです。
注意するのは、余計なデータまで入れ込んでしまうと、(知ってる人が)JSONファイルに直接アクセスして、表示外のデータまで見られてしまいます。
データ転送量も鑑みて、ミニマムにするべきです。
リスペクト上位5名をランダム順(鯖缶除く)
SELECT * FROM
(SELECT a.name/*,a.score*/ FROM exile.account as a WHERE a.name <> “nabe" ORDER BY a.score DESC LIMIT 5) as rank
ORDER BY RAND();
キル数上位5名をランダム順(鯖缶除く)
SELECT * FROM
(SELECT a.name/*,a.kills*/ FROM exile.account as a WHERE a.name <> “nabe" ORDER BY a.kills DESC LIMIT 5) as rank
ORDER BY RAND();
最近50件のデスリスト(鯖缶除く)
SELECT
h.name,
h.died_at,
cast(round(h.position_x,0) as unsigned) as x,
cast(round(h.position_y) as unsigned) as y
FROM exile.player_history as h
WHERE name <> 'nabe’
ORDER BY died_at DESC
LIMIT 50;
全プレーヤーの詳細リスト(名前無し)
SELECT
/*a.uid,a.name,*/
cast(round(p.damage*100,0) as unsigned) as damage,
cast(truncate(p.hunger,0) as unsigned) as hunger,
cast(truncate(p.thirst,0) as unsigned) as thirst,
p.money,
cast(round(p.position_x,0) as unsigned) as x,
cast(round(p.position_y,0) as unsigned) as y,
p.primary_weapon as w,
p.spawned_at as sp,
datediff(now(),p.spawned_at) as alive,
a.deaths,a.kills,a.score,
a.total_connections as tc,
last_disconnect_at as ld,
datediff(now(),a.last_disconnect_at) as last,
a.first_connect_at as fc,
datediff(now(),a.first_connect_at) as days
FROM exile.account as a LEFT JOIN exile.player as p ON a.uid = p.account_uid
WHERE a.name <> 'DMS_PersistentVehicle’
ORDER BY a.last_disconnect_at DESC;
所有ポップタブのランキング上位10名
SELECT rank.name,CAST(rank.poptabs as UNSIGNED) as money FROM
(SELECT a.uid,a.name,
(IFNULL((SELECT SUM(p.money) FROM exile.player as p WHERE p.account_uid = a.uid),0)+
IFNULL((SELECT SUM(v.money) FROM exile.vehicle as v WHERE v.account_uid = a.uid),0)+
IFNULL((SELECT SUM(c.money) FROM exile.container as c WHERE c.account_uid = a.uid),0)) as poptabs
FROM exile.account as a WHERE NOT a.name in ('nabe’,’DMS_PersistentVehicle’) ORDER BY poptabs DESC LIMIT 10) as rank
ORDER BY money DESC;
所有エロ本のランキング上位10名
SELECT /*a.uid,*/a.name,
CAST(
IFNULL((SELECT
TRUNCATE((LENGTH(CONCAT(p.backpack_magazines,p.uniform_magazines,p.vest_magazines))-LENGTH(REPLACE(CONCAT(p.backpack_magazines,p.uniform_magazines,p.vest_magazines),’Exile_Item_Magazine0′,")))/LENGTH('Exile_Item_Magazine0’),0) as cnt1
FROM exile.player as p
WHERE p.account_uid = a.uid),0)+
IFNULL((SELECT SUM(TRUNCATE((LENGTH(v.cargo_magazines)-LENGTH(REPLACE(v.cargo_magazines,’Exile_Item_Magazine0′,")))/LENGTH('Exile_Item_Magazine0’),0)) as cnt2
FROM exile.vehicle as v
WHERE v.account_uid = a.uid),0)+
IFNULL((SELECT SUM(TRUNCATE((LENGTH(c.cargo_magazines)-LENGTH(REPLACE(c.cargo_magazines,’Exile_Item_Magazine0′,")))/LENGTH('Exile_Item_Magazine0’),0)) as cnt3
FROM exile.container as c
WHERE c.account_uid = a.uid),0)
AS SIGNED) as books
FROM exile.account as a WHERE NOT a.name in ('DMS_PersistentVehicle’) ORDER BY books DESC LIMIT 10;
現在の接続数と更新時間が欲しいので・・
SELECT CAST(COUNT(a.uid) as UNSIGNED) as connected,NOW() as tm FROM exile.account as a WHERE a.last_connect_at > a.last_disconnect_at;
当方で利用してるSQLは、こんな感じです。
お好きな、欲しいデータを出力するSQLを書いてそれぞれに用意します。
JSONを解釈するJavaScript側で、Decimal型をうまく扱えないようで、結構面倒です。なので、実数だけならSQL内でUNSIGNED型に変換しています。
Arma3サーバ側コード
CRONでの設定(定期的実行)
30 * * * * /home/****/arma3/getExileDB.sh
Windowsだと、タスクスケジューラとかかな?
ファイルを送信するシェルスクリプト
Arma3サーバが起動していれば、実行したいSQLをファイルにして、JSONファイルを生成して、まとめてウェブサーバへコピーするだけです。
Windowsだと、PowerShellやBATファイルでの記述になります。中身は結構変わりますが、やる事はシンプルなので。SCPの部分は、別途Termソフトで実現できるかと思います。
下記、青文字部分がCron内で動いてくれない様子なので、緑色文字のようにして、動作確認しました。こっちの方がシンプルw
■getExileDB.sh
#!/bin/sh
count=`ps -e|grep -v grep arma3server|wc -l`
if [ $count -ge 1 ]; then
count=`pgrep arma3`
if [ -n $count ]; then
cd /home/****/arma3
cat list_death.sql|python3 get_ExileDB.py > d.json
cat list_players.sql|python3 get_ExileDB.py > p.json
cat rank10_death.sql|python3 get_ExileDB.py > rd.json
cat rank10_kill.sql|python3 get_ExileDB.py > rk.json
cat rank10_score.sql|python3 get_ExileDB.py > rs.json
cat rank10_money.sql|python3 get_ExileDB.py > rm.json
cat rank10_books.sql|python3 get_ExileDB.py > rb.json
cat connected.sql|python3 get_ExileDB.py > cn.json
scp -i ~/.ssh/id_rsa /home/****/arma3/*.json (ユーザーID)@(Webサーバ):/(ウェブサーバ側コピー先)
fi
SQL結果からJSONファイルを生成するPythonコード
Python3用なので、古いシステムなら注意。必要なら、pipでmysql.connectorをインストールします。
丸っとDBのパスワードが書かれるので、パーミッション設定は確実に(または他の方法で隠してください)
Windowsも、Pythonを別途インストールすればそのまま利用できます(別にPython経由しなくても、MySQLユーティリティ単体で出力できる・・かもしれない、未調査)
■get_ExileDB.py
import sys
import mysql.connector
import json
from datetime import date, datetime
import codecs
input_sql = sys.stdin.readlines()
# db
data = “"
conn = mysql.connector.connect(host=’DBサーバアドレス',port=’3306′,user=’DBユーザー',password=’DBパスワード',database=’exile’)
cursor = conn.cursor(dictionary=True)
try:
cursor.execute(“".join(input_sql))
data = cursor.fetchall()
finally:
cursor.close()
conn.close()
def json_serial(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError (“Type %s not serializable" % type(obj))
# convert
#print(data)
print(json.dumps(data, default=json_serial))
ウェブサーバ側コード
ウェブ公開ホルダーではなく、ユーザーホームのsabamisoにコピーするようにしてますので、事前に、リンクファイルをウェブ公開ホルダーに生成しておきます。
無事コピーされてるようでしたら、後は簡単です。JSONとJavascriptは相性バツグンです。
ウェブサーバ側にデータがあるので、クロスドメインも気にする事もありません。
HTMLコードは、ブラウザから見れるので、ここで紹介する事もないですけども。
var URL_DATA_PLAYER = 'p.json’;
var URL_DATA_DEATH = 'd.json’;
var URL_RANK_BOOK = 'rb.json’;
var URL_RANK_DEATH = 'rd.json’;
var URL_RANK_KILL = 'rk.json’;
var URL_RANK_MONEY = 'rm.json’;
var URL_RANK_SCORE = 'rs.json’;
var URL_CONNECTED = 'cn.json’;
function getJSON(urlServer,fncCall){
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if(req.readyState == 4 && req.status == 200){
var data = JSON.parse(req.responseText);
fncCall(data);
}
};
req.open(“GET",urlServer,false);
req.send(null);
}
お約束のコードを書いたら。後は、受信後の部分。
ある程度の加工は、このタイミングで実施します。
$(document).ready(function(){
getJSON(URL_DATA_PLAYER,
function(res){
// Modify data
res.forEach(function(obj){
if(obj['alive’] == null){obj['alive’]="";}
else{obj['alive’]=obj['alive’]-obj['last’]+1;};
var str = obj['w’];
if(str == null){str="";};
str = str.replace(/^Exile_Weapon_/,");
str = str.replace(/^gac_JSDF_W_R_/,");
str = str.replace(/^srifle_/,");
str = str.replace(/^arifle_/,");
str = str.replace(/_F$/,");
obj['w’] = str;
if(obj['damage’] == 0){obj['damage’]="無";};
});
マップ上に、マッピングする方法も簡単に実装してます。
res.forEach(function(obj){
var x = obj['x’];
var y = obj['y’];
if(x!=null && y!=null){
// Marker
$('#s_map’).append(
“<div style=’position:absolute;left:"+(x/40-12)+"px;bottom:"+(y/40-12)+"px;’><img src=’images/duck.png’><div>"
);
};
});
プレーヤーの情報リストはこんな感じで書いてます。Microsoft .Netのデータバインディングの感じで、簡単に設定できます。なお、この段階まで来ると値の加工は出来ません(フィルターは可能)
※eisenbraun-columns
// Update Player Deail List
$('#columns’).columns({
data:res,
schema: [
{“header":"武器", “key":"w","template":"{{#w}}<strong>{{w}}</strong>{{/w}}"},
{“header":"生存", “key":"alive","template":"{{#alive}}{{alive}}{{/alive}}"},
{“header":"デス数", “key":"deaths"},
{“header":"キル数", “key":"kills"},
{“header":"リスペクト", “key":"score"},
{“header":"負傷","key":"damage","template":"{{#damage}}{{damage}}{{/damage}}"},
{“header":"空腹", “key":"hunger","template":"{{#hunger}}{{hunger}}{{/hunger}}"},
{“header":"水", “key":"thirst","template":"{{#thirst}}{{thirst}}{{/thirst}}"},
{“header":"所持金", “key":"money","template":"{{#money}}{{money}}{{/money}}"},
{“header":"接続数", “key":"tc"},
{“header":"最近", “key":"last"}
]
})
});
なお、JSON結果をそのまんま貼り付けるだけなら、これで結構です。
getJSON(’JSONファイル’,
function(res){
$('どこかのDIV').columns({data:res})
});
データの塊だけでなく、以下は、任意のデータを出力したい場合などの例。
現在「接続してるプレーヤー」、「更新時間」の表示を行ってます。
getJSON(URL_CONNECTED,
function(res){
var dt = new Date(res[0]['tm’]);
$('#apply_time’).text((dt.getMonth()+1)+"/"+dt.getDate()+" “+dt.getHours()+":"+dt.getMinutes());
$('#connected’).text(res[0]['connected’]);
});
量的に全てを公開できないので、分からない方はぜひともご連絡ください。