/** * Grasscutter Tools * Copyright (C) 2022 jie65535 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * **/ using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using GrasscutterTools.DispatchServer; using GrasscutterTools.DispatchServer.Model; using GrasscutterTools.Game; using GrasscutterTools.GOOD; using GrasscutterTools.OpenCommand; using GrasscutterTools.Properties; using GrasscutterTools.Utils; using Newtonsoft.Json; namespace GrasscutterTools.Pages { internal partial class PageOpenCommand : BasePage { public override string Text => Resources.PageOpenCommandTitle; private const string TAG = nameof(PageOpenCommand); public PageOpenCommand() { InitializeComponent(); if (DesignMode) return; InitServerRecords(); if (!string.IsNullOrEmpty(Settings.Default.Host)) TxtHost.Items.Add(Settings.Default.Host); TxtHost.Items.AddRange(ServerRecords.Select(it => it.Host).ToArray()); NUDRemotePlayerId.Value = Settings.Default.RemoteUid; TxtHost.Text = Settings.Default.Host; if (!string.IsNullOrEmpty(Settings.Default.Host) && !string.IsNullOrEmpty(Settings.Default.TokenCache)) { Common.OC = new OpenCommandAPI(Settings.Default.Host, Settings.Default.TokenCache); TxtToken.Text = Settings.Default.TokenCache; Task.Delay(1000).ContinueWith(_ => ShowTipInRunButton?.Invoke(Resources.TokenRestoredFromCache)); } if (string.IsNullOrEmpty(TxtHost.Text)) { TxtHost.Items.Add("http://127.0.0.1:443"); TxtHost.SelectedIndex = 0; } } #region - 服务器记录 - private class ServerRecord { public string Tag { get; set; } public string Host { get; set; } public int Uid { get; set; } public string Token { get; set; } } private readonly string ServerRecordsFilePath = Common.GetAppDataFile("Servers.json"); private List ServerRecords = new List(); //{ // new ServerRecord // { // Host = "http://127.0.0.1:443", // Tag = "Localhost", // Token = "123456", // Uid = 10001, // } //}; private void InitServerRecords() { if (!File.Exists(ServerRecordsFilePath)) return; try { Logger.I(TAG, "Loading ServerRecords json file from: " + ServerRecordsFilePath); ServerRecords = JsonConvert.DeserializeObject>(File.ReadAllText(ServerRecordsFilePath)); } catch (Exception ex) { Logger.W(TAG, "Parsing Servers.json failed.", ex); } } private void SaveServerRecords() { try { if (ServerRecords.Count == 0) return; File.WriteAllText(ServerRecordsFilePath, JsonConvert.SerializeObject(ServerRecords)); } catch (Exception ex) { Logger.W(TAG, "Save all server records failed.", ex); } } #endregion - 服务器记录 - /// /// 在运行按钮上显示提示,要求主窗口设置 /// public Action ShowTipInRunButton { get; set; } public override void OnEnter() { #if !DEBUG if (string.IsNullOrEmpty(Settings.Default.Host) || string.IsNullOrEmpty(Settings.Default.TokenCache)) { // 自动尝试查询本地服务端地址,降低使用门槛 Task.Run(async () => { var localhostList = new[] { "http://127.0.0.1:443", "https://127.0.0.1", "http://127.0.0.1", "https://127.0.0.1:80", "http://127.0.0.1:8080", "https://127.0.0.1:8080", }; foreach (var host in localhostList) { try { await UpdateServerStatus(host); // 自动填写本地服务端地址 BeginInvoke(new Action(() => TxtHost.Text = host)); break; } catch (Exception) { // Ignore } } }); } #endif } /// /// 保存开放命令参数 /// public override void OnClosed() { Settings.Default.RemoteUid = NUDRemotePlayerId.Value; Settings.Default.Host = TxtHost.Text; Settings.Default.TokenCache = Common.OC?.Token; } /// /// 更新服务器状态 /// /// 主机地址 private async Task UpdateServerStatus(string host) { var status = await DispatchServerAPI.QueryServerStatus(host); if (InvokeRequired) BeginInvoke(new Action(ShowServerStatus), status); else ShowServerStatus(status); } private void ShowServerStatus(ServerStatus status) { LblServerVersion.Text = status.Version; LblPlayerCount.Text = status.MaxPlayer > 0 ? $"{status.PlayerCount}/{status.MaxPlayer}" : status.PlayerCount.ToString(); } /// /// 输入服务器地址按下回车时触发 /// private void TxtHost_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) BtnQueryServerStatus_Click(BtnQueryServerStatus, e); } /// /// 地址栏选中项改变时触发 /// private void TxtHost_SelectedIndexChanged(object sender, EventArgs e) { if (TxtHost.SelectedIndex >= 0 && TxtHost.SelectedIndex < ServerRecords.Count) { // 还原记录 var record = ServerRecords[TxtHost.SelectedIndex]; TxtToken.Text = record.Token; NUDRemotePlayerId.Value = record.Uid; } } /// /// 点击查询服务器状态按钮时触发 /// private async void BtnQueryServerStatus_Click(object sender, EventArgs e) { var btn = sender as Button; btn.Enabled = false; btn.Cursor = Cursors.WaitCursor; try { // "http://127.0.0.1/" -> "http://127.0.0.1" var host = TxtHost.Text.TrimEnd('/'); try { await UpdateServerStatus(host); } catch (Exception ex) { MessageBox.Show(Resources.QueryServerStatusFailed + ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } Settings.Default.Host = host; var isOcEnabled = false; try { Common.OC = new OpenCommandAPI(host); isOcEnabled = await Common.OC.Ping(); } catch (Exception ex) { #if DEBUG MessageBox.Show(ex.ToString(), Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); #else MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); #endif } if (isOcEnabled) { LblOpenCommandSupport.Text = "√"; LblOpenCommandSupport.ForeColor = Color.Green; GrpRemoteCommand.Enabled = true; } else { LblOpenCommandSupport.Text = "×"; LblOpenCommandSupport.ForeColor = Color.Red; GrpRemoteCommand.Enabled = false; } } catch (Exception ex) { MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { btn.Cursor = Cursors.Default; btn.Enabled = true; } } /// /// 玩家ID输入框按下回车时触发 /// private void NUDRemotePlayerId_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) BtnSendVerificationCode_Click(BtnSendVerificationCode, e); } /// /// 点击发送校验码按钮时触发 /// private async void BtnSendVerificationCode_Click(object sender, EventArgs e) { var btn = sender as Button; var t = btn.Text; btn.Enabled = false; NUDRemotePlayerId.Enabled = false; try { btn.Text = Resources.CodeSending; await Common.OC.SendCode((int)NUDRemotePlayerId.Value); BtnConnectOpenCommand.Enabled = true; NUDVerificationCode.Enabled = true; NUDVerificationCode.Focus(); for (int i = 60; i > 0 && !Common.OC.CanInvoke; i--) { btn.Text = string.Format(Resources.CodeResendTip, i); await Task.Delay(1000); } } catch (Exception ex) { MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { btn.Text = t; btn.Enabled = true; NUDRemotePlayerId.Enabled = true; } } /// /// 验证码输入框按下回车时触发 /// private void NUDVerificationCode_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) BtnConnectOpenCommand_Click(BtnConnectOpenCommand, e); } /// /// 点击连接到开放命令按钮时触发 /// /// /// private async void BtnConnectOpenCommand_Click(object sender, EventArgs e) { var btn = sender as Button; btn.Enabled = false; try { await Common.OC.Verify((int)NUDVerificationCode.Value); GrpRemoteCommand.Enabled = false; ShowTipInRunButton?.Invoke(Resources.ConnectedTip); ButtonOpenGOODImport.Enabled = true; var r = ServerRecords.Find(it => it.Host == TxtHost.Text); if (r != null) { r.Token = Common.OC.Token; r.Uid = (int)NUDRemotePlayerId.Value; } else { ServerRecords.Add(new ServerRecord { Host = Common.OC.Host, Tag = "TODO", Token = Common.OC.Token, Uid = (int)NUDRemotePlayerId.Value }); TxtHost.Items.Add(Common.OC.Host); } SaveServerRecords(); } catch (Exception ex) { MessageBox.Show(ex.Message, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { btn.Cursor = Cursors.Default; btn.Enabled = true; } } /// /// Token 输入框按下回车时触发 /// private void TxtToken_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) BtnConsoleConnect_Click(BtnConsoleConnect, e); } /// /// 点击控制台连接按钮时触发 /// private void BtnConsoleConnect_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(TxtToken.Text)) { MessageBox.Show(Resources.TokenCannotBeEmpty, Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } Common.OC.Token = TxtToken.Text; BtnConnectOpenCommand_Click(sender, e); } /// /// 点击开放命令标签时触发 /// private void LnkOpenCommandLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { UIUtil.OpenURL("https://github.com/jie65535/gc-opencommand-plugin"); } /// /// 点击帮助连接标签时触发 /// private void LnkRCHelp_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { MessageBox.Show(Resources.OpenCommandHelp, Resources.Help, MessageBoxButtons.OK, MessageBoxIcon.Information); } /// /// 点击库存扫描链接标签时触发 /// private void LnkInventoryKamera_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { UIUtil.OpenURL("https://github.com/Andrewthe13th/Inventory_Kamera"); } /// /// 点击GOOD帮助链接标签时触发 /// private void LnkGOODHelp_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { UIUtil.OpenURL("https://frzyc.github.io/genshin-optimizer/#/doc"); } /// /// 点击链接帮助标签时触发 /// private void LnkLinks_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { var links = new List { "https://frzyc.github.io/genshin-optimizer/", "https://genshin.aspirine.su/", "https://genshin.mingyulab.com/", "https://genshin-center.com/", "https://github.com/Andrewthe13th/Inventory_Kamera", "https://github.com/daydreaming666/Amenoma", "https://seelie.me/", "https://www.mona-uranai.com/", }; MessageBox.Show(string.Join("\n", links), "Links", MessageBoxButtons.OK, MessageBoxIcon.Information); } #region - 导入存档 GOOD - /// /// 点击GOOD导入存档按钮时触发 /// private async void ButtonOpenGOODImport_Click(object sender, EventArgs e) { if (Common.OC == null || !Common.OC.CanInvoke) { UIUtil.ShowTip(Resources.RequireOpenCommandTip, ButtonOpenGOODImport); return; } OpenFileDialog openFileDialog1 = new OpenFileDialog { Filter = "GOOD file (*.GOOD;*.json)|*.GOOD;*.json|All files (*.*)|*.*", }; if (openFileDialog1.ShowDialog() == DialogResult.OK) { if (DialogResult.Yes != MessageBox.Show(Resources.GOODImportText + openFileDialog1.FileName + "?", Resources.GOODImportTitle, MessageBoxButtons.YesNo)) return; try { GOOD.GOOD good = JsonConvert.DeserializeObject(File.ReadAllText(openFileDialog1.FileName)); var commands_list = new List(); var missingItems = new List(); if (good.Characters != null) { foreach (var character in good.Characters) { if (character.Name != "Traveler") { if (GOODData.Avatars.TryGetValue(character.Name, out var character_id)) { if (CommandVersion.Check(CommandVersion.V1_4_1)) { // 取最低的技能等级 var skillLevel = Math.Min(Math.Min(character.Talents.Auto, character.Talents.Skill), character.Talents.Burst); commands_list.Add($"/give {character_id} lv{character.Level} c{character.Constellation} sl{skillLevel}"); } else commands_list.Add($"/give {character_id} lv{character.Level} c{character.Constellation}"); } else missingItems.Add(character.Name); // TODO: Implement command to set talent level when giving character in Grasscutter } } } if (good.Weapons != null) { foreach (var weapon in good.Weapons) { if (GOODData.Weapons.TryGetValue(weapon.Name, out var weapon_id)) commands_list.Add($"/give {weapon_id} lv{weapon.Level} r{weapon.RefinementLevel}"); else missingItems.Add(weapon.Name); // TODO: Implement command to give weapon directly to character in Grasscutter } } if (good.Artifacts != null) { foreach (var artifact in good.Artifacts) { // Format: set rarity slot if (!GOODData.ArtifactCats.TryGetValue(artifact.SetName, out var artifact_set_id)) { missingItems.Add(artifact.SetName); continue; } var artifact_id = artifact_set_id * 1000 + artifact.Rarity * 100 + GOODData.ArtifactSlotMap[artifact.GearSlot] * 10; for (int i = 4; i > 0; i--) { if (Array.IndexOf(GameData.Artifacts.Ids, artifact_id + i) != -1) { artifact_id += i; break; } } if (artifact_id % 10 == 0) artifact_id += 4; var artifact_mainStat_id = GOODData.ArtifactMainAttribution[artifact.MainStat]; var artifact_substats = ""; var artifact_substat_prefix = artifact.Rarity + "0"; int substat_count = 0; foreach (var substat in artifact.SubStats) { if (substat.Value <= 0) continue; substat_count++; var substat_key = substat.Stat; var substat_key_id = GOODData.ArtifactSubAttribution[substat_key]; var substat_indices = ArtifactUtils.SplitSubstats(substat_key, artifact.Rarity, substat.Value); foreach (int index in substat_indices) { artifact_substats += artifact_substat_prefix + substat_key_id + index.ToString() + " "; } } // HACK: Add def+2 substat to counteract Grasscutter automatically adding another substat if (substat_count == 4) artifact_substats += "101081 "; commands_list.Add($"/give {artifact_id} lv{artifact.Level} {artifact_mainStat_id} {artifact_substats}"); // TODO: Implement command to give artifact directly to character in Grasscutter } } // TODO: Materials //if (good.Materials != null) //{ // foreach (var material in good.Materials) // { // } //} var msg = string.Format("Loaded {0} Characters\nLoaded {1} Weapons\nLoaded {2} Artifacts\n", good.Characters?.Count ?? 0, good.Weapons?.Count ?? 0, good.Artifacts?.Count ?? 0 ); if (missingItems.Count > 0) { msg += string.Format("There are {0} pieces of data that cannot be parsed, including:\n{1}", missingItems.Count, string.Join("\n", missingItems.Take(10))); if (missingItems.Count > 10) msg += "......"; } msg += "Do you want to start?"; if (DialogResult.Yes != MessageBox.Show(msg, Resources.Tips, MessageBoxButtons.YesNo, MessageBoxIcon.Information)) return; if (await RunCommands(commands_list.ToArray())) MessageBox.Show(Resources.GOODImportSuccess); } catch (Exception ex) { MessageBox.Show(ex.ToString(), Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } #endregion - 导入存档 GOOD - } }