プレイ絵日記・その1

Oxygen Not Includedというコロニーゲームの全実績解除を目指してバニラ版をプレイ中。 全実績の中で、肉食動物、超持続可能、地産地消の3つは条件から外れると解除不能なため、開始から縛りを入れたプレイが必要。実績解除後は縛りを解いても無効にならない。

方針としては以下となる。 - 肉食動物 ハッチを育てる - 地産地消 実績解除するまで農業をせずにハッチとマッシュバーを食べる - 超持続可能 人力発電と水力発電、蒸気発電でがんばる

0サイクル

初期メンバーとして選べる3人は、それぞれ採掘、研究、牧畜のスキルを持ったやつを選ぶ。

3サイクル

トイレと手洗いとベットの建設、水を1か所に貯める。研究はこれから1人専任で常時行う。

37サイクル

水素発電を稼働させる。酸素生成はこれだけで賄えるようになった。電力はこれと人力発電。

ハッチ厩舎を作る。部屋レイヤーが厩舎となる最大96タイルで部屋を作るが、ハッチは右4マスに閉じ込めて毛繕いしやすいようにする。1部屋に8匹まで入れて、卵が生まれると自動扉が開いて卵だけ下に落ちる仕組みになっている。これ考えた奴天才。一番下の部屋に水を溜めて、卵が孵化すると同時に溺れさせて自動捕殺できるらしいのだが、プレイ時は上手くいかなくて手動で捕殺した。

47サイクル

ハッチ厩舎が大体出来上がる。ここまでほぼハッチは捕殺せずに卵育て機でハッチの数を増やす。食事はマッシュバーが主食。

卵育て機の自動化設定。フィルター5秒、バッファー200秒、荷重プレート29kg超過の設定で上手くいった。これは電力節約のための設定で、テイマーが子守唄を歌う時間がフィルターの設定、子守唄バフが切れる時間がバッファーの設定となり、子守唄バフがかかっている間は電力を消費しない。荷重プレートの設定は複製人間の体重が30kgのため。

この時点で25000カロリーだった。

61サイクル

ハッチを食べつつ飼育数も増やしていく。この時点で90000キロカロリーの消費。間に合いそうになかったので卵育て機を2機増設中。 水洗トイレとシャワールームを作成。シャワールームは自然保護区となっていて、かなりの士気アップとなる。

76サイクル

ハッチ厩舎4段だと足りなそうなので5段目を作成中。

コロニー近くに汚染水間欠泉を見つけたので塩素消毒して水資源として活用する。

トイレの下水や汚染水間欠泉の水は、そのままだと食中毒菌まみれなので、消毒する必要がある。塩素が充満した部屋の水タンクの中で1サイクル経過すると完全消毒されるのでタイマーで切り替えて調節する。

左から来ているのはトイレの下水。合流させて浄水器で水に変えた後に消毒室に入れている。

この時点で220000キロカロリーの消費。

94サイクル

残り6サイクルで肉食動物達成。ハッチの数を増やしつつ消費も進み上手くいった。卵育て機は8個いる。この実績、ネット攻略情報を調べまくって効率的は配置を採用してギリギリ達成できる難易度だと思う。 地産地消はいつの間にか達成していた。

複製人間の推移は50サイクルまでに8人で、最終的に10人。人力発電に人がいるのと、カロリー消費するためにも人は多めでよかった。 次の目標の実績は超持続可能。38000/240000キロジュールとのことで先が長い

C#プロジェクトを開くとOmniSharpの参照エラーが発生する

MacOSで久しぶりに開発しようとdotnet new console でプロジェクト作ってVSCodeで開くと、OmniSharpのエラーが出て、インテリセンスもコードレンズも無効になっちゃった。 こんなログ。

[fail]: OmniSharp.MSBuild.ProjectLoader
        .NETFramework,Version=v5.0 の参照アセンブリが見つかりませんでした。この問題を解決するには、このフレームワーク バージョンの Developer Pack (SDK/Targeting Pack) をインストールするか、アプリケーションのターゲットを再設定してください。https://aka.ms/msbuild/developerpacks で .NET Framework Developer Pack をダウンロードできます。

ネットで調べてもいい情報が出てこなかったんだけど、VisualStudio CodeのC#のExtensionの説明にちゃんと注意が乗ってましたw f:id:yk0x69:20201217215604p:plain

Omnisharp: Use Global Monoの値をneverにしたら無事解決した! f:id:yk0x69:20201217215626p:plain

Microsoft Graph Client LibraryでTeamsのメッセージを取得する

Microsoft Teamsに投稿されたメッセージをプログラムで取得したい、そんなときはGraph APIを使って取得します。 ナマでREST通信するのはめんどくさいですが、C#で作るのであれば便利なライブラリが用意されています。 この記事ではそのライブラリを使ってメッセージを取得する例をまとめました。

ライブラリについて

