CORDEA blog

Android applications engineer

Age of Empires IV で Game Mode の Mod 作るときの tips

小ネタ

Age of Empires IV Mod Workshop

勝敗を決める

Core_SetPlayerVictorious/Core_SetPlayerDefeated を使う
これは新しく Mod 作った時の example code にある

注意点としては、Player が負けて AI が生き残っているような場合を作ると概要が開けず待ちぼうけをくらうことになる
なので、Player が残っていない時点で AI の勝敗も決めておくとよさそう
なお、勝者なしの場合は Core_WinnerlessGameOver を使用する

あと Defeated が呼び出されると Core_IsPlayerEliminated が変わり勝敗が判定できる
勝利条件にもよると思うので変わらなければ Player_IsAlive とかも見るとよさそう

Unit を作成・配置する

UnitEntry_DeploySquads を使う
これも example の通り

もしまだ読み込まれていない Unit (時代が今よりも先, 他国の固有, etc) の場合は、豆腐になるので読み込む必要がある
これには Squad_Precache を使う

第 1 引数は Deploy に渡している Blueprint, 第 3 引数は PlayerID
ここでいう PlayerID は integer ではない、Player_GetID で取得できる値は integer なので注意
他の引数は空でも良い (0 あるいは "")
読み込みが完了したことを検知するには GE_EntityPrecached を使用する、Global Event については下で

Unit の一覧は Content Editor 下部のオブジェクトブラウザから templates > ebps > races 以下を見ればだいたいわかる

Unit の suffix (人種コード)

Player_GetRaceName で得られる人種名は full name なので変換が必要

Race name Race code
abbasid abb
chinese chi
english eng
french fre
hre hre
mongol mon
rus rus
sultanate sul

そのうち Util_ とかで提供されるんじゃないですかね

Unit の殺害 Event

Global Event の中でもよく使いそうな
Unit (Entity) が殺されたことを Event で取得できる
Rule_AddGlobalEvent の第 2 引数に GE_EntityKilled を渡す
function に返る context の中身は

  • victimOwner
    • 被害者側の player
  • victim
    • 対象 Unit
  • victimSquad
  • killer
    • victim を殺した Unit

など。killerOwner とかもあったかも
killer と victim が一緒だと自殺 (Player による delete)

Unit が何人残っているか

Player_GetEntityCountByUnitType を使う

