From f89f308c525e9deebc6d2cf6416e27dfe1a299dc Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sun, 19 May 2019 16:03:38 +0200 Subject: Cleanup DiscoBot Project --- DiscoBot/DSA_Game/Characters/Character.cs | 303 ------------------------------ DiscoBot/DSA_Game/Characters/NPC.cs | 112 ----------- DiscoBot/DSA_Game/Characters/SaveChar.cs | 45 ----- DiscoBot/DSA_Game/Dsa.cs | 72 ------- DiscoBot/DSA_Game/Save/Properties.cs | 88 --------- DiscoBot/DSA_Game/Save/SaveCommand.cs | 82 -------- DiscoBot/DSA_Game/Save/Session.cs | 60 ------ 7 files changed, 762 deletions(-) delete mode 100644 DiscoBot/DSA_Game/Characters/Character.cs delete mode 100644 DiscoBot/DSA_Game/Characters/NPC.cs delete mode 100644 DiscoBot/DSA_Game/Characters/SaveChar.cs delete mode 100644 DiscoBot/DSA_Game/Dsa.cs delete mode 100644 DiscoBot/DSA_Game/Save/Properties.cs delete mode 100644 DiscoBot/DSA_Game/Save/SaveCommand.cs delete mode 100644 DiscoBot/DSA_Game/Save/Session.cs (limited to 'DiscoBot/DSA_Game') diff --git a/DiscoBot/DSA_Game/Characters/Character.cs b/DiscoBot/DSA_Game/Characters/Character.cs deleted file mode 100644 index 81c11fc..0000000 --- a/DiscoBot/DSA_Game/Characters/Character.cs +++ /dev/null @@ -1,303 +0,0 @@ -using DSALib; -using DSALib.Characters; - -namespace DiscoBot.DSA_Game.Characters -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Xml; - - using DiscoBot.Audio; - using DiscoBot.Auxiliary; - - public class Character : Being, ICharacter - { - public Character() - { - this.PropTable.Add("MU", "Mut"); // routing - this.PropTable.Add("KL", "Klugheit"); - this.PropTable.Add("IN", "Intuition"); - this.PropTable.Add("CH", "Charisma"); - this.PropTable.Add("FF", "Fingerfertigkeit"); - this.PropTable.Add("GE", "Gewandtheit"); - this.PropTable.Add("KO", "Konstitution"); - this.PropTable.Add("KK", "Körperkraft"); - - } - - public Character(string path) : this() - { - this.Load(path); // load - this.Post_process(); // calculate derived values - } - - public Character(Character c, string name, int stDv = 2) : this() - { - this.Name = name; - foreach (var i in c.Eigenschaften) - { - this.Eigenschaften.Add(i.Key, i.Value + (int)Math.Round(RandomMisc.Random(stDv))); - } - - foreach (var i in c.Vorteile) - { - this.Vorteile.Add(new Vorteil(i.Name, i.Value + (int)Math.Round(RandomMisc.Random(stDv)))); - } - - foreach (var i in c.Talente) - { - this.Talente.Add(new Talent(i.Name, i.Probe, i.Value + (int)Math.Round(RandomMisc.Random(stDv)))); - } - - foreach (var i in c.Zauber) - { - this.Zauber.Add(new Zauber(i.Name, i.Probe, i.Value + (int)Math.Round(RandomMisc.Random(stDv)), i.Complexity, i.Representation)); - } - - foreach (var i in c.Kampftalente) - { - this.Kampftalente.Add(new KampfTalent(i.Name, i.At + (int)Math.Round(RandomMisc.Random(stDv)), i.Pa + (int)Math.Round(RandomMisc.Random(stDv)))); - } - - this.Post_process(); // calculate derived values - } - - public Dictionary Eigenschaften { get; set; } = new Dictionary(); // char properties - - public List Talente { get; set; } = new List(); // list of talent objects (talents) - - public List Zauber { get; set; } = new List(); // list of spell objects - - public List Kampftalente { get; set; } = new List(); // list of combat objects - - public List Vorteile { get; set; } = new List(); - - public Dictionary PropTable { get; set; } = new Dictionary(); // -> Körperkraft - - public string TestTalent(string talent, int erschwernis = 0) // Talentprobe - { - return this.Talente.ProbenTest(this, talent, erschwernis); - } - - public string TestZauber(string zauber, int erschwernis = 0) // Talentprobe - { - return this.Zauber.ProbenTest(this, zauber, erschwernis); - } - - public string TestEigenschaft(string eigenschaft, int erschwernis = 0) - { - var output = new StringBuilder(); - var prop = this.PropTable[eigenschaft.ToUpper()]; - int tap = this.Eigenschaften[prop]; - output.AppendFormat( - "{0}-Eigenschaftsprobe ew:{1} {2} \n", - prop, - tap, - erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis); - int roll = Dice.Roll(); - output.Append($"Gewürfelt: {roll} übrig: {tap - roll - erschwernis}"); - return output.ToString(); - } - - public string Angriff(string talent, int erschwernis = 0) // pretty self explanatory - { - var output = new StringBuilder(); - var sc = new SpellCorrect(); - var attack = this.Kampftalente.OrderBy(x => sc.Compare(talent, x.Name)).First(); - if (sc.Compare(talent, attack.Name) > SpellCorrect.ErrorThreshold) - { - try - { - SoundEffects.Play("Wrong"); - } - catch { } - - return $"{this.Name} kann nicht mit der Waffenart {talent} umgehen..."; - } - - int tap = attack.At; - output.AppendFormat( - "{0}-Angriff taw:{1} {2} \n", - attack.Name, - tap, - erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis); - - int temp = Dice.Roll(); - output.Append(temp - erschwernis); - return output.ToString(); - } - - public string Parade(string talent, int erschwernis = 0) - { - var output = new StringBuilder(); - var sc = new SpellCorrect(); - var attack = this.Kampftalente.OrderBy(x => sc.Compare(talent, x.Name)).First(); - - if (sc.Compare(talent, attack.Name) > SpellCorrect.ErrorThreshold) - { - try - { - SoundEffects.Play("Wrong"); - } - catch { } - return $"{this.Name} kann nicht mit der Waffenart {talent} umgehen..."; - } - - int tap = attack.Pa; - output.AppendFormat( - "{0}-Parade taw:{1} {2}\n", - attack.Name, - tap, - erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis); - - int temp = Dice.Roll(); - output.Append(temp - erschwernis); - return output.ToString(); - } - - public string Fernkampf(string talent, int erschwernis = 0) - { - var output = new StringBuilder(); - var sc = new SpellCorrect(); - int fk = this.Eigenschaften["fk"]; - var attack = this.Talente.OrderBy(x => sc.Compare(talent, x.Name)).First(); - if (sc.Compare(talent, attack.Name) > SpellCorrect.ErrorThreshold) - { - try - { - SoundEffects.Play("Wrong"); - } - catch { } - return $"{this.Name} kann nicht mit der Waffenart {talent} umgehen..."; - } - - int tap = attack.Value; - output.AppendFormat( - "{0} taw:{1} {2} \n", - attack.Name, - tap, - erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis); - tap -= erschwernis; - int temp = Dice.Roll(); - tap -= temp > fk ? temp - fk : 0; - output.Append($"W20: {temp} tap: {tap}"); - return output.ToString(); - } - - private void Post_process() - { - var LE_Wert = this.Eigenschaften["Lebensenergie"]; - var AE_Wert = this.Eigenschaften.First(s => s.Key.Contains("Astralenergie")).Value; - - //var KL_Wert = this.Eigenschaften.First(s => s.Key.Contains("Klugheit")).Value; - var MU_Wert = this.Eigenschaften.First(s => s.Key.Contains("Mut")).Value; - var IN_Wert = this.Eigenschaften.First(s => s.Key.Contains("Intuition")).Value; - var CH_Wert = this.Eigenschaften.First(s => s.Key.Contains("Charisma")).Value; - var KK_Wert = this.Eigenschaften["Körperkraft"]; - var KO__Wert = this.Eigenschaften["Konstitution"]; - - this.Astralpunkte_Basis = 0; - - this.Ausdauer_Basis = 0; - - this.Lebenspunkte_Basis = LE_Wert + (int)(KO__Wert + (KK_Wert / 2.0) + 0.5); - - if (this.Vorteile.Exists(x => x.Name.ToLower().Contains("zauberer"))) - { - this.Astralpunkte_Basis = AE_Wert + (int)((MU_Wert + IN_Wert + CH_Wert) / 2.0 + 0.5); - } - - this.Lebenspunkte_Aktuell = this.Lebenspunkte_Basis; - this.Astralpunkte_Aktuell = this.Astralpunkte_Basis; - this.Ausdauer_Aktuell = this.Ausdauer_Basis; - - } - - - private void Load(string path) - { - var reader = new XmlTextReader(path); - while (reader.Read()) - { - // read until he hits keywords - if (reader.NodeType != XmlNodeType.Element) - { - continue; - } - - switch (reader.Name) - { - case "Wesen": - reader.Skip(); - break; - case "held": - this.Name = reader.GetAttribute("name"); // name - break; - case "eigenschaft": - this.Eigenschaften.Add( - reader.GetAttribute("name") ?? throw new InvalidOperationException(), - Convert.ToInt32(reader.GetAttribute("value")) + Convert.ToInt32(reader.GetAttribute("mod"))); - break; - case "vt": - reader.Read(); - while (reader.Name.Equals("vorteil")) - { - try - { - this.Vorteile.Add(new Vorteil( - reader.GetAttribute("name"), - // Convert.ToInt32(reader.GetAttribute("value")))); - reader.GetAttribute("value"))); - } - catch - { - this.Vorteile.Add(new Vorteil(reader.GetAttribute("name"))); - } - - reader.Read(); - } - - break; - case "talentliste": - reader.Read(); - while (reader.Name.Equals("talent")) - { - this.Talente.Add( - new Talent( - reader.GetAttribute("name"), - reader.GetAttribute("probe")?.Remove(0, 2).Trim(')'), - Convert.ToInt32(reader.GetAttribute("value")))); - reader.Read(); - } - - break; - case "zauberliste": - reader.Read(); - while (reader.Name.Equals("zauber")) - { - this.Zauber.Add( - new Zauber( - reader.GetAttribute("name"), - reader.GetAttribute("probe")?.Remove(0, 2).Trim(')'), - Convert.ToInt32(reader.GetAttribute("value")), - reader.GetAttribute("k").ToCharArray()[0], - reader.GetAttribute("repraesentation"))); - reader.Read(); - } - - break; - case "kampfwerte": - string atName = reader.GetAttribute("name"); - reader.Read(); - int at = Convert.ToInt32(reader.GetAttribute("value")); - reader.Read(); - int pa = Convert.ToInt32(reader.GetAttribute("value")); - this.Kampftalente.Add(new KampfTalent(atName, at, pa)); - break; - } - } - } - } -} \ No newline at end of file diff --git a/DiscoBot/DSA_Game/Characters/NPC.cs b/DiscoBot/DSA_Game/Characters/NPC.cs deleted file mode 100644 index b1b8e82..0000000 --- a/DiscoBot/DSA_Game/Characters/NPC.cs +++ /dev/null @@ -1,112 +0,0 @@ -namespace DiscoBot.Characters -{ - using System; - - using DiscoBot.Auxiliary; - using DiscoBot.DSA_Game.Characters; - using DSALib.Characters; - - public class Npc : Being, ICharacter - { - private readonly int mean, stDv; - - public Npc(string name, int mean, int stDv) - { - this.mean = mean; - this.stDv = stDv; - this.Name = name; - } - - public string TestTalent(string talent, int tap = 3) - { - for (int i = 0; i <= 2; i++) - { - // foreach property, dice and tap - int temp = Dice.Roll(); - int eigenschaft = (int)Math.Round(RandomMisc.Random(this.stDv, this.mean)); - - if (eigenschaft < temp) - { - tap -= temp - eigenschaft; - } - } - - if (tap >= 0) - { - return $"{this.Name} vollführt {talent} erfolgreich"; - } - - - return $"{this.Name} scheitert an {talent}"; - } - - public string TestEigenschaft(string eigenschaft, int erschwernis = 0) - { - int temp = Dice.Roll(); - int prop = (int)Math.Round(RandomMisc.Random(this.stDv, this.stDv)); - - if (temp + erschwernis < prop) - { - return $"{this.Name} vollführt {eigenschaft} erfolgreich"; - } - - return $"{this.Name} scheitert an {eigenschaft}"; - } - - public string Angriff(string waffe, int erschwernis = 0) - { - int temp = Dice.Roll(); - - if (temp == 1) - { - return $"{this.Name} greift kritisch mit {waffe} an"; - } - - if (temp < erschwernis) - { - return $"{this.Name} greift mit {waffe} an"; - } - - return $"{this.Name} haut mit {waffe} daneben"; - } - - public string Parade(string waffe, int erschwernis = 0) - { - int temp = Dice.Roll(); - - if (temp == 1) - { - return $"{this.Name} pariert mit {waffe} meisterlich"; - } - - if (temp < erschwernis) - { - return $"{this.Name} pariert mit {waffe} an"; - } - - return $"{this.Name} schafft es nicht mit {waffe} zu parieren"; - } - - public string Fernkampf(string waffe, int erschwernis = 0) - { - int temp = Dice.Roll(); - - if (temp == 1) - { - return $"{this.Name} trifft kritisch mit {waffe}"; - } - - if (temp < erschwernis) - { - return $"{this.Name} greift mit {waffe} an"; - } - - return $"{this.Name} schießt mit {waffe} daneben"; - } - - public string TestZauber(string zauber, int erschwernis) - { - return TestTalent(zauber, erschwernis); - } - } -} diff --git a/DiscoBot/DSA_Game/Characters/SaveChar.cs b/DiscoBot/DSA_Game/Characters/SaveChar.cs deleted file mode 100644 index 272f516..0000000 --- a/DiscoBot/DSA_Game/Characters/SaveChar.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace DiscoBot.DSA_Game.Characters -{ - using Discord; - using DSALib.Characters; - - public class SaveChar - { - public string Name { get; set; } - - public int Lebenspunkte_Aktuell { get; set; } - - public int Ausdauer_Aktuell { get; set; } - - public int Astralpunkte_Aktuell { get; set; } - - public static SaveChar FromICharacter(ICharacter c) - { - return new SaveChar - { - Astralpunkte_Aktuell = c.Astralpunkte_Aktuell, - Ausdauer_Aktuell = c.Ausdauer_Aktuell, - Lebenspunkte_Aktuell = c.Lebenspunkte_Aktuell, - Name = c.Name - }; - } - } - - - public static class ICharExtension - { - public static void Update(this ICharacter c, SaveChar s) - { - c.Astralpunkte_Aktuell = s.Astralpunkte_Aktuell; - c.Ausdauer_Aktuell = s.Ausdauer_Aktuell; - c.Lebenspunkte_Aktuell = s.Lebenspunkte_Aktuell; - c.Name = s.Name; - } - } -} diff --git a/DiscoBot/DSA_Game/Dsa.cs b/DiscoBot/DSA_Game/Dsa.cs deleted file mode 100644 index fd60c9a..0000000 --- a/DiscoBot/DSA_Game/Dsa.cs +++ /dev/null @@ -1,72 +0,0 @@ -using DSALib; -using DSALib.Characters; - -namespace DiscoBot.DSA_Game -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - - using DiscoBot.Audio; - using DiscoBot.Commands; - using DiscoBot.DSA_Game.Characters; - using DiscoBot.DSA_Game.Save; - - using Discord.Commands; - - public static class Dsa - { - private static Session s_session; - - public static ICommandContext GeneralContext { get; set; } - - public static AudioService Service { get; set; } - - public static List Chars { get; set; } = new List(); // list of all characters - - public static List Talente { get; set; } = new List(); - - public static Session Session - { - get - { - s_session.Chars = Chars.Select(x => SaveChar.FromICharacter(x)).ToList(); - return s_session; - } - - set - { - s_session = value; - foreach (var x in value.Chars) - { - Chars.Find(c => c.Name.Equals(x.Name)).Update(x); - } - } - } - - public static void Startup() - { - //new DiscoBot.Auxiliary.Calculator.StringSolver("1d100 - (1d200 + 1) * -50000").Solve(); - /*Session = new Session();*/ - // relation.Add("Papo", "Pump aus der Gosse"); - foreach (var filename in Directory.GetFiles("helden", "*.xml")) - { - Chars.Add(new Character(filename)); - (Chars.Last() as Character)?.Talente.Select(x => new Talent(x.Name, x.Probe, 0)) - .Where(c => !Talente.Exists(v => v.Name.Equals(c.Name))).ToList().ForEach(v => Talente.Add(v)); - } - - Properties.Deserialize(); - Properties.Serialize(); - - Talente = Talente.OrderBy(x => x.Name).ToList(); - Session = new Session - { - Chars = Chars.Select(x => SaveChar.FromICharacter(x)).ToList(), - GeneralContext = GeneralContext - }; - Session.Save(); - } - } -} \ No newline at end of file diff --git a/DiscoBot/DSA_Game/Save/Properties.cs b/DiscoBot/DSA_Game/Save/Properties.cs deleted file mode 100644 index 67d30b0..0000000 --- a/DiscoBot/DSA_Game/Save/Properties.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace DiscoBot.DSA_Game.Save -{ - using System.Collections; - using System.IO; - using System.Reflection; - using System.Runtime.CompilerServices; - - using DiscoBot.Audio; - using DiscoBot.Auxiliary; - using DiscoBot.Commands; - - using Discord; - - using Newtonsoft.Json; - - public static class Properties - { - private static Dictionary objects; - - static Properties() - { - objects = new Dictionary(); - /*this.objects.Add("Sounds", new List()); - this.objects.Add("CommandInfos", new List());*/ - } - - public static List CommandInfos { get => objects["CommandInfo"] as List; set => objects["CommandInfo"] = value; } // use Properties.Commandinfos to access the abstract Object array - - public static List Sounds { get => objects["Sound"] as List; set => objects["Sound"] = value; } - - public static void Deserialize(string path = @"..\..\Properties") - { - - var files = Directory.GetFiles(path, "*.json"); - - foreach (string file in files) - { - try - { - string name = file.Split('\\').Last().Split('.')[0].Replace('-', '.'); - string data = File.ReadAllText(file); - Type type = Type.GetType(name); - if (data.StartsWith("[")) - { - type = typeof(List<>).MakeGenericType(type); - } - - var o = JsonConvert.DeserializeObject(data, type); - objects.Add(name.Split('.').Last(), o); - } - catch (Exception e) - { - // ignored - var log = new LogMessage(LogSeverity.Warning, "Properties", $"Laden von Save-File {file} fehlgeschlagen.", e); - Console.WriteLine(log); - } - - } - - } - - public static void Serialize(string path = @"..\..\Properties\") - { - try - { - foreach (var o in objects) - { - string assembly = o.Value is IList list ? ((IList)list)[0]?.GetType().FullName : o.Value.GetType().FullName; - - var name = path + assembly.Replace('.', '-') + ".json"; - File.WriteAllText(name, JsonConvert.SerializeObject(o.Value, Formatting.Indented)); // Deserialize Data and create CommandInfo Struct - } - } - catch (Exception e) - { - // ignored - var log = new LogMessage(LogSeverity.Warning, "Properties", $"Speichern von Save-File fehlgeschlagen.", e); - Console.WriteLine(log); - } - } - } -} diff --git a/DiscoBot/DSA_Game/Save/SaveCommand.cs b/DiscoBot/DSA_Game/Save/SaveCommand.cs deleted file mode 100644 index 1f160ec..0000000 --- a/DiscoBot/DSA_Game/Save/SaveCommand.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace DiscoBot.DSA_Game.Save -{ - using System.Diagnostics; - using System.IO; - using System.Net; - using System.Net.Http; - - using DiscoBot.Auxiliary; - - using Discord.Commands; - - public class SaveCommand : ModuleBase - { - [Command("load"), Summary("Load Session")] - [Alias("session")] - public async Task LoadSessionAsync([Remainder, Summary("Session Name")] string name = "") - { - if (name.Equals("?") || name.Equals(string.Empty)) - { - await this.ReplyAsync($"Gespeicherte Sessions:"); - await this.ReplyAsync(this.ListSessions()); - return; - } - - var path = DSA_Game.Save.Session.DirectoryPath + @"\" + name; - - var files = Directory.GetFiles(path); - var session = files.OrderByDescending(x => Convert.ToInt32(x.Split('-').Last().Split('.').First())).First(); - Dsa.Session = Session.Load(session); - - await this.ReplyAsync($"{name} wurde geladen"); - } - - [Command("save", RunMode = RunMode.Async), Summary("Save Session")] - public async Task SessionSaveAsync([Remainder, Summary("Session Name")] string name = "") - { - //var sendFile = this.Context.Channel.SendWebFile("https://cdn.discordapp.com/attachments/377123019673567232/465615882048110603/giphy.gif"); - - if (name.Equals("?") || name.Equals(string.Empty)) - { - await this.ReplyAsync($"Gespeicherte Sessions:"); - await this.ReplyAsync(this.ListSessions()); - return; - } - - var path = DSA_Game.Save.Session.DirectoryPath + @"\" + name; - if (Directory.Exists(path)) - { - var files = Directory.GetFiles(path); - int current = files.Max(x => Convert.ToInt32(x.Split('-').Last().Split('.').First())); - Dsa.Session.SessionName = name; - Dsa.Session.Save(path + "\\" + name + $"-{++current}.json"); - } - else - { - Directory.CreateDirectory(path); - Dsa.Session.SessionName = name; - Dsa.Session.Save(path + "\\" + name + $"-0.json"); - } - - await this.ReplyAsync($"{name} wurde gespeichert"); - //await sendFile; - } - - private string[] ListSessions() - { - string[] dirs = Directory.GetDirectories(Session.DirectoryPath).OrderByDescending(x => new DirectoryInfo(x).LastAccessTime.Ticks).ToArray(); - for (int i = 0; i < dirs.Length; i++) - { - dirs[i] += "; " + new DirectoryInfo(dirs[i]).LastAccessTime; - } - - return dirs; - } - } -} diff --git a/DiscoBot/DSA_Game/Save/Session.cs b/DiscoBot/DSA_Game/Save/Session.cs deleted file mode 100644 index 578fa50..0000000 --- a/DiscoBot/DSA_Game/Save/Session.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace DiscoBot.DSA_Game.Save -{ - using System.IO; - using System.Runtime.CompilerServices; - - using DiscoBot.DSA_Game.Characters; - - using Discord; - using Discord.Commands; - - using Newtonsoft.Json; - - public class Session - { - public static string DirectoryPath { get; set; } = @"..\..\sessions"; - - public ICommandContext GeneralContext { get; set; } - - public Dictionary Relation { get; set; } = new Dictionary(); // dictionary to match the char - - public List Chars { get; set; } = new List(); // list of all characters - - public string SessionName { get; set; } - - public static Session Load(string path = @"..\..\session.json") - { - try - { - return JsonConvert.DeserializeObject(File.ReadAllText(path)); // Deserialize Data and create Session Object - } - catch (Exception e) - { - // ignored - var log = new LogMessage(LogSeverity.Warning, "Properties", $"Laden von Save-File {path} fehlgeschlagen.", e); - Console.WriteLine(log); - return null; - } - } - - public void Save(string path = @"..\..\session.json") - { - try - { - File.WriteAllText(path, JsonConvert.SerializeObject(this, Formatting.Indented)); // Deserialize Data and create CommandInfo Struct - } - catch (Exception e) - { - var log = new LogMessage(LogSeverity.Warning, "Properties", $"Speichern von Save-File {path} fehlgeschlagen.", e); - Console.WriteLine(log); - // ignored - } - } - } -} -- cgit v1.2.3-54-g00ecf