Microsoft Graph .Net Client Library。Graph API とのやりとりを簡単にしてくれます。NugetでMicrosoft.Graphという名前で公開されていますので簡単にプロジェクトにインストールできます。Graph APIのBeta機能を使いたいときはMicroosft.Graph.Betaという名の別のパッケージが用意されていますので、そちらをインストールしてください。チームメッセージの取得は2020年5月時点でBeta機能となります。

github.com

メッセージ取得方法

Teamsにはチャネル内のメッセージング機能だけではなく、ユーザーを指定した1 on 1チャットの機能もあります。こちらのチャットのメッセージの取得方法は別なのでご注意ください。
Graph APIの仕様上、自分がアクセス権を持っているメッセージを一度にすべて取得することはできません。メッセージを取得するには、必ずそのメッセージが投稿されたチャネルのIDを指定しなければいけないためです。また、1度に取得できるメッセージの数も決まっていたり、メッセージの返信を取得するには返信元のメッセージのIDが必要などなかなか複雑で、それはライブラリを使っても変わらず、複数回に分けてメッセージを取得する必要があります。
Team(チーム) -> Channel(チャネル) -> Message(メッセージ) -> Message Reply(メッセージの返信) という階層構造をイメージしていただき、それぞれの情報を取得するには親階層のIDが必要となります。

ライブラリの初期化

GraphServiceClientというクラスをインスタンス化します。下の例では匿名関数の中でGraph APIへの認証トークンをセットしています。これは一例であっていろいろなやり方が選択できますので、認証トークンの取得を含め、詳しい説明はMicrosoftのドキュメントを参照してください。

_client = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
    {
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
        await Task.FromResult(0);
    })
);

チームの取得

まず大元となるチームのIDを取得します。チームのIDがわかっている場合は必要ありませんが、チームのIDがわからない場合は、自分の所属するチームの一覧を取得します。

var teams = await _client.Me.JoinedTeams.Request().GetAsync();
foreach (var team in teams)
{
    Console.WriteLine($"Team Id:{team.Id} DisplayName:{team.DisplayName}");
}

チャネルの取得

チャネルのIDを取得します。そのチャネルが所属するチームのIDを指定します。

var channels = await _client.Teams[teamId].Channels.Request().GetAsync();
foreach (var channel in channels)
{
    Console.WriteLine($"Channel Id:{channel.Id} DisplayName:{channel.DisplayName}");
}

メッセージの取得

メッセージを取得します。メッセージが投稿されたチャネル、チームのIDが必要になります。

var messages = await _client.Teams[teamId].Channels[channelId].Messages.Delta().Request().GetAsync(); // 差分がいらない場合はMesssages.Request().GetAsync();
foreach (var message in messages)
{
    Console.WriteLine($"Message Id:{message.Id} Content:{message.Body.Content}");
}

メッセージは最新の投稿順に取得できますが一度に最大50件までしか取得できません。そのため、それ以上のメッセージがチャネル内にある場合は複数回に分けて取得する必要があります。 初回にリクエストした戻り値の中に、NextPageRequestという値があります。その値がNullでない場合は続きがある、ということになります。その値のGetAsyncメソッドを呼ぶことで続きのメッセージを取得できます。

var messages = await messages.NextPageRequest.GetAsync();

メッセージ取得は差分読み込みをサポートしています。 最後まで読み込んだ(NextPageRequestがNullになった)時に、次回取得時にここまで読み切ったという情報をGraphAPIに渡すためのリンクが取得できます。

if (messages.AdditionalData.ContainsKey("@odata.deltaLink"))
{
    deltaLink = messages.AdditionalData["@odata.deltaLink"].ToString();
}

このリンクをどこかに保存しておき、次回メッセージを取得する際にリクエストにセットすることで差分のメッセージのみを取得できます。

var messages = new ChatMessageDeltaCollectionPage();
messages.InitializeNextPageRequest(_client, deltaLink);
messages = await messages.NextPageRequest.GetAsync();

ここらへんのGraph APIの仕様について、詳しくはこちらを参照してください。

docs.microsoft.com

メッセージの返信の取得

1 on 1チャットと違い、チャネル内メッセージには、メッセージとその返信という2つの階層に分かれています。返信を取得するにはメッセージのIDが必要になります。

var messages = await _client.Teams[teamId].Channels[channelId].Messages[messageId].Replies.Request().GetAsync();
foreach (var message in messages)
{
    Console.WriteLine($"Message Id:{message.Id} Content:{message.Body.Content}");
}

返信もメッセージと同じく一度に取得できる数が決まっているため、戻り値のNextPageRequestの値がNullでなかった場合は続きがあり、GetAsyncメソッドを呼ぶことで続きを取得できます。
また、差分取得に関しては、返信自体には用意されていませんが、返信が追加されるとその返信元となるメッセージのほうの差分に乗ってきます。そのため、親となるメッセージを取得後、あらためて返信を取ってくる、という実装になるかと思います。