この Unit type が何かというと、自分もオブジェクトブラウザから調べる方法を知らない
チューニングパックを作成すると作られる attrib/*.xml で unit を表示して
extensions > type_ext > unit_type_list の中を見ると書いてあるのが Unit type
なので、村人の場合は "villager"、軍事 Unit 全体の場合は "military"

なお、特定の Unit がどの type かを知るためには Entity_IsOfType が使用できる

Console 出力系

Debug に

  • print
  • Util_PrintIf
  • Util_PrintTable

とりあえずまだ Beta なので Document も完全ではなく割と苦労します。頑張りましょう
いくつかデモで作ったので参考までに

github.com
github.com
github.com

Windows PC で BSoD 頻発してたのを直した

Windows PC が BSoD 起こすようになったけど直ったメモ

大まかな構成

問題としては

  • BSoD (VIDEO_TDR_FAILURE)
  • BSoD (DPC_WATCHDOG_VIOLATION)
  • Driver 読み込みエラー (code 43)

このあたりが起動後 10 分以内にほぼ 100% 起きる

  • 復元
  • Driver 更新
  • Clean install

を何回か実行したけど効果なし

Solution

最終的に UEFI (BIOS) から PCIe x16 Bus Interface を Gen3 にすることで平和になった
私の環境では Advanced -> AMD PBS -> PCIe x16 Bus Interface にあった

Flutter で 2 つの Path を組み合わせる Path#combine

Android における Path#op あるいは Canvas#clipPath のようなことをする Path#combine


api.flutter.dev


以下のような形で Path を組み合わせる

final path = Path.combine(
  _pathOperation,
  Path()
    ..addOval(Rect.fromCircle(
      center: Offset(centerX - 50, centerY),
      radius: 100,
    ))
    ..close(),
  Path()
    ..addOval(Rect.fromCircle(
      center: Offset(centerX + 50, centerY),
      radius: 100,
    ))
    ..close(),
);

canvas.drawPath(path, _paint);

PathOperation

Android と一緒です

api.flutter.dev

difference


f:id:CORDEA:20210821153045p:plain

intersect

f:id:CORDEA:20210821153118p:plain

union

f:id:CORDEA:20210821153140p:plain

xor

f:id:CORDEA:20210821153154p:plain

reverseDifference


f:id:CORDEA:20210821153128p:plain



github.com

Lua で SQLCipher (lsqlcipher) を使う

lsqlcipher の Install から使うまで

Install

LuaRocks で install します。

luarocks.org

$ luarocks install lsqlcipher

私の今の環境 (macOS) だと LDFLAGS で指定されている path に lcrypto がないので sqlcipher の install に失敗します。

$ luarocks install lsqlcipher 
Installing https://luarocks.org/lsqlcipher-0.9.5-3.src.rock
Missing dependencies for lsqlcipher 0.9.5-3:
   sqlcipher (not installed)

lsqlcipher 0.9.5-3 depends on lua >= 5.1, < 5.5 (5.4-1 provided by VM)
lsqlcipher 0.9.5-3 depends on sqlcipher (not installed)
Installing https://luarocks.org/sqlcipher-4.4.2-2.rockspec

...

configure: error: C compiler cannot create executables
See `config.log' for more details

Error: Failed installing dependency: https://luarocks.org/sqlcipher-4.4.2-2.rockspec - Build error: Failed building.


やり方は色々だと思いますが、symbolic link を貼りました。

$ ln -s /usr/local/Cellar/openssl@1.1/1.1.1k/lib/libcrypto.dylib /usr/local/lib/
$ ln -s /usr/local/Cellar/openssl@1.1/1.1.1k/include/openssl /usr/local/include/

必ずしも原因が同じとは限らないので、error が出た場合は config.log を見るのが良さそうです。
といっても config.log 消えてると思うので、その場合は Source になっている sqlcipher を自分で build して試してみると良いと思います。

https://luarocks.org/sqlcipher-4.4.2-2.rockspec

Usage

local sqlite3 = require("lsqlcipher")
local db = sqlite3.open("./foo.sqlite")

db:key("bar")
db:exec [[
    CREATE TABLE IF NOT EXISTS baz (
        id INTEGER PRIMARY KEY
    );
]]

あとは lsqlite3 と一緒です

luarocks.org

Ktor Client で Twitter の Filtered stream を取得する

小ネタ

developer.twitter.com

HttpClient

このへんはあまり関係ないのでよしなに

val client = HttpClient(CIO) {
    defaultRequest {
        url {
            protocol = URLProtocol.HTTPS
            host = "api.twitter.com"
        }
        header("Authorization", "Bearer $token")
    }

    Json {
        serializer = KotlinxSerializer(json)
    }
}

Add rules

取得する Tweets の rules を POST する

返り値は String でも HttpResponse でも任意に定義した Response でも何でもいいですが、
kotlinx.serialization でそのまま decode する場合は、errors など key がない場合があるので ignoreUnknownKeys = true を指定しておくと良いです

val response = client.post<String> {
    url {
        encodedPath = "2/tweets/search/stream/rules"
    }
    body = StreamRulesRequest(rules)
}

Get tweets

Tweets を real-time に取得する
Ktor Client の doc に書いてある通り、HttpStatement を使用します。

Streaming—Ktor

client.get<HttpStatement> {
    url { encodedPath = "2/tweets/search/stream" }
}.execute { response ->
    val channel = response.receive<ByteReadChannel>()
    do {
        val tweet = channel.readUTF8Line() ?: break
    } while (tweet.isNotBlank())
}

Flow とかで emit すれば扱いやすくなると思います。

もう少し詳しく見たければこちらを

info-provision-bot/TwitterClient.kt at main · CORDEA/info-provision-bot · GitHub

Android app の Owner 変更と Google Play Developer account の削除

をしたので手順のメモ

Owner 変更はだいたいここに書いてあるとおり。
チェックすべきことも書いてあるので一度目を通しておくと良さそうです。

Transfer apps to a different developer account - Play Console Help

1. 移行先の Google Play Developer account を登録する

これはまぁ普通に。
Registration fee を支払う必要がありますが、先の Help に書いてあるとおり、元の Account を削除する際に返金してもらうことができるようです。
何も言わなければ返金されないので、あとの手順で Support にメールする時ちゃんと「返金して」って書いておくと良いと思います。

2. 移行先の Transaction ID を取得する

これは console にあるものではなく、支払ったときに付与される ID です。
なので、Registration fee を支払ったときに来る Google Payments からのメールに記載されています。
日本語だと「Google の注文番号」ってやつです。

https://pay.google.com からも参照できます。この場合は「取引 ID」です。

3. App の移行申請

ここから申請します。全部移行する場合は全部選択します。

https://play.google.com/console/developers/app-transfer

App が多い場合など、最初から Support にメールして進めたほうが早いかもしれません。
2-3 営業日でメールが来ます。

4. Developer account の削除依頼

以下に書いてあるとおり、3 で来たメールに返信する形で削除を依頼します。

Manage your developer account information - Play Console Help

まぁいい感じに私の Account 削除してください的なこと書いておけばいいと思います。 (返金を求める場合はそれも)
その際、削除対象の Account の Transaction ID や Developer name を記載しておくとスムーズです。
Developer name は Google Play Console の設定 -> デベロッパーアカウント -> デベロッパーページ で参照できます。

その後、移行先の Account の情報とか聞かれたりして、答えていくと削除してくれます。


なんだかんだ Owner 変更と削除で 2 週間くらいかかりました。

Reports API で G Suite 監査ログの更新通知を受け取る

G Suite の Admin console には監査ログというものがあり、そこで色々見たり Alert を上げたりすることが出来るのですが、
G Suite Admin SDKReports API を使用すると、この監査ログを取得することが出来ます
また、Callback URL を登録することで更新通知を受け取ることも出来ます。

ということでこの記事では Meet の更新通知を受け取ってみます。
Meet は非対応ですが、主要なイベントだと Alert Center でメールに飛ばせるのでメールならそちらのほうが良いでしょう。

注意

監査ログはリアルタイムに通知されるわけではなく、利用できるようになるまでに一定の時間がかかります。また、この時間はサービスによって異なります。
Meet はほぼリアルタイムに更新されるようですが、私が見ていたところでは監査ログに表示されるまでに概ね 3~5 分程度かかっていました。

Data retention and lag times - G Suite Admin Help

また、更新通知の受け取りに関してですが、最初は届いていたのですが何度も試していたせいか途中から届かなくなってしまいました。(同期用の Message は届くが監査ログの更新は通知されない)
Stackoverflow でもいくつか同様の投稿が見受けられましたので、使用する際には注意したほうが良いかもしれません。

個人で契約している G Suite Basic なので、Business とか Enterprise だと違ったりするのかもしれません。

実装

実装したやつ置いときます

github.com

通知を受け取る Web API の用意

とりあえず通知が POST されるので、その先を予め用意しておく必要があります。

Flask だと以下のようになります。

@app.route('/events', methods=['POST'])
def events():
  headers = request.headers

if __name__ == '__main__':
  app.run(debug=True)

まず、ここにはいくつかの Message が POST されますが、そのうちの一つに同期用の Sync Message があります。

これは登録後に POST される確認用の Message です。
この Message には登録解除用の ID などが含まれていますが、監査ログとしての情報はないため、ここでは無視します。

@app.route('/events', methods=['POST'])
def events():
  headers = request.headers
  if headers['X-Goog-Resource-State'] == 'sync':
    return {}

Header に含まれる情報はここに書いてあります。

あとは通知の Parse です。
POST される json には種別や ID などが含まれますが、おそらく大体の人が必要としている情報は events にあります。
配下は配列となっており、その中身には Event type, name と Event の parameter が含まれています。
Event の parameter についてはそれぞれの Reference に記載があります。
Meet の場合は以下

Hangouts Meet Audit Activity Events  |  Reports API  |  Google Developers

あとは欲しい情報を取得してなんかいい感じにするだけです。

@app.route('/events', methods=['POST'])
def events():
  headers = request.headers
  if headers['X-Goog-Resource-State'] == 'sync':
    return {}
  for event in request.json['events']:
    print(event['name'])

Deploy する

さきほど用意した Web API をどこか見えるところに Deploy します。
Callback URL に登録するためにはいくつか条件があります。

  • https であること
    • 自己署名などは不可です
  • Search console で domain の所有権が確認できること

私の場合は Google App Engine に deploy しました。
Repository に app.yaml を commit しておいたので同じように試すときは gcloud app deploy で試すことが出来ます。

Domain を登録する

Google API Console にアクセスし、必要なら任意の Project を選択します (無いなら作る)。
Domain verification から Add domain を選択し
Callback URL の domain を入力します。
この時、所有権が確認できていない場合は Seach Console での domain 登録を求められますので、その通りにします。
私は meta tag で行ったので Repository に index.html.example として commit してあります。

Reports API を有効化する

おそらく Admin SDK という名前になっているのですが、これを有効化して credentials.json を持ってくる必要があります。

Python Quickstart  |  Reports API  |  Google Developers

Callback URL を登録する

Activities: watch  |  Reports API  |  Google Developers

あとは Callback URL を登録するだけです。
Python だったらこのへんを参考にします。

Python Quickstart  |  Reports API  |  Google Developers

def __get_google_service():
    creds = None
    if os.path.exists(TOKEN_PICKLE):
        with open(TOKEN_PICKLE, 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json',
                SCOPES
            )
            creds = flow.run_local_server(port=0)
        with open(TOKEN_PICKLE, 'wb') as token:
            pickle.dump(creds, token)
    return build('admin', 'reports_v1', credentials=creds)


def __register():
    response = __get_google_service().activities().watch(
        userKey='all',
        applicationName='meet',
        body={
            'address': settings.CALLBACK_URL,
            'type': 'web_hook',
            'id': ID
        }
    ).execute()

applicationName は今回は Meet の Event を取得するため 'meet' にします。Calendar の場合は 'calendar' です。
userKey は特定の User に絞りたい場合は使用します。今回は全て見たいので 'all' とします。

Response で得られる Resource ID と ID は保持するなり覚えておくなりすると登録解除が出来ます。
登録状態を見る API は無いため、Resource ID と ID がどこかへ行ってしまうと期限が切れる (たぶん 6 時間) のを待つ以外にありません。

登録するとしばらくして Sync Message が届くはずです。

登録を解除する

Channels: stop  |  Reports API  |  Google Developers

登録の解除は以下のように行います。

__get_google_service().channels().stop(
    body={
        'id': id,
        'resourceId': resource_id
    }
).execute()

が、どうやら Python の Client library だとこの stop API の URL が間違っているようなので Issue を参考に書き換えます。

channels._baseUrl = channels._baseUrl.replace('/admin/reports/v1', '')

なお Issue では AuthorizedSession を使う方法も提案されていて、そちらのほうが良さげです。


おわり