「score」タグアーカイブ

【Arma3】スコアをウェブ公開する方法@Exileサーバ

image

動作サンプルはこちら(鯖味噌サーバ密告書)

ゲーム動作時でもリアルタイムのプレーヤースコア公開が可能です。

Exile ModによるArma3  Dedicated Serverでサーバ運用されてる管理者さんの参考にさればと、公開してます。

なお、こちらで公開しているのは、Arma3側もウェブサーバ側もLinuxですので、Windowsの場合は、適宜書き換えてください。

また、動けばOKッ!というスタイルで短時間で書いた代物ですので、小汚く低品質コードなのは了承ください。

(;^ω^)参考程度に・・

分からないという方は、ご連絡ください。

動作仕組み

Arma3サーバ側

Cronにて、以下を定期的に実行するようにします。当方では1時間毎。

MySQLデータベースから必要なデータを取り込み、JSONファイルを複数生成します。

SCP(秘密鍵)にて、Webサーバへコピーします。念の為、ユーザーのホームへコピーします。

ウェブサーバ側

コピーされたJSONファイルへ、ウェブ公開ホルダーにリンクを貼っておきます。

ここから先は、HTMLのお話なので、割愛しますが、当方では、eisenbraun-columnsで、そのままJSONデータを表示しています。とっても楽です。

SQLコード

まずは、欲しいデータの要求が正常に行えるようSQLコードを作ります。

image

使い慣れた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))

ウェブサーバ側コード

image

ウェブ公開ホルダーではなく、ユーザーホームの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’]);
     });

量的に全てを公開できないので、分からない方はぜひともご連絡ください。