summaryrefslogtreecommitdiff
path: root/dsa
diff options
context:
space:
mode:
authoruzvkl <dennis.kobert@student.kit.edu>2019-06-11 23:05:52 +0200
committeruzvkl <dennis.kobert@student.kit.edu>2019-06-11 23:05:52 +0200
commite6181c24124d97f2fbc932b8a68311e625463156 (patch)
treec1f097c344ca266b7941c9668590b0fd35c7870a /dsa
parent2490ad5d31fe2ac778ff9303776f0e91f47a2862 (diff)
Move dsa related stuff to subfolder
Diffstat (limited to 'dsa')
-rw-r--r--dsa/DSACore/Controllers/CommandsController.cs56
-rw-r--r--dsa/DSACore/Controllers/LobbyController.cs32
-rw-r--r--dsa/DSACore/Controllers/TokensController.cs25
-rw-r--r--dsa/DSACore/Controllers/ValuesController.cs45
-rw-r--r--dsa/DSACore/DSACore.csproj28
-rw-r--r--dsa/DSACore/Hubs/Login.cs205
-rw-r--r--dsa/DSACore/Models/Network/Group.cs43
-rw-r--r--dsa/DSACore/Models/Network/Token.cs21
-rw-r--r--dsa/DSACore/Models/Network/User.cs9
-rw-r--r--dsa/DSACore/Program.cs24
-rw-r--r--dsa/DSACore/Properties/DSALib-Auxiliary-CommandInfo.json101
-rw-r--r--dsa/DSACore/Properties/DSALib-DSA_Game-Characters-Character.json290
-rw-r--r--dsa/DSACore/Properties/PublishProfiles/FolderProfile.pubxml23
-rw-r--r--dsa/DSACore/Properties/PublishProfiles/FolderProfile1.pubxml23
-rw-r--r--dsa/DSACore/Properties/launchSettings.json30
-rw-r--r--dsa/DSACore/PropertiesDSALib-Auxiliary-CommandInfo.json101
-rw-r--r--dsa/DSACore/PropertiesDSALib-DSA_Game-Characters-Character.json290
-rw-r--r--dsa/DSACore/PropertiesNewtonsoft-Json-Linq-JProperty.json30
-rw-r--r--dsa/DSACore/Startup.cs47
-rw-r--r--dsa/DSACore/Token1
-rw-r--r--dsa/DSACore/appsettings.Development.json9
-rw-r--r--dsa/DSACore/appsettings.json10
-rw-r--r--dsa/DSALib/Auxiliary/Calculator/Argument.cs35
-rw-r--r--dsa/DSALib/Auxiliary/Calculator/ISolvable.cs10
-rw-r--r--dsa/DSALib/Auxiliary/Calculator/Operator.cs51
-rw-r--r--dsa/DSALib/Auxiliary/Calculator/Ops.cs13
-rw-r--r--dsa/DSALib/Auxiliary/Calculator/StringSolver.cs183
-rw-r--r--dsa/DSALib/Auxiliary/CommandInfo.cs28
-rw-r--r--dsa/DSALib/Auxiliary/Dice.cs45
-rw-r--r--dsa/DSALib/Auxiliary/Extensions.cs25
-rw-r--r--dsa/DSALib/Auxiliary/IDataObjectEnumerableExtension.cs25
-rw-r--r--dsa/DSALib/Auxiliary/RandomMisc.cs52
-rw-r--r--dsa/DSALib/Auxiliary/SpellCorrect.cs61
-rw-r--r--dsa/DSALib/Auxiliary/TalentEnumerableExtension.cs74
-rw-r--r--dsa/DSALib/Auxiliary/WeaponImporter.cs175
-rw-r--r--dsa/DSALib/Characters/Being.cs17
-rw-r--r--dsa/DSALib/Characters/Critter.cs88
-rw-r--r--dsa/DSALib/Characters/Entity.cs12
-rw-r--r--dsa/DSALib/Characters/ICharacter.cs15
-rw-r--r--dsa/DSALib/Characters/ICombatant.cs20
-rw-r--r--dsa/DSALib/Commands/CommandHandler.cs135
-rw-r--r--dsa/DSALib/Commands/CommandTypes.cs13
-rw-r--r--dsa/DSALib/Commands/FileHandler.cs32
-rw-r--r--dsa/DSALib/Commands/Gm.cs176
-rw-r--r--dsa/DSALib/Commands/HeldList.cs174
-rw-r--r--dsa/DSALib/Commands/Help.cs54
-rw-r--r--dsa/DSALib/Commands/LebenUndAstral.cs172
-rw-r--r--dsa/DSALib/Commands/List.cs39
-rw-r--r--dsa/DSALib/Commands/MiscCommands.cs219
-rw-r--r--dsa/DSALib/Commands/NpcCommands.cs35
-rw-r--r--dsa/DSALib/Commands/ProbenTest.cs85
-rw-r--r--dsa/DSALib/DSALib.csproj11
-rw-r--r--dsa/DSALib/DSA_Game/Characters/Character.cs269
-rw-r--r--dsa/DSALib/DSA_Game/Characters/NPC.cs83
-rw-r--r--dsa/DSALib/DSA_Game/Characters/SaveChar.cs38
-rw-r--r--dsa/DSALib/DSA_Game/Dsa.cs95
-rw-r--r--dsa/DSALib/DSA_Game/Save/Properties.cs74
-rw-r--r--dsa/DSALib/DSA_Game/Save/SaveCommand.cs66
-rw-r--r--dsa/DSALib/DSA_Game/Save/Session.cs51
-rw-r--r--dsa/DSALib/FireBase/Database.cs270
-rw-r--r--dsa/DSALib/Models/Database/DataObject.cs13
-rw-r--r--dsa/DSALib/Models/Database/Dsa/Advantage.cs16
-rw-r--r--dsa/DSALib/Models/Database/Dsa/CharSpell.cs16
-rw-r--r--dsa/DSALib/Models/Database/Dsa/DatabaseChar.cs63
-rw-r--r--dsa/DSALib/Models/Database/Dsa/Field.cs16
-rw-r--r--dsa/DSALib/Models/Database/Dsa/GeneralSpell.cs20
-rw-r--r--dsa/DSALib/Models/Database/Dsa/GroupChar.cs13
-rw-r--r--dsa/DSALib/Models/Database/Dsa/Inventory.cs12
-rw-r--r--dsa/DSALib/Models/Database/Dsa/Talent.cs24
-rw-r--r--dsa/DSALib/Models/Database/Dsa/Weapon.cs52
-rw-r--r--dsa/DSALib/Models/Database/Dsa/WeaponTalent.cs18
-rw-r--r--dsa/DSALib/Models/Database/Groups/DSAGroup.cs10
-rw-r--r--dsa/DSALib/Models/Database/Groups/Group.cs9
-rw-r--r--dsa/DSALib/Models/Database/IDataObject.cs7
-rw-r--r--dsa/DSALib/Models/Dsa/CritterAttack.cs19
-rw-r--r--dsa/DSALib/Models/Dsa/KampfTalent.cs16
-rw-r--r--dsa/DSALib/Models/Dsa/Talent.cs43
-rw-r--r--dsa/DSALib/Models/Dsa/Vorteil.cs16
-rw-r--r--dsa/DSALib/Models/Dsa/Zauber.cs16
-rw-r--r--dsa/DSALib/Models/Network/Command.cs18
-rw-r--r--dsa/DSALib/Models/Network/CommandResponse.cs28
-rw-r--r--dsa/DSALib/Properties-DSACore-Auxiliary-CommandInfo.json101
-rw-r--r--dsa/DSALib/Properties-DSACore-DSA_Game-Characters-Character.json290
-rw-r--r--dsa/DSALib/PropertiesNewtonsoft-Json-Linq-JProperty.json30
-rw-r--r--dsa/DiscoBot/App.config42
-rw-r--r--dsa/DiscoBot/Auxiliary/CommandExtension.cs98
-rw-r--r--dsa/DiscoBot/Auxiliary/Dice.cs31
-rw-r--r--dsa/DiscoBot/Auxiliary/Permissions.cs32
-rw-r--r--dsa/DiscoBot/Auxiliary/RandomMisc.cs36
-rw-r--r--dsa/DiscoBot/Auxiliary/SpellCorrect.cs105
-rw-r--r--dsa/DiscoBot/Commands/CommandExtension.cs119
-rw-r--r--dsa/DiscoBot/Commands/FileHandler.cs25
-rw-r--r--dsa/DiscoBot/Commands/MiscCommands.cs189
-rw-r--r--dsa/DiscoBot/DiscoBot.csproj149
-rw-r--r--dsa/DiscoBot/Help.json120
-rw-r--r--dsa/DiscoBot/Program.cs113
-rw-r--r--dsa/DiscoBot/Properties.json129
-rw-r--r--dsa/DiscoBot/Properties/AssemblyInfo.cs35
-rw-r--r--dsa/DiscoBot/Properties/DiscoBot-Audio-Sound.json7
-rw-r--r--dsa/DiscoBot/Properties/DiscoBot-Auxiliary-CommandInfo.json101
-rw-r--r--dsa/DiscoBot/Properties/DiscoBot-DSA_Game-Characters-Character.json290
-rw-r--r--dsa/DiscoBot/Properties/Settings.Designer.cs38
-rw-r--r--dsa/DiscoBot/Properties/Settings.settings9
-rw-r--r--dsa/DiscoBot/Rework/Permissions.cs43
-rw-r--r--dsa/DiscoBot/Token1
-rw-r--r--dsa/DiscoBot/packages.config63
-rw-r--r--dsa/DiscoBot/session.json6
-rw-r--r--dsa/DiscoBot/sessions/TheCrew/TheCrew-0.json83
-rw-r--r--dsa/DiscoBot/sessions/copy/copy-0.json79
-rw-r--r--dsa/DiscoBot/sessions/test/test-0.json79
-rw-r--r--dsa/DiscoBot/sessions/test/test-1.json79
-rw-r--r--dsa/DiscoBot/sessions/test/test-2.json81
-rw-r--r--dsa/DiscoBot/sessions/test/test-3.json81
-rw-r--r--dsa/DiscoBot/sessions/test/test-4.json81
-rw-r--r--dsa/FireBase/ExceptionEventArgs.cs28
-rw-r--r--dsa/FireBase/Extensions/ObservableExtensions.cs41
-rw-r--r--dsa/FireBase/Extensions/TaskExtensions.cs23
-rw-r--r--dsa/FireBase/FireBase.csproj13
-rw-r--r--dsa/FireBase/FirebaseClient.cs49
-rw-r--r--dsa/FireBase/FirebaseException.cs53
-rw-r--r--dsa/FireBase/FirebaseKeyGenerator.cs79
-rw-r--r--dsa/FireBase/FirebaseObject.cs27
-rw-r--r--dsa/FireBase/FirebaseOptions.cs52
-rw-r--r--dsa/FireBase/Http/HttpClientExtensions.cs123
-rw-r--r--dsa/FireBase/Http/PostResult.cs13
-rw-r--r--dsa/FireBase/ObservableExtensions.cs40
-rw-r--r--dsa/FireBase/Offline/ConcurrentOfflineDatabase.cs233
-rw-r--r--dsa/FireBase/Offline/DatabaseExtensions.cs257
-rw-r--r--dsa/FireBase/Offline/ISetHandler.cs10
-rw-r--r--dsa/FireBase/Offline/InitialPullStrategy.cs23
-rw-r--r--dsa/FireBase/Offline/Internals/MemberAccessVisitor.cs46
-rw-r--r--dsa/FireBase/Offline/OfflineCacheAdapter.cs152
-rw-r--r--dsa/FireBase/Offline/OfflineDatabase.cs228
-rw-r--r--dsa/FireBase/Offline/OfflineEntry.cs99
-rw-r--r--dsa/FireBase/Offline/RealtimeDatabase.cs479
-rw-r--r--dsa/FireBase/Offline/SetHandler.cs19
-rw-r--r--dsa/FireBase/Offline/StreamingOptions.cs23
-rw-r--r--dsa/FireBase/Offline/SyncOptions.cs28
-rw-r--r--dsa/FireBase/Query/AuthQuery.cs34
-rw-r--r--dsa/FireBase/Query/ChildQuery.cs50
-rw-r--r--dsa/FireBase/Query/FilterQuery.cs77
-rw-r--r--dsa/FireBase/Query/FirebaseQuery.cs314
-rw-r--r--dsa/FireBase/Query/IFirebaseQuery.cs40
-rw-r--r--dsa/FireBase/Query/OrderQuery.cs34
-rw-r--r--dsa/FireBase/Query/ParameterQuery.cs43
-rw-r--r--dsa/FireBase/Query/QueryExtensions.cs210
-rw-r--r--dsa/FireBase/Query/QueryFactoryExtensions.cs187
-rw-r--r--dsa/FireBase/Query/SilentQuery.cs18
-rw-r--r--dsa/FireBase/Settings.StyleCop77
-rw-r--r--dsa/FireBase/Streaming/FirebaseCache.cs181
-rw-r--r--dsa/FireBase/Streaming/FirebaseEvent.cs37
-rw-r--r--dsa/FireBase/Streaming/FirebaseEventSource.cs38
-rw-r--r--dsa/FireBase/Streaming/FirebaseEventType.cs18
-rw-r--r--dsa/FireBase/Streaming/FirebaseServerEventType.cs15
-rw-r--r--dsa/FireBase/Streaming/FirebaseSubscription.cs217
-rw-r--r--dsa/FireBase/Streaming/NonBlockingStreamReader.cs63
-rw-r--r--dsa/NUnitTestProject1/Auxiliary/Calculator/ArgumentTests.cs59
-rw-r--r--dsa/NUnitTestProject1/Auxiliary/Calculator/StringSolverTests.cs105
-rw-r--r--dsa/NUnitTestProject1/Auxiliary/DiceTests.cs71
-rw-r--r--dsa/NUnitTestProject1/NUnitTest.csproj20
160 files changed, 11719 insertions, 0 deletions
diff --git a/dsa/DSACore/Controllers/CommandsController.cs b/dsa/DSACore/Controllers/CommandsController.cs
new file mode 100644
index 0000000..b6e0be2
--- /dev/null
+++ b/dsa/DSACore/Controllers/CommandsController.cs
@@ -0,0 +1,56 @@
+using System;
+using DSACore.Models.Network;
+using DSALib.Commands;
+using DSALib.Models.Network;
+using Microsoft.AspNetCore.Mvc;
+
+// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
+
+namespace DSACore.Controllers
+{
+ [Route("dsa/[controller]")]
+ public class CommandsController : Controller
+ {
+ // GET: api/<controller>
+ [HttpGet]
+ public string Get()
+ {
+ return "Usage: post the command to execute";
+ }
+
+ // GET api/<controller>/5
+ /*[HttpGet("{id}")]
+ public string Get(int id)
+ {
+ return "value";
+ }*/
+
+ // POST api/<controller>/Felis
+ [HttpPost]
+ public string Post([FromBody] Command cmd)
+ {
+ try
+ {
+ return CommandHandler.ExecuteCommand(cmd).message;
+ }
+ catch (Exception e)
+ {
+ return $"Ein Fehler ist aufgetreten: \n {e.Message}";
+ }
+ }
+
+/*
+
+ // PUT api/<controller>/5
+ [HttpPut("{id}")]
+ public void Put(int id, [FromBody]string value)
+ {
+ }
+
+ // DELETE api/<controller>/5
+ [HttpDelete("{id}")]
+ public void Delete(int id)
+ {
+ }*/
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Controllers/LobbyController.cs b/dsa/DSACore/Controllers/LobbyController.cs
new file mode 100644
index 0000000..7890b4f
--- /dev/null
+++ b/dsa/DSACore/Controllers/LobbyController.cs
@@ -0,0 +1,32 @@
+using System;
+using DSACore.Models.Network;
+using DSALib.Commands;
+using DSALib.Models.Network;
+using Microsoft.AspNetCore.Mvc;
+
+namespace DSACore.Controllers
+{
+ public class ScribbleController : Controller
+ {
+ [Route("[controller]")]
+ // GET: api/<controller>
+ [HttpGet]
+ public string Get()
+ {
+ return "Usage: get /tokens/{Token}";
+ }
+
+ [HttpPost]
+ public string Post([FromBody] Command cmd)
+ {
+ try
+ {
+ return CommandHandler.ExecuteCommand(cmd).message;
+ }
+ catch (Exception e)
+ {
+ return $"Ein Fehler ist aufgetreten: \n {e.Message}";
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Controllers/TokensController.cs b/dsa/DSACore/Controllers/TokensController.cs
new file mode 100644
index 0000000..a85cabe
--- /dev/null
+++ b/dsa/DSACore/Controllers/TokensController.cs
@@ -0,0 +1,25 @@
+using DSACore.Hubs;
+using Microsoft.AspNetCore.Mvc;
+
+namespace DSACore.Controllers
+{
+ [Route("lobby/[controller]")]
+ [ApiController]
+ public class TokensController : Controller
+ {
+ // GET
+ [HttpGet("{token}")]
+ public ActionResult<string> Get(string token)
+ {
+ if (!int.TryParse(token, out var intToken))
+ return BadRequest("The token has to be a 32 bit unsigned integer");
+
+ if (intToken == 42) return Ok("Scribble");
+
+ if (!Users.Tokens.Exists(x => x.GetHashCode() == intToken)) return NotFound();
+
+ var group = Users.Tokens.Find(x => x.GetHashCode() == intToken);
+ return Ok(group.Group);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Controllers/ValuesController.cs b/dsa/DSACore/Controllers/ValuesController.cs
new file mode 100644
index 0000000..eb08898
--- /dev/null
+++ b/dsa/DSACore/Controllers/ValuesController.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+
+namespace DSACore.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class ValuesController : ControllerBase
+ {
+ // GET api/values
+ [HttpGet]
+ public ActionResult<IEnumerable<string>> Get()
+ {
+ return new string[] { "value1", "value2" };
+ }
+
+ // GET api/values/5
+ [HttpGet("{id}")]
+ public ActionResult<string> Get(int id)
+ {
+ return "value";
+ }
+
+ // POST api/values
+ [HttpPost]
+ public void Post([FromBody] string value)
+ {
+ }
+
+ // PUT api/values/5
+ [HttpPut("{id}")]
+ public void Put(int id, [FromBody] string value)
+ {
+ }
+
+ // DELETE api/values/5
+ [HttpDelete("{id}")]
+ public void Delete(int id)
+ {
+ }
+ }
+}
diff --git a/dsa/DSACore/DSACore.csproj b/dsa/DSACore/DSACore.csproj
new file mode 100644
index 0000000..f7def31
--- /dev/null
+++ b/dsa/DSACore/DSACore.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp2.2</TargetFramework>
+ <StartupObject>DSACore.Program</StartupObject>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <Compile Remove="wwwroot\**" />
+ <Content Remove="wwwroot\**" />
+ <EmbeddedResource Remove="wwwroot\**" />
+ <None Remove="wwwroot\**" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.AspNetCore.App" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\DSALib\DSALib.csproj" />
+ <ProjectReference Include="..\FireBase\FireBase.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Compile Remove="Controllers\ValuesController.cs" />
+ </ItemGroup>
+
+</Project>
diff --git a/dsa/DSACore/Hubs/Login.cs b/dsa/DSACore/Hubs/Login.cs
new file mode 100644
index 0000000..f08c24a
--- /dev/null
+++ b/dsa/DSACore/Hubs/Login.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DSACore.Models.Network;
+using DSALib.Commands;
+using DSALib.DSA_Game.Characters;
+using DSALib.FireBase;
+using DSALib.Models.Network;
+using Microsoft.AspNetCore.SignalR;
+using Group = DSACore.Models.Network.Group;
+
+namespace DSACore.Hubs
+{
+ public class Users : Hub
+ {
+ //private static Dictionary<string, User> UserGroup = new Dictionary<string, User>();
+
+ private const string ReceiveMethod = "ReceiveMessage"; //receiveMethod;
+
+ static Users() {
+ DsaGroups = Database.GetGroups().Result.Select(x=>new Group(x.Item1, x.Item2)).ToList();
+ DsaGroups.Add(new Group("login", ""));
+ DsaGroups.Add(new Group("online", ""));
+ //AddGroups();
+ }
+
+ private static List<Group> DsaGroups { get; }
+ public static List<Token> Tokens { get; } = new List<Token>();
+
+
+ public async Task SendMessage(string user, string message)
+ {
+ try
+ {
+ var group = getGroup(Context.ConnectionId).Name;
+ }
+ catch (InvalidOperationException)
+ {
+ await Clients.Caller.SendCoreAsync(ReceiveMethod,
+ new object[] { "Nutzer ist in keiner Gruppe. Erst joinen!" });
+ }
+
+ if (message[0] == '/')
+ {
+ var args = message.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
+
+ var Timon = args.Any(x => x == "hallo");
+
+ var ident = args.First().Replace("/", "");
+ if (args.Count > 0) args.RemoveAt(0);
+
+ var ret = CommandHandler.ExecuteCommand(new Command
+ {
+ CharId = 0,
+ CmdIdentifier = ident,
+ CmdTexts = args,
+ Name = user
+ });
+
+ switch (ret.ResponseType)
+ {
+ case ResponseType.Caller:
+ case ResponseType.Error:
+ await Clients.Caller.SendAsync(ReceiveMethod, ret.message);
+ break;
+ case ResponseType.Broadcast:
+ await SendToGroup(ret.message);
+ break;
+ }
+ }
+ else
+ {
+ await SendToGroup(message);
+ }
+ }
+
+ private Task SendToGroup(string message)
+ {
+ try
+ {
+ var group = getGroup(Context.ConnectionId).Name;
+ return Clients.Group(group).SendCoreAsync(ReceiveMethod,
+ new object[] {getUser(Context.ConnectionId).Name, message});
+ }
+ catch (InvalidOperationException)
+ {
+ return Clients.Caller.SendCoreAsync(ReceiveMethod,
+ new object[] {"Nutzer ist in keiner Gruppe. Erst joinen!"});
+ }
+ }
+
+ private Group getGroup(string id)
+ {
+ return DsaGroups.First(x => x.Users.Exists(y => y.ConnectionId.Equals(id)));
+ }
+
+ private User getUser(string id)
+ {
+ return DsaGroups.First(x => x.Users.Exists(y => y.ConnectionId.Equals(id))).Users
+ .First(z => z.ConnectionId.Equals(id));
+ }
+
+ public async Task GetGroups() {
+ var test = await Database.GetGroups();
+ foreach (var group in test.Select(x => new Group(x.Item1, x.Item2)).ToList())
+ if (!DsaGroups.Exists(x => x.Name.Equals(group.Name)))
+ DsaGroups.Add(group);
+
+ await Clients.Caller.SendCoreAsync("ListGroups", new object[] {DsaGroups.Select(x => x.SendGroup())});
+ //throw new NotImplementedException("add database call to get groups");
+ }
+
+ public async Task AddGroup(string group, string password)
+ {
+ DsaGroups.Add(new Group(group, password));
+ var Dgroup = new DSALib.Models.Database.Groups.Group {Name = group, Id = DsaGroups.Count - 1};
+ //Database.AddGroup(Dgroup);
+ await Clients.Caller.SendCoreAsync(ReceiveMethod, new[] {$"group {group} sucessfully added"});
+ //throw new NotImplementedException("add database call to add groups");
+ }
+
+ public async Task UploadChar(string xml)
+ {
+ var group = getGroup(Context.ConnectionId);
+
+ await Database.AddChar(new Character(new MemoryStream(Encoding.UTF8.GetBytes(xml))), group.Name);
+ //throw new NotImplementedException("add database call to add groups");
+ }
+
+ public async Task Login(string group, string user, string hash)
+ {
+ //string password = System.Text.Encoding.UTF8.GetString(hash);
+ if (hash == DsaGroups.First(x => x.Name == group).Password)
+ {
+ var gGroup = DsaGroups.First(x => x.Name.Equals(group));
+ if (!gGroup.Users.Exists(x => x.Name.Equals(user)))
+ {
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, "login");
+ await Groups.AddToGroupAsync(Context.ConnectionId, group);
+ gGroup.Users.Add(new User {ConnectionId = Context.ConnectionId, Name = user});
+ await SendToGroup("Ein neuer Nutzer hat die Gruppe betreten");
+ await Clients.Caller.SendAsync("LoginResponse", 0);
+ await Clients.Caller.SendAsync("PlayerStatusChanged", new[] {user, "online"});
+
+ Tokens.Add(new Token(group));
+ await Clients.Caller.SendAsync("Token", Tokens.Last().GetHashCode());
+ purgeTokens();
+ }
+ else
+ {
+ await Clients.Caller.SendAsync("LoginResponse", 1);
+ }
+ }
+ else
+ {
+ await Clients.Caller.SendAsync("LoginResponse", 2);
+ //await Clients.Caller.SendAsync(receiveMethod, "Falsches Passwort!");
+ }
+ }
+
+ private void purgeTokens()
+ {
+ Tokens.RemoveAll(x => !x.IsValid());
+ }
+
+ public override Task OnDisconnectedAsync(Exception exception)
+ {
+ Disconnect().Wait();
+ return base.OnDisconnectedAsync(exception);
+ }
+
+ public override Task OnConnectedAsync()
+ {
+ Groups.AddToGroupAsync(Context.ConnectionId, "login").Wait();
+ Groups.AddToGroupAsync(Context.ConnectionId, "online").Wait();
+ return base.OnConnectedAsync();
+ }
+
+ public async Task Disconnect()
+ {
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, "online");
+ if (DsaGroups.Exists(x => x.Users.Exists(y => y.ConnectionId == Context.ConnectionId)))
+ try
+ {
+ var group = getGroup(Context.ConnectionId);
+
+
+ var user = getUser(Context.ConnectionId);
+
+ await Clients.Caller.SendAsync("PlayerStatusChanged", new[] {user.Name, "offline"});
+ //await SendToGroup(user.Name + " disconnected from the Server");
+ group.Users.Remove(user);
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, group.Name);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ //throw;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Models/Network/Group.cs b/dsa/DSACore/Models/Network/Group.cs
new file mode 100644
index 0000000..efe12ee
--- /dev/null
+++ b/dsa/DSACore/Models/Network/Group.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+
+namespace DSACore.Models.Network
+{
+ public class Group
+ {
+ public Group(string name, string password)
+ {
+ Name = name;
+ Password = password;
+ }
+
+ public Group(string name, int userOnline)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ }
+
+ public string Name { get; set; }
+ public string Password { get; set; }
+ public List<User> Users { get; set; } = new List<User>();
+
+ public int UserCount => Users.Count;
+
+ public SendGroup SendGroup()
+ {
+ return new SendGroup(Name, UserCount);
+ }
+ }
+
+ public class SendGroup
+ {
+ public SendGroup(string name, int userCount)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ UserCount = userCount;
+ }
+
+ public string Name { get; set; }
+
+ public int UserCount { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Models/Network/Token.cs b/dsa/DSACore/Models/Network/Token.cs
new file mode 100644
index 0000000..451cafc
--- /dev/null
+++ b/dsa/DSACore/Models/Network/Token.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace DSACore.Models.Network
+{
+ public class Token
+ {
+ private readonly DateTime creation = DateTime.Now;
+
+ public Token(string group)
+ {
+ Group = group;
+ }
+
+ public string Group { get; set; }
+
+ public bool IsValid()
+ {
+ return DateTime.Now - creation < TimeSpan.FromMinutes(1);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Models/Network/User.cs b/dsa/DSACore/Models/Network/User.cs
new file mode 100644
index 0000000..8b8008c
--- /dev/null
+++ b/dsa/DSACore/Models/Network/User.cs
@@ -0,0 +1,9 @@
+namespace DSACore.Models.Network
+{
+ public class User
+ {
+ public string Name { get; set; }
+ public string ConnectionId { get; set; }
+ public int Char { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Program.cs b/dsa/DSACore/Program.cs
new file mode 100644
index 0000000..8af0a74
--- /dev/null
+++ b/dsa/DSACore/Program.cs
@@ -0,0 +1,24 @@
+using DSALib.DSA_Game;
+using DSALib.FireBase;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+
+namespace DSACore
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ Database.GetGroup(0).Wait();
+ Dsa.Startup();
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args)
+ {
+ return WebHost.CreateDefaultBuilder(args)
+ .UseStartup<Startup>()
+ .UseUrls("http://0.0.0.0:5000");
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Properties/DSALib-Auxiliary-CommandInfo.json b/dsa/DSACore/Properties/DSALib-Auxiliary-CommandInfo.json
new file mode 100644
index 0000000..b9941f2
--- /dev/null
+++ b/dsa/DSACore/Properties/DSALib-Auxiliary-CommandInfo.json
@@ -0,0 +1,101 @@
+[
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Setzt den gespielten Charakter fest",
+ "Description": [
+ "Mit \"!Ich bin\" kann der gespielte Charakter definiert, bzw. gewechselt werden.\n",
+ " Die Charaktere müssen als *.xml Dateien hinterlegt sein.\n\n",
+ " !ich Zeigt an welcher Charakter zur Zeit gespielt wird\n",
+ " !ich bin Zalibius Wechsel zum Helden Zalibius\n",
+ " !ich Rhoktar Orkische Variante von !ich bin.\n",
+ " Wechselt zu Rhoktar.\n\n",
+ " !list chars Zeigt die Liste verfügbarer Charaktere.\n",
+ " \n"
+ ]
+ },
+ {
+ "Name": "List",
+ "Scope": "All",
+ "Brief": "Anzeige vonSpielrelevanten Listen",
+ "Description": [
+ "Mit \"!list\" lassen sich spielrelevante Listen ausgeben. Die Angezeigte Liste wird nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !list chars Liste aller verfügbaren Helden (eingelesen per *.xml)\n",
+ " und NSCs.\n",
+ " (Mit \"!ich bin\" kann der Held ausgewählt werden.)\n",
+ " !list commands Liste aller verwendbaren Bot-Kommandos.\n",
+ " !list sounds Liste der Soundeffekte."
+ ]
+ },
+ {
+ "Name": "Held",
+ "Scope": "All",
+ "Brief": "Anzeige von Heldenwerten",
+ "Description": [
+ "Mit \"!Held\" lassen sich Heldenwerte ausgeben. Mehrere Werte können gleichzeitig angefordert werde. \"!Held LE Waffen\" liefert so z.B. Informationen zur Lebensenergie und den Kampfwerten.\n Bis auf wenige Ausnahmen wird die Angezeigte Liste nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !Held Zeigt den Heldenbrief an.\n",
+ " (Diese Liste wird nicht automatisch gelöscht)\n",
+ " !Held Eigenschaften Zeigt die Eigenschaften MU/KL/CH/IN/KK/GE/FF/KO.\n",
+ " !Held e Kurzform von \"!Held Eigenschaften\".\n",
+ " !Held LE Zeigt LE an.\n",
+ " !Held AE Zeigt AE an.\n",
+ " !Held stats Zeigt Eigenschaften und zusätzlich SO/LE/AE.\n",
+ " !Held Kampfwerte Zeigt AT/PA für aktivierte Waffentalente.\n",
+ " !Held Waffe/!list w Kurzformen von \"!Held Kampfwerte\".\n",
+ " !Held Vorteile Zeigt Vor- und Nachteile an.\n",
+ " !Held v Kurzform von \"!Held Vorteile\".\n",
+ " !Held Talente Zeigt die Liste aller aktivierten Talente, deren TaW,\n",
+ " sowie die Probe.\n",
+ " !Held t Kurzform von \"!Held Talente\".\n",
+ " !Held Zauber Zeigt die Liste aller aktivierten Zauber, deren ZaW,\n",
+ " sowie die Probe.\n",
+ " !Held z Kurzform von \"!Held Zauber\".\n"
+ ]
+ },
+ {
+ "Name": "LE",
+ "Scope": "All",
+ "Brief": "Ändert dein Leben - im wahrsten Sinne des Wortes",
+ "Description": [
+ "Mit !LE zeigt man die Lebensenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !LE Zeigt Lebensenergie an\n",
+ " !LE 30 Setzt LE auf 30\n",
+ " !LE +5 Erhöht LE um 5 (bis zum Maximum)\n",
+ " !LE ++5 Erhöht LE um 5 (ignoriert Maximum)\n",
+ " !LE -5 Verringert LE um 5\n \n"
+ ]
+ },
+ {
+ "Name": "AE",
+ "Scope": "All",
+ "Brief": "Ändert Astralenergie",
+ "Description": [
+ "Mit !AE (oder !Asp) zeigt man die Astralenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !AE Zeigt Astralenergie an\n",
+ " !AE 30 Setzt Asp auf 30\n",
+ " !AE +5 Erhöht Asp um 5 (bis zum Maximum)\n",
+ " !AE ++5 Erhöht Asp um 5 (ignoriert Maximum)\n",
+ " !AE -5 Verringert Asp um 5 (Minimum 0)\n"
+ ]
+ },
+ {
+ "Name": "Gm",
+ "Scope": "Meister",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ }
+] \ No newline at end of file
diff --git a/dsa/DSACore/Properties/DSALib-DSA_Game-Characters-Character.json b/dsa/DSACore/Properties/DSALib-DSA_Game-Characters-Character.json
new file mode 100644
index 0000000..fd387f5
--- /dev/null
+++ b/dsa/DSACore/Properties/DSALib-DSA_Game-Characters-Character.json
@@ -0,0 +1,290 @@
+[
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 30,
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 20,
+ "Astralpunkte_Aktuell": 20,
+ "Name": "Felis Exodus Schattenwald"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 29,
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Gardist"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 31,
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Hartmut Reiher"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 21,
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 35,
+ "Astralpunkte_Aktuell": 35,
+ "Name": "Helga vom Drachenei, Tausendsasserin"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 25,
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Krenko"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Ledur Torfinson"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 26,
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Morla"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 40,
+ "Astralpunkte_Aktuell": 40,
+ "Name": "Numeri Illuminus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 16,
+ "Astralpunkte_Aktuell": 16,
+ "Name": "Potus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 18,
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Pump aus der Gosse"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 34,
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 17,
+ "Astralpunkte_Aktuell": 17,
+ "Name": "Rhoktar4"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 43,
+ "Astralpunkte_Aktuell": 43,
+ "Name": "Volant"
+ }
+] \ No newline at end of file
diff --git a/dsa/DSACore/Properties/PublishProfiles/FolderProfile.pubxml b/dsa/DSACore/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..2fd07c5
--- /dev/null
+++ b/dsa/DSACore/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Diese Datei wird vom Veröffentlichungs-/Paketierungsprozess Ihres Webprojekts verwendet. Sie können das Verhalten dieses Prozesses anpassen,
+indem Sie diese MSBuild-Datei bearbeiten. Weitere Informationen hierzu finden Sie unter https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <WebPublishMethod>FileSystem</WebPublishMethod>
+ <PublishProvider>FileSystem</PublishProvider>
+ <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
+ <LastUsedPlatform>Any CPU</LastUsedPlatform>
+ <SiteUrlToLaunchAfterPublish />
+ <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
+ <ExcludeApp_Data>False</ExcludeApp_Data>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <ProjectGuid>35a5e2cc-0fd4-4bc0-acbf-38599caed1c4</ProjectGuid>
+ <SelfContained>false</SelfContained>
+ <_IsPortable>true</_IsPortable>
+ <publishUrl>bin\Release\netcoreapp2.1\publish\</publishUrl>
+ <DeleteExistingFiles>False</DeleteExistingFiles>
+ <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+ </PropertyGroup>
+</Project> \ No newline at end of file
diff --git a/dsa/DSACore/Properties/PublishProfiles/FolderProfile1.pubxml b/dsa/DSACore/Properties/PublishProfiles/FolderProfile1.pubxml
new file mode 100644
index 0000000..e03b55a
--- /dev/null
+++ b/dsa/DSACore/Properties/PublishProfiles/FolderProfile1.pubxml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Diese Datei wird vom Veröffentlichungs-/Paketierungsprozess Ihres Webprojekts verwendet. Sie können das Verhalten dieses Prozesses anpassen,
+indem Sie diese MSBuild-Datei bearbeiten. Weitere Informationen hierzu finden Sie unter https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <WebPublishMethod>FileSystem</WebPublishMethod>
+ <PublishProvider>FileSystem</PublishProvider>
+ <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
+ <LastUsedPlatform>Any CPU</LastUsedPlatform>
+ <SiteUrlToLaunchAfterPublish />
+ <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
+ <ExcludeApp_Data>False</ExcludeApp_Data>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
+ <ProjectGuid>35a5e2cc-0fd4-4bc0-acbf-38599caed1c4</ProjectGuid>
+ <SelfContained>false</SelfContained>
+ <_IsPortable>true</_IsPortable>
+ <publishUrl>bin\Release\netcoreapp2.1\publish\</publishUrl>
+ <DeleteExistingFiles>False</DeleteExistingFiles>
+ </PropertyGroup>
+</Project> \ No newline at end of file
diff --git a/dsa/DSACore/Properties/launchSettings.json b/dsa/DSACore/Properties/launchSettings.json
new file mode 100644
index 0000000..2da5fec
--- /dev/null
+++ b/dsa/DSACore/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:2170",
+ "sslPort": 44365
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "api/commands",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "DSACore": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "api/commands",
+ "applicationUrl": "https://0.0.0.0:5001;http://0.0.0.0:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/PropertiesDSALib-Auxiliary-CommandInfo.json b/dsa/DSACore/PropertiesDSALib-Auxiliary-CommandInfo.json
new file mode 100644
index 0000000..b9941f2
--- /dev/null
+++ b/dsa/DSACore/PropertiesDSALib-Auxiliary-CommandInfo.json
@@ -0,0 +1,101 @@
+[
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Setzt den gespielten Charakter fest",
+ "Description": [
+ "Mit \"!Ich bin\" kann der gespielte Charakter definiert, bzw. gewechselt werden.\n",
+ " Die Charaktere müssen als *.xml Dateien hinterlegt sein.\n\n",
+ " !ich Zeigt an welcher Charakter zur Zeit gespielt wird\n",
+ " !ich bin Zalibius Wechsel zum Helden Zalibius\n",
+ " !ich Rhoktar Orkische Variante von !ich bin.\n",
+ " Wechselt zu Rhoktar.\n\n",
+ " !list chars Zeigt die Liste verfügbarer Charaktere.\n",
+ " \n"
+ ]
+ },
+ {
+ "Name": "List",
+ "Scope": "All",
+ "Brief": "Anzeige vonSpielrelevanten Listen",
+ "Description": [
+ "Mit \"!list\" lassen sich spielrelevante Listen ausgeben. Die Angezeigte Liste wird nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !list chars Liste aller verfügbaren Helden (eingelesen per *.xml)\n",
+ " und NSCs.\n",
+ " (Mit \"!ich bin\" kann der Held ausgewählt werden.)\n",
+ " !list commands Liste aller verwendbaren Bot-Kommandos.\n",
+ " !list sounds Liste der Soundeffekte."
+ ]
+ },
+ {
+ "Name": "Held",
+ "Scope": "All",
+ "Brief": "Anzeige von Heldenwerten",
+ "Description": [
+ "Mit \"!Held\" lassen sich Heldenwerte ausgeben. Mehrere Werte können gleichzeitig angefordert werde. \"!Held LE Waffen\" liefert so z.B. Informationen zur Lebensenergie und den Kampfwerten.\n Bis auf wenige Ausnahmen wird die Angezeigte Liste nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !Held Zeigt den Heldenbrief an.\n",
+ " (Diese Liste wird nicht automatisch gelöscht)\n",
+ " !Held Eigenschaften Zeigt die Eigenschaften MU/KL/CH/IN/KK/GE/FF/KO.\n",
+ " !Held e Kurzform von \"!Held Eigenschaften\".\n",
+ " !Held LE Zeigt LE an.\n",
+ " !Held AE Zeigt AE an.\n",
+ " !Held stats Zeigt Eigenschaften und zusätzlich SO/LE/AE.\n",
+ " !Held Kampfwerte Zeigt AT/PA für aktivierte Waffentalente.\n",
+ " !Held Waffe/!list w Kurzformen von \"!Held Kampfwerte\".\n",
+ " !Held Vorteile Zeigt Vor- und Nachteile an.\n",
+ " !Held v Kurzform von \"!Held Vorteile\".\n",
+ " !Held Talente Zeigt die Liste aller aktivierten Talente, deren TaW,\n",
+ " sowie die Probe.\n",
+ " !Held t Kurzform von \"!Held Talente\".\n",
+ " !Held Zauber Zeigt die Liste aller aktivierten Zauber, deren ZaW,\n",
+ " sowie die Probe.\n",
+ " !Held z Kurzform von \"!Held Zauber\".\n"
+ ]
+ },
+ {
+ "Name": "LE",
+ "Scope": "All",
+ "Brief": "Ändert dein Leben - im wahrsten Sinne des Wortes",
+ "Description": [
+ "Mit !LE zeigt man die Lebensenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !LE Zeigt Lebensenergie an\n",
+ " !LE 30 Setzt LE auf 30\n",
+ " !LE +5 Erhöht LE um 5 (bis zum Maximum)\n",
+ " !LE ++5 Erhöht LE um 5 (ignoriert Maximum)\n",
+ " !LE -5 Verringert LE um 5\n \n"
+ ]
+ },
+ {
+ "Name": "AE",
+ "Scope": "All",
+ "Brief": "Ändert Astralenergie",
+ "Description": [
+ "Mit !AE (oder !Asp) zeigt man die Astralenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !AE Zeigt Astralenergie an\n",
+ " !AE 30 Setzt Asp auf 30\n",
+ " !AE +5 Erhöht Asp um 5 (bis zum Maximum)\n",
+ " !AE ++5 Erhöht Asp um 5 (ignoriert Maximum)\n",
+ " !AE -5 Verringert Asp um 5 (Minimum 0)\n"
+ ]
+ },
+ {
+ "Name": "Gm",
+ "Scope": "Meister",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ }
+] \ No newline at end of file
diff --git a/dsa/DSACore/PropertiesDSALib-DSA_Game-Characters-Character.json b/dsa/DSACore/PropertiesDSALib-DSA_Game-Characters-Character.json
new file mode 100644
index 0000000..fd387f5
--- /dev/null
+++ b/dsa/DSACore/PropertiesDSALib-DSA_Game-Characters-Character.json
@@ -0,0 +1,290 @@
+[
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 30,
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 20,
+ "Astralpunkte_Aktuell": 20,
+ "Name": "Felis Exodus Schattenwald"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 29,
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Gardist"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 31,
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Hartmut Reiher"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 21,
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 35,
+ "Astralpunkte_Aktuell": 35,
+ "Name": "Helga vom Drachenei, Tausendsasserin"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 25,
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Krenko"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Ledur Torfinson"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 26,
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Morla"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 40,
+ "Astralpunkte_Aktuell": 40,
+ "Name": "Numeri Illuminus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 16,
+ "Astralpunkte_Aktuell": 16,
+ "Name": "Potus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 18,
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Pump aus der Gosse"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 34,
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 17,
+ "Astralpunkte_Aktuell": 17,
+ "Name": "Rhoktar4"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 43,
+ "Astralpunkte_Aktuell": 43,
+ "Name": "Volant"
+ }
+] \ No newline at end of file
diff --git a/dsa/DSACore/PropertiesNewtonsoft-Json-Linq-JProperty.json b/dsa/DSACore/PropertiesNewtonsoft-Json-Linq-JProperty.json
new file mode 100644
index 0000000..0ed0f48
--- /dev/null
+++ b/dsa/DSACore/PropertiesNewtonsoft-Json-Linq-JProperty.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:2170",
+ "sslPort": 44365
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "api/commands",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "DSACore": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "api/commands",
+ "applicationUrl": "https://0.0.0.0:5001;http://0.0.0.0:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Startup.cs b/dsa/DSACore/Startup.cs
new file mode 100644
index 0000000..ef22802
--- /dev/null
+++ b/dsa/DSACore/Startup.cs
@@ -0,0 +1,47 @@
+using DSACore.Hubs;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace DSACore
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+
+ services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
+
+ services.AddSignalR();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ if (env.IsDevelopment())
+ app.UseDeveloperExceptionPage();
+ else
+ app.UseHsts();
+
+ app.UseCors("CorsPolicy");
+
+ app.UseSignalR(routes => { routes.MapHub<Users>("/login"); });
+
+ app.UseWebSockets();
+
+ //app.UseCors("AllowSpecificOrigin");
+ app.UseHttpsRedirection();
+ app.UseMvc();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSACore/Token b/dsa/DSACore/Token
new file mode 100644
index 0000000..eb02248
--- /dev/null
+++ b/dsa/DSACore/Token
@@ -0,0 +1 @@
+fBhKAyCEJlqqU1l6gS1LNWwEZByQ23oAWbvtXXB5 \ No newline at end of file
diff --git a/dsa/DSACore/appsettings.Development.json b/dsa/DSACore/appsettings.Development.json
new file mode 100644
index 0000000..e203e94
--- /dev/null
+++ b/dsa/DSACore/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/dsa/DSACore/appsettings.json b/dsa/DSACore/appsettings.json
new file mode 100644
index 0000000..dee968c
--- /dev/null
+++ b/dsa/DSACore/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Microsoft.AspNetCore.SignalR": "Debug",
+ "Microsoft.AspNetCore.Http.Connections": "Debug",
+ "Default": "Debug"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/dsa/DSALib/Auxiliary/Calculator/Argument.cs b/dsa/DSALib/Auxiliary/Calculator/Argument.cs
new file mode 100644
index 0000000..e681377
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/Calculator/Argument.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace DSALib.Auxiliary.Calculator
+{
+ /// <summary>
+ /// Provides an ISolvable class to save numbers. The class handles Argument checking and conversion from string to int.
+ /// </summary>
+ public class Argument : ISolvable
+ {
+ private readonly int value;
+
+ public Argument(string value)
+ {
+ // check whether the value given is an empty string
+ if (string.IsNullOrEmpty(value))
+ throw new ArgumentException("Argument kann nicht mit einem leeren string instanziert werden. ",
+ nameof(value));
+
+ if (!int.TryParse(value, out var result))
+ throw new ArgumentException($"Kann {value} nicht in Integer konvertieren");
+
+ this.value = result;
+ }
+
+ public int Solve()
+ {
+ return value;
+ }
+
+ public override string ToString()
+ {
+ return value.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/Calculator/ISolvable.cs b/dsa/DSALib/Auxiliary/Calculator/ISolvable.cs
new file mode 100644
index 0000000..844e9b3
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/Calculator/ISolvable.cs
@@ -0,0 +1,10 @@
+namespace DSALib.Auxiliary.Calculator
+{
+ /// <summary>
+ /// Object has to be able to return an integer as it's value
+ /// </summary>
+ public interface ISolvable
+ {
+ int Solve();
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/Calculator/Operator.cs b/dsa/DSALib/Auxiliary/Calculator/Operator.cs
new file mode 100644
index 0000000..e6aeec6
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/Calculator/Operator.cs
@@ -0,0 +1,51 @@
+using System;
+using DSALibv.Auxiliary.Calculator;
+
+namespace DSALib.Auxiliary.Calculator
+{
+ /// <summary>
+ /// The Operator Class represents a binary operator with tow Arguments and an Operation type
+ /// </summary>
+ public class Operator : ISolvable
+ {
+ private readonly ISolvable arg1, arg2;
+
+ public Operator(ISolvable arg1, ISolvable arg2, Ops operatorType)
+ {
+ this.arg1 = arg1;
+ this.arg2 = arg2;
+ OperatorType = operatorType;
+ }
+
+ public Ops OperatorType { get; set; }
+
+ public int Solve()
+ {
+ int result;
+ switch (OperatorType)
+ {
+ case Ops.Dice:
+ result = Dice.Roll(arg1.Solve(), arg2.Solve());
+ break;
+ case Ops.Multiply:
+ result = arg1.Solve() * arg2.Solve();
+ break;
+ case Ops.Add:
+ result = arg1.Solve() + arg2.Solve();
+ break;
+ case Ops.Subtract:
+ result = arg1.Solve() - arg2.Solve();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return result;
+ }
+
+ public override string ToString()
+ {
+ return $"({arg1} {OperatorType} {arg2})";
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/Calculator/Ops.cs b/dsa/DSALib/Auxiliary/Calculator/Ops.cs
new file mode 100644
index 0000000..93046d0
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/Calculator/Ops.cs
@@ -0,0 +1,13 @@
+namespace DSALibv.Auxiliary.Calculator
+{
+ /// <summary>
+ /// The Different Operations, witch can be performed in execution-order
+ /// </summary>
+ public enum Ops
+ {
+ Dice,
+ Multiply,
+ Subtract,
+ Add
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/Calculator/StringSolver.cs b/dsa/DSALib/Auxiliary/Calculator/StringSolver.cs
new file mode 100644
index 0000000..45d6a54
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/Calculator/StringSolver.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using DSALibv.Auxiliary.Calculator;
+
+namespace DSALib.Auxiliary.Calculator
+{
+ /// <summary>
+ /// The StringSolver divides the calculation string into operations and SubStringSolvers if the string contains
+ /// parentheses
+ /// </summary>
+ public class StringSolver : ISolvable
+ {
+ private readonly List<object> arguments = new List<object>();
+ private readonly string input;
+
+ public StringSolver(string input)
+ {
+ this.input = input;
+ }
+
+ public int Solve()
+ {
+ var workInput = "0+" + input.Replace(" ", string.Empty).ToLower();
+ workInput = ExpandParentheses(workInput);
+
+ // Create a List of the different parts of the calculation, e.g.:{"0", "+", "(5+6)", "d", "3"}.
+ AtomizeOperations(workInput);
+
+ // traverse the List in order of Operation to Create the binary operation tree .
+ NestOperations();
+
+ // the List now contains only the top operation node, witch can be solved recursively,
+ return ((ISolvable) arguments.First()).Solve();
+ }
+
+ public override string ToString()
+ {
+ return "(0+" + input.Replace(" ", string.Empty).ToLower() + ")";
+ }
+
+ private static string
+ GetInner(ref string input) // extract the inner bracket an remove the section from the input string
+ {
+ var depth = 0;
+ for (var index = 1; index < input.Length; index++)
+ {
+ var c = input[index];
+ switch (c)
+ {
+ case '(':
+ depth++;
+ break;
+ case ')':
+ if (depth == 0)
+ {
+ var split = input.Substring(1, index - 1);
+ input = input.Substring(index + 1);
+ return split.Equals(string.Empty) ? "0" : split;
+ }
+ else
+ {
+ depth--;
+ }
+
+ break;
+ }
+ }
+
+ throw new ArgumentException("Invalid brace sequence");
+ }
+
+ private static Ops GetOps(char c)
+ {
+ switch (c)
+ {
+ case 'd':
+ case 'w':
+ return Ops.Dice;
+ case '+':
+ return Ops.Add;
+ case '-':
+ return Ops.Subtract;
+ case '*':
+ return Ops.Multiply;
+ default:
+ return Ops.Multiply;
+ }
+ }
+
+ private static string ExpandParentheses(string input) // insert * between Parentheses and digits
+ {
+ for (var i = 0; i < input.Length - 1; i++)
+ if (input[i + 1] == '(' && char.IsNumber(input[i]))
+ input = input.Insert(i + 1, "*");
+
+ for (var i = 1; i < input.Length; i++)
+ if (input[i - 1] == ')' && char.IsNumber(input[i]))
+ input = input.Insert(i, "*");
+
+ return input;
+ }
+
+ private void AtomizeOperations(string workInput)
+ {
+ for (var index = 0; index < workInput.Length; index++)
+ {
+ var c = workInput[index];
+
+ if (char.IsNumber(c))
+ {
+ // if char number, check if at end of string, else continue looping
+ if (index == workInput.Length - 1)
+ // if at end of string; add remaining number to arguments
+ arguments.Add(new Argument(workInput.Substring(0, index + 1)));
+
+ continue;
+ }
+
+ switch (c)
+ {
+ case ')':
+ throw new ArgumentException("Invalid brace sequence");
+ case '(':
+ arguments.Add(new StringSolver(GetInner(ref workInput)));
+ index = -1;
+ break;
+ default:
+ if (index > 0) arguments.Add(new Argument(workInput.Substring(0, index)));
+
+ arguments.Add(GetOps(c));
+ workInput = workInput.Remove(0, index + 1);
+ index = -1;
+ break;
+ }
+ }
+ }
+
+ private void NestOperations()
+ {
+ foreach (Ops currentOp in Enum.GetValues(typeof(Ops)))
+ // cycle through operators in operational order
+ for (var index = 0; index < arguments.Count; index++)
+ {
+ var arg = arguments[index];
+
+ if (arg.GetType() != typeof(Ops)) continue;
+
+ // arg is of type Ops
+ var op = (Ops) arg;
+
+ if (op != currentOp) continue;
+
+ // arg describes the current operation
+ HandleSpecialFormatting(ref index, op); // Deal with special needs...
+
+ // replace the previous current and next Element in the List with one Operation object
+ var temp = new Operator((ISolvable) arguments[index - 1], (ISolvable) arguments[index + 1], op);
+ arguments[index - 1] = temp;
+ arguments.RemoveRange(index, 2);
+ index--;
+ }
+ }
+
+ private void HandleSpecialFormatting(ref int index, Ops op)
+ {
+ var arg1 = arguments[index - 1];
+ if (arg1.GetType() == typeof(Ops))
+ {
+ if (op == Ops.Dice) arguments.Insert(index++, new Argument("1")); // w6 -> 1w6
+
+ if (op == Ops.Subtract) arguments.Insert(index++, new Argument("0")); // +-3 -> +0-3
+ }
+
+ var arg2 = arguments[index + 1]; // 3+-5 -> 3+(0-5)
+ if (arg2.GetType() == typeof(Ops))
+ {
+ arguments[index + 1] = new Operator(new Argument("0"), (ISolvable) arguments[index + 2], (Ops) arg2);
+ arguments.RemoveAt(index + 2);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/CommandInfo.cs b/dsa/DSALib/Auxiliary/CommandInfo.cs
new file mode 100644
index 0000000..d8e2188
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/CommandInfo.cs
@@ -0,0 +1,28 @@
+using System.Linq;
+
+namespace DSALib.Auxiliary
+{
+ public struct CommandInfo
+ {
+ public CommandInfo(string name, string brief, string[] description, string scope)
+ {
+ Name = name;
+ Scope = scope;
+ Brief = brief;
+ Description = description;
+ }
+
+ public string Name { get; }
+
+ public string Scope { get; }
+
+ public string Brief { get; }
+
+ public string[] Description { get; }
+
+ public string GetDescription()
+ {
+ return Description.Aggregate((s, c) => s + c);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/Dice.cs b/dsa/DSALib/Auxiliary/Dice.cs
new file mode 100644
index 0000000..0bfabeb
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/Dice.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Linq;
+
+namespace DSALib.Auxiliary
+{
+ public static class Dice // roll it!
+ {
+ private static readonly Random Rnd = new Random();
+
+ public static int Roll(int d = 20)
+ {
+ return Rnd.Next(d) + 1;
+ }
+
+ public static int Roll(string input)
+ {
+ var strings = input.ToLower().Split(new[] {'w', 'd'}, 2, StringSplitOptions.RemoveEmptyEntries).ToList();
+
+
+ if (strings.Count != 2)
+ throw new ArgumentException($"{input}: does not satisfy the format requirements( dice count (d|w) die size)");
+
+ var count = Convert.ToInt32(strings[0]);
+ var d = Convert.ToInt32(strings[0]);
+
+ return Roll(count, d);
+ }
+
+ public static int Roll(int count, int d)
+ {
+ if (d <= 0 || count <= 0) return 0;
+
+ var sum = 0;
+ for (var i = 0; i < Math.Abs(count); i++)
+ {
+ var roll = Roll(d);
+ sum += roll;
+ }
+
+ sum *= Math.Abs(count) / count;
+
+ return sum;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/Extensions.cs b/dsa/DSALib/Auxiliary/Extensions.cs
new file mode 100644
index 0000000..7d367a5
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/Extensions.cs
@@ -0,0 +1,25 @@
+namespace DSALib.Auxiliary
+{
+ public static class StringExtension
+ {
+ //This mehod extends string. It adds spaces until a fixed length is reached.
+ //If the original string is already longer, it is returner unmodified.
+ public static string AddSpaces(this string str, int length)
+ {
+ var temp = str;
+ for (var i = str.Length; i < length; i++) temp += " ";
+ return temp;
+ }
+
+
+ //This mehod extends string.
+ //It adds spaces at the HEAD of a string until a fixed length is reached.
+ //If the original string is already longer, it is returner unmodified.
+ public static string AddSpacesAtHead(this string str, int length)
+ {
+ var temp = "";
+ for (var i = str.Length; i < length; i++) temp += " ";
+ return temp + str;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/IDataObjectEnumerableExtension.cs b/dsa/DSALib/Auxiliary/IDataObjectEnumerableExtension.cs
new file mode 100644
index 0000000..b8a6067
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/IDataObjectEnumerableExtension.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using DSALib.Auxiliary;
+using DSALib.Models.Database;
+
+namespace DSACore.Auxiliary
+{
+ public static class DataObjectEnumerableExtension
+ {
+ public static IDataObject Match(this IEnumerable<IDataObject> dataObjects, string name)
+ {
+ return (dataObjects as IOrderedEnumerable<IDataObject> ?? throw new InvalidOperationException()).OrderBy(x => SpellCorrect.Compare(name,x.Name)).Last();
+ }
+
+ public static bool TryMatch(this IEnumerable<IDataObject> dataObjects,out IDataObject data, string name)
+ {
+ data = (dataObjects as IOrderedEnumerable<IDataObject> ?? throw new InvalidOperationException()).OrderBy(x => SpellCorrect.Compare(name,x.Name)).Last();
+
+ return SpellCorrect.IsMatch(name, data.Name);
+ }
+ }
+}
diff --git a/dsa/DSALib/Auxiliary/RandomMisc.cs b/dsa/DSALib/Auxiliary/RandomMisc.cs
new file mode 100644
index 0000000..2723930
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/RandomMisc.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace DSALib.Auxiliary
+{
+ public static class RandomMisc
+ {
+ private static readonly Random Rand = new Random();
+
+ // use: 4w6 +4
+ public static string Roll(string input)
+ {
+ var output = new StringBuilder();
+ var strings = input.Split('w', 'd').ToList();
+ var count = Convert.ToInt32(strings[0]);
+ strings = strings[1].Split(' ').ToList();
+ var d = Convert.ToInt32(strings[0]);
+
+ if (strings.Count > 0)
+ {
+ }
+
+ var sum = 0;
+ for (var i = 0; i < count; i++)
+ {
+ var roll = Dice.Roll(d);
+ sum += roll;
+ output.Append("[" + roll + "] ");
+ }
+
+ if (strings.Count > 1)
+ {
+ sum += Convert.ToInt32(strings[1]);
+ output.Append("sum: " + sum);
+ }
+
+ return output.ToString();
+ }
+
+ public static double Random(double stdDev = 1, double mean = 0)
+ {
+ var u1 = Rand.NextDouble(); // uniform(0,1) random doubles
+ var u2 = Rand.NextDouble();
+ var randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) *
+ Math.Sin(2.0 * Math.PI * u2); // random normal(0,1)
+ var randNormal =
+ mean + stdDev * randStdNormal; // random normal(mean,stdDev^2)
+ return randNormal;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/SpellCorrect.cs b/dsa/DSALib/Auxiliary/SpellCorrect.cs
new file mode 100644
index 0000000..79908c4
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/SpellCorrect.cs
@@ -0,0 +1,61 @@
+using System;
+
+namespace DSALib.Auxiliary
+{
+ public class SpellCorrect
+ {
+ public const double ErrorThreshold = 1 / 3.0;
+ private const double Match = 3.0;
+ private const double Gap = -1.5;
+ private const double Mismatch = -2.0;
+
+ public static double Compare(string s, string q)
+ {
+ s = s.ToLower();
+ q = q.ToLower();
+
+ int i, j;
+
+ var matrix = new double[s.Length + 1, q.Length + 1];
+ var max = 0.0;
+ matrix[0, 0] = 0.0;
+
+ for (i = 1; i < s.Length; i++)
+ matrix[i, 0] = i * Gap;
+
+ for (i = 1; i < q.Length; i++) matrix[0, i] = 0.0;
+
+
+ for (i = 1; i <= s.Length; i++)
+ for (j = 1; j <= q.Length; j++)
+ {
+ double decay = j / (s.Length * 1000.0);
+ var add = s[i - 1] == q[j - 1] ? Match - decay : Mismatch;
+ var score = matrix[i - 1, j - 1] + add;
+
+ if (score < matrix[i - 1, j] + Gap) score = matrix[i - 1, j] + Gap;
+
+ if (score < matrix[i, j - 1] + Gap) score = matrix[i, j - 1] + Gap;
+
+ if (i > 1 && j > 1)
+ if (s[i - 1] == q[j - 2] && s[i - 2] == q[j - 1])
+ {
+ add = 3 / 2.0 * Match - decay;
+ if (score < matrix[i - 2, j - 2] + add) score = matrix[i - 2, j - 2] + add;
+ }
+
+ if (max < score && i == s.Length) max = score;
+
+ matrix[i, j] = score;
+ }
+
+ return max;
+ }
+
+ public static bool IsMatch(string s1, string s2)
+ {
+ var score = Compare(s1, s2);
+ return score > ErrorThreshold * s1.Length;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/TalentEnumerableExtension.cs b/dsa/DSALib/Auxiliary/TalentEnumerableExtension.cs
new file mode 100644
index 0000000..6ec7fcc
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/TalentEnumerableExtension.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using DSACore.Auxiliary;
+using DSALib.DSA_Game.Characters;
+using DSALib.Models.Dsa;
+
+namespace DSALib.Auxiliary
+{
+ public static class TalentEnumerableExtension
+ {
+ public static string ProbenTest(this IEnumerable<Talent> List, Character c, string talentName, int erschwernis = 0)
+ {
+ var output = new StringBuilder();
+ var sc = new SpellCorrect();
+
+ if (!List.TryMatch(out var iTalent, talentName))
+ return $"{c.Name} kann nicht {talentName}...";
+
+ var talent = (Talent) iTalent;
+ var props = talent.GetEigenschaften(); // get the required properties
+ var tap = talent.Value; // get taw
+ var werte = props.Select(p => c.Eigenschaften[c.PropTable[p]]).ToArray();
+
+ output.AppendFormat(
+ "{0} würfelt: {1} \n{2} - {3} taw:{4} {5} \n",
+ c.Name,
+ talent.Name,
+ talent.Probe,
+ string.Join("/", werte),
+ talent.Value,
+ erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis);
+
+ output.Append(" ");
+ tap -= erschwernis;
+ var gesamtErschwernis = tap;
+ if (gesamtErschwernis < 0)
+ {
+ tap = 0;
+ for (var i = 0; i <= 2; i++)
+ {
+ // foreach property, dice and tap
+ var temp = Dice.Roll();
+ var eigenschaft = c.Eigenschaften[c.PropTable[props[i]]];
+
+ if (eigenschaft + gesamtErschwernis < temp) tap -= temp - (eigenschaft + gesamtErschwernis);
+
+ output.Append($"[{temp}]"); // add to string
+ }
+
+ if (tap >= 0) tap = 1;
+ }
+ else
+ {
+ for (var i = 0; i <= 2; i++)
+ {
+ // foreach property, dice and tap
+ var temp = Dice.Roll();
+ var eigenschaft = c.Eigenschaften[c.PropTable[props[i]]];
+
+ if (eigenschaft < temp) tap -= temp - eigenschaft;
+
+ output.Append($"[{temp}]"); // add to string
+ }
+ }
+
+ tap = tap == 0 ? 1 : tap;
+
+ output.AppendFormat(" tap: {0,2}", tap);
+
+ return output.ToString(); // return output
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Auxiliary/WeaponImporter.cs b/dsa/DSALib/Auxiliary/WeaponImporter.cs
new file mode 100644
index 0000000..61eb33e
--- /dev/null
+++ b/dsa/DSALib/Auxiliary/WeaponImporter.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using DSALib.FireBase;
+using DSALib.Models.Database.Dsa;
+
+namespace DSALib.Auxiliary
+{
+ public class WeaponImporter
+ {
+ private readonly List<RangedWeapon> Range = new List<RangedWeapon>();
+ private readonly List<MeleeWeapon> Weapons = new List<MeleeWeapon>();
+
+ public async Task DownloadWeapons()
+ {
+ var client = new HttpClient();
+
+
+ for (var i = 1; i <= 25; i++)
+ {
+ var responseString =
+ await client.GetStringAsync("http://diarium.eu/dsa4-forge/ajax/categoryChanged/" + i);
+
+ var talentRegex = new Regex(@"(?<=<option value="")([0-9]*)("">)(.*?)(?=<)");
+ //Regex idsRegex = new Regex(@"(?<=<option value=\"")([0-9]*)");
+
+
+ var talentMatch = talentRegex.Matches(responseString);
+ //var idMatch = idsRegex.Matches(responseString);
+
+ var lines = new List<string>();
+ var ids = new List<int>();
+
+ foreach (var matchGroup in talentMatch.ToList())
+ if (matchGroup.Success)
+ {
+ lines.Add(matchGroup.Groups[3].Value);
+ ids.Add(int.Parse(matchGroup.Groups[1].Value));
+ }
+
+
+ for (var j = 0; j < lines.Count; j++)
+ {
+ var talent = lines[j];
+
+ var values = await client.GetStringAsync("http://diarium.eu/dsa4-forge/ajax/calculate/" + i + "/" +
+ ids[j] + "/0/0/0/0/0/10/0/0/0");
+
+ values = Regex.Unescape(values.Replace(@"\t", ""));
+ // ... Use named group in regular expression.
+ var expression =
+ new Regex(
+ @"(((?<=(<td>))|(?<=(<td style=\""padding:2px\"">))).*?(?=<\/td>))|((?<=<span style=\""font-weight:bold;text-decoration:underline;\"">).*?(?=<\/span>))");
+
+ // ... See if we matched.
+ var matches = expression.Matches(values).Select(x => x.ToString()).ToList();
+
+ // ... Get group by name.
+ await AddMelee(i, talent, matches);
+ Console.Write(j + ",");
+ //await Task.Delay(TimeSpan.FromSeconds(5));
+ }
+
+ Console.WriteLine($"{i}: {ids.Count} => {Weapons.Count}");
+ //await Task.Delay(TimeSpan.FromSeconds(5));
+ }
+
+ Console.ReadLine();
+ }
+
+ private async Task AddMelee(int i, string talent, List<string> matches)
+ {
+ var name = talent.Replace(' ', '_').Replace(".", "");
+ if (!matches[1].Equals(string.Empty))
+ {
+ var temp = new MeleeWeapon(
+ name,
+ matches[1],
+ int.TryParse(matches[10], out var weight) ? weight : 0,
+ matches[0].Split(':', StringSplitOptions.RemoveEmptyEntries).First(),
+ matches[11])
+ {
+ INI = int.TryParse(matches[3], out var ini) ? ini : 0,
+ MW = matches[4],
+ TpKK = matches[2]
+ };
+
+ Weapons.Add(temp);
+ await Database.AddWeapon(temp);
+ }
+
+ /*if (i > 23)
+ {
+ var range = new RangedWeapon(
+ name,
+ matches[13],
+ int.TryParse(matches[10], out int weight) ? weight : 0,
+ matches[0].Split(':', StringSplitOptions.RemoveEmptyEntries).First(),
+ matches[11])
+ {
+ AtMod = int.TryParse(matches[10], out int atMod) ? atMod : 0,
+ KKMod = int.TryParse(matches[11], out int kkMod) ? kkMod : 0,
+ AtReach = matches[3],
+ TpReach = matches[4],
+ LoadTime = int.TryParse(matches[5], out int loadTime) ? loadTime : 0
+ };
+ Range.Add(range);
+ await Database.AddWeapon(range);
+ return;
+ }*/
+ if (i > 18)
+ {
+ var range = new RangedWeapon(
+ name,
+ matches[13].Replace(' ', '+'),
+ int.TryParse(matches[10], out var weight) ? weight : 0,
+ matches[0].Split(':', StringSplitOptions.RemoveEmptyEntries).First(),
+ matches[11])
+ {
+ AtMod = int.TryParse(matches[18], out var atMod) ? atMod : 0,
+ KKMod = int.TryParse(matches[17], out var kkMod) ? kkMod : 0,
+ AtReach = matches[14],
+ TpReach = matches[15],
+ LoadTime = int.TryParse(matches[18], out var loadTime) ? loadTime : 0
+ };
+ Range.Add(range);
+ await Database.AddWeapon(range);
+ }
+ }
+
+ private async Task AddRanged(int i, string talent, List<string> matches)
+ {
+ var name = talent.Replace(' ', '_').Replace(".", "");
+ if (!matches[1].Equals(string.Empty))
+ {
+ var temp = new MeleeWeapon(
+ name,
+ matches[1],
+ int.TryParse(matches[10], out var weight) ? weight : 0,
+ matches[0].Split(':', StringSplitOptions.RemoveEmptyEntries).First(),
+ matches[11])
+ {
+ INI = int.TryParse(matches[3], out var ini) ? ini : 0,
+ MW = matches[4],
+ TpKK = matches[2]
+ };
+
+ Weapons.Add(temp);
+ await Database.AddWeapon(temp);
+ }
+
+ if (i > 18)
+ {
+ var range = new RangedWeapon(
+ name,
+ matches[13].Replace(' ', '+'),
+ int.TryParse(matches[10], out var weight) ? weight : 0,
+ matches[0].Split(':', StringSplitOptions.RemoveEmptyEntries).First(),
+ matches[11])
+ {
+ AtMod = int.TryParse(matches[18], out var atMod) ? atMod : 0,
+ KKMod = int.TryParse(matches[17], out var kkMod) ? kkMod : 0,
+ AtReach = matches[14],
+ TpReach = matches[15],
+ LoadTime = int.TryParse(matches[18], out var loadTime) ? loadTime : 0
+ };
+ Range.Add(range);
+ await Database.AddWeapon(range);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Characters/Being.cs b/dsa/DSALib/Characters/Being.cs
new file mode 100644
index 0000000..27879a1
--- /dev/null
+++ b/dsa/DSALib/Characters/Being.cs
@@ -0,0 +1,17 @@
+namespace DSALib.Characters
+{
+ public class Being : Entity
+ {
+ public int Lebenspunkte_Basis { get; set; } = 30;
+
+ public int Lebenspunkte_Aktuell { get; set; } = 30;
+
+ public int Ausdauer_Basis { get; set; } = 30;
+
+ public int Ausdauer_Aktuell { get; set; } = 30;
+
+ public int Astralpunkte_Basis { get; set; } = 0;
+
+ public int Astralpunkte_Aktuell { get; set; } = 0;
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Characters/Critter.cs b/dsa/DSALib/Characters/Critter.cs
new file mode 100644
index 0000000..dcedccb
--- /dev/null
+++ b/dsa/DSALib/Characters/Critter.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using DiscoBot.DSA_Game.Characters;
+using DSALib.Models.Dsa;
+using Newtonsoft.Json;
+
+namespace DSALib.Characters
+{
+ public class Critter : Being, ICombatant
+ {
+ public CritterAttack lastAttack;
+
+ public Critter(int gw, int gs, int rs, int mr, int ko, int pa, string ini, List<CritterAttack> critterAttacks)
+ {
+ Gw = gw;
+ Gs = gs;
+ Rs = rs;
+ Mr = mr;
+ Ko = ko;
+ Pa = pa;
+ Ini = ini;
+ CritterAttacks = critterAttacks;
+ lastAttack = CritterAttacks[new Random().Next(critterAttacks.Count)];
+ }
+
+ public Critter()
+ {
+ }
+
+ public int Rs { get; set; }
+
+ public int Mr { get; set; }
+
+ public int Ko { get; set; }
+
+ public int Pa { get; set; }
+
+ public int Gs { get; set; }
+
+ public int Gw { get; set; }
+
+ public string Ini { get; set; }
+
+ public string Comment { get; set; }
+
+ public List<CritterAttack> CritterAttacks { get; set; }
+
+ public string Angriff(string talent, int erschwernis = 0)
+ {
+ throw new NotImplementedException();
+ }
+
+ public string Parade(string talent, int erschwernis = 0)
+ {
+ throw new NotImplementedException();
+ }
+
+ public static Critter Load(string path)
+ {
+ try
+ {
+ return
+ JsonConvert.DeserializeObject<Critter>(
+ File.ReadAllText(path)); // Deserialize Data and create Session Object
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Laden von Save-File {path} fehlgeschlagen." + e);
+ return null;
+ }
+ }
+
+ public void Save(string path = @"..\..\Critters\")
+ {
+ try
+ {
+ File.WriteAllText(path + Name + ".json",
+ JsonConvert.SerializeObject(this,
+ Formatting.Indented)); // Deserialize Data and create CommandInfo Struct
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Speichern von Save-File {path} fehlgeschlagen." + e);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Characters/Entity.cs b/dsa/DSALib/Characters/Entity.cs
new file mode 100644
index 0000000..a8a5e81
--- /dev/null
+++ b/dsa/DSALib/Characters/Entity.cs
@@ -0,0 +1,12 @@
+namespace DSALib.Characters
+{
+ public class Entity
+ {
+ public string Name { get; set; }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Characters/ICharacter.cs b/dsa/DSALib/Characters/ICharacter.cs
new file mode 100644
index 0000000..256fecd
--- /dev/null
+++ b/dsa/DSALib/Characters/ICharacter.cs
@@ -0,0 +1,15 @@
+using DiscoBot.DSA_Game.Characters;
+
+namespace DSALib.Characters
+{
+ public interface ICharacter : ICombatant
+ {
+ string TestTalent(string talent, int erschwernis = 0);
+
+ string TestEigenschaft(string eigenschaft, int erschwernis = 0);
+
+ string Fernkampf(string talent, int erschwernis = 0);
+
+ string TestZauber(string waffe, int erschwernis);
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Characters/ICombatant.cs b/dsa/DSALib/Characters/ICombatant.cs
new file mode 100644
index 0000000..a4ce601
--- /dev/null
+++ b/dsa/DSALib/Characters/ICombatant.cs
@@ -0,0 +1,20 @@
+namespace DiscoBot.DSA_Game.Characters
+{
+ public interface ICombatant
+ {
+ string Name { get; set; }
+
+ int Lebenspunkte_Basis { get; set; }
+ int Lebenspunkte_Aktuell { get; set; }
+
+ int Ausdauer_Basis { get; set; }
+ int Ausdauer_Aktuell { get; set; }
+
+ int Astralpunkte_Basis { get; set; }
+ int Astralpunkte_Aktuell { get; set; }
+
+ string Angriff(string talent, int erschwernis = 0);
+
+ string Parade(string talent, int erschwernis = 0);
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/CommandHandler.cs b/dsa/DSALib/Commands/CommandHandler.cs
new file mode 100644
index 0000000..e63d7b8
--- /dev/null
+++ b/dsa/DSALib/Commands/CommandHandler.cs
@@ -0,0 +1,135 @@
+using System;
+using DSALib.Auxiliary;
+using DSALib.Auxiliary.Calculator;
+using DSALib.Commands;
+using DSALib.DSA_Game;
+using DSALib.Models.Network;
+
+namespace DSALib.Commands
+{
+ public class CommandHandler
+ {
+ public static CommandResponse ExecuteCommand(Command cmd)
+ {
+ var res = string.Empty;
+ var type = ResponseType.Broadcast;
+ switch (cmd.CmdIdentifier.ToLower())
+ {
+ case "addChar":
+ res = FileHandler.AddChar(cmd.CharId, cmd.CmdText);
+ break;
+ case "held":
+ case "wert":
+ case "werte":
+ case "char":
+ res = HeldList.ListAsync(cmd.CharId, cmd.CmdText);
+ break;
+ case "help":
+ case "man":
+ case "hilfe":
+ case "h":
+ res = Help.ShowHelp(cmd.CmdTexts.ToArray());
+ type = ResponseType.Caller;
+ break;
+ case "le":
+ case "leben":
+ case "lp":
+ res = LE.LEAsync(cmd.CharId, cmd.CmdText);
+ break;
+ case "ae":
+ case "astral":
+ case "asp":
+ res = AE.AEAsync(cmd.CharId, cmd.CmdText);
+ break;
+ case "list":
+ res = List.ListAsync(cmd.CmdText);
+ type = ResponseType.Caller;
+ break;
+ case "r":
+ case "roll":
+ res = RandomMisc.Roll(cmd.CmdText + " " + cmd.Cmdmodifier);
+ break;
+ case "solve":
+ res = new StringSolver(cmd.CmdText + cmd.Cmdmodifier).Solve().ToString();
+ break;
+ case "npc":
+ res = NpcCommands.CreateNpc(cmd.CharId, cmd.CmdTexts, cmd.Cmdmodifier);
+ break;
+ }
+
+ if (res == string.Empty) res = Proben(cmd.Name, cmd.CmdIdentifier, cmd.CmdText, cmd.Cmdmodifier);
+ if (res != string.Empty) return new CommandResponse(res, type);
+ return new CommandResponse($"Kommando {cmd.CmdIdentifier} nicht gefunden", ResponseType.Error);
+ }
+
+ private static string Proben(string name, string command, string waffe, int erschwernis = 0)
+ {
+ var res = string.Empty;
+ switch (command.ToLower())
+ {
+ case "f":
+ case "fern":
+ case "fernkampf":
+ res = CheckCommand(name, CommandTypes.Fernkampf, waffe, erschwernis);
+ break;
+ case "t":
+ case "ta":
+ case "talent":
+ case "talente":
+ res = CheckCommand(name, CommandTypes.Talent, waffe, erschwernis);
+ break;
+ case "e":
+ case "ei":
+ case "eigenschaft":
+ res = CheckCommand(name, CommandTypes.Eigenschaft, waffe, erschwernis);
+ break;
+ case "z":
+ case "za":
+ case "zauber":
+ case "magie":
+ case "m":
+ res = CheckCommand(name, CommandTypes.Talent, waffe, erschwernis);
+ break;
+ case "a":
+ case "at":
+ case "an":
+ case "angrif":
+ case "angriff":
+ res = CheckCommand(name, CommandTypes.Angriff, waffe, erschwernis);
+ break;
+ case "p":
+ case "pa":
+ case "parade":
+ res = CheckCommand(name, CommandTypes.Parade, waffe, erschwernis);
+ break;
+ }
+
+ return res;
+ }
+
+ private static string CheckCommand(string name, CommandTypes command, string waffe, int erschwernis = 0)
+ {
+ var chr = Dsa.GetCharacter(0);
+
+ switch (command)
+ {
+ case CommandTypes.Talent:
+ return chr.TestTalent(waffe, erschwernis);
+ case CommandTypes.Eigenschaft:
+ return chr.TestEigenschaft(waffe, erschwernis);
+ case CommandTypes.Angriff:
+ return chr.Angriff(waffe, erschwernis);
+ case CommandTypes.Parade:
+ return chr.Parade(waffe, erschwernis);
+ case CommandTypes.Fernkampf:
+ return chr.Fernkampf(waffe, erschwernis);
+ case CommandTypes.Zauber:
+ return chr.TestZauber(waffe, erschwernis);
+ }
+
+ return $"{name} verwendet {waffe}";
+
+ throw new NotImplementedException("access char by id ore name and group id");
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/CommandTypes.cs b/dsa/DSALib/Commands/CommandTypes.cs
new file mode 100644
index 0000000..62b8b0f
--- /dev/null
+++ b/dsa/DSALib/Commands/CommandTypes.cs
@@ -0,0 +1,13 @@
+namespace DSALib.Commands
+{
+ public enum CommandTypes
+ {
+ Talent,
+ Eigenschaft,
+ Angriff,
+ Parade,
+ Fernkampf,
+ KeinChar,
+ Zauber
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/FileHandler.cs b/dsa/DSALib/Commands/FileHandler.cs
new file mode 100644
index 0000000..d117040
--- /dev/null
+++ b/dsa/DSALib/Commands/FileHandler.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Linq;
+using System.Net;
+using DSALib.DSA_Game;
+using DSALib.DSA_Game.Characters;
+using DSALib;
+using DSALib.Models.Dsa;
+
+namespace DSALib.Commands
+{
+ public class FileHandler
+ {
+ public static string AddChar(ulong id, string url)
+ {
+ if (url == string.Empty) throw new ArgumentException("Es wurde keine Datei angehängt");
+
+
+ if (!url.EndsWith(".xml")) throw new ArgumentException("Es wurde kein xml Held mitgeschickt");
+
+ using (var client = new WebClient())
+ {
+ client.DownloadFile(url, "helden\\" + url.Split("/").Last());
+ }
+
+ Dsa.Chars.Add(new Character("helden\\" + url.Split("/").Last()));
+ (Dsa.Chars.Last() as Character)?.Talente.Select(x => new Talent(x.Name, x.Probe, 0))
+ .Where(c => !Dsa.Talente.Exists(v => v.Name.Equals(c.Name))).ToList().ForEach(v => Dsa.Talente.Add(v));
+
+ return $"{url.Split("/").Last()} wurde erfolgreich gespeichert";
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/Gm.cs b/dsa/DSALib/Commands/Gm.cs
new file mode 100644
index 0000000..74fd673
--- /dev/null
+++ b/dsa/DSALib/Commands/Gm.cs
@@ -0,0 +1,176 @@
+namespace DSALib.Commands
+{
+ /*public class Iam
+ {
+
+ [Command("Iam"), Summary("Wechselt den Character")]
+ [Alias("iam", "I_am", "i_am", "IchBin", "ichbin", "Ichbin", "Ich_bin", "ich_bin", "Ich", "ich", "I", "i")]
+ public Task Change_Character(params string[] givenName) // use fancy parameters
+ {
+ string res;
+ string name;
+
+ if (givenName.Length == 0 || (givenName.Length == 1 && (givenName[0].ToLower().Equals("bin") || givenName[0].ToLower().Equals("am"))))
+ {
+ res = " \nDu bist " + Dsa.Session.Relation[this.Context.User.Username] + "!\n \n";
+
+ return this.ReplyAsync("```xl\n" + res + "\n```");
+ }
+
+ if (givenName.Length > 1 && (givenName[0].ToLower().Equals("bin") || givenName[0].ToLower().Equals("am")) )
+ {
+ name = givenName.Skip(1).Aggregate((s, c) => s + c); // (Skip(1)) don't use the first element; Aggregate: take source s and do operation s = s+c for all elements
+ }
+ else
+ {
+ name = givenName.Aggregate((s, c) => s + c);
+ }
+
+ if (name.ToLower().Equals("man") || name.ToLower().Equals("help"))
+ {
+ return this.ReplyAsync("```xl\n" + Help.Get_Specific_Help("ich bin") + "\n```");
+
+ }
+
+ var character = Dsa.Chars.OrderBy(x => SpellCorrect.CompareEasy(name, x.Name)).First(); // usage of compareEasy
+
+ Dsa.Session.Relation[this.Context.User.Username] = character.Name;
+ res = " \nWillkommen " + character.Name + "!\n \n";
+
+
+ return this.ReplyAsync("```xl\n" + res + "\n```");
+ }
+ }
+
+
+ public class Gm : ModuleBase
+ {
+ public static string CheckCommand(string name, CommandTypes command, string waffe, int erschwernis = 0)
+ {
+ var comp = new SpellCorrect();
+ var chr = Dsa.Chars.OrderBy(x => comp.Compare(name, x.Name)).First();
+
+ switch (command)
+ {
+ case CommandTypes.Talent:
+ return chr.TestTalent(waffe, erschwernis);
+ case CommandTypes.Eigenschaft:
+ return chr.TestEigenschaft(waffe, erschwernis);
+ case CommandTypes.Angriff:
+ return chr.Angriff(waffe, erschwernis);
+ case CommandTypes.Parade:
+ return chr.Parade(waffe, erschwernis);
+ case CommandTypes.Fernkampf:
+ return chr.Fernkampf(waffe, erschwernis);
+ case CommandTypes.Zauber:
+ return chr.TestZauber(waffe, erschwernis);
+ }
+
+ return $"{name} verwendet {waffe}";
+ }
+
+ [Command("gm"), Summary("Führt eine probe aus")]
+ [Alias("GM", "as", "As", "als")]
+ public async Task ProbeAsync([Summary("Fernkampfwaffe")] string name, string command, string cmdText = "", int modifier = 0)
+ {
+ if (!Permissions.Test(this.Context, "Meister")) return;
+
+ command = command.ToLower();
+
+ string res;
+ string temp = string.Empty;
+ ICharacter cha = Dsa.Chars.OrderBy(x =>
+ SpellCorrect.CompareEasy(name, x.Name)).First();
+ switch (command)
+ {
+ case "le":
+ case "leben":
+ case "lp":
+ LE le = new LE();
+ temp = string.Empty;
+
+ if (modifier != 0)
+ {
+ temp = modifier.ToString();
+ }
+
+ res = cha.get_LE_Text(cmdText.Trim() + temp);
+
+ break;
+ case "ae":
+ case "asp":
+ case "astral":
+ AE ae = new AE();
+ temp = string.Empty;
+
+ if (modifier != 0)
+ {
+ temp = modifier.ToString();
+ }
+
+ res = cha.get_AE_Text(cmdText.Trim() + temp);
+
+ break;
+ default:
+ res = this.Test(name, command, cmdText, modifier);
+ break;
+ }
+
+
+ if (Dsa.GeneralContext != null && Dsa.GeneralContext.Channel.Id != this.Context.Channel.Id)
+ {
+ await Dsa.GeneralContext.Channel.SendMessageAsync("```xl\n" + res + "\n```");
+ }
+
+ await this.ReplyAsync("```xl\n" + res + "\n```");
+ }
+
+ private string Test(string name, string command, string waffe, int erschwernis = 0)
+ {
+ string res;
+ switch (command.ToLower())
+ {
+ case "f":
+ case "fern":
+ case "fernkampf":
+ res = CheckCommand(name, CommandTypes.Fernkampf, waffe, erschwernis);
+ break;
+ case "t":
+ case "ta":
+ case "talent":
+ case "talente":
+ res = CheckCommand(name, CommandTypes.Talent, waffe, erschwernis);
+ break;
+ case "e":
+ case "ei":
+ case "eigenschaft":
+ res = CheckCommand(name, CommandTypes.Eigenschaft, waffe, erschwernis);
+ break;
+ case "z":
+ case "za":
+ case "zauber":
+ case "magie":
+ case "m":
+ res = CheckCommand(name, CommandTypes.Talent, waffe, erschwernis);
+ break;
+ case "a":
+ case "at":
+ case "an":
+ case "angrif":
+ case "angriff":
+ res = CheckCommand(name, CommandTypes.Angriff, waffe, erschwernis);
+ break;
+ case "p":
+ case "pa":
+ case "parade":
+ res = CheckCommand(name, CommandTypes.Parade, waffe, erschwernis);
+ break;
+ default:
+ res = $"Kommando {command} nicht gefunden";
+ break;
+ }
+
+ return res;
+ }
+ }*/
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/HeldList.cs b/dsa/DSALib/Commands/HeldList.cs
new file mode 100644
index 0000000..ef29a14
--- /dev/null
+++ b/dsa/DSALib/Commands/HeldList.cs
@@ -0,0 +1,174 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using DSALib.Auxiliary;
+using DSALib.DSA_Game;
+using DSALib.DSA_Game.Characters;
+
+namespace DSALib.Commands
+{
+ public class HeldList
+ {
+ public static string ListAsync(ulong id, params string[] prop_list)
+ {
+ var res = new List<string>();
+
+ var character = Dsa.GetCharacter(id) as Character;
+
+ var first_column_width = 18;
+
+
+ if (prop_list.Length == 0 || prop_list[0].ToLower().StartsWith("all") ||
+ prop_list[0].ToLower().StartsWith("brief") || prop_list[0].ToLower().StartsWith("zettel"))
+ {
+ res.Add(character.Name + ":\n");
+ //Eigenschaften
+ res.AddRange(
+ character.Eigenschaften.Take(9).Select(s => s.Key + ":\t " + s.Value));
+ res.Add("");
+ //LE/AE
+ res.Add("LE:\t " + character.Lebenspunkte_Aktuell + "/" + character.Lebenspunkte_Basis);
+ if (character.Astralpunkte_Basis > 0)
+ res.Add("AE:\t " + character.Astralpunkte_Aktuell + "/" + character.Astralpunkte_Basis);
+ res.Add("");
+ //Kampfwerte
+ res.Add("".AddSpaces(first_column_width) + " AT/PA");
+ res.AddRange(
+ character.Kampftalente.Select(s =>
+ s.Name.AddSpaces(first_column_width) + " " + s.At.ToString().AddSpacesAtHead(2) + "/" +
+ s.Pa.ToString().AddSpacesAtHead(2)));
+ res.Add("");
+ //Fernkampf
+ res.Add("".AddSpaces(first_column_width) + " FK");
+ res.AddRange(
+ character.Talente.Where(x => x.IstFernkampftalent()).Select(s =>
+ s.Name.AddSpaces(first_column_width) + " " +
+ (character.Eigenschaften["fk"] + s.Value).ToString().AddSpacesAtHead(2)));
+ res.Add("");
+ //Vorteile
+ res.AddRange(
+ character.Vorteile
+ .Select(s => s.Name + "\t " + s.Value));
+ res.Add("");
+ //Talente
+ res.AddRange(
+ character.Talente.Select(s =>
+ (s.Name.AddSpaces(first_column_width) + " " + s.Value).AddSpaces(first_column_width + 5) + " " +
+ s.Probe));
+ res.Add("");
+ //evtl Zauber
+ if (character.Zauber.Count > 0)
+ res.AddRange(
+ character.Zauber.Select(s =>
+ (s.Name.AddSpaces(first_column_width) + " " + s.Value).AddSpaces(first_column_width + 5) +
+ " " + s.Probe));
+ }
+ else if (prop_list[0].ToLower().StartsWith("man") || prop_list[0].ToLower().StartsWith("help") ||
+ prop_list[0].ToLower().StartsWith("hilf"))
+ {
+ return "```xl\n" + Help.Get_Specific_Help("Held") + "\n```";
+ }
+ else
+ {
+ res.Add(character.Name + ":\n");
+
+ foreach (var prop in prop_list)
+ {
+ switch (prop.ToLower())
+ {
+ case "e":
+ case "eig":
+ case "eigenschaft":
+ case "eigenschaften":
+ res.AddRange(
+ character.Eigenschaften.Take(8).Select(s => s.Key + ":\t " + s.Value));
+ break;
+ case "stat":
+ case "stats":
+ res.AddRange(
+ character.Eigenschaften.Take(9).Select(s => s.Key + ":\t " + s.Value));
+ res.Add("");
+ res.Add("LE:\t " + character.Lebenspunkte_Aktuell + "/" + character.Lebenspunkte_Basis);
+ if (character.Astralpunkte_Basis > 0)
+ res.Add("AE:\t " + character.Astralpunkte_Aktuell + "/" + character.Astralpunkte_Basis);
+ break;
+ case "le":
+ res.Add("LE:\t " + character.Lebenspunkte_Aktuell + "/" + character.Lebenspunkte_Basis);
+ break;
+ case "ae":
+ res.Add("AE:\t " + character.Astralpunkte_Aktuell + "/" + character.Astralpunkte_Basis);
+ break;
+ case "t":
+ case "ta":
+ case "talent":
+ case "talente":
+ res.AddRange(
+ character.Talente.Select(s =>
+ (s.Name.AddSpaces(first_column_width) + " " + s.Value).AddSpaces(
+ first_column_width + 5) + " " + s.Probe));
+ break;
+ case "zauber":
+ case "z":
+ res.AddRange(
+ character.Zauber.Select(s =>
+ (s.Name.AddSpaces(first_column_width) + " " + s.Value).AddSpaces(
+ first_column_width + 5) + " " + s.Probe));
+ break;
+ case "w":
+ case "waffe":
+ case "waffen":
+ case "kampf":
+ case "kampfwert":
+ case "kampfwerte":
+ res.Add("".AddSpaces(first_column_width) + " AT/PA");
+ res.AddRange(
+ character.Kampftalente.Select(s =>
+ s.Name.AddSpaces(first_column_width) + " " + s.At.ToString().AddSpacesAtHead(2) +
+ "/" + s.Pa.ToString().AddSpacesAtHead(2)));
+ break;
+ case "f":
+ case "fern":
+ res.Add("".AddSpaces(first_column_width) + " FK");
+ res.AddRange(
+ character.Talente.Where(x => x.IstFernkampftalent()).Select(s =>
+ s.Name.AddSpaces(first_column_width) + " " +
+ (character.Eigenschaften["fk"] + s.Value).ToString().AddSpacesAtHead(2)));
+ break;
+ case "v":
+ case "vt":
+ case "vor":
+ case "vorteil":
+ case "vorteile":
+ case "nachteil":
+ case "nachteile":
+ res.AddRange(
+ character.Vorteile
+ .Select(s => s.Name + "\t " + s.Value));
+ break;
+
+ default:
+ res.Add($"Kommando {prop} nicht gefunden");
+ break;
+ }
+
+ res.Add("");
+ }
+ }
+
+
+ var sb = new StringBuilder();
+ foreach (var re in res) sb.AppendLine(re);
+
+ return sb.ToString();
+ /*
+ if (persist == 1)
+ {
+ await this.ReplyAsync(res, true);
+ }
+ else
+ {
+ await this.ReplyAsync(res, TimeSpan.FromSeconds(90));
+ }*/
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/Help.cs b/dsa/DSALib/Commands/Help.cs
new file mode 100644
index 0000000..4506821
--- /dev/null
+++ b/dsa/DSALib/Commands/Help.cs
@@ -0,0 +1,54 @@
+using System.Linq;
+using DSALib.Auxiliary;
+using DSALib.DSA_Game.Save;
+
+namespace DSALib.Commands
+{
+ public class Help
+ {
+ //public static List<CommandInfo> Commands { get; } = new List<CommandInfo>();
+
+
+ public static string Get_Specific_Help(string command)
+ {
+ // return command specific help
+ var com = Properties.CommandInfos
+ .OrderBy(x => SpellCorrect.Compare(x.Name, command.ToLower())).Last(); // get best fit command
+ return com.GetDescription();
+ }
+
+ public static string Get_Generic_Help()
+ {
+ var res = "";
+ foreach (var com in Properties.CommandInfos)
+ {
+ var first_column_width = 8;
+ res += ("!" + com.Name + ": ").AddSpaces(first_column_width) + com.Brief;
+
+ if (com.Description.Length > 1)
+ res += "\n" + "".AddSpaces(first_column_width) + "(\"!man " + com.Name +
+ "\" gibt genauere Informationen)";
+
+ res += "\n\n";
+ }
+
+ return res;
+ }
+
+ public static string ShowHelp(params string[] commandList)
+ {
+ var command = "";
+ if (commandList.Length > 0) command = commandList.Aggregate((s, c) => s + " " + c);
+
+ if (command.Equals(string.Empty)) // return generic Help
+ {
+ var res = Get_Generic_Help();
+
+ return res;
+ }
+
+
+ return Get_Specific_Help(command);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/LebenUndAstral.cs b/dsa/DSALib/Commands/LebenUndAstral.cs
new file mode 100644
index 0000000..ac11c91
--- /dev/null
+++ b/dsa/DSALib/Commands/LebenUndAstral.cs
@@ -0,0 +1,172 @@
+using System;
+using DSALib.Auxiliary;
+using DSALib.DSA_Game;
+using DSALib.Characters;
+
+namespace DSALib.Commands
+{
+ public class LE
+ {
+ public static string LEAsync(ulong id, string modifier)
+ {
+ //This is the string that will be printed
+ var res = "";
+
+
+ //Get the actual text
+ res += Dsa.GetCharacter(id).get_LE_Text(modifier);
+
+
+ return res;
+ }
+ }
+
+ public class AE
+ {
+ public static string AEAsync(ulong id, string modifier)
+ {
+ //This is the string that will be printed
+ var res = "";
+
+
+ //Get the actual text
+ res += Dsa.GetCharacter(id).get_AE_Text(modifier);
+
+ return res;
+ }
+ }
+
+ public static class StatExtension
+ {
+ public static string get_LE_Text(this ICharacter c, string prop)
+ {
+ var res = "";
+ var comp = new SpellCorrect();
+ var character = c;
+
+ res += character.Name + ":\n";
+
+ //If there is actual input we process it
+ if (prop.Length > 0)
+ {
+ res += "LE: ";
+ res += character.Lebenspunkte_Aktuell + "/" + character.Lebenspunkte_Basis + " -> ";
+
+ // Apply a change to current value
+ if (prop.StartsWith("+") || prop.StartsWith("-"))
+ {
+ //Allow overflowing the max
+ if (prop.StartsWith("++"))
+ {
+ character.Lebenspunkte_Aktuell = character.Lebenspunkte_Aktuell +
+ Convert.ToInt32(prop.Substring(1, prop.Length - 1));
+ }
+ else
+ {
+ var temp = character.Lebenspunkte_Aktuell + Convert.ToInt32(prop) -
+ character.Lebenspunkte_Basis;
+ //Stop from overflow overflow
+ if (temp > 0 && prop.StartsWith("+"))
+ {
+ character.Lebenspunkte_Aktuell =
+ character.Lebenspunkte_Basis > character.Lebenspunkte_Aktuell
+ ? character.Lebenspunkte_Basis
+ : character.Lebenspunkte_Aktuell;
+ res += " Maximale Lebenspunkte sind erreicht ";
+ }
+ //Simply apply change
+ else
+ {
+ character.Lebenspunkte_Aktuell = character.Lebenspunkte_Aktuell + Convert.ToInt32(prop);
+ }
+ }
+
+ res += character.Lebenspunkte_Aktuell + "/" + character.Lebenspunkte_Basis;
+ }
+ else
+ {
+ // Set to new value regardless of original
+ character.Lebenspunkte_Aktuell = Convert.ToInt32(prop);
+
+ res += character.Lebenspunkte_Aktuell + "/" + character.Lebenspunkte_Basis;
+ }
+ }
+ //If no value is passed, the curent value is displayed
+ else
+ {
+ res += "LE: " + character.Lebenspunkte_Aktuell + "/" + character.Lebenspunkte_Basis;
+ }
+
+ return res;
+ }
+
+ public static string get_AE_Text(this ICharacter c, string prop)
+ {
+ var res = "";
+ var comp = new SpellCorrect();
+ var character = c;
+
+ res += character.Name + ":\n";
+
+ //If there is actual input we process it
+ if (prop.Length > 0)
+ {
+ res += "AE: ";
+ res += character.Astralpunkte_Aktuell + "/" + character.Astralpunkte_Basis + " -> ";
+
+ // Apply a change to current value
+ if (prop.StartsWith("+") || prop.StartsWith("-"))
+ {
+ //Allow overflowing the max
+ if (prop.StartsWith("++"))
+ {
+ character.Astralpunkte_Aktuell = character.Astralpunkte_Aktuell +
+ Convert.ToInt32(prop.Substring(1, prop.Length - 1));
+ }
+ else
+ {
+ var temp = character.Astralpunkte_Aktuell + Convert.ToInt32(prop) -
+ character.Astralpunkte_Basis;
+ //Stop from overflow overflow
+ if (temp > 0 && prop.StartsWith("+"))
+ {
+ character.Astralpunkte_Aktuell =
+ character.Astralpunkte_Basis > character.Astralpunkte_Aktuell
+ ? character.Astralpunkte_Basis
+ : character.Astralpunkte_Aktuell;
+ res += " Maximale Astralpunkte sind erreicht ";
+ }
+ //Simply apply change
+ else
+ {
+ character.Astralpunkte_Aktuell = character.Astralpunkte_Aktuell + Convert.ToInt32(prop);
+ }
+ }
+
+ if (character.Astralpunkte_Aktuell < 0)
+ {
+ res += "Nicht genügend Astralpunkte! ";
+ character.Astralpunkte_Aktuell = 0;
+ }
+
+ res += character.Astralpunkte_Aktuell + "/" + character.Astralpunkte_Basis;
+ }
+ //Set to new value regardless of original
+ else
+ {
+ character.Astralpunkte_Aktuell = Convert.ToInt32(prop);
+
+ res += character.Astralpunkte_Aktuell + "/" + character.Astralpunkte_Basis;
+ }
+ }
+ //If no value is passed, the curent value is displayed
+ else
+ {
+ res += "AE: " + character.Astralpunkte_Aktuell + "/" + character.Astralpunkte_Basis;
+ }
+
+
+ return res;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/List.cs b/dsa/DSALib/Commands/List.cs
new file mode 100644
index 0000000..1213f85
--- /dev/null
+++ b/dsa/DSALib/Commands/List.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using DSALib.DSA_Game;
+
+namespace DSALib.Commands
+{
+ public class List
+ {
+ public static string ListAsync(string prop)
+ {
+ var res = new List<string>();
+
+ //int persist = 0;
+
+ switch (prop.ToLower())
+ {
+ case "man":
+ case "help":
+ return Help.Get_Specific_Help("List");
+ // break;
+ case "chars":
+ res.AddRange(Dsa.Chars.Select(x => x.Name));
+ break;
+ case "commands":
+ // res.AddRange(Help.Commands.Select(x => x.Name));
+ res.Add(Help.Get_Generic_Help());
+ break;
+
+ default:
+ res.Add($"Kommando {prop} nicht gefunden");
+ break;
+ }
+
+
+ return res.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/MiscCommands.cs b/dsa/DSALib/Commands/MiscCommands.cs
new file mode 100644
index 0000000..69b2ffe
--- /dev/null
+++ b/dsa/DSALib/Commands/MiscCommands.cs
@@ -0,0 +1,219 @@
+namespace DSALib.Commands
+{
+ public class MiscCommands
+ {
+ /*[Command("r"), Summary("Würfelt ")]
+ [Alias("R", "Roll", "roll", "Würfle")]
+ public Task RollAsync([Remainder, Summary("Weapon")] string roll)
+ {
+ //return this.ReplyAsync("```xl\n" + new Auxiliary.Calculator.StringSolver(roll).Solve() + "\n```");
+ return this.ReplyAsync("```xl\n" + RandomMisc.Roll(roll) + "\n```");
+ }
+
+ [Command("rd"), Summary("Würfel Dennis ")]
+ public Task RollDennisAsync([Remainder, Summary("Weapon")] string roll)
+ {
+ return this.ReplyAsync("```xl\n" + new DSALib.Auxiliary.Calculator.StringSolver(roll).Solve() + "\n```");
+ }*/
+/*
+
+ [Command("general"), Summary("Set General ")]
+ public Task SetGeneralAsync([Remainder, Summary("Set General")] int i = 0)
+ {
+ Dsa.GeneralContext = this.Context;
+ return this.Context.Channel.SendMessageAsync($"```xl\n Der Dachs hat in '{this.Context.Channel.Name}' ein Zuhause gefunden. Gm Nachrichten werden nun auch in diesem Channel gepostet. \n```");
+ }
+
+ [Command("say"), Summary("Echos a message.")]
+ [Alias("s")]
+ public Task SayAsync([Remainder, Summary("The text to echo")] string echo)
+ {
+ return this.ReplyAsync(echo);
+ }
+
+ [Command("liebe"), Summary("Echos a message.")]
+ [Alias("Liebe", "<3", "love")]
+ public async Task LoveAsync()
+ {
+ Random rand = new Random();
+ var user = HostingApplication.Context.Channel.GetUsersAsync().ToList().Result.ToList().First().Where(x=>x.Status!= UserStatus.Offline).OrderBy(x => rand.Next()).First();
+ await this.ReplyAsync(":heart: :heart: :heart: Verteilt die Liebe! :heart: :heart: :heart: \n Besondere Liebe geht an " + user.Username);
+ //await this.ReplyAsync("!liebe");
+ }
+
+ [Command("maul"), Summary("Echos a message.")]
+ public Task MaulAsync()
+ {
+ return this.ReplyAsync("Maul...? Du meintest doch sicher Maulwürfe oder? \n:heart: :heart: :heart: \nGanz viel Liebe für Maulwürfe !\n:heart: :heart: :heart:");
+
+ }
+
+ [Command("report"), Summary("Report a Tweet")]
+ public async Task ReportAsync([Remainder, Summary("Link")] string link)
+ {
+ var content = new System.Net.Http.StringContent(link);
+
+ using (HttpClient client = new HttpClient())
+ {
+ var response = await client.PostAsync("http://www.example.com/recepticle.aspx", content);
+ }
+
+ await this.ReplyAsync($"Dein report wurde hinzugefügt");
+ }
+
+ [Command("match"), Summary("Tinder.")]
+ [Alias("mach","pass", "passt")]
+ public Task TinderAsync(string s1, string s2)
+ {
+
+ var sc = new SpellCorrect();
+ var rand = new System.Random((s1+s2).GetHashCode());
+
+ var wert = Math.Log10(Math.Floor(1000.0 * (SpellCorrect.CompareExact(s1, s2) + rand.NextDouble() * 10.0)) / 1000.0);
+ wert = ((wert * 100.0) < 100.0 ? wert * 100.0 : 100.0 - wert);
+ wert = wert < 0 ? -wert : wert;
+ return this.ReplyAsync($"Ihr passt zu {Math.Floor(100.0 * wert )/ 100.0}% zusammen");
+
+ }
+
+ [Command("reddit"), Summary("Reddit.")]
+ public Task RedditAsync()
+ {
+ return this.ReplyAsync($"Ein Archiv der Vergangenen Aktionen findet man hier: https://www.reddit.com/r/ReconquistaInternet/");
+
+ }
+
+ [Command("compare"), Summary("Echos a message.")]
+ public async Task KickAsync()
+ {
+ //await this.Context.Guild.DownloadUsersAsync();
+ var users = HostingApplication.Context.Guild.GetUsersAsync(CacheMode.AllowDownload);
+ var test = File.ReadAllLines("RG.txt");
+ await users;
+ var us = users.Result.Select(x => x.Username);
+
+ var lines = test.Where(x => !x.Equals(string.Empty)).ToList();
+
+
+ var sc = new SpellCorrect();
+
+ var res = new List<string>();
+
+ foreach (string line in lines)
+ {
+ var best = us.OrderBy(user => sc.Compare(user, line)).First();
+
+ double fit = sc.Compare(best, line);
+
+ if (fit < SpellCorrect.ErrorThreshold - 20000)
+ {
+ if (fit.Equals(0))
+ {
+ res.Add($"@\t{best} !!! => {line}");
+ }
+ else
+ {
+ res.Add($"-\t{best} hat Ähnlichkeit mit: {line}");
+ }
+ }
+ }
+
+ var sb = new StringBuilder();
+ foreach (string re in res)
+ {
+ if (sb.Length + re.Length > 1798)
+ {
+ await this.ReplyTimedAsync(sb.ToString(), TimeSpan.FromSeconds(90));
+ sb.Clear();
+ }
+
+ sb.AppendLine(re);
+ }
+
+ if(Permissions.Check(this.Context, new []{"Admin", "Mod"}))
+ await this.ReplyTimedAsync(sb.ToString(), TimeSpan.FromSeconds(90));
+
+ //await this.ReplyAsync($"{count} Duplikate gefunden");
+
+ }
+
+
+ [Command("clear"), Summary("Cleans up messages.")]
+ public async Task DeleteAsync(int count)
+ {
+ var messagesAsync = HostingApplication.Context.Channel.GetMessagesAsync(count);
+ Task.WaitAll(messagesAsync.ToArray());
+ var list = messagesAsync.ToEnumerable().ToList();
+ var messages = new List<IMessage>();
+ foreach (var task in list)
+ {
+ messages.AddRange(task.ToList());
+ }
+
+ if (Permissions.Check(HostingApplication.Context, new[] { "Admin", "Mod", "Meister" }))
+ {
+
+ var waiters = new List<Task>();
+ foreach (var message in messages)
+ {
+ waiters.Add((message as IUserMessage).DeleteAsync());
+ }
+
+ Task.WaitAll(waiters.ToArray());
+ }
+
+ }
+
+ [Command("check"), Summary("Echos a message.")]
+ [Alias("Check")]
+ public async Task CheckAsync(string quarry)
+ {
+ var perm = new List<string> { "Admin", "Mod", "Privatpolizei" };
+
+ Permissions.Test(this.Context, perm.ToArray());
+
+ var test = File.ReadAllLines("RG.txt");
+
+ var lines = test.Where(x => !x.Equals(string.Empty)).ToList();
+
+
+ var sc = new SpellCorrect();
+ var count = lines.OrderBy(line => sc.Compare(quarry, line)).First();
+
+ var fit = sc.Compare(count, quarry);
+
+ string Antwort;
+
+ if (fit < SpellCorrect.ErrorThreshold - 20000)
+ {
+ Antwort= $"```xl\nAuf anderem Server Match gefunden: {count}";
+ }
+ else
+ {
+ Antwort = $"```xl\nAuf anderem Server Kein Match gefunden: {quarry}";
+ }
+
+
+ var users = HostingApplication.Context.Guild.GetUsersAsync(CacheMode.AllowDownload);
+ await users;
+ var us = users.Result.Select(x => x.Username);
+
+ sc = new SpellCorrect();
+ count = us.OrderBy(line => sc.Compare(quarry, line)).First();
+
+ fit = sc.Compare(count, quarry);
+
+ if (fit < SpellCorrect.ErrorThreshold - 20000)
+ {
+ Antwort = Antwort + $"\nAuf unserem Server Match gefunden: {count}\n```";
+ }
+ else
+ {
+ Antwort = Antwort + $"\nAuf unserem Server Kein Match gefunden: {quarry} \n```";
+ }
+
+ await ReplyAsync(Antwort);
+
+ }*/
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/NpcCommands.cs b/dsa/DSALib/Commands/NpcCommands.cs
new file mode 100644
index 0000000..510b78b
--- /dev/null
+++ b/dsa/DSALib/Commands/NpcCommands.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using DSALib.Characters;
+using DSALib.DSA_Game;
+using DSALib.DSA_Game.Characters;
+
+namespace DSALib.Commands
+{
+ public class NpcCommands
+ {
+ public static string CreateNpc(ulong id, IEnumerable<string> props, int modifier)
+ {
+ if (int.TryParse(props.Last(), out var mean)) return Random(id, props.First(), mean, modifier);
+
+ return Copy(id, props.First(), props.Last(), modifier);
+ }
+
+ private static string Random(ulong id, string npcName, int mean = 9, int stDv = 1)
+ {
+ throw new NotImplementedException();
+ //Dsa.Chars.Add(new Npc(npcName, mean, stDv));
+ //return $"{npcName} wurde zufällig generiert";
+ }
+
+ private static string Copy(ulong id, string npcName, string source, int stDv = 1)
+ {
+ if (Dsa.Chars.Exists(x => x.Name.Equals(npcName))) throw new Exception("Char gibt es schon");
+ throw new NotImplementedException();
+ //var chr = Dsa.GetCharacter(id);
+ //Dsa.Chars.Add(new Character(chr as Character, npcName, stDv));
+ //return $"{npcName} wurde als variierte Kopie von {source} erstellt";
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Commands/ProbenTest.cs b/dsa/DSALib/Commands/ProbenTest.cs
new file mode 100644
index 0000000..7c88480
--- /dev/null
+++ b/dsa/DSALib/Commands/ProbenTest.cs
@@ -0,0 +1,85 @@
+namespace DSALib.Commands
+{
+ public class ProbenTest
+ {
+ /*[Command("t"), Summary("Würfelt ein Talent-/Zauberprobe")]
+ [Alias("T", "Talent", "talent", "versuche")]
+ public Task TalentAsync([Summary("Talent oder Zaubername")] string talent, int erschwernis = 0)
+ {
+ string res;
+ try
+ {
+ res = Gm.CheckCommand(
+ Dsa.Session.Relation[this.Context.User.Username],
+ CommandTypes.Talent,
+ talent,
+ erschwernis);
+ }
+ catch
+ {
+ res = Gm.CheckCommand(
+ Dsa.Session.Relation["Tardis"],
+ CommandTypes.Talent,
+ talent,
+ erschwernis);
+ }
+
+ return this.ReplyAsync("```xl\n" + res + "\n```");
+ }
+
+ [Command("Zauber"), Summary("Würfelt ein Zauberprobe")]
+ [Alias("Z", "zauber", "z")]
+ public Task ZauberAsync([Summary("Zaubername")] string zauber, int erschwernis = 0)
+ {
+ string res;
+ try
+ {
+ res = Gm.CheckCommand(
+ Dsa.Session.Relation[this.Context.User.Username],
+ CommandTypes.Zauber,
+ zauber,
+ erschwernis);
+ }
+ catch
+ {
+ res = Gm.CheckCommand(
+ Dsa.Session.Relation["Tardis"],
+ CommandTypes.Zauber,
+ zauber,
+ erschwernis);
+ }
+
+ return this.ReplyAsync("```xl\n" + res + "\n```");
+ }
+
+ [Command("e"), Summary("Würfelt eine Eigenschaftsprobe")]
+ [Alias("E", "Eigenschaft", "eigenschaft", "eigen")]
+ public Task EigenschaftAsync([Summary("Eigenschaftskürzel und Erschwernis")] string talent, int erschwernis = 0)
+ {
+ var chr = Dsa.Chars.Find(x => x.Name.Equals(Dsa.Session.Relation[this.Context.User.Username]));
+ string res = chr.TestEigenschaft(talent, erschwernis);
+ return this.ReplyAsync("```xl\n" + res + "\n```");
+ }
+
+ [Command("a"), Summary("Würfelt ein Angriff")]
+ [Alias("A", "At", "at", "Angriff", "angriff", "attackiere_mit", "attacke", "Attacke")]
+ public Task AngriffAsync([Summary("Weapon")] string weapon, int erschwernis = 0)
+ {
+ return this.ReplyAsync("```xl\n" + Dsa.Chars.Find(x => x.Name.Equals(Dsa.Session.Relation[this.Context.User.Username])).Angriff(weapon, erschwernis) + "\n```");
+ }
+
+ [Command("p"), Summary("Würfelt eine Parade Probe")]
+ [Alias("P", "Parade", "parade", "pariere_mit")]
+ public Task ParadeAsync([Summary("Parade Weapon")] string talent, int erschwernis = 0)
+ {
+ return this.ReplyAsync("```xl\n" + Dsa.Chars.Find(x => x.Name.Equals(Dsa.Session.Relation[this.Context.User.Username])).Parade(talent, erschwernis) + "\n```");
+ }
+
+ [Command("f"), Summary("Führt eine Fernkampfprobe aus")]
+ [Alias("F", "fern", "Fern", "Schuss", "schuss", "fernkampf", "Fernkampf", "schieße", "schieße_mit")]
+ public Task FernkampfAsync([Summary("Fernkampfwaffe")] string waffe, int erschwernis = 0)
+ {
+ return this.ReplyAsync("```xl\n" + Dsa.Chars.Find(x => x.Name.Equals(Dsa.Session.Relation[this.Context.User.Username])).Fernkampf(waffe, erschwernis) + "\n```");
+ }*/
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/DSALib.csproj b/dsa/DSALib/DSALib.csproj
new file mode 100644
index 0000000..2281bd6
--- /dev/null
+++ b/dsa/DSALib/DSALib.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp2.2</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\FireBase\FireBase.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/dsa/DSALib/DSA_Game/Characters/Character.cs b/dsa/DSALib/DSA_Game/Characters/Character.cs
new file mode 100644
index 0000000..aea5671
--- /dev/null
+++ b/dsa/DSALib/DSA_Game/Characters/Character.cs
@@ -0,0 +1,269 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using DSACore.Auxiliary;
+using DSALib.Auxiliary;
+using DSALib.Characters;
+using DSALib.Models.Dsa;
+
+namespace DSALib.DSA_Game.Characters
+{
+ public class Character : Being, ICharacter
+ {
+ public Character()
+ {
+ PropTable.Add("MU", "Mut"); // routing
+ PropTable.Add("KL", "Klugheit");
+ PropTable.Add("IN", "Intuition");
+ PropTable.Add("CH", "Charisma");
+ PropTable.Add("FF", "Fingerfertigkeit");
+ PropTable.Add("GE", "Gewandtheit");
+ PropTable.Add("KO", "Konstitution");
+ PropTable.Add("KK", "Körperkraft");
+ }
+
+ public Character(string path) : this()
+ {
+ Load(new MemoryStream(File.ReadAllBytes(path))); // load
+ Post_process(); // calculate derived values
+ }
+
+ public Character(MemoryStream stream) : this()
+ {
+ Load(stream); // load
+ Post_process(); // calculate derived values
+ }
+
+ public Character(Character c, string name, int stDv = 2) : this()
+ {
+ Name = name;
+ foreach (var i in c.Eigenschaften)
+ Eigenschaften.Add(i.Key, i.Value + (int) Math.Round(RandomMisc.Random(stDv)));
+
+ foreach (var i in c.Vorteile)
+ Vorteile.Add(new Vorteil(i.Name, i.Value + (int) Math.Round(RandomMisc.Random(stDv))));
+
+ foreach (var i in c.Talente)
+ Talente.Add(new Talent(i.Name, i.Probe, i.Value + (int) Math.Round(RandomMisc.Random(stDv))));
+
+ foreach (var i in c.Zauber)
+ 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)
+ Kampftalente.Add(new KampfTalent(i.Name, i.At + (int) Math.Round(RandomMisc.Random(stDv)),
+ i.Pa + (int) Math.Round(RandomMisc.Random(stDv))));
+
+ Post_process(); // calculate derived values
+ }
+
+ public Dictionary<string, int> Eigenschaften { get; set; } = new Dictionary<string, int>(); // char properties
+
+ public List<Talent> Talente { get; set; } = new List<Talent>(); // list of talent objects (talents)
+
+ public List<Zauber> Zauber { get; set; } = new List<Zauber>(); // list of spell objects
+
+ public List<KampfTalent> Kampftalente { get; set; } = new List<KampfTalent>(); // list of combat objects
+
+ public List<Vorteil> Vorteile { get; set; } = new List<Vorteil>();
+
+ public Dictionary<string, string> PropTable { get; set; } = new Dictionary<string, string>(); // -> Körperkraft
+
+ public string TestTalent(string talent, int erschwernis = 0) // Talentprobe
+ {
+ return Talente.ProbenTest(this, talent, erschwernis);
+ }
+
+ public string TestZauber(string zauber, int erschwernis = 0) // Talentprobe
+ {
+ return Zauber.ProbenTest(this, zauber, erschwernis);
+ }
+
+ public string TestEigenschaft(string eigenschaft, int erschwernis = 0)
+ {
+ var output = new StringBuilder();
+ var prop = PropTable[eigenschaft.ToUpper()];
+ var tap = Eigenschaften[prop];
+ output.AppendFormat(
+ "{0}-Eigenschaftsprobe ew:{1} {2} \n",
+ prop,
+ tap,
+ erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis);
+ var 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();
+ if (!Kampftalente.TryMatch(out var iattack, talent))
+ return $"{Name} kann nicht mit der Waffenart {talent} umgehen...";
+ var attack = (KampfTalent) iattack;
+ var tap = attack.At;
+ output.AppendFormat(
+ "{0}-Angriff taw:{1} {2} \n",
+ attack.Name,
+ tap,
+ erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis);
+
+ var temp = Dice.Roll();
+ output.Append(temp - erschwernis);
+ return output.ToString();
+ }
+
+ public string Parade(string talent, int erschwernis = 0)
+ {
+ var output = new StringBuilder();
+
+ if (Kampftalente.TryMatch(out var iAttack , talent))
+ return $"{Name} kann nicht mit der Waffenart {talent} umgehen...";
+
+
+ var attack = (KampfTalent) iAttack;
+ var tap = attack.Pa;
+ output.AppendFormat(
+ "{0}-Parade taw:{1} {2}\n",
+ attack.Name,
+ tap,
+ erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis);
+
+ var temp = Dice.Roll();
+ output.Append(temp - erschwernis);
+ return output.ToString();
+ }
+
+ public string Fernkampf(string talent, int erschwernis = 0)
+ {
+ var output = new StringBuilder();
+ var fk = Eigenschaften["fk"];
+ if (! Talente.TryMatch(out var iAttack, talent))
+ return $"{Name} kann nicht mit der Waffenart {talent} umgehen...";
+
+ var attack = (Talent) iAttack;
+ var tap = attack.Value;
+ output.AppendFormat(
+ "{0} taw:{1} {2} \n",
+ attack.Name,
+ tap,
+ erschwernis.Equals(0) ? string.Empty : "Erschwernis: " + erschwernis);
+ tap -= erschwernis;
+ var 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 = Eigenschaften["Lebensenergie"];
+ var AE_Wert = Eigenschaften.First(s => s.Key.Contains("Astralenergie")).Value;
+
+ //var KL_Wert = this.Eigenschaften.First(s => s.Key.Contains("Klugheit")).Value;
+ var MU_Wert = Eigenschaften.First(s => s.Key.Contains("Mut")).Value;
+ var IN_Wert = Eigenschaften.First(s => s.Key.Contains("Intuition")).Value;
+ var CH_Wert = Eigenschaften.First(s => s.Key.Contains("Charisma")).Value;
+ var KK_Wert = Eigenschaften["Körperkraft"];
+ var KO__Wert = Eigenschaften["Konstitution"];
+
+ Astralpunkte_Basis = 0;
+
+ Ausdauer_Basis = 0;
+
+ Lebenspunkte_Basis = LE_Wert + (int) (KO__Wert + KK_Wert / 2.0 + 0.5);
+
+ if (Vorteile.Exists(x => x.Name.ToLower().Contains("zauberer")))
+ Astralpunkte_Basis = AE_Wert + (int) ((MU_Wert + IN_Wert + CH_Wert) / 2.0 + 0.5);
+
+ Lebenspunkte_Aktuell = Lebenspunkte_Basis;
+ Astralpunkte_Aktuell = Astralpunkte_Basis;
+ Ausdauer_Aktuell = Ausdauer_Basis;
+ }
+
+
+ private void Load(MemoryStream stream)
+ {
+ var reader = new XmlTextReader(stream);
+ while (reader.Read())
+ {
+ // read until he hits keywords
+ if (reader.NodeType != XmlNodeType.Element) continue;
+
+ switch (reader.Name)
+ {
+ case "Wesen":
+ reader.Skip();
+ break;
+ case "held":
+ Name = reader.GetAttribute("name"); // name
+ break;
+ case "eigenschaft":
+ 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
+ {
+ Vorteile.Add(new Vorteil(
+ reader.GetAttribute("name"),
+ // Convert.ToInt32(reader.GetAttribute("value"))));
+ reader.GetAttribute("value")));
+ }
+ catch
+ {
+ Vorteile.Add(new Vorteil(reader.GetAttribute("name")));
+ }
+
+ reader.Read();
+ }
+
+ break;
+ case "talentliste":
+ reader.Read();
+ while (reader.Name.Equals("talent"))
+ {
+ 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"))
+ {
+ 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":
+ var atName = reader.GetAttribute("name");
+ reader.Read();
+ var at = Convert.ToInt32(reader.GetAttribute("value"));
+ reader.Read();
+ var pa = Convert.ToInt32(reader.GetAttribute("value"));
+ Kampftalente.Add(new KampfTalent(atName, at, pa));
+ break;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/DSA_Game/Characters/NPC.cs b/dsa/DSALib/DSA_Game/Characters/NPC.cs
new file mode 100644
index 0000000..105adda
--- /dev/null
+++ b/dsa/DSALib/DSA_Game/Characters/NPC.cs
@@ -0,0 +1,83 @@
+using System;
+using DSALib.Auxiliary;
+using DSALib.Characters;
+
+namespace 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;
+ Name = name;
+ }
+
+ public string TestTalent(string talent, int tap = 3)
+ {
+ for (var i = 0; i <= 2; i++)
+ {
+ // foreach property, dice and tap
+ var temp = Dice.Roll();
+ var eigenschaft = (int) Math.Round(RandomMisc.Random(stDv, mean));
+
+ if (eigenschaft < temp) tap -= temp - eigenschaft;
+ }
+
+ if (tap >= 0) return $"{Name} vollführt {talent} erfolgreich";
+
+
+ return $"{Name} scheitert an {talent}";
+ }
+
+ public string TestEigenschaft(string eigenschaft, int erschwernis = 0)
+ {
+ var temp = Dice.Roll();
+ var prop = (int) Math.Round(RandomMisc.Random(stDv, stDv));
+
+ if (temp + erschwernis < prop) return $"{Name} vollführt {eigenschaft} erfolgreich";
+
+ return $"{Name} scheitert an {eigenschaft}";
+ }
+
+ public string Angriff(string waffe, int erschwernis = 0)
+ {
+ var temp = Dice.Roll();
+
+ if (temp == 1) return $"{Name} greift kritisch mit {waffe} an";
+
+ if (temp < erschwernis) return $"{Name} greift mit {waffe} an";
+
+ return $"{Name} haut mit {waffe} daneben";
+ }
+
+ public string Parade(string waffe, int erschwernis = 0)
+ {
+ var temp = Dice.Roll();
+
+ if (temp == 1) return $"{Name} pariert mit {waffe} meisterlich";
+
+ if (temp < erschwernis) return $"{Name} pariert mit {waffe} an";
+
+ return $"{Name} schafft es nicht mit {waffe} zu parieren";
+ }
+
+ public string Fernkampf(string waffe, int erschwernis = 0)
+ {
+ var temp = Dice.Roll();
+
+ if (temp == 1) return $"{Name} trifft kritisch mit {waffe}";
+
+ if (temp < erschwernis) return $"{Name} greift mit {waffe} an";
+
+ return $"{Name} schießt mit {waffe} daneben";
+ }
+
+ public string TestZauber(string zauber, int erschwernis)
+ {
+ return TestTalent(zauber, erschwernis);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/DSA_Game/Characters/SaveChar.cs b/dsa/DSALib/DSA_Game/Characters/SaveChar.cs
new file mode 100644
index 0000000..00e2f86
--- /dev/null
+++ b/dsa/DSALib/DSA_Game/Characters/SaveChar.cs
@@ -0,0 +1,38 @@
+using DSALib.Characters;
+
+namespace DSALib.DSA_Game.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;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/DSA_Game/Dsa.cs b/dsa/DSALib/DSA_Game/Dsa.cs
new file mode 100644
index 0000000..bcd8951
--- /dev/null
+++ b/dsa/DSALib/DSA_Game/Dsa.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using DSALib.DSA_Game.Characters;
+using DSALib.DSA_Game.Save;
+using DSALib;
+using DSALib.Characters;
+using DSALib.Models.Dsa;
+
+namespace DSALib.DSA_Game
+{
+ public static class Dsa
+ {
+#if DEBUG
+ public const string
+ rootPath = ""; //"C:\\Users\\Dennis\\Source\\Repos\\DiscoBot\\DSALib\\";//"DiscoBot\\DSALib\\";
+#else
+ public const string rootPath = "";//"DiscoBot\\DSALib\\";
+#endif
+ private static Session s_session;
+
+ public static List<ICharacter> Chars { get; set; } = new List<ICharacter>(); // list of all characters
+
+ public static List<Talent> Talente { get; set; } = new List<Talent>();
+
+ public static List<Zauber> Zauber { get; set; } = new List<Zauber>();
+
+ 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 .Auxiliary.Calculator.StringSolver("1d100 - (1d200 + 1) * -50000").Solve();
+ /*Session = new Session();*/
+ // relation.Add("Papo", "Pump aus der Gosse");
+ /*foreach (var filename in Directory.GetFiles(rootPath + "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));
+ (Chars.Last() as Character)?.Zauber.Select(x => new Zauber(x.Name, x.Probe, 0, x.Complexity))
+ .Where(c => !Zauber.Exists(v => v.Name.Equals(c.Name))).ToList().ForEach(v => Zauber.Add(v));
+ }
+*/
+
+ Properties.Deserialize();
+ Properties.Serialize(rootPath + "Properties");
+
+
+ Talente = Talente.OrderBy(x => x.Name).ToList();
+ Zauber = Zauber.OrderBy(x => x.Name).ToList();
+
+ /*foreach (var talent in Talente)
+ {
+ Database.AddTalent(new Models.Database.Talent(talent.Name, talent.Probe));
+ }
+
+ foreach (var talent in Zauber)
+ {
+ Database.AddSpell(new Models.Database.GeneralSpell(talent.Name, talent.Probe, talent.Complexity));
+ }*/
+
+ //new WeaponImporter().DownloadWeapons().Wait();
+
+
+ Session = new Session
+ {
+ Chars = Chars.Select(SaveChar.FromICharacter).ToList()
+ };
+ //Session.Save();
+ }
+
+ public static ICharacter GetCharacter(ulong id)
+ {
+ throw new NotImplementedException();
+ }
+
+ public static ICharacter GetCharacter(string name, ulong groupId)
+ {
+ throw new NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/DSA_Game/Save/Properties.cs b/dsa/DSALib/DSA_Game/Save/Properties.cs
new file mode 100644
index 0000000..2312af0
--- /dev/null
+++ b/dsa/DSALib/DSA_Game/Save/Properties.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using DSALib.Auxiliary;
+using Newtonsoft.Json;
+
+namespace DSALib.DSA_Game.Save
+{
+ public static class Properties
+ {
+ public static Dictionary<string, object> objects;
+
+ static Properties()
+ {
+ objects = new Dictionary<string, object>();
+ /*this.objects.Add("Sounds", new List<Sound>());
+ this.objects.Add("CommandInfos", new List<CommandInfo>());*/
+ }
+
+ public static List<CommandInfo> CommandInfos
+ {
+ get => objects["CommandInfo"] as List<CommandInfo>;
+ set => objects["CommandInfo"] = value;
+ } // use Properties.Commandinfos to access the abstract Object array
+
+
+ public static void Deserialize(string path = @"Properties")
+ {
+ var files = Directory.GetFiles(path, "*.json");
+
+ foreach (var file in files)
+ try
+ {
+ var name = file.Split('\\').Last().Split('.')[0].Replace('-', '.');
+ var data = File.ReadAllText(file);
+ var 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
+ Console.WriteLine($"Laden von Save-File {file} fehlgeschlagen." + e);
+ }
+ }
+
+ public static void Serialize(string path = @"..\..\Properties\")
+ {
+ try
+ {
+ foreach (var o in objects)
+ {
+ var assembly = o.Value is IList list
+ ? 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
+ Console.WriteLine("Speichern von Save-File fehlgeschlagen." + e);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/DSA_Game/Save/SaveCommand.cs b/dsa/DSALib/DSA_Game/Save/SaveCommand.cs
new file mode 100644
index 0000000..c5a1bb4
--- /dev/null
+++ b/dsa/DSALib/DSA_Game/Save/SaveCommand.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+using System.Linq;
+
+namespace DSALib.DSA_Game.Save
+{
+ public class SaveCommand
+ {
+ public void LoadSession(string name = "")
+ {
+ if (name.Equals("?") || name.Equals(string.Empty))
+ {
+ Console.WriteLine("Gespeicherte Sessions:");
+ Console.WriteLine(ListSessions());
+ return;
+ }
+
+ var path = 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);
+
+ Console.WriteLine($"{name} wurde geladen");
+ }
+
+ public void SessionSave(string name = "")
+ {
+ //var sendFile = this.Context.Channel.SendWebFile("https://cdn.discordapp.com/attachments/377123019673567232/465615882048110603/giphy.gif");
+
+ if (name.Equals("?") || name.Equals(string.Empty))
+ {
+ Console.WriteLine("Gespeicherte Sessions:");
+ Console.WriteLine(ListSessions());
+ return;
+ }
+
+ var path = Session.DirectoryPath + @"\" + name;
+ if (Directory.Exists(path))
+ {
+ var files = Directory.GetFiles(path);
+ var 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");
+ }
+
+ Console.WriteLine($"{name} wurde gespeichert");
+ //await sendFile;
+ }
+
+ private string[] ListSessions()
+ {
+ var dirs = Directory.GetDirectories(Session.DirectoryPath)
+ .OrderByDescending(x => new DirectoryInfo(x).LastAccessTime.Ticks).ToArray();
+ for (var i = 0; i < dirs.Length; i++) dirs[i] += "; " + new DirectoryInfo(dirs[i]).LastAccessTime;
+
+ return dirs;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/DSA_Game/Save/Session.cs b/dsa/DSALib/DSA_Game/Save/Session.cs
new file mode 100644
index 0000000..62aa8f6
--- /dev/null
+++ b/dsa/DSALib/DSA_Game/Save/Session.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using DSALib.DSA_Game.Characters;
+using Newtonsoft.Json;
+
+namespace DSALib.DSA_Game.Save
+{
+ public class Session
+ {
+ public static string DirectoryPath { get; set; } = Dsa.rootPath + @"sessions";
+
+ public Dictionary<string, string> Relation { get; set; } =
+ new Dictionary<string, string>(); // dictionary to match the char
+
+ public List<SaveChar> Chars { get; set; } = new List<SaveChar>(); // list of all characters
+
+ public string SessionName { get; set; }
+
+ public static Session Load(string path)
+ {
+ try
+ {
+ return
+ JsonConvert.DeserializeObject<Session>(
+ File.ReadAllText(path)); // Deserialize Data and create Session Object
+ }
+ catch (Exception e)
+ {
+ // ignored
+ Console.WriteLine($"Laden von Save-File {path} fehlgeschlagen." + e);
+ return null;
+ }
+ }
+
+ public void Save(string path)
+ {
+ try
+ {
+ File.WriteAllText(path,
+ JsonConvert.SerializeObject(this,
+ Formatting.Indented)); // Deserialize Data and create CommandInfo Struct
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Speichern von Save-File {path} fehlgeschlagen.\n" + e);
+ // ignored
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/FireBase/Database.cs b/dsa/DSALib/FireBase/Database.cs
new file mode 100644
index 0000000..1edd699
--- /dev/null
+++ b/dsa/DSALib/FireBase/Database.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using DSALib.DSA_Game;
+using DSALib.DSA_Game.Characters;
+using DSALib.Models.Database.Dsa;
+using Firebase.Database;
+using Firebase.Database.Query;
+
+namespace DSALib.FireBase
+{
+ public static class Database
+ {
+ public static FirebaseClient Firebase;
+
+ public static Dictionary<string, DatabaseChar> Chars = new Dictionary<string, DatabaseChar>();
+
+ public static Dictionary<string, MeleeWeapon> MeleeList = new Dictionary<string, MeleeWeapon>();
+
+ public static Dictionary<string, RangedWeapon> RangedWeapons = new Dictionary<string, RangedWeapon>();
+
+ public static Dictionary<string, DSALib.Models.Database.Dsa.Talent> Talents = new Dictionary<string, DSALib.Models.Database.Dsa.Talent>();
+
+ public static Dictionary<string, GeneralSpell> Spells = new Dictionary<string, GeneralSpell>();
+
+ static Database()
+ {
+ var auth = File.ReadAllText(Dsa.rootPath + "Token"); // your app secret
+ Firebase = new FirebaseClient(
+ "https://heldenonline-4d828.firebaseio.com/",
+ new FirebaseOptions
+ {
+ AuthTokenAsyncFactory = () => Task.FromResult(auth)
+ });
+
+ Task.Run(Initialize);
+ }
+
+ private static void Initialize() {
+ var waiting = new[] {
+ // ToDo IntializeCollection("Chars", Chars),
+ IntializeCollection("MeleeWeapons", MeleeList),
+ IntializeCollection("RangedWeapons", RangedWeapons),
+ IntializeCollection("Talents", Talents),
+ IntializeCollection("Spells", Spells),
+ };
+ Task.WaitAll(waiting);
+ }
+
+ private static async Task IntializeCollection<T>(string path, Dictionary<string, T> list)
+ {
+ var temp = await Firebase
+ .Child(path)
+ .OrderByKey()
+ .OnceAsync<T>();
+
+ foreach (var firebaseObject in temp) list.Add(firebaseObject.Key, firebaseObject.Object);
+ }
+
+ public static async Task<int> AddChar(Character file, string group)
+ {
+ DatabaseChar.LoadChar(file, out var groupChar, out var data);
+
+ var lastChar = await Firebase
+ .Child("Chars")
+ .OrderByKey()
+ .LimitToLast(1)
+ .OnceAsync<DatabaseChar>();
+ var id = groupChar.Id = data.Id = lastChar.First().Object.Id + 1;
+
+ await Firebase //TODO Reomve await Operators
+ .Child("Groups")
+ .Child("Char" + id)
+ .PutAsync(groupChar);
+
+ await Firebase
+ .Child("Chars")
+ .Child("Char" + id)
+ .PutAsync(data);
+
+ Chars["Char" + id] = data;
+
+ await Firebase
+ .Child("Inventories")
+ .Child("Inventory" + id)
+ .PutAsync(new Inventory());
+
+ return id + 1;
+ }
+
+ public static async Task RemoveChar(int id)
+ {
+ await Firebase
+ .Child("Groups")
+ .Child("Char" + id)
+ .DeleteAsync();
+
+ await Firebase
+ .Child("Chars")
+ .Child("Char" + id)
+ .DeleteAsync();
+
+ Chars.Remove("Char" + id);
+
+ await Firebase
+ .Child("Inventories")
+ .Child("Inventory" + id)
+ .DeleteAsync();
+ }
+
+ public static DatabaseChar GetChar(int id)
+ {
+ /*var chr = await firebase
+ .Child("Chars")
+ .Child("Char" + id)
+ .OnceSingleAsync<DatabaseChar>();
+ return chr;*/
+ return Chars["Char" + id];
+ }
+
+ public static async Task<Inventory> GetInventory(int id)
+ {
+ var inv = await Firebase
+ .Child("Inventories")
+ .Child("Inventory" + id)
+ .OnceSingleAsync<Inventory>();
+ return inv;
+ }
+
+ public static async Task SetInventory(int id, Inventory inv)
+ {
+ await Firebase
+ .Child("Inventories")
+ .Child("Inventory" + id)
+ .PutAsync(inv);
+ }
+
+ public static async Task AddTalent(DSALib.Models.Database.Dsa.Talent tal)
+ {
+ await Firebase
+ .Child("Talents")
+ .Child(tal.Name)
+ .PutAsync(tal);
+ }
+
+ public static async Task RemoveTalent(string talent)
+ {
+ await Firebase
+ .Child("Talents")
+ .Child(talent)
+ .DeleteAsync();
+ }
+
+ public static DSALib.Models.Database.Dsa.Talent GetTalent(string talent)
+ {
+ /*
+ return await firebase
+ .Child("Talents")
+ .Child(talent)
+ .OnceSingleAsync<Talent>();*/
+ return Talents[talent];
+ }
+
+ public static async Task AddSpell(GeneralSpell tal)
+ {
+ await Firebase
+ .Child("Spells")
+ .Child(tal.Name)
+ .PutAsync(tal);
+ }
+
+ public static async Task RemoveSpell(string spell)
+ {
+ await Firebase
+ .Child("Spells")
+ .Child(spell)
+ .DeleteAsync();
+ }
+
+ public static GeneralSpell GetSpell(string spell)
+ {
+ return Spells[spell];
+ }
+
+
+ public static async Task AddWeapon(Weapon wep)
+ {
+ var collection = wep.GetType() == typeof(MeleeWeapon) ? "MeleeWeapons" : "RangedWeapons";
+ await Firebase
+ .Child(collection)
+ .Child(wep.Name)
+ .PutAsync(wep);
+ }
+
+ public static async Task RemoveWeapon(string weapon, bool ranged = false)
+ {
+ var collection = ranged ? "RangedWeapons" : "MeleeWeapons";
+ await Firebase
+ .Child(collection)
+ .Child(weapon)
+ .DeleteAsync();
+ }
+
+ public static async Task<Weapon> GetWeapon(string weapon, bool ranged = false)
+ {
+ var collection = ranged ? "RangedWeapons" : "MeleeWeapons";
+ return await Firebase
+ .Child(collection)
+ .Child(weapon)
+ .OnceSingleAsync<Weapon>();
+ }
+
+ public static async Task<List<Tuple<string, string>>> GetGroups()
+ {
+ var groups = await Firebase
+ .Child("Groups")
+ .OrderByKey()
+ .OnceAsync<DSALib.Models.Database.Groups.Group>();
+ var ret = new List<Tuple<string, string>>();
+
+ foreach (var firebaseObject in groups)
+ ret.Add(new Tuple<string, string>(firebaseObject.Object.Name, firebaseObject.Object.Password));
+
+ return ret;
+ }
+
+ public static async Task<DSALib.Models.Database.Groups.Group> GetGroup(int id)
+ {
+ var group = await Firebase
+ .Child("Groups")
+ .Child("Group" + id)
+ .OnceSingleAsync<DSALib.Models.Database.Groups.Group>();
+ return group;
+ }
+
+ public static async Task AddGroup(DSALib.Models.Database.Groups.Group group)
+ {
+ var lastChar = await Firebase
+ .Child("Groups")
+ .OrderByKey()
+ .LimitToLast(1)
+ .OnceAsync<DSALib.Models.Database.Groups.Group>();
+ var id = group.Id = lastChar.First().Object.Id + 1;
+
+ await Firebase
+ .Child("Groups")
+ .Child("Group" + id)
+ .PutAsync(group);
+ }
+
+ public static async void SetGroup(DSALib.Models.Database.Groups.Group group)
+ {
+ await Firebase
+ .Child("Groups")
+ .Child("Group" + group.Id)
+ .PutAsync(group);
+ }
+
+ public static async void DeleteGroup(int id)
+ {
+ await Firebase
+ .Child("Groups")
+ .Child("Group" + id)
+ .DeleteAsync();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/DataObject.cs b/dsa/DSALib/Models/Database/DataObject.cs
new file mode 100644
index 0000000..59cfdf2
--- /dev/null
+++ b/dsa/DSALib/Models/Database/DataObject.cs
@@ -0,0 +1,13 @@
+namespace DSALib.Models.Database
+{
+ public class DataObject : IDataObject
+ {
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public string Name { get; set; }
+ }
+}
diff --git a/dsa/DSALib/Models/Database/Dsa/Advantage.cs b/dsa/DSALib/Models/Database/Dsa/Advantage.cs
new file mode 100644
index 0000000..2ed0bf9
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/Advantage.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class Advantage
+ {
+ public Advantage(string name, string value = "")
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ Value = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ public string Name { get; set; }
+ public string Value { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/CharSpell.cs b/dsa/DSALib/Models/Database/Dsa/CharSpell.cs
new file mode 100644
index 0000000..d08bc74
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/CharSpell.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class CharSpell
+ {
+ public CharSpell(string representation, int value)
+ {
+ this.representation = representation ?? throw new ArgumentNullException(nameof(representation));
+ this.value = value;
+ }
+
+ public string representation { get; set; }
+ public int value { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/DatabaseChar.cs b/dsa/DSALib/Models/Database/Dsa/DatabaseChar.cs
new file mode 100644
index 0000000..1312f95
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/DatabaseChar.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using DSALib.DSA_Game.Characters;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class DatabaseChar
+ {
+ public DatabaseChar()
+ {
+ }
+
+ public DatabaseChar(int id, string name, string rasse, List<Field> skills, List<Field> talents,
+ List<Advantage> advantages, List<CharSpell> spells, List<WeaponTalent> weaponTalents)
+ {
+ Id = id;
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ Race = rasse ?? throw new ArgumentNullException(nameof(rasse));
+ Skills = skills ?? throw new ArgumentNullException(nameof(skills));
+ Talents = talents ?? throw new ArgumentNullException(nameof(talents));
+ Advantages = advantages ?? throw new ArgumentNullException(nameof(advantages));
+ Spells = spells ?? throw new ArgumentNullException(nameof(spells));
+ WeaponTalents = weaponTalents ?? throw new ArgumentNullException(nameof(weaponTalents));
+ }
+
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public string Race { get; set; }
+
+ public List<Field> Skills { get; set; } = new List<Field>();
+
+ public List<Field> Talents { get; set; } = new List<Field>();
+
+ public List<Advantage> Advantages { get; set; } = new List<Advantage>();
+
+ public List<CharSpell> Spells { get; set; } = new List<CharSpell>();
+
+ public List<WeaponTalent> WeaponTalents { get; set; } = new List<WeaponTalent>();
+
+
+ public static void LoadChar(Character file, out GroupChar group, out DatabaseChar data)
+ {
+ group = new GroupChar();
+ data = new DatabaseChar();
+
+ group.Name = file.Name.Split(' ').First();
+ group.Weapon = new Weapon();
+ group.Lp = group.LpMax = file.Lebenspunkte_Basis;
+ group.As = group.AsMax = file.Astralpunkte_Basis;
+ group.Weapon = new Weapon();
+
+ data.Name = file.Name;
+ data.Advantages = file.Vorteile.Select(x => new Advantage(x.Name, x.Value)).ToList();
+ data.Skills = file.Eigenschaften.Select(x => new Field(x.Key, x.Value)).ToList();
+ data.Spells = file.Zauber.Select(x => new CharSpell(x.Representation, x.Value)).ToList();
+ data.Talents = file.Talente.Select(x => new Field(x.Name, x.Value)).ToList();
+ data.WeaponTalents = file.Kampftalente.Select(x => new WeaponTalent(x.Name, x.At, x.Pa)).ToList();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/Field.cs b/dsa/DSALib/Models/Database/Dsa/Field.cs
new file mode 100644
index 0000000..6d1b82e
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/Field.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class Field
+ {
+ public Field(string name, int value = 0)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ Value = value;
+ }
+
+ public string Name { get; set; }
+ public int Value { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/GeneralSpell.cs b/dsa/DSALib/Models/Database/Dsa/GeneralSpell.cs
new file mode 100644
index 0000000..964c38e
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/GeneralSpell.cs
@@ -0,0 +1,20 @@
+namespace DSALib.Models.Database.Dsa
+{
+ public class GeneralSpell : Talent
+ {
+ public char Comlexity = 'A';
+
+ public GeneralSpell(string name, string roll, char comlexity = 'A') : base(name, roll)
+ {
+ Comlexity = comlexity;
+ }
+
+ public GeneralSpell(string name, string roll) : base(name, roll)
+ {
+ }
+
+ public GeneralSpell()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/GroupChar.cs b/dsa/DSALib/Models/Database/Dsa/GroupChar.cs
new file mode 100644
index 0000000..a0115cd
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/GroupChar.cs
@@ -0,0 +1,13 @@
+namespace DSALib.Models.Database.Dsa
+{
+ public class GroupChar
+ {
+ public string Name { get; set; }
+ public int Id { get; set; }
+ public int Lp { get; set; }
+ public int LpMax { get; set; }
+ public int As { get; set; }
+ public int AsMax { get; set; }
+ public Weapon Weapon { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/Inventory.cs b/dsa/DSALib/Models/Database/Dsa/Inventory.cs
new file mode 100644
index 0000000..f3f5d7a
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/Inventory.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class Inventory
+ {
+ public int Id { get; set; }
+ public Dictionary<string, bool> Items { get; set; } = new Dictionary<string, bool>();
+ public Dictionary<string, bool> Food { get; set; } = new Dictionary<string, bool>();
+ public List<Weapon> Weapons { get; set; } = new List<Weapon>();
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/Talent.cs b/dsa/DSALib/Models/Database/Dsa/Talent.cs
new file mode 100644
index 0000000..214aecc
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/Talent.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class Talent : DSALib.Models.Database.DataObject
+ {
+ public Talent()
+ {
+ }
+
+ public Talent(string name)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ }
+
+ public Talent(string name, string roll)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ Roll = roll.Split('/');
+ }
+
+ public string[] Roll { get; set; } = new string[3];
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/Weapon.cs b/dsa/DSALib/Models/Database/Dsa/Weapon.cs
new file mode 100644
index 0000000..308c6c5
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/Weapon.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class Weapon
+ {
+ public Weapon()
+ {
+ }
+
+ public Weapon(string name, string damage, int weight, string weaponTalent, string price)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ Damage = damage ?? throw new ArgumentNullException(nameof(damage));
+ Weight = weight;
+ WeaponTalent = weaponTalent ?? throw new ArgumentNullException(nameof(weaponTalent));
+ Price = price;
+ }
+
+ public string Name { get; set; }
+ public string Damage { get; set; }
+ public int Weight { get; set; }
+ public string WeaponTalent { get; set; }
+ public string Price { get; set; }
+ }
+
+ public class MeleeWeapon : Weapon
+ {
+ public MeleeWeapon(string name, string damage, int weight, string weaponTalent, string price) : base(name,
+ damage, weight, weaponTalent, price)
+ {
+ }
+
+ public string TpKK { get; set; }
+ public int INI { get; set; }
+ public string MW { get; set; }
+ }
+
+ public class RangedWeapon : Weapon
+ {
+ public RangedWeapon(string name, string damage, int weight, string weaponTalent, string price) : base(name,
+ damage, weight, weaponTalent, price)
+ {
+ }
+
+ public int AtMod { get; set; }
+ public int KKMod { get; set; }
+ public string AtReach { get; set; }
+ public string TpReach { get; set; }
+ public int LoadTime { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Dsa/WeaponTalent.cs b/dsa/DSALib/Models/Database/Dsa/WeaponTalent.cs
new file mode 100644
index 0000000..2ab921b
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Dsa/WeaponTalent.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace DSALib.Models.Database.Dsa
+{
+ public class WeaponTalent
+ {
+ public WeaponTalent(string name, int at, int pa)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ At = at;
+ Pa = pa;
+ }
+
+ public string Name { get; set; }
+ public int At { get; set; }
+ public int Pa { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Groups/DSAGroup.cs b/dsa/DSALib/Models/Database/Groups/DSAGroup.cs
new file mode 100644
index 0000000..adbd0ac
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Groups/DSAGroup.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using DSALib.Models.Database.Dsa;
+
+namespace DSALib.Models.Database.Groups
+{
+ public class DsaGroup : Group
+ {
+ public List<GroupChar> Chars { get; set; } = new List<GroupChar>();
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/Groups/Group.cs b/dsa/DSALib/Models/Database/Groups/Group.cs
new file mode 100644
index 0000000..096f2be
--- /dev/null
+++ b/dsa/DSALib/Models/Database/Groups/Group.cs
@@ -0,0 +1,9 @@
+namespace DSALib.Models.Database.Groups
+{
+ public class Group
+ {
+ public string Name { get; set; }
+ public string Password { get; set; }
+ public int Id { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Database/IDataObject.cs b/dsa/DSALib/Models/Database/IDataObject.cs
new file mode 100644
index 0000000..bdc88b7
--- /dev/null
+++ b/dsa/DSALib/Models/Database/IDataObject.cs
@@ -0,0 +1,7 @@
+namespace DSALib.Models.Database
+{
+ public interface IDataObject
+ {
+ string Name { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Dsa/CritterAttack.cs b/dsa/DSALib/Models/Dsa/CritterAttack.cs
new file mode 100644
index 0000000..8cd8b09
--- /dev/null
+++ b/dsa/DSALib/Models/Dsa/CritterAttack.cs
@@ -0,0 +1,19 @@
+namespace DSALib.Models.Dsa
+{
+ public class CritterAttack : Database.DataObject
+ {
+ public CritterAttack(string name, int at, string tp, string comment = "")
+ {
+ Name = name;
+ At = at;
+ Tp = tp;
+ Comment = comment;
+ }
+
+ public int At { get; set; }
+
+ public string Tp { get; set; }
+
+ public string Comment { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Dsa/KampfTalent.cs b/dsa/DSALib/Models/Dsa/KampfTalent.cs
new file mode 100644
index 0000000..51ad255
--- /dev/null
+++ b/dsa/DSALib/Models/Dsa/KampfTalent.cs
@@ -0,0 +1,16 @@
+namespace DSALib.Models.Dsa
+{
+ public class KampfTalent : Database.DataObject
+ {
+ public KampfTalent(string name, int at, int pa)
+ {
+ Name = name;
+ At = at;
+ Pa = pa;
+ }
+
+ public int At { get; set; }
+
+ public int Pa { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Dsa/Talent.cs b/dsa/DSALib/Models/Dsa/Talent.cs
new file mode 100644
index 0000000..5771a74
--- /dev/null
+++ b/dsa/DSALib/Models/Dsa/Talent.cs
@@ -0,0 +1,43 @@
+namespace DSALib.Models.Dsa
+{
+ public class Talent : Database.DataObject // talent objekt
+ {
+ public Talent(string name, string probe, int value)
+ {
+ Name = name;
+ Probe = probe;
+ Value = value;
+ }
+
+ public string Probe { get; set; }
+
+ public int Value { get; set; }
+
+ public string[] GetEigenschaften() // turn XX/XX/XX into string[]{XX,XX,XX}
+ {
+ var temp = Probe.Split('/');
+ for (var index = 0; index < temp.Length; index++) temp[index] = temp[index].Replace("/", string.Empty);
+
+ return temp;
+ }
+
+ public bool IstFernkampftalent()
+ {
+ switch (Name)
+ {
+ case "Armbrust":
+ case "Belagerungswaffen":
+ case "Blasrohr":
+ case "Bogen":
+ case "Diskus":
+ case "Schleuder":
+ case "Wurfbeile":
+ case "Wurfmesser":
+ case "Wurfspeere":
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Dsa/Vorteil.cs b/dsa/DSALib/Models/Dsa/Vorteil.cs
new file mode 100644
index 0000000..e37af20
--- /dev/null
+++ b/dsa/DSALib/Models/Dsa/Vorteil.cs
@@ -0,0 +1,16 @@
+namespace DSALib.Models.Dsa
+{
+ public class Vorteil : Database.DataObject // talent objekt
+ {
+ public Vorteil(string name, string value = "")
+ {
+ Name = name;
+ Value = value;
+ // this.Choice = choice;
+ }
+
+ public string Value { get; set; }
+
+ //public string Choice { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Dsa/Zauber.cs b/dsa/DSALib/Models/Dsa/Zauber.cs
new file mode 100644
index 0000000..e4387bf
--- /dev/null
+++ b/dsa/DSALib/Models/Dsa/Zauber.cs
@@ -0,0 +1,16 @@
+namespace DSALib.Models.Dsa
+{
+ public class Zauber : Talent
+ {
+ public Zauber(string name, string probe, int value, char complexity = 'A', string representation = "Magier")
+ : base(name, probe, value)
+ {
+ Complexity = complexity;
+ Representation = Representation;
+ }
+
+ public char Complexity { get; }
+
+ public string Representation { get; }
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Network/Command.cs b/dsa/DSALib/Models/Network/Command.cs
new file mode 100644
index 0000000..5a97e88
--- /dev/null
+++ b/dsa/DSALib/Models/Network/Command.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace DSALib.Models.Network
+{
+ public class Command
+ {
+ public ulong GroupId { get; set; } = 0;
+ public ulong CharId { get; set; }
+ public string Name { get; set; }
+ public string CmdIdentifier { get; set; }
+ public List<string> CmdTexts { get; set; }
+ public string CmdText => CmdTexts.Count != 0 ? CmdTexts.First() : "";
+
+ public int Cmdmodifier => CmdTexts.Count != 0 && int.TryParse(CmdTexts.Last(), out var mod) ? mod : 0;
+ public bool IsDm { get; set; } = false;
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Models/Network/CommandResponse.cs b/dsa/DSALib/Models/Network/CommandResponse.cs
new file mode 100644
index 0000000..0816e4a
--- /dev/null
+++ b/dsa/DSALib/Models/Network/CommandResponse.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace DSALib.Models.Network
+{
+ public class CommandResponse
+ {
+ public CommandResponse(string message, ResponseType responseType = ResponseType.Broadcast)
+ {
+ this.message = message ?? throw new ArgumentNullException(nameof(message));
+ ResponseType = responseType;
+ }
+
+ public string message { get; }
+ public ResponseType ResponseType { get; }
+
+ public override string ToString()
+ {
+ return message;
+ }
+ }
+
+ public enum ResponseType
+ {
+ Broadcast,
+ Caller,
+ Error
+ }
+} \ No newline at end of file
diff --git a/dsa/DSALib/Properties-DSACore-Auxiliary-CommandInfo.json b/dsa/DSALib/Properties-DSACore-Auxiliary-CommandInfo.json
new file mode 100644
index 0000000..b9941f2
--- /dev/null
+++ b/dsa/DSALib/Properties-DSACore-Auxiliary-CommandInfo.json
@@ -0,0 +1,101 @@
+[
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Setzt den gespielten Charakter fest",
+ "Description": [
+ "Mit \"!Ich bin\" kann der gespielte Charakter definiert, bzw. gewechselt werden.\n",
+ " Die Charaktere müssen als *.xml Dateien hinterlegt sein.\n\n",
+ " !ich Zeigt an welcher Charakter zur Zeit gespielt wird\n",
+ " !ich bin Zalibius Wechsel zum Helden Zalibius\n",
+ " !ich Rhoktar Orkische Variante von !ich bin.\n",
+ " Wechselt zu Rhoktar.\n\n",
+ " !list chars Zeigt die Liste verfügbarer Charaktere.\n",
+ " \n"
+ ]
+ },
+ {
+ "Name": "List",
+ "Scope": "All",
+ "Brief": "Anzeige vonSpielrelevanten Listen",
+ "Description": [
+ "Mit \"!list\" lassen sich spielrelevante Listen ausgeben. Die Angezeigte Liste wird nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !list chars Liste aller verfügbaren Helden (eingelesen per *.xml)\n",
+ " und NSCs.\n",
+ " (Mit \"!ich bin\" kann der Held ausgewählt werden.)\n",
+ " !list commands Liste aller verwendbaren Bot-Kommandos.\n",
+ " !list sounds Liste der Soundeffekte."
+ ]
+ },
+ {
+ "Name": "Held",
+ "Scope": "All",
+ "Brief": "Anzeige von Heldenwerten",
+ "Description": [
+ "Mit \"!Held\" lassen sich Heldenwerte ausgeben. Mehrere Werte können gleichzeitig angefordert werde. \"!Held LE Waffen\" liefert so z.B. Informationen zur Lebensenergie und den Kampfwerten.\n Bis auf wenige Ausnahmen wird die Angezeigte Liste nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !Held Zeigt den Heldenbrief an.\n",
+ " (Diese Liste wird nicht automatisch gelöscht)\n",
+ " !Held Eigenschaften Zeigt die Eigenschaften MU/KL/CH/IN/KK/GE/FF/KO.\n",
+ " !Held e Kurzform von \"!Held Eigenschaften\".\n",
+ " !Held LE Zeigt LE an.\n",
+ " !Held AE Zeigt AE an.\n",
+ " !Held stats Zeigt Eigenschaften und zusätzlich SO/LE/AE.\n",
+ " !Held Kampfwerte Zeigt AT/PA für aktivierte Waffentalente.\n",
+ " !Held Waffe/!list w Kurzformen von \"!Held Kampfwerte\".\n",
+ " !Held Vorteile Zeigt Vor- und Nachteile an.\n",
+ " !Held v Kurzform von \"!Held Vorteile\".\n",
+ " !Held Talente Zeigt die Liste aller aktivierten Talente, deren TaW,\n",
+ " sowie die Probe.\n",
+ " !Held t Kurzform von \"!Held Talente\".\n",
+ " !Held Zauber Zeigt die Liste aller aktivierten Zauber, deren ZaW,\n",
+ " sowie die Probe.\n",
+ " !Held z Kurzform von \"!Held Zauber\".\n"
+ ]
+ },
+ {
+ "Name": "LE",
+ "Scope": "All",
+ "Brief": "Ändert dein Leben - im wahrsten Sinne des Wortes",
+ "Description": [
+ "Mit !LE zeigt man die Lebensenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !LE Zeigt Lebensenergie an\n",
+ " !LE 30 Setzt LE auf 30\n",
+ " !LE +5 Erhöht LE um 5 (bis zum Maximum)\n",
+ " !LE ++5 Erhöht LE um 5 (ignoriert Maximum)\n",
+ " !LE -5 Verringert LE um 5\n \n"
+ ]
+ },
+ {
+ "Name": "AE",
+ "Scope": "All",
+ "Brief": "Ändert Astralenergie",
+ "Description": [
+ "Mit !AE (oder !Asp) zeigt man die Astralenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !AE Zeigt Astralenergie an\n",
+ " !AE 30 Setzt Asp auf 30\n",
+ " !AE +5 Erhöht Asp um 5 (bis zum Maximum)\n",
+ " !AE ++5 Erhöht Asp um 5 (ignoriert Maximum)\n",
+ " !AE -5 Verringert Asp um 5 (Minimum 0)\n"
+ ]
+ },
+ {
+ "Name": "Gm",
+ "Scope": "Meister",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ }
+] \ No newline at end of file
diff --git a/dsa/DSALib/Properties-DSACore-DSA_Game-Characters-Character.json b/dsa/DSALib/Properties-DSACore-DSA_Game-Characters-Character.json
new file mode 100644
index 0000000..fd387f5
--- /dev/null
+++ b/dsa/DSALib/Properties-DSACore-DSA_Game-Characters-Character.json
@@ -0,0 +1,290 @@
+[
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 30,
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 20,
+ "Astralpunkte_Aktuell": 20,
+ "Name": "Felis Exodus Schattenwald"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 29,
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Gardist"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 31,
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Hartmut Reiher"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 21,
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 35,
+ "Astralpunkte_Aktuell": 35,
+ "Name": "Helga vom Drachenei, Tausendsasserin"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 25,
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Krenko"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Ledur Torfinson"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 26,
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Morla"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 40,
+ "Astralpunkte_Aktuell": 40,
+ "Name": "Numeri Illuminus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 16,
+ "Astralpunkte_Aktuell": 16,
+ "Name": "Potus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 18,
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Pump aus der Gosse"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 34,
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 17,
+ "Astralpunkte_Aktuell": 17,
+ "Name": "Rhoktar4"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 43,
+ "Astralpunkte_Aktuell": 43,
+ "Name": "Volant"
+ }
+] \ No newline at end of file
diff --git a/dsa/DSALib/PropertiesNewtonsoft-Json-Linq-JProperty.json b/dsa/DSALib/PropertiesNewtonsoft-Json-Linq-JProperty.json
new file mode 100644
index 0000000..2544397
--- /dev/null
+++ b/dsa/DSALib/PropertiesNewtonsoft-Json-Linq-JProperty.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:2170",
+ "sslPort": 44365
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "api/commands",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "DSALib": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "api/commands",
+ "applicationUrl": "https://0.0.0.0:5001;http://0.0.0.0:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/App.config b/dsa/DiscoBot/App.config
new file mode 100644
index 0000000..c862b4e
--- /dev/null
+++ b/dsa/DiscoBot/App.config
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <configSections>
+ <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+ <section name="DiscoBot.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
+ </sectionGroup>
+ </configSections>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+ </startup>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="System.Interactive.Async" publicKeyToken="94bc3704cddfc263" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-3.2.0.0" newVersion="3.2.0.0" />
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-1.2.3.0" newVersion="1.2.3.0" />
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion="4.2.0.0" />
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+ <userSettings>
+ <DiscoBot.Properties.Settings>
+ <setting name="Token" serializeAs="String">
+ <value>Mjk0NTU0MDU4Nzg4NzAwMTYx.DOzDcQ.J-nCikbZdZtdrug0E8TwwV_2ITw</value>
+ </setting>
+ </DiscoBot.Properties.Settings>
+ </userSettings>
+</configuration>
diff --git a/dsa/DiscoBot/Auxiliary/CommandExtension.cs b/dsa/DiscoBot/Auxiliary/CommandExtension.cs
new file mode 100644
index 0000000..ad9f323
--- /dev/null
+++ b/dsa/DiscoBot/Auxiliary/CommandExtension.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+
+namespace DiscoBot.Auxiliary
+{
+ public static class CommandExtension
+ {
+ private static WebClient _client;
+
+ public static async Task ReplyTimedAsync(this ModuleBase m, string message, TimeSpan time)
+ {
+ var token = message.GetHashCode();
+ var send = m.Context.Channel.SendMessageAsync($"#{token}\n```xl\n{message}```");
+
+ var barInvoker = new BackgroundWorker();
+ barInvoker.DoWork += delegate
+ {
+ Thread.Sleep(time);
+ Delete(token, m);
+ };
+
+ await send;
+ barInvoker.RunWorkerAsync();
+ }
+
+ private static void Delete(int token, ModuleBase m)
+ {
+ var messagesAsync = m.Context.Channel.GetMessagesAsync();
+ Task.WaitAll(messagesAsync.ToArray());
+ var list = messagesAsync.ToEnumerable().ToList();
+ var messages = new List<IMessage>();
+ foreach (var task in list) messages.AddRange(task.ToList());
+
+ var test = messages.Where(x => x.Content.StartsWith($"#{token}\n") && x.Author.IsBot).Select(c => c);
+ Task.WaitAll(test.Select(message => (message as IUserMessage)?.DeleteAsync()).ToArray());
+ }
+
+ public static async Task ReplyAsync(this ModuleBase m, IEnumerable<string> message, bool directMessage = false)
+ {
+ var sb = new StringBuilder();
+ foreach (var re in message)
+ {
+ if (sb.Length + re.Length > 1798)
+ {
+ if (directMessage)
+ await m.Context.User.SendMessageAsync("```xl\n" + sb + "\n```");
+ else
+ await m.Context.Channel.SendMessageAsync("```xl\n" + sb + "\n```");
+
+ sb.Clear();
+ }
+
+ sb.AppendLine(re);
+ }
+
+ if (directMessage)
+ await m.Context.User.SendMessageAsync("```xl\n" + sb + "\n```");
+ else
+ await m.Context.Channel.SendMessageAsync("```xl\n" + sb + "\n```");
+ }
+
+ public static async Task ReplyAsync(this ModuleBase m, IEnumerable<string> message, TimeSpan time)
+ {
+ var sb = new StringBuilder();
+ foreach (var re in message)
+ {
+ if (sb.Length + re.Length > 1798)
+ {
+ await m.ReplyTimedAsync(sb.ToString(), time);
+
+
+ sb.Clear();
+ }
+
+ sb.AppendLine(re);
+ }
+
+ await m.ReplyTimedAsync(sb.ToString(), TimeSpan.FromSeconds(90));
+ }
+
+ public static async Task SendWebFile(this IMessageChannel channel,
+ string url = "https://i.imgur.com/0iHEycJ.png")
+ {
+ if (_client == null) _client = new WebClient();
+
+ var stream = _client.OpenRead(url);
+ await channel.SendFileAsync(stream, url.Split('/').Last());
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Auxiliary/Dice.cs b/dsa/DiscoBot/Auxiliary/Dice.cs
new file mode 100644
index 0000000..f0f4def
--- /dev/null
+++ b/dsa/DiscoBot/Auxiliary/Dice.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace DiscoBot.Auxiliary
+{
+ public static class Dice // roll it!
+ {
+ private static readonly Random Rnd = new Random();
+
+ public static int Roll(int d = 20)
+ {
+ return Rnd.Next(d) + 1;
+ }
+
+
+ public static int Roll(int count, int d)
+ {
+ if (d <= 0) return 0;
+
+ var sum = 0;
+ for (var i = 0; i < Math.Abs(count); i++)
+ {
+ var roll = Roll(d);
+ sum += roll;
+ }
+
+ sum *= Math.Abs(count) / count;
+
+ return sum;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Auxiliary/Permissions.cs b/dsa/DiscoBot/Auxiliary/Permissions.cs
new file mode 100644
index 0000000..3ec4a2e
--- /dev/null
+++ b/dsa/DiscoBot/Auxiliary/Permissions.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Linq;
+using Discord.Commands;
+using Discord.WebSocket;
+
+namespace DiscoBot.Auxiliary
+{
+ public static class Permissions
+ {
+ public static bool Check(ICommandContext c, string role)
+ {
+ return ((SocketGuildUser) c.User).Roles.ToList().Exists(v => v.Name.Equals(role));
+ }
+
+ public static bool Check(ICommandContext c, IEnumerable<string> roles)
+ {
+ return roles.Any(role => ((SocketGuildUser) c.User).Roles.ToList().Exists(v => v.Name.Equals(role)));
+ }
+
+ public static bool Test(ICommandContext c, string role)
+ {
+ if (Check(c, role)) return true;
+ c.Channel.SendMessageAsync("```xl\n Keine ausreichenden Berechtigungen\n```").Wait();
+ return false;
+ }
+
+ public static void Test(ICommandContext c, string[] roles)
+ {
+ if (!Check(c, roles)) c.Channel.SendMessageAsync("```xl\n Keine ausreichenden Berechtigungen\n```").Wait();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Auxiliary/RandomMisc.cs b/dsa/DiscoBot/Auxiliary/RandomMisc.cs
new file mode 100644
index 0000000..205b3a7
--- /dev/null
+++ b/dsa/DiscoBot/Auxiliary/RandomMisc.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace DiscoBot.Auxiliary
+{
+ public static class RandomMisc
+ {
+ public static string Roll(string input)
+ {
+ var output = new StringBuilder();
+ var strings = input.Split('w', 'd').ToList();
+ var count = Convert.ToInt32(strings[0]);
+ strings = strings[1].Split(' ').ToList();
+ var d = Convert.ToInt32(strings[0]);
+
+ if (strings.Count > 0)
+ {
+ }
+
+ var sum = 0;
+ for (var i = 0; i < count; i++)
+ {
+ var roll = Dice.Roll(d);
+ sum += roll;
+ output.Append("[" + roll + "] ");
+ }
+
+ if (strings.Count <= 1) return output.ToString();
+ sum += Convert.ToInt32(strings[1]);
+ output.Append("sum: " + sum);
+
+ return output.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Auxiliary/SpellCorrect.cs b/dsa/DiscoBot/Auxiliary/SpellCorrect.cs
new file mode 100644
index 0000000..c4bd4bf
--- /dev/null
+++ b/dsa/DiscoBot/Auxiliary/SpellCorrect.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Diagnostics;
+
+namespace DiscoBot.Auxiliary
+{
+ public class SpellCorrect : StringComparer
+ {
+ public const int ErrorThreshold = 94100;
+
+ public override int Compare(string x, string y)
+ {
+ return CompareEasy(x, y);
+ }
+
+ public static int CompareEasy(string x, string y)
+ {
+ if (string.IsNullOrEmpty(x)) throw new ArgumentException("message", nameof(x));
+
+ if (string.IsNullOrEmpty(y)) throw new ArgumentException("message", nameof(y));
+
+ if (x.Equals(y)) return 0;
+
+ x = x.ToLower();
+ y = y.ToLower();
+ if (x.Equals(y)) return 1;
+
+ var subs = y.Split(' ', '/');
+ var score = subs.Length;
+ foreach (var s in subs)
+ if (s.Equals(x))
+ score--;
+
+ if (score < subs.Length) return score + 1;
+
+ return 100000 - (int) (CompareExact(x, y) * 1000.0);
+ /*if (y.Contains(x))
+ return 6;*/
+ }
+
+ public override bool Equals(string x, string y)
+ {
+ Debug.Assert(x != null, nameof(x) + " != null");
+ return x.Equals(y);
+ }
+
+ public override int GetHashCode(string obj)
+ {
+ throw new NotImplementedException();
+ }
+
+ public static double CompareExact(string s, string q)
+ {
+ s = s.ToLower();
+ q = q.ToLower();
+
+ int i, j;
+ const double match = 3.0;
+ const double gap = -2.0;
+ const double mismatch = -2.0;
+
+ double decay;
+
+ var matrix = new double[s.Length + 1, q.Length + 1];
+ var max = 0.0;
+ matrix[0, 0] = 0.0;
+
+ for (i = 1; i < s.Length; i++)
+ // matrix[i, 0] = 0.0;
+ matrix[i, 0] = i * gap;
+
+ for (i = 1; i < q.Length; i++) matrix[0, i] = 0.0;
+
+
+ for (i = 1; i <= s.Length; i++)
+ for (j = 1; j <= q.Length; j++)
+ {
+ decay = j / (double) (s.Length * 1000);
+ var add = s[i - 1] == q[j - 1] ? match - decay : mismatch;
+ var score = matrix[i - 1, j - 1] + add;
+
+ if (score < matrix[i - 1, j] + gap) score = matrix[i - 1, j] + gap;
+
+ if (score < matrix[i, j - 1] + gap) score = matrix[i, j - 1] + gap;
+
+ if (i > 1 && j > 1)
+ if (s[i - 1] == q[j - 2] && s[i - 2] == q[j - 1])
+ {
+ add = 3 / 2.0 * match - decay;
+ if (score < matrix[i - 2, j - 2] + add) score = matrix[i - 2, j - 2] + add;
+ }
+
+ // if (score < 0)
+ // {
+ // score = 0;
+ // }
+
+ if (max < score && i == s.Length) max = score;
+
+ matrix[i, j] = score;
+ }
+
+ return max;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Commands/CommandExtension.cs b/dsa/DiscoBot/Commands/CommandExtension.cs
new file mode 100644
index 0000000..098e35f
--- /dev/null
+++ b/dsa/DiscoBot/Commands/CommandExtension.cs
@@ -0,0 +1,119 @@
+namespace DiscoBot.Auxiliary
+{
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ using Discord;
+ using Discord.Commands;
+
+ public static class CommandExtension
+ {
+ private static WebClient client;
+
+ public static async Task ReplyTimedAsync(this ModuleBase m, string message, TimeSpan time)
+ {
+ var token = message.GetHashCode();
+ var send = m.Context.Channel.SendMessageAsync($"#{token}\n```xl\n{message}```", false);
+
+ var barInvoker = new BackgroundWorker();
+ barInvoker.DoWork += delegate
+ {
+ Thread.Sleep(time);
+ Delete(token, m);
+ };
+
+ await send;
+ barInvoker.RunWorkerAsync();
+ }
+
+ private static void Delete(int token, ModuleBase m)
+ {
+ var messagesAsync = m.Context.Channel.GetMessagesAsync();
+ Task.WaitAll(messagesAsync.ToArray());
+ var list = messagesAsync.ToEnumerable().ToList();
+ var messages = new List<IMessage>();
+ foreach (var task in list)
+ {
+ messages.AddRange(task.ToList());
+ }
+
+ var test = messages.Where(x => x.Content.StartsWith($"#{token}\n") && x.Author.IsBot).Select(c=>c );
+ var waiters = new List<Task>();
+ foreach (var message in test)
+ {
+ waiters.Add((message as IUserMessage).DeleteAsync());
+ }
+ Task.WaitAll(waiters.ToArray());
+ }
+
+ public static async Task ReplyAsync(this ModuleBase m, IEnumerable<string> message, bool directMessage = false)
+ {
+ var sb = new StringBuilder();
+ foreach (string re in message)
+ {
+ if (sb.Length + re.Length > 1798)
+ {
+ if (directMessage)
+ {
+ await m.Context.User.SendMessageAsync("```xl\n" + sb + "\n```");
+ }
+ else
+ {
+ await m.Context.Channel.SendMessageAsync("```xl\n" + sb + "\n```");
+ }
+
+ sb.Clear();
+ }
+
+ sb.AppendLine(re);
+ }
+
+ if (directMessage)
+ {
+ await m.Context.User.SendMessageAsync("```xl\n" + sb + "\n```");
+ }
+ else
+ {
+ await m.Context.Channel.SendMessageAsync("```xl\n" + sb + "\n```");
+ }
+ }
+
+ public static async Task ReplyAsync(this ModuleBase m, IEnumerable<string> message, TimeSpan time)
+ {
+ var sb = new StringBuilder();
+ foreach (string re in message)
+ {
+ if (sb.Length + re.Length > 1798)
+ {
+
+ await m.ReplyTimedAsync(sb.ToString(), time);
+
+
+ sb.Clear();
+ }
+
+ sb.AppendLine(re);
+ }
+
+ await m.ReplyTimedAsync(sb.ToString(), TimeSpan.FromSeconds(90));
+ }
+
+ public static async Task SendWebFile(this IMessageChannel channel, string url = "https://i.imgur.com/0iHEycJ.png")
+ {
+ if (client == null)
+ {
+ client = new WebClient();
+ }
+
+ Stream stream = client.OpenRead(url);
+ await channel.SendFileAsync(stream, url.Split('/').Last());
+ }
+ }
+}
diff --git a/dsa/DiscoBot/Commands/FileHandler.cs b/dsa/DiscoBot/Commands/FileHandler.cs
new file mode 100644
index 0000000..4f8a785
--- /dev/null
+++ b/dsa/DiscoBot/Commands/FileHandler.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Discord.Commands;
+
+namespace DiscoBot.Commands
+{
+ public class FileHandler : ModuleBase
+ {
+ //[Command("send"), Summary("fügt Helden hinzu")]
+ public void AddChar()
+ {
+ var msg = Context.Message;
+ if (msg.Attachments == null) throw new ArgumentException("Es wurde keine Datei angehängt");
+
+ var attachments = msg.Attachments.ToList();
+
+ if (!attachments.Any(x => x.Filename.EndsWith(".xml")))
+ throw new ArgumentException("Es wurde kein xml Held mitgeschickt");
+
+ foreach (var attachment in attachments.Where(x => x.Filename.EndsWith(".xml")))
+ throw new NotImplementedException("send File to Server");
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Commands/MiscCommands.cs b/dsa/DiscoBot/Commands/MiscCommands.cs
new file mode 100644
index 0000000..738796c
--- /dev/null
+++ b/dsa/DiscoBot/Commands/MiscCommands.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DiscoBot.Auxiliary;
+using Discord;
+using Discord.Commands;
+
+namespace DiscoBot.Commands
+{
+ public class MiscCommands : ModuleBase
+ {
+ [Command("r")]
+ [Summary("Würfelt ")]
+ [Alias("R", "Roll", "roll", "Würfle")]
+ public Task RollAsync([Remainder] [Summary("Weapon")] string roll)
+ {
+ //return this.ReplyAsync("```xl\n" + new Auxiliary.Calculator.StringSolver(roll).Solve() + "\n```");
+ return ReplyAsync("```xl\n" + RandomMisc.Roll(roll) + "\n```");
+ }
+
+
+ [Command("say")]
+ [Summary("Echos a message.")]
+ [Alias("s")]
+ public Task SayAsync([Remainder] [Summary("The text to echo")]
+ string echo)
+ {
+ return ReplyAsync(echo);
+ }
+
+ [Command("liebe")]
+ [Summary("Echos a message.")]
+ [Alias("Liebe", "<3", "love")]
+ public async Task LoveAsync()
+ {
+ var rand = new Random();
+ var user = Context.Channel.GetUsersAsync().ToList().Result.ToList().First()
+ .Where(x => x.Status != UserStatus.Offline).OrderBy(x => rand.Next()).First();
+ await ReplyAsync(
+ ":heart: :heart: :heart: Verteilt die Liebe! :heart: :heart: :heart: \n Besondere Liebe geht an " +
+ user.Username);
+ //await this.ReplyAsync("!liebe");
+ }
+
+ [Command("maul")]
+ [Summary("Echos a message.")]
+ public Task MaulAsync()
+ {
+ return ReplyAsync(
+ "Maul...? Du meintest doch sicher Maulwürfe oder? \n:heart: :heart: :heart: \nGanz viel Liebe für Maulwürfe !\n:heart: :heart: :heart:");
+ }
+
+
+ [Command("match")]
+ [Summary("Tinder.")]
+ [Alias("mach", "pass", "passt")]
+ public Task TinderAsync(string s1, string s2)
+ {
+ var rand = new Random((s1 + s2).GetHashCode());
+
+ var wert = Math.Log10(Math.Floor(1000.0 * (SpellCorrect.CompareExact(s1, s2) + rand.NextDouble() * 10.0)) /
+ 1000.0);
+ wert = wert * 100.0 < 100.0 ? wert * 100.0 : 100.0 - wert;
+ wert = wert < 0 ? -wert : wert;
+ return ReplyAsync($"Ihr passt zu {Math.Floor(100.0 * wert) / 100.0}% zusammen");
+ }
+
+ [Command("reddit")]
+ [Summary("Reddit.")]
+ public Task RedditAsync()
+ {
+ return ReplyAsync(
+ "Ein Archiv der Vergangenen Aktionen findet man hier: https://www.reddit.com/r/ReconquistaInternet/");
+ }
+
+ [Command("compare")]
+ [Summary("Echos a message.")]
+ public async Task KickAsync()
+ {
+ //await this.Context.Guild.DownloadUsersAsync();
+ var users = Context.Guild.GetUsersAsync();
+ var test = File.ReadAllLines("RG.txt");
+ await users;
+ var us = users.Result.Select(x => x.Username);
+
+ var lines = test.Where(x => !x.Equals(string.Empty)).ToList();
+
+
+ var sc = new SpellCorrect();
+
+ var res = new List<string>();
+
+ foreach (var line in lines)
+ {
+ var best = us.OrderBy(user => sc.Compare(user, line)).First();
+
+ double fit = sc.Compare(best, line);
+
+ if (!(fit < SpellCorrect.ErrorThreshold - 20000)) continue;
+ res.Add(fit.Equals(0) ? $"@\t{best} !!! => {line}" : $"-\t{best} hat Ähnlichkeit mit: {line}");
+ }
+
+ var sb = new StringBuilder();
+ foreach (var re in res)
+ {
+ if (sb.Length + re.Length > 1798)
+ {
+ await this.ReplyTimedAsync(sb.ToString(), TimeSpan.FromSeconds(90));
+ sb.Clear();
+ }
+
+ sb.AppendLine(re);
+ }
+
+ if (Permissions.Check(Context, new[] {"Admin", "Mod"}))
+ await this.ReplyTimedAsync(sb.ToString(), TimeSpan.FromSeconds(90));
+
+ //await this.ReplyAsync($"{count} Duplikate gefunden");
+ }
+
+
+ [Command("clear")]
+ [Summary("Cleans up messages.")]
+ public void DeleteAsync(int count)
+ {
+ var messagesAsync = Context.Channel.GetMessagesAsync(count);
+ if (messagesAsync != null)
+ {
+ Task.WaitAll(messagesAsync.ToArray());
+ var list = messagesAsync.ToEnumerable().ToList();
+ var messages = new List<IMessage>();
+ foreach (var task in list) messages.AddRange(task.ToList());
+
+ if (Permissions.Check(Context, new[] {"Admin", "Mod", "Meister"}))
+ {
+ var waiters = new List<Task>();
+ foreach (var message in messages) waiters.Add(((IUserMessage) message).DeleteAsync());
+
+ Task.WaitAll(waiters.ToArray());
+ }
+ }
+ }
+
+ [Command("check")]
+ [Summary("Echos a message.")]
+ [Alias("Check")]
+ public async Task CheckAsync(string quarry)
+ {
+ var perm = new List<string> {"Admin", "Mod", "Privatpolizei"};
+
+ Permissions.Test(Context, perm.ToArray());
+
+ var test = File.ReadAllLines("RG.txt");
+
+ var lines = test.Where(x => !x.Equals(string.Empty)).ToList();
+
+
+ var sc = new SpellCorrect();
+ var count = lines.OrderBy(line => sc.Compare(quarry, line)).First();
+
+ var fit = sc.Compare(count, quarry);
+
+ string antwort;
+
+ antwort = fit < SpellCorrect.ErrorThreshold - 20000
+ ? $"```xl\nAuf anderem Server Match gefunden: {count}"
+ : $"```xl\nAuf anderem Server Kein Match gefunden: {quarry}";
+
+
+ var users = Context.Guild.GetUsersAsync();
+ await users;
+ var us = users.Result.Select(x => x.Username);
+
+ sc = new SpellCorrect();
+ count = us.OrderBy(line => sc.Compare(quarry, line)).First();
+
+ fit = sc.Compare(count, quarry);
+
+ antwort = fit < SpellCorrect.ErrorThreshold - 20000
+ ? $"{antwort}\nAuf unserem Server Match gefunden: {count}\n```"
+ : $"{antwort}\nAuf unserem Server Kein Match gefunden: {quarry} \n```";
+
+ await ReplyAsync(antwort);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/DiscoBot.csproj b/dsa/DiscoBot/DiscoBot.csproj
new file mode 100644
index 0000000..09f4cfd
--- /dev/null
+++ b/dsa/DiscoBot/DiscoBot.csproj
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{1186AF1C-BC46-4B3D-BEE0-CE478B8AEAC7}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <RootNamespace>DiscoBot</RootNamespace>
+ <AssemblyName>DiscoBot</AssemblyName>
+ <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <TargetFrameworkProfile />
+ <NuGetPackageImportStamp>
+ </NuGetPackageImportStamp>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Discord.Net.Commands, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\Discord.Net.Commands.2.1.0\lib\net46\Discord.Net.Commands.dll</HintPath>
+ </Reference>
+ <Reference Include="Discord.Net.Core, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\Discord.Net.Core.2.1.0\lib\net46\Discord.Net.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="Discord.Net.Rest, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\Discord.Net.Rest.2.1.0\lib\net46\Discord.Net.Rest.dll</HintPath>
+ </Reference>
+ <Reference Include="Discord.Net.Rpc, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\Discord.Net.Rpc.1.0.2\lib\net45\Discord.Net.Rpc.dll</HintPath>
+ </Reference>
+ <Reference Include="Discord.Net.Webhook, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\Discord.Net.Webhook.2.1.0\lib\netstandard1.3\Discord.Net.Webhook.dll</HintPath>
+ </Reference>
+ <Reference Include="Discord.Net.WebSocket, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\Discord.Net.WebSocket.2.1.0\lib\net46\Discord.Net.WebSocket.dll</HintPath>
+ </Reference>
+ <Reference Include="FSharp.Core, Version=4.6.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\packages\FSharp.Core.4.6.2\lib\net45\FSharp.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.Extensions.DependencyInjection, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+ <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.2.2.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+ <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
+ </Reference>
+ <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
+ </Reference>
+ <Reference Include="System.ComponentModel.Composition" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.3.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Diagnostics.DiagnosticSource.4.5.1\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Interactive.Async, Version=3.2.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Interactive.Async.3.2.0\lib\net46\System.Interactive.Async.dll</HintPath>
+ </Reference>
+ <Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Net.Http, Version=4.1.1.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll</HintPath>
+ <Private>True</Private>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Runtime, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll</HintPath>
+ <Private>True</Private>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System.Runtime.Extensions, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll</HintPath>
+ <Private>True</Private>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System.Security.Cryptography.Algorithms, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net461\System.Security.Cryptography.Algorithms.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Text.RegularExpressions.4.3.1\lib\net463\System.Text.RegularExpressions.dll</HintPath>
+ <Private>True</Private>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Auxiliary\CommandExtension.cs" />
+ <Compile Include="Auxiliary\Dice.cs" />
+ <Compile Include="Auxiliary\Permissions.cs" />
+ <Compile Include="Commands\MiscCommands.cs" />
+ <Compile Include="Auxiliary\SpellCorrect.cs" />
+ <Compile Include="Commands\FileHandler.cs" />
+ <Compile Include="Auxiliary\RandomMisc.cs" />
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ <DependentUpon>Settings.settings</DependentUpon>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ <None Include="Help.json" />
+ <None Include="packages.config" />
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="ToRework\" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" />
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+ <PropertyGroup>
+ <ErrorText>Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".</ErrorText>
+ </PropertyGroup>
+ <Error Condition="!Exists('..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets'))" />
+ </Target>
+</Project> \ No newline at end of file
diff --git a/dsa/DiscoBot/Help.json b/dsa/DiscoBot/Help.json
new file mode 100644
index 0000000..44ce054
--- /dev/null
+++ b/dsa/DiscoBot/Help.json
@@ -0,0 +1,120 @@
+{
+ "CommandInfos": [
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Setzt den gespielten Charakter fest",
+ "Description": [
+ "Mit \"!Ich bin\" kann der gespielte Charakter definiert, bzw. gewechselt werden.\n",
+ " Die Charaktere müssen als *.xml Dateien hinterlegt sein.\n\n",
+ " !ich Zeigt an welcher Charakter zur Zeit gespielt wird\n",
+ " !ich bin Zalibius Wechsel zum Helden Zalibius\n",
+ " !ich Rhoktar Orkische Variante von !ich bin.\n",
+ " Wechselt zu Rhoktar.\n\n",
+ " !list chars Zeigt die Liste verfügbarer Charaktere.\n",
+ " \n"
+ ]
+ },
+ {
+ "Name": "List",
+ "Scope": "All",
+ "Brief": "Anzeige vonSpielrelevanten Listen",
+ "Description": [
+ "Mit \"!list\" lassen sich spielrelevante Listen ausgeben. Die Angezeigte Liste wird nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !list chars Liste aller verfügbaren Helden (eingelesen per *.xml)\n",
+ " und NSCs.\n",
+ " (Mit \"!ich bin\" kann der Held ausgewählt werden.)\n",
+ " !list commands Liste aller verwendbaren Bot-Kommandos.\n",
+ " !list sounds Liste der Soundeffekte." ]
+ },
+ {
+ "Name": "Held",
+ "Scope": "All",
+ "Brief": "Anzeige von Heldenwerten",
+ "Description": [
+ "Mit \"!Held\" lassen sich Heldenwerte ausgeben. Mehrere Werte können gleichzeitig angefordert werde. \"!Held LE Waffen\" liefert so z.B. Informationen zur Lebensenergie und den Kampfwerten.\n Bis auf wenige Ausnahmen wird die Angezeigte Liste nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !Held Zeigt den Heldenbrief an.\n",
+ " (Diese Liste wird nicht automatisch gelöscht)\n",
+ " !Held Eigenschaften Zeigt die Eigenschaften MU/KL/CH/IN/KK/GE/FF/KO.\n",
+ " !Held e Kurzform von \"!Held Eigenschaften\".\n",
+ " !Held LE Zeigt LE an.\n",
+ " !Held AE Zeigt AE an.\n",
+ " !Held stats Zeigt Eigenschaften und zusätzlich SO/LE/AE.\n",
+ " !Held Kampfwerte Zeigt AT/PA für aktivierte Waffentalente.\n",
+ " !Held Waffe/!list w Kurzformen von \"!Held Kampfwerte\".\n",
+ " !Held Vorteile Zeigt Vor- und Nachteile an.\n",
+ " !Held v Kurzform von \"!Held Vorteile\".\n",
+ " !Held Talente Zeigt die Liste aller aktivierten Talente, deren TaW,\n",
+ " sowie die Probe.\n",
+ " !Held t Kurzform von \"!Held Talente\".\n",
+ " !Held Zauber Zeigt die Liste aller aktivierten Zauber, deren ZaW,\n",
+ " sowie die Probe.\n",
+ " !Held z Kurzform von \"!Held Zauber\".\n"
+ ]
+ },
+ {
+ "Name": "LE",
+ "Scope": "All",
+ "Brief": "Ändert dein Leben - im wahrsten Sinne des Wortes",
+ "Description": [
+ "Mit !LE zeigt man die Lebensenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !LE Zeigt Lebensenergie an\n",
+ " !LE 30 Setzt LE auf 30\n",
+ " !LE +5 Erhöht LE um 5 (bis zum Maximum)\n",
+ " !LE ++5 Erhöht LE um 5 (ignoriert Maximum)\n",
+ " !LE -5 Verringert LE um 5\n \n"
+ ]
+ },
+ {
+ "Name": "AE",
+ "Scope": "All",
+ "Brief": "Ändert Astralenergie",
+ "Description": [
+ "Mit !AE (oder !Asp) zeigt man die Astralenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !AE Zeigt Astralenergie an\n",
+ " !AE 30 Setzt Asp auf 30\n",
+ " !AE +5 Erhöht Asp um 5 (bis zum Maximum)\n",
+ " !AE ++5 Erhöht Asp um 5 (ignoriert Maximum)\n",
+ " !AE -5 Verringert Asp um 5 (Minimum 0)\n"
+ ]
+ },
+ {
+ "Name": "Gm",
+ "Scope": "Meister",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ },
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ }
+ ]
+}
diff --git a/dsa/DiscoBot/Program.cs b/dsa/DiscoBot/Program.cs
new file mode 100644
index 0000000..4314a8d
--- /dev/null
+++ b/dsa/DiscoBot/Program.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+
+namespace DiscoBot
+{
+ public class Program
+ {
+ private DiscordSocketClient client;
+ private CommandService commands;
+ private IServiceProvider services = null;
+
+ public static void Main(string[] args)
+ {
+ new Program().StartAsync().GetAwaiter().GetResult();
+ }
+
+ public async Task StartAsync()
+ {
+ client = new DiscordSocketClient();
+ commands = new CommandService();
+
+
+ var token = File.ReadAllText("Token");
+ //Properties.Settings.Default.Token;
+
+ AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
+
+ await InstallCommandsAsync();
+
+ await client.LoginAsync(TokenType.Bot, token);
+ await client.StartAsync();
+
+ await Task.Delay(-1);
+ }
+
+ public Task InstallCommandsAsync()
+ {
+ // Hook the MessageReceived Event into our Command Handler
+ client.MessageReceived += HandleCommandAsync;
+
+ // Discover all of the commands in this assembly and load them.
+ return commands.AddModulesAsync(Assembly.GetEntryAssembly());
+ }
+
+ public async Task HandleCommandAsync(SocketMessage messageParam)
+ {
+ // Don't process the command if it was a System Message
+ if (!(messageParam is SocketUserMessage message)) return;
+
+ // Create a number to track where the prefix ends and the command begins
+ var argPos = 0;
+
+ // Determine if the message is a command, based on if it starts with '!' or a mention prefix
+ if (!(message.HasCharPrefix('!', ref argPos) ||
+ message.HasMentionPrefix(client.CurrentUser, ref argPos))) return;
+
+
+ // Create a Command Context
+ var context = new CommandContext(client, message);
+
+ // Execute the command. (result does not indicate a return value,
+ // rather an object stating if the command executed successfully)
+ var result = await commands.ExecuteAsync(context, argPos, services);
+ if (result.Error == CommandError.UnknownCommand)
+ await context.Channel.SendMessageAsync(SendCommand(message.Author.Username, message.Content,
+ "https://kobert.dev/api/dsa/commands"));
+ else if (!result.IsSuccess) await context.Channel.SendMessageAsync(result.ErrorReason);
+ }
+
+ private static string SendCommand(string name, string command, string url)
+ {
+ var httpWebRequest = (HttpWebRequest) WebRequest.Create(url);
+ httpWebRequest.ContentType = "application/json";
+ httpWebRequest.Method = "POST";
+
+ using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
+ {
+ command = command.Remove(0, 1);
+ var args = command.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
+
+ var content = string.Empty;
+ if (args.Length > 1) content = "\"" + args.Skip(1).Aggregate((s, n) => s + "\", \"" + n) + "\"";
+
+ var json = "{\"Name\":\"" + name + "\"," +
+ "\"CmdIdentifier\":\"" + args.First() + "\"," +
+ "\"CmdTexts\": [" + content + "] }";
+
+
+ streamWriter.Write(json);
+ streamWriter.Flush();
+ streamWriter.Close();
+ }
+
+ var httpResponse = (HttpWebResponse) httpWebRequest.GetResponse();
+ using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
+ {
+ return streamReader.ReadToEnd();
+ }
+ }
+
+ private static void OnProcessExit(object sender, EventArgs e)
+ {
+ Console.WriteLine("I'm out of here");
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Properties.json b/dsa/DiscoBot/Properties.json
new file mode 100644
index 0000000..8808dbf
--- /dev/null
+++ b/dsa/DiscoBot/Properties.json
@@ -0,0 +1,129 @@
+{
+ "CommandInfos": [
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Setzt den gespielten Charakter fest",
+ "Description": [
+ "Mit \"!Ich bin\" kann der gespielte Charakter definiert, bzw. gewechselt werden.\n",
+ " Die Charaktere müssen als *.xml Dateien hinterlegt sein.\n\n",
+ " !ich Zeigt an welcher Charakter zur Zeit gespielt wird\n",
+ " !ich bin Zalibius Wechsel zum Helden Zalibius\n",
+ " !ich Rhoktar Orkische Variante von !ich bin.\n",
+ " Wechselt zu Rhoktar.\n\n",
+ " !list chars Zeigt die Liste verfügbarer Charaktere.\n",
+ " \n"
+ ]
+ },
+ {
+ "Name": "List",
+ "Scope": "All",
+ "Brief": "Anzeige vonSpielrelevanten Listen",
+ "Description": [
+ "Mit \"!list\" lassen sich spielrelevante Listen ausgeben. Die Angezeigte Liste wird nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !list chars Liste aller verfügbaren Helden (eingelesen per *.xml)\n",
+ " und NSCs.\n",
+ " (Mit \"!ich bin\" kann der Held ausgewählt werden.)\n",
+ " !list commands Liste aller verwendbaren Bot-Kommandos.\n",
+ " !list sounds Liste der Soundeffekte."
+ ]
+ },
+ {
+ "Name": "Held",
+ "Scope": "All",
+ "Brief": "Anzeige von Heldenwerten",
+ "Description": [
+ "Mit \"!Held\" lassen sich Heldenwerte ausgeben. Mehrere Werte können gleichzeitig angefordert werde. \"!Held LE Waffen\" liefert so z.B. Informationen zur Lebensenergie und den Kampfwerten.\n Bis auf wenige Ausnahmen wird die Angezeigte Liste nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !Held Zeigt den Heldenbrief an.\n",
+ " (Diese Liste wird nicht automatisch gelöscht)\n",
+ " !Held Eigenschaften Zeigt die Eigenschaften MU/KL/CH/IN/KK/GE/FF/KO.\n",
+ " !Held e Kurzform von \"!Held Eigenschaften\".\n",
+ " !Held LE Zeigt LE an.\n",
+ " !Held AE Zeigt AE an.\n",
+ " !Held stats Zeigt Eigenschaften und zusätzlich SO/LE/AE.\n",
+ " !Held Kampfwerte Zeigt AT/PA für aktivierte Waffentalente.\n",
+ " !Held Waffe/!list w Kurzformen von \"!Held Kampfwerte\".\n",
+ " !Held Vorteile Zeigt Vor- und Nachteile an.\n",
+ " !Held v Kurzform von \"!Held Vorteile\".\n",
+ " !Held Talente Zeigt die Liste aller aktivierten Talente, deren TaW,\n",
+ " sowie die Probe.\n",
+ " !Held t Kurzform von \"!Held Talente\".\n",
+ " !Held Zauber Zeigt die Liste aller aktivierten Zauber, deren ZaW,\n",
+ " sowie die Probe.\n",
+ " !Held z Kurzform von \"!Held Zauber\".\n"
+ ]
+ },
+ {
+ "Name": "LE",
+ "Scope": "All",
+ "Brief": "Ändert dein Leben - im wahrsten Sinne des Wortes",
+ "Description": [
+ "Mit !LE zeigt man die Lebensenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !LE Zeigt Lebensenergie an\n",
+ " !LE 30 Setzt LE auf 30\n",
+ " !LE +5 Erhöht LE um 5 (bis zum Maximum)\n",
+ " !LE ++5 Erhöht LE um 5 (ignoriert Maximum)\n",
+ " !LE -5 Verringert LE um 5\n \n"
+ ]
+ },
+ {
+ "Name": "AE",
+ "Scope": "All",
+ "Brief": "Ändert Astralenergie",
+ "Description": [
+ "Mit !AE (oder !Asp) zeigt man die Astralenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !AE Zeigt Astralenergie an\n",
+ " !AE 30 Setzt Asp auf 30\n",
+ " !AE +5 Erhöht Asp um 5 (bis zum Maximum)\n",
+ " !AE ++5 Erhöht Asp um 5 (ignoriert Maximum)\n",
+ " !AE -5 Verringert Asp um 5 (Minimum 0)\n"
+ ]
+ },
+ {
+ "Name": "Gm",
+ "Scope": "Meister",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ },
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ }
+ ],
+ "Sounds": [
+ {
+ "Name": "Test",
+ "Url": "http",
+ "Volume": 100
+ }
+
+ ]
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/Properties/AssemblyInfo.cs b/dsa/DiscoBot/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b0c2901
--- /dev/null
+++ b/dsa/DiscoBot/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// Allgemeine Informationen über eine Assembly werden über die folgenden
+// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
+// die einer Assembly zugeordnet sind.
+[assembly: AssemblyTitle("DiscoBot")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DiscoBot")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
+// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
+// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
+[assembly: Guid("1186af1c-bc46-4b3d-bee0-ce478b8aeac7")]
+
+// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
+//
+// Hauptversion
+// Nebenversion
+// Buildnummer
+// Revision
+//
+// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
+// übernehmen, indem Sie "*" eingeben:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file
diff --git a/dsa/DiscoBot/Properties/DiscoBot-Audio-Sound.json b/dsa/DiscoBot/Properties/DiscoBot-Audio-Sound.json
new file mode 100644
index 0000000..87a0e6b
--- /dev/null
+++ b/dsa/DiscoBot/Properties/DiscoBot-Audio-Sound.json
@@ -0,0 +1,7 @@
+[
+ {
+ "Name": "Test",
+ "Url": "http",
+ "Volume": 100
+ }
+] \ No newline at end of file
diff --git a/dsa/DiscoBot/Properties/DiscoBot-Auxiliary-CommandInfo.json b/dsa/DiscoBot/Properties/DiscoBot-Auxiliary-CommandInfo.json
new file mode 100644
index 0000000..b9941f2
--- /dev/null
+++ b/dsa/DiscoBot/Properties/DiscoBot-Auxiliary-CommandInfo.json
@@ -0,0 +1,101 @@
+[
+ {
+ "Name": "ich bin",
+ "Scope": "All",
+ "Brief": "Setzt den gespielten Charakter fest",
+ "Description": [
+ "Mit \"!Ich bin\" kann der gespielte Charakter definiert, bzw. gewechselt werden.\n",
+ " Die Charaktere müssen als *.xml Dateien hinterlegt sein.\n\n",
+ " !ich Zeigt an welcher Charakter zur Zeit gespielt wird\n",
+ " !ich bin Zalibius Wechsel zum Helden Zalibius\n",
+ " !ich Rhoktar Orkische Variante von !ich bin.\n",
+ " Wechselt zu Rhoktar.\n\n",
+ " !list chars Zeigt die Liste verfügbarer Charaktere.\n",
+ " \n"
+ ]
+ },
+ {
+ "Name": "List",
+ "Scope": "All",
+ "Brief": "Anzeige vonSpielrelevanten Listen",
+ "Description": [
+ "Mit \"!list\" lassen sich spielrelevante Listen ausgeben. Die Angezeigte Liste wird nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !list chars Liste aller verfügbaren Helden (eingelesen per *.xml)\n",
+ " und NSCs.\n",
+ " (Mit \"!ich bin\" kann der Held ausgewählt werden.)\n",
+ " !list commands Liste aller verwendbaren Bot-Kommandos.\n",
+ " !list sounds Liste der Soundeffekte."
+ ]
+ },
+ {
+ "Name": "Held",
+ "Scope": "All",
+ "Brief": "Anzeige von Heldenwerten",
+ "Description": [
+ "Mit \"!Held\" lassen sich Heldenwerte ausgeben. Mehrere Werte können gleichzeitig angefordert werde. \"!Held LE Waffen\" liefert so z.B. Informationen zur Lebensenergie und den Kampfwerten.\n Bis auf wenige Ausnahmen wird die Angezeigte Liste nach einiger Zeit wieder gelöscht.\n",
+ "\n",
+ " !Held Zeigt den Heldenbrief an.\n",
+ " (Diese Liste wird nicht automatisch gelöscht)\n",
+ " !Held Eigenschaften Zeigt die Eigenschaften MU/KL/CH/IN/KK/GE/FF/KO.\n",
+ " !Held e Kurzform von \"!Held Eigenschaften\".\n",
+ " !Held LE Zeigt LE an.\n",
+ " !Held AE Zeigt AE an.\n",
+ " !Held stats Zeigt Eigenschaften und zusätzlich SO/LE/AE.\n",
+ " !Held Kampfwerte Zeigt AT/PA für aktivierte Waffentalente.\n",
+ " !Held Waffe/!list w Kurzformen von \"!Held Kampfwerte\".\n",
+ " !Held Vorteile Zeigt Vor- und Nachteile an.\n",
+ " !Held v Kurzform von \"!Held Vorteile\".\n",
+ " !Held Talente Zeigt die Liste aller aktivierten Talente, deren TaW,\n",
+ " sowie die Probe.\n",
+ " !Held t Kurzform von \"!Held Talente\".\n",
+ " !Held Zauber Zeigt die Liste aller aktivierten Zauber, deren ZaW,\n",
+ " sowie die Probe.\n",
+ " !Held z Kurzform von \"!Held Zauber\".\n"
+ ]
+ },
+ {
+ "Name": "LE",
+ "Scope": "All",
+ "Brief": "Ändert dein Leben - im wahrsten Sinne des Wortes",
+ "Description": [
+ "Mit !LE zeigt man die Lebensenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !LE Zeigt Lebensenergie an\n",
+ " !LE 30 Setzt LE auf 30\n",
+ " !LE +5 Erhöht LE um 5 (bis zum Maximum)\n",
+ " !LE ++5 Erhöht LE um 5 (ignoriert Maximum)\n",
+ " !LE -5 Verringert LE um 5\n \n"
+ ]
+ },
+ {
+ "Name": "AE",
+ "Scope": "All",
+ "Brief": "Ändert Astralenergie",
+ "Description": [
+ "Mit !AE (oder !Asp) zeigt man die Astralenergie an, ändert sie, oder setzt sie auf einen neuen Wert\n\n",
+ " !AE Zeigt Astralenergie an\n",
+ " !AE 30 Setzt Asp auf 30\n",
+ " !AE +5 Erhöht Asp um 5 (bis zum Maximum)\n",
+ " !AE ++5 Erhöht Asp um 5 (ignoriert Maximum)\n",
+ " !AE -5 Verringert Asp um 5 (Minimum 0)\n"
+ ]
+ },
+ {
+ "Name": "Gm",
+ "Scope": "Meister",
+ "Brief": "Kontrolliere andere Charaktere",
+ "Description": [
+ "Mit !GM fürhrt man commands als eine andere Person aus.",
+ " !GM [charaktername] [command] [commandattribut] [mofifier]",
+ " Unterstützte [commands]'s:",
+ " !GM [name] LE",
+ " !GM [name] AE",
+ " !GM [name] Talent",
+ " !GM [name] Fernkampf",
+ " !GM [name] Eigenschaft",
+ " !GM [name] Zauber",
+ " !GM [name] Angriff",
+ " !GM [name] Parade"
+ ]
+ }
+] \ No newline at end of file
diff --git a/dsa/DiscoBot/Properties/DiscoBot-DSA_Game-Characters-Character.json b/dsa/DiscoBot/Properties/DiscoBot-DSA_Game-Characters-Character.json
new file mode 100644
index 0000000..fd387f5
--- /dev/null
+++ b/dsa/DiscoBot/Properties/DiscoBot-DSA_Game-Characters-Character.json
@@ -0,0 +1,290 @@
+[
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 30,
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 20,
+ "Astralpunkte_Aktuell": 20,
+ "Name": "Felis Exodus Schattenwald"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 29,
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Gardist"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 31,
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Hartmut Reiher"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 21,
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 35,
+ "Astralpunkte_Aktuell": 35,
+ "Name": "Helga vom Drachenei, Tausendsasserin"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 25,
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Krenko"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 0,
+ "Astralpunkte_Aktuell": 0,
+ "Name": "Ledur Torfinson"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 26,
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Morla"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 40,
+ "Astralpunkte_Aktuell": 40,
+ "Name": "Numeri Illuminus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 39,
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 16,
+ "Astralpunkte_Aktuell": 16,
+ "Name": "Potus"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 18,
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 13,
+ "Astralpunkte_Aktuell": 13,
+ "Name": "Pump aus der Gosse"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 34,
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 17,
+ "Astralpunkte_Aktuell": 17,
+ "Name": "Rhoktar4"
+ },
+ {
+ "Eigenschaften": {},
+ "Talente": [],
+ "Zauber": [],
+ "Kampftalente": [],
+ "Vorteile": [],
+ "PropTable": {
+ "MU": "Mut",
+ "KL": "Klugheit",
+ "IN": "Intuition",
+ "CH": "Charisma",
+ "FF": "Fingerfertigkeit",
+ "GE": "Gewandtheit",
+ "KO": "Konstitution",
+ "KK": "Körperkraft"
+ },
+ "Lebenspunkte_Basis": 28,
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Basis": 0,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Basis": 43,
+ "Astralpunkte_Aktuell": 43,
+ "Name": "Volant"
+ }
+] \ No newline at end of file
diff --git a/dsa/DiscoBot/Properties/Settings.Designer.cs b/dsa/DiscoBot/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..f80dfa5
--- /dev/null
+++ b/dsa/DiscoBot/Properties/Settings.Designer.cs
@@ -0,0 +1,38 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// Dieser Code wurde von einem Tool generiert.
+// Laufzeitversion:4.0.30319.42000
+//
+// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
+// der Code erneut generiert wird.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DiscoBot.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("Mjk0NTU0MDU4Nzg4NzAwMTYx.DOzDcQ.J-nCikbZdZtdrug0E8TwwV_2ITw")]
+ public string Token {
+ get {
+ return ((string)(this["Token"]));
+ }
+ set {
+ this["Token"] = value;
+ }
+ }
+ }
+}
diff --git a/dsa/DiscoBot/Properties/Settings.settings b/dsa/DiscoBot/Properties/Settings.settings
new file mode 100644
index 0000000..f3be2b7
--- /dev/null
+++ b/dsa/DiscoBot/Properties/Settings.settings
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="DiscoBot.Properties" GeneratedClassName="Settings">
+ <Profiles />
+ <Settings>
+ <Setting Name="Token" Type="System.String" Scope="User">
+ <Value Profile="(Default)">Mjk0NTU0MDU4Nzg4NzAwMTYx.DOzDcQ.J-nCikbZdZtdrug0E8TwwV_2ITw</Value>
+ </Setting>
+ </Settings>
+</SettingsFile> \ No newline at end of file
diff --git a/dsa/DiscoBot/Rework/Permissions.cs b/dsa/DiscoBot/Rework/Permissions.cs
new file mode 100644
index 0000000..4d73146
--- /dev/null
+++ b/dsa/DiscoBot/Rework/Permissions.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DiscoBot.Auxiliary
+{
+ using Discord.Commands;
+ using Discord.WebSocket;
+
+ public static class Permissions
+ {
+ public static bool Check(ICommandContext c, string role)
+ {
+ return ((SocketGuildUser)c.User).Roles.ToList().Exists(v => v.Name.Equals(role));
+ }
+
+ public static bool Check(ICommandContext c, string[] roles)
+ {
+ return roles.Any(role => ((SocketGuildUser)c.User).Roles.ToList().Exists(v => v.Name.Equals(role)));
+ }
+
+ public static bool Test(ICommandContext c, string role)
+ {
+ if (!Check(c, role))
+ {
+ c.Channel.SendMessageAsync("```xl\n Keine ausreichenden Berechtigungen\n```").Wait();
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void Test(ICommandContext c, string[] roles)
+ {
+ if (!Check(c, roles))
+ {
+ c.Channel.SendMessageAsync("```xl\n Keine ausreichenden Berechtigungen\n```").Wait();
+ }
+ }
+ }
+}
diff --git a/dsa/DiscoBot/Token b/dsa/DiscoBot/Token
new file mode 100644
index 0000000..4b78e50
--- /dev/null
+++ b/dsa/DiscoBot/Token
@@ -0,0 +1 @@
+Mjk0NTU0MDU4Nzg4NzAwMTYx.DgAvuw.amZ0Ep7-FKjToTf_wnY3h5Ep4Ow \ No newline at end of file
diff --git a/dsa/DiscoBot/packages.config b/dsa/DiscoBot/packages.config
new file mode 100644
index 0000000..75a1f83
--- /dev/null
+++ b/dsa/DiscoBot/packages.config
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Discord.Net" version="2.1.0" targetFramework="net472" />
+ <package id="Discord.Net.Commands" version="2.1.0" targetFramework="net472" />
+ <package id="Discord.Net.Core" version="2.1.0" targetFramework="net472" />
+ <package id="Discord.Net.Rest" version="2.1.0" targetFramework="net472" />
+ <package id="Discord.Net.Rpc" version="1.0.2" targetFramework="net461" />
+ <package id="Discord.Net.Webhook" version="2.1.0" targetFramework="net472" />
+ <package id="Discord.Net.WebSocket" version="2.1.0" targetFramework="net472" />
+ <package id="FSharp.Core" version="4.6.2" targetFramework="net472" />
+ <package id="Microsoft.Extensions.DependencyInjection" version="2.2.0" targetFramework="net472" />
+ <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="2.2.0" targetFramework="net472" />
+ <package id="Microsoft.NETCore.Platforms" version="2.2.1" targetFramework="net472" />
+ <package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net461" />
+ <package id="NETStandard.Library" version="2.0.3" targetFramework="net461" />
+ <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
+ <package id="System.AppContext" version="4.3.0" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.Collections" version="4.3.0" targetFramework="net461" />
+ <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net461" />
+ <package id="System.Collections.Immutable" version="1.5.0" targetFramework="net461" />
+ <package id="System.ComponentModel" version="4.3.0" targetFramework="net461" />
+ <package id="System.Console" version="4.3.1" targetFramework="net461" />
+ <package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net461" />
+ <package id="System.Diagnostics.DiagnosticSource" version="4.5.1" targetFramework="net472" />
+ <package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net461" />
+ <package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.Globalization" version="4.3.0" targetFramework="net461" />
+ <package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net461" />
+ <package id="System.Interactive.Async" version="3.2.0" targetFramework="net461" />
+ <package id="System.IO" version="4.3.0" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.IO.Compression" version="4.3.0" targetFramework="net461" />
+ <package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net461" />
+ <package id="System.IO.FileSystem" version="4.3.0" targetFramework="net461" />
+ <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net461" />
+ <package id="System.Linq" version="4.3.0" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.Net.Http" version="4.3.4" targetFramework="net472" />
+ <package id="System.Net.Primitives" version="4.3.1" targetFramework="net472" />
+ <package id="System.Net.Sockets" version="4.3.0" targetFramework="net461" />
+ <package id="System.ObjectModel" version="4.3.0" targetFramework="net461" />
+ <package id="System.Reflection" version="4.3.0" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net461" />
+ <package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net461" />
+ <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime" version="4.3.1" targetFramework="net472" />
+ <package id="System.Runtime.Extensions" version="4.3.1" targetFramework="net472" />
+ <package id="System.Runtime.Handles" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net461" />
+ <package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net461" requireReinstallation="true" />
+ <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net461" />
+ <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net461" />
+ <package id="System.Security.Cryptography.X509Certificates" version="4.3.2" targetFramework="net461" />
+ <package id="System.Text.Encoding" version="4.3.0" targetFramework="net461" />
+ <package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net461" />
+ <package id="System.Text.RegularExpressions" version="4.3.1" targetFramework="net472" />
+ <package id="System.Threading" version="4.3.0" targetFramework="net461" />
+ <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net461" />
+ <package id="System.Threading.Timer" version="4.3.0" targetFramework="net461" />
+ <package id="System.Xml.ReaderWriter" version="4.3.1" targetFramework="net461" />
+ <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net461" />
+</packages> \ No newline at end of file
diff --git a/dsa/DiscoBot/session.json b/dsa/DiscoBot/session.json
new file mode 100644
index 0000000..c81cbf9
--- /dev/null
+++ b/dsa/DiscoBot/session.json
@@ -0,0 +1,6 @@
+{
+ "GeneralContext": null,
+ "Relation": {},
+ "Chars": [],
+ "SessionName": null
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/sessions/TheCrew/TheCrew-0.json b/dsa/DiscoBot/sessions/TheCrew/TheCrew-0.json
new file mode 100644
index 0000000..575cd54
--- /dev/null
+++ b/dsa/DiscoBot/sessions/TheCrew/TheCrew-0.json
@@ -0,0 +1,83 @@
+{
+ "GeneralContext": null,
+ "Relation": {
+ "Nicolas": "Hartmut Reiher",
+ "MagicBro5": "Krenko",
+ "TrueKuehli": "Ledur Torfinson"
+ },
+ "Chars": [
+ {
+ "Name": "Felis Exodus Schattenwald",
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 20
+ },
+ {
+ "Name": "Gardist",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Hartmut Reiher",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Helga vom Drachenei, Tausendsasserin",
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 35
+ },
+ {
+ "Name": "Krenko",
+ "Lebenspunkte_Aktuell": 22,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Ledur Torfinson",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Morla",
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Numeri Illuminus",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 40
+ },
+ {
+ "Name": "Potus",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 16
+ },
+ {
+ "Name": "Pump aus der Gosse",
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Rhoktar4",
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 17
+ },
+ {
+ "Name": "Volant",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 43
+ }
+ ],
+ "SessionName": "TheCrew"
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/sessions/copy/copy-0.json b/dsa/DiscoBot/sessions/copy/copy-0.json
new file mode 100644
index 0000000..03c46f3
--- /dev/null
+++ b/dsa/DiscoBot/sessions/copy/copy-0.json
@@ -0,0 +1,79 @@
+{
+ "GeneralContext": null,
+ "Relation": {},
+ "Chars": [
+ {
+ "Name": "Felis Exodus Schattenwald",
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 20
+ },
+ {
+ "Name": "Gardist",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Hartmut Reiher",
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Helga vom Drachenei, Tausendsasserin",
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 35
+ },
+ {
+ "Name": "Krenko",
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Ledur Torfinson",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Morla",
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Numeri Illuminus",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 40
+ },
+ {
+ "Name": "Potus",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 16
+ },
+ {
+ "Name": "Pump aus der Gosse",
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Rhoktar4",
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 17
+ },
+ {
+ "Name": "Volant",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 43
+ }
+ ],
+ "SessionName": null
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/sessions/test/test-0.json b/dsa/DiscoBot/sessions/test/test-0.json
new file mode 100644
index 0000000..03c46f3
--- /dev/null
+++ b/dsa/DiscoBot/sessions/test/test-0.json
@@ -0,0 +1,79 @@
+{
+ "GeneralContext": null,
+ "Relation": {},
+ "Chars": [
+ {
+ "Name": "Felis Exodus Schattenwald",
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 20
+ },
+ {
+ "Name": "Gardist",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Hartmut Reiher",
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Helga vom Drachenei, Tausendsasserin",
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 35
+ },
+ {
+ "Name": "Krenko",
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Ledur Torfinson",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Morla",
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Numeri Illuminus",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 40
+ },
+ {
+ "Name": "Potus",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 16
+ },
+ {
+ "Name": "Pump aus der Gosse",
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Rhoktar4",
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 17
+ },
+ {
+ "Name": "Volant",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 43
+ }
+ ],
+ "SessionName": null
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/sessions/test/test-1.json b/dsa/DiscoBot/sessions/test/test-1.json
new file mode 100644
index 0000000..03c46f3
--- /dev/null
+++ b/dsa/DiscoBot/sessions/test/test-1.json
@@ -0,0 +1,79 @@
+{
+ "GeneralContext": null,
+ "Relation": {},
+ "Chars": [
+ {
+ "Name": "Felis Exodus Schattenwald",
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 20
+ },
+ {
+ "Name": "Gardist",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Hartmut Reiher",
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Helga vom Drachenei, Tausendsasserin",
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 35
+ },
+ {
+ "Name": "Krenko",
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Ledur Torfinson",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Morla",
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Numeri Illuminus",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 40
+ },
+ {
+ "Name": "Potus",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 16
+ },
+ {
+ "Name": "Pump aus der Gosse",
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Rhoktar4",
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 17
+ },
+ {
+ "Name": "Volant",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 43
+ }
+ ],
+ "SessionName": null
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/sessions/test/test-2.json b/dsa/DiscoBot/sessions/test/test-2.json
new file mode 100644
index 0000000..3458c52
--- /dev/null
+++ b/dsa/DiscoBot/sessions/test/test-2.json
@@ -0,0 +1,81 @@
+{
+ "GeneralContext": null,
+ "Relation": {
+ "The Doctor": "Felis Exodus Schattenwald"
+ },
+ "Chars": [
+ {
+ "Name": "Felis Exodus Schattenwald",
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 20
+ },
+ {
+ "Name": "Gardist",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Hartmut Reiher",
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Helga vom Drachenei, Tausendsasserin",
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 35
+ },
+ {
+ "Name": "Krenko",
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Ledur Torfinson",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Morla",
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Numeri Illuminus",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 40
+ },
+ {
+ "Name": "Potus",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 16
+ },
+ {
+ "Name": "Pump aus der Gosse",
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Rhoktar4",
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 17
+ },
+ {
+ "Name": "Volant",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 43
+ }
+ ],
+ "SessionName": null
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/sessions/test/test-3.json b/dsa/DiscoBot/sessions/test/test-3.json
new file mode 100644
index 0000000..3458c52
--- /dev/null
+++ b/dsa/DiscoBot/sessions/test/test-3.json
@@ -0,0 +1,81 @@
+{
+ "GeneralContext": null,
+ "Relation": {
+ "The Doctor": "Felis Exodus Schattenwald"
+ },
+ "Chars": [
+ {
+ "Name": "Felis Exodus Schattenwald",
+ "Lebenspunkte_Aktuell": 30,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 20
+ },
+ {
+ "Name": "Gardist",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Hartmut Reiher",
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Helga vom Drachenei, Tausendsasserin",
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 35
+ },
+ {
+ "Name": "Krenko",
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Ledur Torfinson",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Morla",
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Numeri Illuminus",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 40
+ },
+ {
+ "Name": "Potus",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 16
+ },
+ {
+ "Name": "Pump aus der Gosse",
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Rhoktar4",
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 17
+ },
+ {
+ "Name": "Volant",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 43
+ }
+ ],
+ "SessionName": null
+} \ No newline at end of file
diff --git a/dsa/DiscoBot/sessions/test/test-4.json b/dsa/DiscoBot/sessions/test/test-4.json
new file mode 100644
index 0000000..46853cf
--- /dev/null
+++ b/dsa/DiscoBot/sessions/test/test-4.json
@@ -0,0 +1,81 @@
+{
+ "GeneralContext": null,
+ "Relation": {
+ "The Doctor": "Felis Exodus Schattenwald"
+ },
+ "Chars": [
+ {
+ "Name": "Felis Exodus Schattenwald",
+ "Lebenspunkte_Aktuell": 20,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 20
+ },
+ {
+ "Name": "Gardist",
+ "Lebenspunkte_Aktuell": 29,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Hartmut Reiher",
+ "Lebenspunkte_Aktuell": 31,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Helga vom Drachenei, Tausendsasserin",
+ "Lebenspunkte_Aktuell": 21,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 35
+ },
+ {
+ "Name": "Krenko",
+ "Lebenspunkte_Aktuell": 25,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Ledur Torfinson",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 0
+ },
+ {
+ "Name": "Morla",
+ "Lebenspunkte_Aktuell": 26,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Numeri Illuminus",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 40
+ },
+ {
+ "Name": "Potus",
+ "Lebenspunkte_Aktuell": 39,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 16
+ },
+ {
+ "Name": "Pump aus der Gosse",
+ "Lebenspunkte_Aktuell": 18,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 13
+ },
+ {
+ "Name": "Rhoktar4",
+ "Lebenspunkte_Aktuell": 34,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 17
+ },
+ {
+ "Name": "Volant",
+ "Lebenspunkte_Aktuell": 28,
+ "Ausdauer_Aktuell": 0,
+ "Astralpunkte_Aktuell": 43
+ }
+ ],
+ "SessionName": null
+} \ No newline at end of file
diff --git a/dsa/FireBase/ExceptionEventArgs.cs b/dsa/FireBase/ExceptionEventArgs.cs
new file mode 100644
index 0000000..09c205a
--- /dev/null
+++ b/dsa/FireBase/ExceptionEventArgs.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Firebase.Database
+{
+ /// <summary>
+ /// Event args holding the <see cref="Exception" /> object.
+ /// </summary>
+ public class ExceptionEventArgs<T> : EventArgs where T : Exception
+ {
+ public readonly T Exception;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExceptionEventArgs" /> class.
+ /// </summary>
+ /// <param name="exception"> The exception. </param>
+ public ExceptionEventArgs(T exception)
+ {
+ Exception = exception;
+ }
+ }
+
+ public class ExceptionEventArgs : ExceptionEventArgs<Exception>
+ {
+ public ExceptionEventArgs(Exception exception) : base(exception)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Extensions/ObservableExtensions.cs b/dsa/FireBase/Extensions/ObservableExtensions.cs
new file mode 100644
index 0000000..0a672d7
--- /dev/null
+++ b/dsa/FireBase/Extensions/ObservableExtensions.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Reactive.Linq;
+
+namespace Firebase.Database.Extensions
+{
+ public static class ObservableExtensions
+ {
+ /// <summary>
+ /// Returns a cold observable which retries (re-subscribes to) the source observable on error until it successfully
+ /// terminates.
+ /// </summary>
+ /// <param name="source">The source observable.</param>
+ /// <param name="dueTime">How long to wait between attempts.</param>
+ /// <param name="retryOnError">A predicate determining for which exceptions to retry. Defaults to all</param>
+ /// <returns>
+ /// A cold observable which retries (re-subscribes to) the source observable on error up to the
+ /// specified number of times or until it successfully terminates.
+ /// </returns>
+ public static IObservable<T> RetryAfterDelay<T, TException>(
+ this IObservable<T> source,
+ TimeSpan dueTime,
+ Func<TException, bool> retryOnError)
+ where TException : Exception
+ {
+ var attempt = 0;
+
+ return Observable.Defer(() =>
+ {
+ return (++attempt == 1 ? source : source.DelaySubscription(dueTime))
+ .Select(item => new Tuple<bool, T, Exception>(true, item, null))
+ .Catch<Tuple<bool, T, Exception>, TException>(e => retryOnError(e)
+ ? Observable.Throw<Tuple<bool, T, Exception>>(e)
+ : Observable.Return(new Tuple<bool, T, Exception>(false, default(T), e)));
+ })
+ .Retry()
+ .SelectMany(t => t.Item1
+ ? Observable.Return(t.Item2)
+ : Observable.Throw<T>(t.Item3));
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Extensions/TaskExtensions.cs b/dsa/FireBase/Extensions/TaskExtensions.cs
new file mode 100644
index 0000000..c955b3a
--- /dev/null
+++ b/dsa/FireBase/Extensions/TaskExtensions.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Firebase.Database.Extensions
+{
+ public static class TaskExtensions
+ {
+ /// <summary>
+ /// Instead of unwrapping <see cref="AggregateException" /> it throws it as it is.
+ /// </summary>
+ public static async Task WithAggregateException(this Task source)
+ {
+ try
+ {
+ await source.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw source.Exception ?? ex;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/FireBase.csproj b/dsa/FireBase/FireBase.csproj
new file mode 100644
index 0000000..2a47b27
--- /dev/null
+++ b/dsa/FireBase/FireBase.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp2.2</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="LiteDB" Version="4.1.4" />
+ <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
+ <PackageReference Include="System.Reactive" Version="4.1.0" />
+ </ItemGroup>
+
+</Project>
diff --git a/dsa/FireBase/FirebaseClient.cs b/dsa/FireBase/FirebaseClient.cs
new file mode 100644
index 0000000..3079f3b
--- /dev/null
+++ b/dsa/FireBase/FirebaseClient.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using Firebase.Database.Query;
+
+[assembly: InternalsVisibleTo("Firebase.Database.Tests")]
+
+namespace Firebase.Database
+{
+ /// <summary>
+ /// Firebase client which acts as an entry point to the online database.
+ /// </summary>
+ public class FirebaseClient : IDisposable
+ {
+ private readonly string baseUrl;
+ internal readonly HttpClient HttpClient;
+ internal readonly FirebaseOptions Options;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FirebaseClient" /> class.
+ /// </summary>
+ /// <param name="baseUrl"> The base url. </param>
+ /// <param name="offlineDatabaseFactory"> Offline database. </param>
+ public FirebaseClient(string baseUrl, FirebaseOptions options = null)
+ {
+ HttpClient = new HttpClient();
+ Options = options ?? new FirebaseOptions();
+
+ this.baseUrl = baseUrl;
+
+ if (!this.baseUrl.EndsWith("/")) this.baseUrl += "/";
+ }
+
+ public void Dispose()
+ {
+ HttpClient?.Dispose();
+ }
+
+ /// <summary>
+ /// Queries for a child of the data root.
+ /// </summary>
+ /// <param name="resourceName"> Name of the child. </param>
+ /// <returns> <see cref="ChildQuery" />. </returns>
+ public ChildQuery Child(string resourceName)
+ {
+ return new ChildQuery(this, () => baseUrl + resourceName);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/FirebaseException.cs b/dsa/FireBase/FirebaseException.cs
new file mode 100644
index 0000000..cfc09d3
--- /dev/null
+++ b/dsa/FireBase/FirebaseException.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Net;
+
+namespace Firebase.Database
+{
+ public class FirebaseException : Exception
+ {
+ public FirebaseException(string requestUrl, string requestData, string responseData, HttpStatusCode statusCode)
+ : base(GenerateExceptionMessage(requestUrl, requestData, responseData))
+ {
+ RequestUrl = requestUrl;
+ RequestData = requestData;
+ ResponseData = responseData;
+ StatusCode = statusCode;
+ }
+
+ public FirebaseException(string requestUrl, string requestData, string responseData, HttpStatusCode statusCode,
+ Exception innerException)
+ : base(GenerateExceptionMessage(requestUrl, requestData, responseData), innerException)
+ {
+ RequestUrl = requestUrl;
+ RequestData = requestData;
+ ResponseData = responseData;
+ StatusCode = statusCode;
+ }
+
+ /// <summary>
+ /// Post data passed to the authentication service.
+ /// </summary>
+ public string RequestData { get; }
+
+ /// <summary>
+ /// Original url of the request.
+ /// </summary>
+ public string RequestUrl { get; }
+
+ /// <summary>
+ /// Response from the authentication service.
+ /// </summary>
+ public string ResponseData { get; }
+
+ /// <summary>
+ /// Status code of the response.
+ /// </summary>
+ public HttpStatusCode StatusCode { get; }
+
+ private static string GenerateExceptionMessage(string requestUrl, string requestData, string responseData)
+ {
+ return
+ $"Exception occured while processing the request.\nUrl: {requestUrl}\nRequest Data: {requestData}\nResponse: {responseData}";
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/FirebaseKeyGenerator.cs b/dsa/FireBase/FirebaseKeyGenerator.cs
new file mode 100644
index 0000000..37beed5
--- /dev/null
+++ b/dsa/FireBase/FirebaseKeyGenerator.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Text;
+
+namespace Firebase.Database
+{
+ /// <summary>
+ /// Offline key generator which mimics the official Firebase generators.
+ /// Credit: https://github.com/bubbafat/FirebaseSharp/blob/master/src/FirebaseSharp.Portable/FireBasePushIdGenerator.cs
+ /// </summary>
+ public class FirebaseKeyGenerator
+ {
+ // Modeled after base64 web-safe chars, but ordered by ASCII.
+ private const string PushCharsString = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
+ private static readonly char[] PushChars;
+ private static readonly DateTimeOffset Epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero);
+
+ private static readonly Random random = new Random();
+ private static readonly byte[] lastRandChars = new byte[12];
+
+ // Timestamp of last push, used to prevent local collisions if you push twice in one ms.
+ private static long lastPushTime;
+
+ static FirebaseKeyGenerator()
+ {
+ PushChars = Encoding.UTF8.GetChars(Encoding.UTF8.GetBytes(PushCharsString));
+ }
+
+ /// <summary>
+ /// Returns next firebase key based on current time.
+ /// </summary>
+ /// <returns>
+ /// The <see cref="string" />.
+ /// </returns>
+ public static string Next()
+ {
+ // We generate 72-bits of randomness which get turned into 12 characters and
+ // appended to the timestamp to prevent collisions with other clients. We store the last
+ // characters we generated because in the event of a collision, we'll use those same
+ // characters except "incremented" by one.
+ var id = new StringBuilder(20);
+ var now = (long) (DateTimeOffset.Now - Epoch).TotalMilliseconds;
+ var duplicateTime = now == lastPushTime;
+ lastPushTime = now;
+
+ var timeStampChars = new char[8];
+ for (var i = 7; i >= 0; i--)
+ {
+ var index = (int) (now % PushChars.Length);
+ timeStampChars[i] = PushChars[index];
+ now = (long) Math.Floor((double) now / PushChars.Length);
+ }
+
+ if (now != 0) throw new Exception("We should have converted the entire timestamp.");
+
+ id.Append(timeStampChars);
+
+ if (!duplicateTime)
+ {
+ for (var i = 0; i < 12; i++) lastRandChars[i] = (byte) random.Next(0, PushChars.Length);
+ }
+ else
+ {
+ // If the timestamp hasn't changed since last push, use the same random number,
+ // except incremented by 1.
+ var lastIndex = 11;
+ for (; lastIndex >= 0 && lastRandChars[lastIndex] == PushChars.Length - 1; lastIndex--)
+ lastRandChars[lastIndex] = 0;
+
+ lastRandChars[lastIndex]++;
+ }
+
+ for (var i = 0; i < 12; i++) id.Append(PushChars[lastRandChars[i]]);
+
+ if (id.Length != 20) throw new Exception("Length should be 20.");
+
+ return id.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/FirebaseObject.cs b/dsa/FireBase/FirebaseObject.cs
new file mode 100644
index 0000000..2e0fd20
--- /dev/null
+++ b/dsa/FireBase/FirebaseObject.cs
@@ -0,0 +1,27 @@
+namespace Firebase.Database
+{
+ /// <summary>
+ /// Holds the object of type
+ /// <typeparam name="T" />
+ /// along with its key.
+ /// </summary>
+ /// <typeparam name="T"> Type of the underlying object. </typeparam>
+ public class FirebaseObject<T>
+ {
+ internal FirebaseObject(string key, T obj)
+ {
+ Key = key;
+ Object = obj;
+ }
+
+ /// <summary>
+ /// Gets the key of <see cref="Object" />.
+ /// </summary>
+ public string Key { get; }
+
+ /// <summary>
+ /// Gets the underlying object.
+ /// </summary>
+ public T Object { get; }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/FirebaseOptions.cs b/dsa/FireBase/FirebaseOptions.cs
new file mode 100644
index 0000000..b4a5e51
--- /dev/null
+++ b/dsa/FireBase/FirebaseOptions.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Firebase.Database.Offline;
+using Newtonsoft.Json;
+
+namespace Firebase.Database
+{
+ public class FirebaseOptions
+ {
+ public FirebaseOptions()
+ {
+ OfflineDatabaseFactory = (t, s) => new Dictionary<string, OfflineEntry>();
+ SubscriptionStreamReaderFactory = s => new StreamReader(s);
+ JsonSerializerSettings = new JsonSerializerSettings();
+ SyncPeriod = TimeSpan.FromSeconds(10);
+ }
+
+ /// <summary>
+ /// Gets or sets the factory for Firebase offline database. Default is in-memory dictionary.
+ /// </summary>
+ public Func<Type, string, IDictionary<string, OfflineEntry>> OfflineDatabaseFactory { get; set; }
+
+ /// <summary>
+ /// Gets or sets the method for retrieving auth tokens. Default is null.
+ /// </summary>
+ public Func<Task<string>> AuthTokenAsyncFactory { get; set; }
+
+ /// <summary>
+ /// Gets or sets the factory for <see cref="TextReader" /> used for reading online streams. Default is
+ /// <see cref="StreamReader" />.
+ /// </summary>
+ public Func<Stream, TextReader> SubscriptionStreamReaderFactory { get; set; }
+
+ /// <summary>
+ /// Gets or sets the json serializer settings.
+ /// </summary>
+ public JsonSerializerSettings JsonSerializerSettings { get; set; }
+
+ /// <summary>
+ /// Gets or sets the time between synchronization attempts for pulling and pushing offline entities. Default is 10
+ /// seconds.
+ /// </summary>
+ public TimeSpan SyncPeriod { get; set; }
+
+ /// <summary>
+ /// Specify if token returned by factory will be used as "auth" url parameter or "access_token".
+ /// </summary>
+ public bool AsAccessToken { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Http/HttpClientExtensions.cs b/dsa/FireBase/Http/HttpClientExtensions.cs
new file mode 100644
index 0000000..6582769
--- /dev/null
+++ b/dsa/FireBase/Http/HttpClientExtensions.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace Firebase.Database.Http
+{
+ /// <summary>
+ /// The http client extensions for object deserializations.
+ /// </summary>
+ internal static class HttpClientExtensions
+ {
+ /// <summary>
+ /// The get object collection async.
+ /// </summary>
+ /// <param name="client"> The client. </param>
+ /// <param name="requestUri"> The request uri. </param>
+ /// <param name="jsonSerializerSettings"> The specific JSON Serializer Settings. </param>
+ /// <typeparam name="T"> The type of entities the collection should contain. </typeparam>
+ /// <returns> The <see cref="Task" />. </returns>
+ public static async Task<IReadOnlyCollection<FirebaseObject<T>>> GetObjectCollectionAsync<T>(
+ this HttpClient client, string requestUri,
+ JsonSerializerSettings jsonSerializerSettings)
+ {
+ var responseData = string.Empty;
+ var statusCode = HttpStatusCode.OK;
+
+ try
+ {
+ var response = await client.GetAsync(requestUri).ConfigureAwait(false);
+ statusCode = response.StatusCode;
+ responseData = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ response.EnsureSuccessStatusCode();
+
+ var dictionary =
+ JsonConvert.DeserializeObject<Dictionary<string, T>>(responseData, jsonSerializerSettings);
+
+ if (dictionary == null) return new FirebaseObject<T>[0];
+
+ return dictionary.Select(item => new FirebaseObject<T>(item.Key, item.Value)).ToList();
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException(requestUri, string.Empty, responseData, statusCode, ex);
+ }
+ }
+
+ /*/// <summary>
+ /// The get object collection async.
+ /// </summary>
+ /// <param name="client"> The client. </param>
+ /// <param name="requestUri"> The request uri. </param>
+ /// /// <param name="dataType"> The Data Type. </param>
+ /// <param name="jsonSerializerSettings"> The specific JSON Serializer Settings. </param>
+ /// <typeparam name="T"> The type of entities the collection should contain. </typeparam>
+ /// <returns> The <see cref="Task"/>. </returns>
+ public static async Task<IReadOnlyCollection<FirebaseObject<object>>> GetObjectCollectionAsync(this HttpClient client, string requestUri,
+ JsonSerializerSettings jsonSerializerSettings, Type dataType)
+ {
+ var responseData = string.Empty;
+ var statusCode = HttpStatusCode.OK;
+
+ try
+ {
+ var response = await client.GetAsync(requestUri).ConfigureAwait(false);
+ statusCode = response.StatusCode;
+ responseData = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ response.EnsureSuccessStatusCode();
+
+ Type dicType = typeof(Dictionary<,>).MakeGenericType(typeof(string), dataType);
+
+ var dictionary = JsonConvert.DeserializeObject(responseData,dicType, jsonSerializerSettings) as Dictionary<string, object>;
+
+ if (dictionary == null)
+ {
+ return new FirebaseObject<object>[0];
+ }
+
+ return dictionary.Select(item => new FirebaseObject<object>(item.Key, item.Value)).ToList();
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException(requestUri, string.Empty, responseData, statusCode, ex);
+ }
+ }*/
+
+ /// <summary>
+ /// The get object collection async.
+ /// </summary>
+ /// <param name="data"> The json data. </param>
+ /// <param name="elementType"> The type of entities the collection should contain. </param>
+ /// <returns> The <see cref="Task" />. </returns>
+ public static IEnumerable<FirebaseObject<object>> GetObjectCollection(this string data, Type elementType)
+ {
+ var dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), elementType);
+ IDictionary dictionary = null;
+
+ if (data.StartsWith("["))
+ {
+ var listType = typeof(List<>).MakeGenericType(elementType);
+ var list = JsonConvert.DeserializeObject(data, listType) as IList;
+ dictionary = Activator.CreateInstance(dictionaryType) as IDictionary;
+ var index = 0;
+ foreach (var item in list) dictionary.Add(index++.ToString(), item);
+ }
+ else
+ {
+ dictionary = JsonConvert.DeserializeObject(data, dictionaryType) as IDictionary;
+ }
+
+ if (dictionary == null) yield break;
+
+ foreach (DictionaryEntry item in dictionary)
+ yield return new FirebaseObject<object>((string) item.Key, item.Value);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Http/PostResult.cs b/dsa/FireBase/Http/PostResult.cs
new file mode 100644
index 0000000..15a4894
--- /dev/null
+++ b/dsa/FireBase/Http/PostResult.cs
@@ -0,0 +1,13 @@
+namespace Firebase.Database.Http
+{
+ /// <summary>
+ /// Represents data returned after a successful POST to firebase server.
+ /// </summary>
+ public class PostResult
+ {
+ /// <summary>
+ /// Gets or sets the generated key after a successful post.
+ /// </summary>
+ public string Name { get; set; }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/ObservableExtensions.cs b/dsa/FireBase/ObservableExtensions.cs
new file mode 100644
index 0000000..bc46261
--- /dev/null
+++ b/dsa/FireBase/ObservableExtensions.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.ObjectModel;
+using Firebase.Database.Streaming;
+
+namespace Firebase.Database
+{
+ /// <summary>
+ /// Extensions for <see cref="IObservable{T}" />.
+ /// </summary>
+ public static class ObservableExtensions
+ {
+ /// <summary>
+ /// Starts observing on given firebase observable and propagates event into an <see cref="ObservableCollection{T}" />.
+ /// </summary>
+ /// <param name="observable"> The observable. </param>
+ /// <typeparam name="T"> Type of entity. </typeparam>
+ /// <returns> The <see cref="ObservableCollection{T}" />. </returns>
+ public static ObservableCollection<T> AsObservableCollection<T>(this IObservable<FirebaseEvent<T>> observable)
+ {
+ var collection = new ObservableCollection<T>();
+
+ observable.Subscribe(f =>
+ {
+ if (f.EventType == FirebaseEventType.InsertOrUpdate)
+ {
+ var i = collection.IndexOf(f.Object);
+ if (i >= 0) collection.RemoveAt(i);
+
+ collection.Add(f.Object);
+ }
+ else
+ {
+ collection.Remove(f.Object);
+ }
+ });
+
+ return collection;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/ConcurrentOfflineDatabase.cs b/dsa/FireBase/Offline/ConcurrentOfflineDatabase.cs
new file mode 100644
index 0000000..1a9e607
--- /dev/null
+++ b/dsa/FireBase/Offline/ConcurrentOfflineDatabase.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using LiteDB;
+
+namespace Firebase.Database.Offline
+{
+ /// <summary>
+ /// The offline database.
+ /// </summary>
+ public class ConcurrentOfflineDatabase : IDictionary<string, OfflineEntry>
+ {
+ private readonly ConcurrentDictionary<string, OfflineEntry> ccache;
+ private readonly LiteRepository db;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OfflineDatabase" /> class.
+ /// </summary>
+ /// <param name="itemType"> The item type which is used to determine the database file name. </param>
+ /// <param name="filenameModifier"> Custom string which will get appended to the file name. </param>
+ public ConcurrentOfflineDatabase(Type itemType, string filenameModifier)
+ {
+ var fullName = GetFileName(itemType.ToString());
+ if (fullName.Length > 100) fullName = fullName.Substring(0, 100);
+
+ var mapper = BsonMapper.Global;
+ mapper.Entity<OfflineEntry>().Id(o => o.Key);
+
+ var root = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ var filename = fullName + filenameModifier + ".db";
+ var path = Path.Combine(root, filename);
+ db = new LiteRepository(new LiteDatabase(path, mapper));
+
+ var cache = db.Database
+ .GetCollection<OfflineEntry>()
+ .FindAll()
+ .ToDictionary(o => o.Key, o => o);
+
+ ccache = new ConcurrentDictionary<string, OfflineEntry>(cache);
+ }
+
+ /// <summary>
+ /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ /// <returns> The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />. </returns>
+ public int Count => ccache.Count;
+
+ /// <summary>
+ /// Gets a value indicating whether this is a read-only collection.
+ /// </summary>
+ public bool IsReadOnly => false;
+
+ /// <summary>
+ /// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the
+ /// <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the object that
+ /// implements <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </returns>
+ public ICollection<string> Keys => ccache.Keys;
+
+ /// <summary>
+ /// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the
+ /// <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the object that
+ /// implements <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </returns>
+ public ICollection<OfflineEntry> Values => ccache.Values;
+
+ /// <summary>
+ /// Gets or sets the element with the specified key.
+ /// </summary>
+ /// <param name="key">The key of the element to get or set.</param>
+ /// <returns> The element with the specified key. </returns>
+ public OfflineEntry this[string key]
+ {
+ get => ccache[key];
+
+ set
+ {
+ ccache.AddOrUpdate(key, value, (k, existing) => value);
+ db.Upsert(value);
+ }
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the collection.
+ /// </summary>
+ /// <returns> An enumerator that can be used to iterate through the collection. </returns>
+ public IEnumerator<KeyValuePair<string, OfflineEntry>> GetEnumerator()
+ {
+ return ccache.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ /// <summary>
+ /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
+ public void Add(KeyValuePair<string, OfflineEntry> item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ /// <summary>
+ /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ public void Clear()
+ {
+ ccache.Clear();
+ db.Delete<OfflineEntry>(LiteDB.Query.All());
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.
+ /// </summary>
+ /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
+ /// <returns>
+ /// True if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />;
+ /// otherwise, false.
+ /// </returns>
+ public bool Contains(KeyValuePair<string, OfflineEntry> item)
+ {
+ return ContainsKey(item.Key);
+ }
+
+ /// <summary>
+ /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> to an
+ /// <see cref="T:System.Array" />, starting at a particular <see cref="T:System.Array" /> index.
+ /// </summary>
+ /// <param name="array">
+ /// The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied
+ /// from <see cref="T:System.Collections.Generic.ICollection`1" />. The <see cref="T:System.Array" /> must have
+ /// zero-based indexing.
+ /// </param>
+ /// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
+ public void CopyTo(KeyValuePair<string, OfflineEntry>[] array, int arrayIndex)
+ {
+ ccache.ToList().CopyTo(array, arrayIndex);
+ }
+
+ /// <summary>
+ /// Removes the first occurrence of a specific object from the
+ /// <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
+ /// <returns>
+ /// True if <paramref name="item" /> was successfully removed from the
+ /// <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false. This method also returns false if
+ /// <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </returns>
+ public bool Remove(KeyValuePair<string, OfflineEntry> item)
+ {
+ return Remove(item.Key);
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the
+ /// specified key.
+ /// </summary>
+ /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2" />.</param>
+ /// <returns>
+ /// True if the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the key;
+ /// otherwise, false.
+ /// </returns>
+ public bool ContainsKey(string key)
+ {
+ return ccache.ContainsKey(key);
+ }
+
+ /// <summary>
+ /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <param name="key">The object to use as the key of the element to add.</param>
+ /// <param name="value">The object to use as the value of the element to add.</param>
+ public void Add(string key, OfflineEntry value)
+ {
+ ccache.AddOrUpdate(key, value, (k, existing) => value);
+ db.Upsert(value);
+ }
+
+ /// <summary>
+ /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <param name="key">The key of the element to remove.</param>
+ /// <returns>
+ /// True if the element is successfully removed; otherwise, false. This method also returns false if
+ /// <paramref name="key" /> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </returns>
+ public bool Remove(string key)
+ {
+ ccache.TryRemove(key, out _);
+ return db.Delete<OfflineEntry>(key);
+ }
+
+ /// <summary>
+ /// Gets the value associated with the specified key.
+ /// </summary>
+ /// <param name="key">The key whose value to get.</param>
+ /// <param name="value">
+ /// When this method returns, the value associated with the specified key, if the key is found;
+ /// otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is passed
+ /// uninitialized.
+ /// </param>
+ /// <returns>
+ /// True if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an
+ /// element with the specified key; otherwise, false.
+ /// </returns>
+ public bool TryGetValue(string key, out OfflineEntry value)
+ {
+ return ccache.TryGetValue(key, out value);
+ }
+
+ private string GetFileName(string fileName)
+ {
+ var invalidChars = new[] {'`', '[', ',', '='};
+ foreach (var c in invalidChars.Concat(Path.GetInvalidFileNameChars()).Distinct())
+ fileName = fileName.Replace(c, '_');
+
+ return fileName;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/DatabaseExtensions.cs b/dsa/FireBase/Offline/DatabaseExtensions.cs
new file mode 100644
index 0000000..e7c4074
--- /dev/null
+++ b/dsa/FireBase/Offline/DatabaseExtensions.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
+using Firebase.Database.Query;
+
+namespace Firebase.Database.Offline
+{
+ public static class DatabaseExtensions
+ {
+ /// <summary>
+ /// Create new instances of the <see cref="RealtimeDatabase{T}" />.
+ /// </summary>
+ /// <typeparam name="T"> Type of elements. </typeparam>
+ /// <param name="filenameModifier"> Custom string which will get appended to the file name. </param>
+ /// <param name="elementRoot"> Optional custom root element of received json items. </param>
+ /// <param name="streamingOptions"> Realtime streaming options. </param>
+ /// <param name="initialPullStrategy"> Specifies what strategy should be used for initial pulling of server data. </param>
+ /// <param name="pushChanges">
+ /// Specifies whether changed items should actually be pushed to the server. It this is false,
+ /// then Put / Post / Delete will not affect server data.
+ /// </param>
+ /// <returns> The <see cref="RealtimeDatabase{T}" />. </returns>
+ public static RealtimeDatabase<T> AsRealtimeDatabase<T>(this ChildQuery query, string filenameModifier = "",
+ string elementRoot = "", StreamingOptions streamingOptions = StreamingOptions.LatestOnly,
+ InitialPullStrategy initialPullStrategy = InitialPullStrategy.MissingOnly, bool pushChanges = true)
+ where T : class
+ {
+ return new RealtimeDatabase<T>(query, elementRoot, query.Client.Options.OfflineDatabaseFactory,
+ filenameModifier, streamingOptions, initialPullStrategy, pushChanges);
+ }
+
+ /// <summary>
+ /// Create new instances of the <see cref="RealtimeDatabase{T}" />.
+ /// </summary>
+ /// <typeparam name="T"> Type of elements. </typeparam>
+ /// <typeparam name="TSetHandler"> Type of the custom <see cref="ISetHandler{T}" /> to use. </typeparam>
+ /// <param name="filenameModifier"> Custom string which will get appended to the file name. </param>
+ /// <param name="elementRoot"> Optional custom root element of received json items. </param>
+ /// <param name="streamingOptions"> Realtime streaming options. </param>
+ /// <param name="initialPullStrategy"> Specifies what strategy should be used for initial pulling of server data. </param>
+ /// <param name="pushChanges">
+ /// Specifies whether changed items should actually be pushed to the server. It this is false,
+ /// then Put / Post / Delete will not affect server data.
+ /// </param>
+ /// <returns> The <see cref="RealtimeDatabase{T}" />. </returns>
+ public static RealtimeDatabase<T> AsRealtimeDatabase<T, TSetHandler>(this ChildQuery query,
+ string filenameModifier = "", string elementRoot = "",
+ StreamingOptions streamingOptions = StreamingOptions.LatestOnly,
+ InitialPullStrategy initialPullStrategy = InitialPullStrategy.MissingOnly, bool pushChanges = true)
+ where T : class
+ where TSetHandler : ISetHandler<T>, new()
+ {
+ return new RealtimeDatabase<T>(query, elementRoot, query.Client.Options.OfflineDatabaseFactory,
+ filenameModifier, streamingOptions, initialPullStrategy, pushChanges,
+ Activator.CreateInstance<TSetHandler>());
+ }
+
+ /// <summary>
+ /// Overwrites existing object with given key leaving any missing properties intact in firebase.
+ /// </summary>
+ /// <param name="key"> The key. </param>
+ /// <param name="obj"> The object to set. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Patch<T>(this RealtimeDatabase<T> db, string key, T obj, bool syncOnline = true,
+ int priority = 1)
+ where T : class
+ {
+ db.Set(key, obj, syncOnline ? SyncOptions.Patch : SyncOptions.None, priority);
+ }
+
+ /// <summary>
+ /// Overwrites existing object with given key.
+ /// </summary>
+ /// <param name="key"> The key. </param>
+ /// <param name="obj"> The object to set. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Put<T>(this RealtimeDatabase<T> db, string key, T obj, bool syncOnline = true,
+ int priority = 1)
+ where T : class
+ {
+ db.Set(key, obj, syncOnline ? SyncOptions.Put : SyncOptions.None, priority);
+ }
+
+ /// <summary>
+ /// Adds a new entity to the Database.
+ /// </summary>
+ /// <param name="obj"> The object to add. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ /// <returns> The generated key for this object. </returns>
+ public static string Post<T>(this RealtimeDatabase<T> db, T obj, bool syncOnline = true, int priority = 1)
+ where T : class
+ {
+ var key = FirebaseKeyGenerator.Next();
+
+ db.Set(key, obj, syncOnline ? SyncOptions.Put : SyncOptions.None, priority);
+
+ return key;
+ }
+
+ /// <summary>
+ /// Deletes the entity with the given key.
+ /// </summary>
+ /// <param name="key"> The key. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Delete<T>(this RealtimeDatabase<T> db, string key, bool syncOnline = true, int priority = 1)
+ where T : class
+ {
+ db.Set(key, null, syncOnline ? SyncOptions.Put : SyncOptions.None, priority);
+ }
+
+ /// <summary>
+ /// Do a Put for a nested property specified by <paramref name="propertyExpression" /> of an object with key
+ /// <paramref name="key" />.
+ /// </summary>
+ /// <typeparam name="T"> Type of the root elements. </typeparam>
+ /// <typeparam name="TProperty"> Type of the property being modified</typeparam>
+ /// <param name="db"> Database instance. </param>
+ /// <param name="key"> Key of the root element to modify. </param>
+ /// <param name="propertyExpression"> Expression on the root element leading to target value to modify. </param>
+ /// <param name="value"> Value to put. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Put<T, TProperty>(this RealtimeDatabase<T> db, string key,
+ Expression<Func<T, TProperty>> propertyExpression, TProperty value, bool syncOnline = true,
+ int priority = 1)
+ where T : class
+ {
+ db.Set(key, propertyExpression, value, syncOnline ? SyncOptions.Put : SyncOptions.None, priority);
+ }
+
+ /// <summary>
+ /// Do a Patch for a nested property specified by <paramref name="propertyExpression" /> of an object with key
+ /// <paramref name="key" />.
+ /// </summary>
+ /// <typeparam name="T"> Type of the root elements. </typeparam>
+ /// <typeparam name="TProperty"> Type of the property being modified</typeparam>
+ /// <param name="db"> Database instance. </param>
+ /// <param name="key"> Key of the root element to modify. </param>
+ /// <param name="propertyExpression"> Expression on the root element leading to target value to modify. </param>
+ /// <param name="value"> Value to patch. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Patch<T, TProperty>(this RealtimeDatabase<T> db, string key,
+ Expression<Func<T, TProperty>> propertyExpression, TProperty value, bool syncOnline = true,
+ int priority = 1)
+ where T : class
+ {
+ db.Set(key, propertyExpression, value, syncOnline ? SyncOptions.Patch : SyncOptions.None, priority);
+ }
+
+ /// <summary>
+ /// Delete a nested property specified by <paramref name="propertyExpression" /> of an object with key
+ /// <paramref name="key" />. This basically does a Put with null value.
+ /// </summary>
+ /// <typeparam name="T"> Type of the root elements. </typeparam>
+ /// <typeparam name="TProperty"> Type of the property being modified</typeparam>
+ /// <param name="db"> Database instance. </param>
+ /// <param name="key"> Key of the root element to modify. </param>
+ /// <param name="propertyExpression"> Expression on the root element leading to target value to modify. </param>
+ /// <param name="value"> Value to put. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Delete<T, TProperty>(this RealtimeDatabase<T> db, string key,
+ Expression<Func<T, TProperty>> propertyExpression, bool syncOnline = true, int priority = 1)
+ where T : class
+ where TProperty : class
+ {
+ db.Set(key, propertyExpression, null, syncOnline ? SyncOptions.Put : SyncOptions.None, priority);
+ }
+
+ /// <summary>
+ /// Post a new entity into the nested dictionary specified by <paramref name="propertyExpression" /> of an object with
+ /// key <paramref name="key" />.
+ /// The key of the new entity is automatically generated.
+ /// </summary>
+ /// <typeparam name="T"> Type of the root elements. </typeparam>
+ /// <typeparam name="TSelector"> Type of the dictionary being modified</typeparam>
+ /// <typeparam name="TProperty"> Type of the value within the dictionary being modified</typeparam>
+ /// <param name="db"> Database instance. </param>
+ /// <param name="key"> Key of the root element to modify. </param>
+ /// <param name="propertyExpression"> Expression on the root element leading to target value to modify. </param>
+ /// <param name="value"> Value to put. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Post<T, TSelector, TProperty>(this RealtimeDatabase<T> db, string key,
+ Expression<Func<T, TSelector>> propertyExpression, TProperty value, bool syncOnline = true,
+ int priority = 1)
+ where T : class
+ where TSelector : IDictionary<string, TProperty>
+ {
+ var nextKey = FirebaseKeyGenerator.Next();
+ var expression = Expression.Lambda<Func<T, TProperty>>(
+ Expression.Call(propertyExpression.Body,
+ typeof(TSelector).GetRuntimeMethod("get_Item", new[] {typeof(string)}),
+ Expression.Constant(nextKey)), propertyExpression.Parameters);
+ db.Set(key, expression, value, syncOnline ? SyncOptions.Put : SyncOptions.None, priority);
+ }
+
+ /// <summary>
+ /// Delete an entity with key <paramref name="dictionaryKey" /> in the nested dictionary specified by
+ /// <paramref name="propertyExpression" /> of an object with key <paramref name="key" />.
+ /// The key of the new entity is automatically generated.
+ /// </summary>
+ /// <typeparam name="T"> Type of the root elements. </typeparam>
+ /// <typeparam name="TSelector"> Type of the dictionary being modified</typeparam>
+ /// <typeparam name="TProperty"> Type of the value within the dictionary being modified</typeparam>
+ /// <param name="db"> Database instance. </param>
+ /// <param name="key"> Key of the root element to modify. </param>
+ /// <param name="propertyExpression"> Expression on the root element leading to target value to modify. </param>
+ /// <param name="dictionaryKey"> Key within the nested dictionary to delete. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public static void Delete<T, TProperty>(this RealtimeDatabase<T> db, string key,
+ Expression<Func<T, IDictionary<string, TProperty>>> propertyExpression, string dictionaryKey,
+ bool syncOnline = true, int priority = 1)
+ where T : class
+ {
+ var expression = Expression.Lambda<Func<T, TProperty>>(
+ Expression.Call(propertyExpression.Body,
+ typeof(IDictionary<string, TProperty>).GetRuntimeMethod("get_Item", new[] {typeof(string)}),
+ Expression.Constant(dictionaryKey)), propertyExpression.Parameters);
+ db.Set(key, expression, null, syncOnline ? SyncOptions.Put : SyncOptions.None, priority);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/ISetHandler.cs b/dsa/FireBase/Offline/ISetHandler.cs
new file mode 100644
index 0000000..c04bd41
--- /dev/null
+++ b/dsa/FireBase/Offline/ISetHandler.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Firebase.Database.Query;
+
+namespace Firebase.Database.Offline
+{
+ public interface ISetHandler<in T>
+ {
+ Task SetAsync(ChildQuery query, string key, OfflineEntry entry);
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/InitialPullStrategy.cs b/dsa/FireBase/Offline/InitialPullStrategy.cs
new file mode 100644
index 0000000..ca2bebf
--- /dev/null
+++ b/dsa/FireBase/Offline/InitialPullStrategy.cs
@@ -0,0 +1,23 @@
+namespace Firebase.Database.Offline
+{
+ /// <summary>
+ /// Specifies the strategy for initial pull of server data.
+ /// </summary>
+ public enum InitialPullStrategy
+ {
+ /// <summary>
+ /// Don't pull anything.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Pull only what isn't already stored offline.
+ /// </summary>
+ MissingOnly,
+
+ /// <summary>
+ /// Pull everything that exists on the server.
+ /// </summary>
+ Everything
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/Internals/MemberAccessVisitor.cs b/dsa/FireBase/Offline/Internals/MemberAccessVisitor.cs
new file mode 100644
index 0000000..89a77da
--- /dev/null
+++ b/dsa/FireBase/Offline/Internals/MemberAccessVisitor.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
+using Newtonsoft.Json;
+
+namespace Firebase.Database.Offline.Internals
+{
+ public class MemberAccessVisitor : ExpressionVisitor
+ {
+ private readonly IList<string> propertyNames = new List<string>();
+
+ private bool wasDictionaryAccess;
+
+ public IEnumerable<string> PropertyNames => propertyNames;
+
+ public override Expression Visit(Expression expr)
+ {
+ if (expr?.NodeType == ExpressionType.MemberAccess)
+ {
+ if (wasDictionaryAccess)
+ {
+ wasDictionaryAccess = false;
+ }
+ else
+ {
+ var memberExpr = (MemberExpression) expr;
+ var jsonAttr = memberExpr.Member.GetCustomAttribute<JsonPropertyAttribute>();
+
+ propertyNames.Add(jsonAttr?.PropertyName ?? memberExpr.Member.Name);
+ }
+ }
+ else if (expr?.NodeType == ExpressionType.Call)
+ {
+ var callExpr = (MethodCallExpression) expr;
+ if (callExpr.Method.Name == "get_Item" && callExpr.Arguments.Count == 1)
+ {
+ var e = Expression.Lambda(callExpr.Arguments[0]).Compile();
+ propertyNames.Add(e.DynamicInvoke().ToString());
+ wasDictionaryAccess = callExpr.Arguments[0].NodeType == ExpressionType.MemberAccess;
+ }
+ }
+
+ return base.Visit(expr);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/OfflineCacheAdapter.cs b/dsa/FireBase/Offline/OfflineCacheAdapter.cs
new file mode 100644
index 0000000..3153d1b
--- /dev/null
+++ b/dsa/FireBase/Offline/OfflineCacheAdapter.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Firebase.Database.Offline
+{
+ internal class OfflineCacheAdapter<TKey, T> : IDictionary<string, T>, IDictionary
+ {
+ private readonly IDictionary<string, OfflineEntry> database;
+
+ public OfflineCacheAdapter(IDictionary<string, OfflineEntry> database)
+ {
+ this.database = database;
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsSynchronized { get; }
+
+ public object SyncRoot { get; }
+
+ object IDictionary.this[object key]
+ {
+ get => database[key.ToString()].Deserialize<T>();
+
+ set
+ {
+ var keyString = key.ToString();
+ if (database.ContainsKey(keyString))
+ database[keyString] = new OfflineEntry(keyString, value, database[keyString].Priority,
+ database[keyString].SyncOptions);
+ else
+ database[keyString] = new OfflineEntry(keyString, value, 1, SyncOptions.None);
+ }
+ }
+
+ ICollection IDictionary.Values { get; }
+
+ ICollection IDictionary.Keys { get; }
+
+ public bool Contains(object key)
+ {
+ return ContainsKey(key.ToString());
+ }
+
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Remove(object key)
+ {
+ Remove(key.ToString());
+ }
+
+ public bool IsFixedSize => false;
+
+ public void Add(object key, object value)
+ {
+ Add(key.ToString(), (T) value);
+ }
+
+ public int Count => database.Count;
+
+ public bool IsReadOnly => database.IsReadOnly;
+
+ public ICollection<string> Keys => database.Keys;
+
+ public ICollection<T> Values => database.Values.Select(o => o.Deserialize<T>()).ToList();
+
+ public T this[string key]
+ {
+ get => database[key].Deserialize<T>();
+
+ set
+ {
+ if (database.ContainsKey(key))
+ database[key] = new OfflineEntry(key, value, database[key].Priority, database[key].SyncOptions);
+ else
+ database[key] = new OfflineEntry(key, value, 1, SyncOptions.None);
+ }
+ }
+
+ public IEnumerator<KeyValuePair<string, T>> GetEnumerator()
+ {
+ return database.Select(d => new KeyValuePair<string, T>(d.Key, d.Value.Deserialize<T>())).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public void Add(KeyValuePair<string, T> item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ public void Clear()
+ {
+ database.Clear();
+ }
+
+ public bool Contains(KeyValuePair<string, T> item)
+ {
+ return ContainsKey(item.Key);
+ }
+
+ public void CopyTo(KeyValuePair<string, T>[] array, int arrayIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool Remove(KeyValuePair<string, T> item)
+ {
+ return database.Remove(item.Key);
+ }
+
+ public void Add(string key, T value)
+ {
+ database.Add(key, new OfflineEntry(key, value, 1, SyncOptions.None));
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return database.ContainsKey(key);
+ }
+
+ public bool Remove(string key)
+ {
+ return database.Remove(key);
+ }
+
+ public bool TryGetValue(string key, out T value)
+ {
+ OfflineEntry val;
+
+ if (database.TryGetValue(key, out val))
+ {
+ value = val.Deserialize<T>();
+ return true;
+ }
+
+ value = default(T);
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/OfflineDatabase.cs b/dsa/FireBase/Offline/OfflineDatabase.cs
new file mode 100644
index 0000000..be0380b
--- /dev/null
+++ b/dsa/FireBase/Offline/OfflineDatabase.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using LiteDB;
+
+namespace Firebase.Database.Offline
+{
+ /// <summary>
+ /// The offline database.
+ /// </summary>
+ public class OfflineDatabase : IDictionary<string, OfflineEntry>
+ {
+ private readonly IDictionary<string, OfflineEntry> cache;
+ private readonly LiteRepository db;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OfflineDatabase" /> class.
+ /// </summary>
+ /// <param name="itemType"> The item type which is used to determine the database file name. </param>
+ /// <param name="filenameModifier"> Custom string which will get appended to the file name. </param>
+ public OfflineDatabase(Type itemType, string filenameModifier)
+ {
+ var fullName = GetFileName(itemType.ToString());
+ if (fullName.Length > 100) fullName = fullName.Substring(0, 100);
+
+ var mapper = BsonMapper.Global;
+ mapper.Entity<OfflineEntry>().Id(o => o.Key);
+
+ var root = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ var filename = fullName + filenameModifier + ".db";
+ var path = Path.Combine(root, filename);
+ db = new LiteRepository(new LiteDatabase(path, mapper));
+
+ cache = db.Database.GetCollection<OfflineEntry>().FindAll()
+ .ToDictionary(o => o.Key, o => o);
+ }
+
+ /// <summary>
+ /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ /// <returns> The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />. </returns>
+ public int Count => cache.Count;
+
+ /// <summary>
+ /// Gets a value indicating whether this is a read-only collection.
+ /// </summary>
+ public bool IsReadOnly => cache.IsReadOnly;
+
+ /// <summary>
+ /// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the
+ /// <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the keys of the object that
+ /// implements <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </returns>
+ public ICollection<string> Keys => cache.Keys;
+
+ /// <summary>
+ /// Gets an <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the
+ /// <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.Generic.ICollection`1" /> containing the values in the object that
+ /// implements <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </returns>
+ public ICollection<OfflineEntry> Values => cache.Values;
+
+ /// <summary>
+ /// Gets or sets the element with the specified key.
+ /// </summary>
+ /// <param name="key">The key of the element to get or set.</param>
+ /// <returns> The element with the specified key. </returns>
+ public OfflineEntry this[string key]
+ {
+ get => cache[key];
+
+ set
+ {
+ cache[key] = value;
+ db.Upsert(value);
+ }
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the collection.
+ /// </summary>
+ /// <returns> An enumerator that can be used to iterate through the collection. </returns>
+ public IEnumerator<KeyValuePair<string, OfflineEntry>> GetEnumerator()
+ {
+ return cache.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ /// <summary>
+ /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
+ public void Add(KeyValuePair<string, OfflineEntry> item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ /// <summary>
+ /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ public void Clear()
+ {
+ cache.Clear();
+ db.Delete<OfflineEntry>(LiteDB.Query.All());
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.
+ /// </summary>
+ /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
+ /// <returns>
+ /// True if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />;
+ /// otherwise, false.
+ /// </returns>
+ public bool Contains(KeyValuePair<string, OfflineEntry> item)
+ {
+ return ContainsKey(item.Key);
+ }
+
+ /// <summary>
+ /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> to an
+ /// <see cref="T:System.Array" />, starting at a particular <see cref="T:System.Array" /> index.
+ /// </summary>
+ /// <param name="array">
+ /// The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied
+ /// from <see cref="T:System.Collections.Generic.ICollection`1" />. The <see cref="T:System.Array" /> must have
+ /// zero-based indexing.
+ /// </param>
+ /// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
+ public void CopyTo(KeyValuePair<string, OfflineEntry>[] array, int arrayIndex)
+ {
+ cache.CopyTo(array, arrayIndex);
+ }
+
+ /// <summary>
+ /// Removes the first occurrence of a specific object from the
+ /// <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </summary>
+ /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
+ /// <returns>
+ /// True if <paramref name="item" /> was successfully removed from the
+ /// <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false. This method also returns false if
+ /// <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.
+ /// </returns>
+ public bool Remove(KeyValuePair<string, OfflineEntry> item)
+ {
+ return Remove(item.Key);
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the
+ /// specified key.
+ /// </summary>
+ /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2" />.</param>
+ /// <returns>
+ /// True if the <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an element with the key;
+ /// otherwise, false.
+ /// </returns>
+ public bool ContainsKey(string key)
+ {
+ return cache.ContainsKey(key);
+ }
+
+ /// <summary>
+ /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <param name="key">The object to use as the key of the element to add.</param>
+ /// <param name="value">The object to use as the value of the element to add.</param>
+ public void Add(string key, OfflineEntry value)
+ {
+ cache.Add(key, value);
+ db.Insert(value);
+ }
+
+ /// <summary>
+ /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </summary>
+ /// <param name="key">The key of the element to remove.</param>
+ /// <returns>
+ /// True if the element is successfully removed; otherwise, false. This method also returns false if
+ /// <paramref name="key" /> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2" />.
+ /// </returns>
+ public bool Remove(string key)
+ {
+ cache.Remove(key);
+ return db.Delete<OfflineEntry>(key);
+ }
+
+ /// <summary>
+ /// Gets the value associated with the specified key.
+ /// </summary>
+ /// <param name="key">The key whose value to get.</param>
+ /// <param name="value">
+ /// When this method returns, the value associated with the specified key, if the key is found;
+ /// otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is passed
+ /// uninitialized.
+ /// </param>
+ /// <returns>
+ /// True if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2" /> contains an
+ /// element with the specified key; otherwise, false.
+ /// </returns>
+ public bool TryGetValue(string key, out OfflineEntry value)
+ {
+ return cache.TryGetValue(key, out value);
+ }
+
+ private string GetFileName(string fileName)
+ {
+ var invalidChars = new[] {'`', '[', ',', '='};
+ foreach (var c in invalidChars.Concat(Path.GetInvalidFileNameChars()).Distinct())
+ fileName = fileName.Replace(c, '_');
+
+ return fileName;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/OfflineEntry.cs b/dsa/FireBase/Offline/OfflineEntry.cs
new file mode 100644
index 0000000..9feffa3
--- /dev/null
+++ b/dsa/FireBase/Offline/OfflineEntry.cs
@@ -0,0 +1,99 @@
+using System;
+using Newtonsoft.Json;
+
+namespace Firebase.Database.Offline
+{
+ /// <summary>
+ /// Represents an object stored in offline storage.
+ /// </summary>
+ public class OfflineEntry
+ {
+ private object dataInstance;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OfflineEntry" /> class with an already serialized object.
+ /// </summary>
+ /// <param name="key"> The key. </param>
+ /// <param name="obj"> The object. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ /// <param name="syncOptions"> The sync options. </param>
+ public OfflineEntry(string key, object obj, string data, int priority, SyncOptions syncOptions,
+ bool isPartial = false)
+ {
+ Key = key;
+ Priority = priority;
+ Data = data;
+ Timestamp = DateTime.UtcNow;
+ SyncOptions = syncOptions;
+ IsPartial = isPartial;
+
+ dataInstance = obj;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OfflineEntry" /> class.
+ /// </summary>
+ /// <param name="key"> The key. </param>
+ /// <param name="obj"> The object. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ /// <param name="syncOptions"> The sync options. </param>
+ public OfflineEntry(string key, object obj, int priority, SyncOptions syncOptions, bool isPartial = false)
+ : this(key, obj, JsonConvert.SerializeObject(obj), priority, syncOptions, isPartial)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OfflineEntry" /> class.
+ /// </summary>
+ public OfflineEntry()
+ {
+ }
+
+ /// <summary>
+ /// Gets or sets the key of this entry.
+ /// </summary>
+ public string Key { get; set; }
+
+ /// <summary>
+ /// Gets or sets the priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </summary>
+ public int Priority { get; set; }
+
+ /// <summary>
+ /// Gets or sets the timestamp when this entry was last touched.
+ /// </summary>
+ public DateTime Timestamp { get; set; }
+
+ /// <summary>
+ /// Gets or sets the <see cref="SyncOptions" /> which define what sync state this entry is in.
+ /// </summary>
+ public SyncOptions SyncOptions { get; set; }
+
+ /// <summary>
+ /// Gets or sets serialized JSON data.
+ /// </summary>
+ public string Data { get; set; }
+
+ /// <summary>
+ /// Specifies whether this is only a partial object.
+ /// </summary>
+ public bool IsPartial { get; set; }
+
+ /// <summary>
+ /// Deserializes <see cref="Data" /> into <typeparamref name="T" />. The result is cached.
+ /// </summary>
+ /// <typeparam name="T"> Type of object to deserialize into. </typeparam>
+ /// <returns> Instance of <typeparamref name="T" />. </returns>
+ public T Deserialize<T>()
+ {
+ return (T) (dataInstance ?? (dataInstance = JsonConvert.DeserializeObject<T>(Data)));
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/RealtimeDatabase.cs b/dsa/FireBase/Offline/RealtimeDatabase.cs
new file mode 100644
index 0000000..973db46
--- /dev/null
+++ b/dsa/FireBase/Offline/RealtimeDatabase.cs
@@ -0,0 +1,479 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Net;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Reactive.Threading.Tasks;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Firebase.Database.Extensions;
+using Firebase.Database.Offline.Internals;
+using Firebase.Database.Query;
+using Firebase.Database.Streaming;
+using Newtonsoft.Json;
+
+namespace Firebase.Database.Offline
+{
+ /// <summary>
+ /// The real-time Database which synchronizes online and offline data.
+ /// </summary>
+ /// <typeparam name="T"> Type of entities. </typeparam>
+ public class RealtimeDatabase<T> : IDisposable where T : class
+ {
+ private readonly ChildQuery childQuery;
+ private readonly string elementRoot;
+ private readonly FirebaseCache<T> firebaseCache;
+ private readonly InitialPullStrategy initialPullStrategy;
+ private readonly bool pushChanges;
+ private readonly StreamingOptions streamingOptions;
+ private readonly Subject<FirebaseEvent<T>> subject;
+ private FirebaseSubscription<T> firebaseSubscription;
+
+ private bool isSyncRunning;
+ private IObservable<FirebaseEvent<T>> observable;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RealtimeDatabase{T}" /> class.
+ /// </summary>
+ /// <param name="childQuery"> The child query. </param>
+ /// <param name="elementRoot"> The element Root. </param>
+ /// <param name="offlineDatabaseFactory"> The offline database factory. </param>
+ /// <param name="filenameModifier"> Custom string which will get appended to the file name. </param>
+ /// <param name="streamChanges"> Specifies whether changes should be streamed from the server. </param>
+ /// <param name="pullEverythingOnStart">
+ /// Specifies if everything should be pull from the online storage on start. It only
+ /// makes sense when <see cref="streamChanges" /> is set to true.
+ /// </param>
+ /// <param name="pushChanges">
+ /// Specifies whether changed items should actually be pushed to the server. If this is false,
+ /// then Put / Post / Delete will not affect server data.
+ /// </param>
+ public RealtimeDatabase(ChildQuery childQuery, string elementRoot,
+ Func<Type, string, IDictionary<string, OfflineEntry>> offlineDatabaseFactory, string filenameModifier,
+ StreamingOptions streamingOptions, InitialPullStrategy initialPullStrategy, bool pushChanges,
+ ISetHandler<T> setHandler = null)
+ {
+ this.childQuery = childQuery;
+ this.elementRoot = elementRoot;
+ this.streamingOptions = streamingOptions;
+ this.initialPullStrategy = initialPullStrategy;
+ this.pushChanges = pushChanges;
+ Database = offlineDatabaseFactory(typeof(T), filenameModifier);
+ firebaseCache = new FirebaseCache<T>(new OfflineCacheAdapter<string, T>(Database));
+ subject = new Subject<FirebaseEvent<T>>();
+
+ PutHandler = setHandler ?? new SetHandler<T>();
+
+ isSyncRunning = true;
+ Task.Factory.StartNew(SynchronizeThread, CancellationToken.None, TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
+ }
+
+ /// <summary>
+ /// Gets the backing Database.
+ /// </summary>
+ public IDictionary<string, OfflineEntry> Database { get; }
+
+ public ISetHandler<T> PutHandler { private get; set; }
+
+ public void Dispose()
+ {
+ subject.OnCompleted();
+ firebaseSubscription?.Dispose();
+ }
+
+ /// <summary>
+ /// Event raised whenever an exception is thrown in the synchronization thread. Exception thrown in there are
+ /// swallowed, so this event is the only way to get to them.
+ /// </summary>
+ public event EventHandler<ExceptionEventArgs> SyncExceptionThrown;
+
+ /// <summary>
+ /// Overwrites existing object with given key.
+ /// </summary>
+ /// <param name="key"> The key. </param>
+ /// <param name="obj"> The object to set. </param>
+ /// <param name="syncOnline"> Indicates whether the item should be synced online. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public void Set(string key, T obj, SyncOptions syncOptions, int priority = 1)
+ {
+ SetAndRaise(key, new OfflineEntry(key, obj, priority, syncOptions));
+ }
+
+ public void Set<TProperty>(string key, Expression<Func<T, TProperty>> propertyExpression, object value,
+ SyncOptions syncOptions, int priority = 1)
+ {
+ var fullKey = GenerateFullKey(key, propertyExpression, syncOptions);
+ var serializedObject = JsonConvert.SerializeObject(value).Trim('"', '\\');
+
+ if (fullKey.Item3)
+ {
+ if (typeof(TProperty) != typeof(string) || value == null)
+ // don't escape non-string primitives and null;
+ serializedObject = $"{{ \"{fullKey.Item2}\" : {serializedObject} }}";
+ else
+ serializedObject = $"{{ \"{fullKey.Item2}\" : \"{serializedObject}\" }}";
+ }
+
+ var setObject = firebaseCache.PushData("/" + fullKey.Item1, serializedObject).First();
+
+ if (!Database.ContainsKey(key) || Database[key].SyncOptions != SyncOptions.Patch &&
+ Database[key].SyncOptions != SyncOptions.Put)
+ Database[fullKey.Item1] =
+ new OfflineEntry(fullKey.Item1, value, serializedObject, priority, syncOptions, true);
+
+ subject.OnNext(new FirebaseEvent<T>(key, setObject.Object,
+ setObject == null ? FirebaseEventType.Delete : FirebaseEventType.InsertOrUpdate,
+ FirebaseEventSource.Offline));
+ }
+
+ /// <summary>
+ /// Fetches an object with the given key and adds it to the Database.
+ /// </summary>
+ /// <param name="key"> The key. </param>
+ /// <param name="priority">
+ /// The priority. Objects with higher priority will be synced first. Higher number indicates higher
+ /// priority.
+ /// </param>
+ public void Pull(string key, int priority = 1)
+ {
+ if (!Database.ContainsKey(key))
+ Database[key] = new OfflineEntry(key, null, priority, SyncOptions.Pull);
+ else if (Database[key].SyncOptions == SyncOptions.None)
+ // pull only if push isn't pending
+ Database[key].SyncOptions = SyncOptions.Pull;
+ }
+
+ /// <summary>
+ /// Fetches everything from the remote database.
+ /// </summary>
+ public async Task PullAsync()
+ {
+ var existingEntries = await childQuery
+ .OnceAsync<T>()
+ .ToObservable()
+ .RetryAfterDelay<IReadOnlyCollection<FirebaseObject<T>>, FirebaseException>(
+ childQuery.Client.Options.SyncPeriod,
+ ex => ex.StatusCode ==
+ HttpStatusCode
+ .OK) // OK implies the request couldn't complete due to network error.
+ .Select(e => ResetDatabaseFromInitial(e, false))
+ .SelectMany(e => e)
+ .Do(e =>
+ {
+ Database[e.Key] = new OfflineEntry(e.Key, e.Object, 1, SyncOptions.None);
+ subject.OnNext(new FirebaseEvent<T>(e.Key, e.Object, FirebaseEventType.InsertOrUpdate,
+ FirebaseEventSource.OnlinePull));
+ })
+ .ToList();
+
+ // Remove items not stored online
+ foreach (var item in Database.Keys.Except(existingEntries.Select(f => f.Key)).ToList())
+ {
+ Database.Remove(item);
+ subject.OnNext(new FirebaseEvent<T>(item, null, FirebaseEventType.Delete,
+ FirebaseEventSource.OnlinePull));
+ }
+ }
+
+ /// <summary>
+ /// Retrieves all offline items currently stored in local database.
+ /// </summary>
+ public IEnumerable<FirebaseObject<T>> Once()
+ {
+ return Database
+ .Where(kvp => !string.IsNullOrEmpty(kvp.Value.Data) && kvp.Value.Data != "null" && !kvp.Value.IsPartial)
+ .Select(kvp => new FirebaseObject<T>(kvp.Key, kvp.Value.Deserialize<T>()))
+ .ToList();
+ }
+
+ /// <summary>
+ /// Starts observing the real-time Database. Events will be fired both when change is done locally and remotely.
+ /// </summary>
+ /// <returns> Stream of <see cref="FirebaseEvent{T}" />. </returns>
+ public IObservable<FirebaseEvent<T>> AsObservable()
+ {
+ if (!isSyncRunning)
+ {
+ isSyncRunning = true;
+ Task.Factory.StartNew(SynchronizeThread, CancellationToken.None, TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
+ }
+
+ if (observable == null)
+ {
+ var initialData = Observable.Return(FirebaseEvent<T>.Empty(FirebaseEventSource.Offline));
+ if (Database.TryGetValue(elementRoot, out var oe))
+ initialData = Observable.Return(oe)
+ .Where(offlineEntry =>
+ !string.IsNullOrEmpty(offlineEntry.Data) && offlineEntry.Data != "null" &&
+ !offlineEntry.IsPartial)
+ .Select(offlineEntry => new FirebaseEvent<T>(offlineEntry.Key, offlineEntry.Deserialize<T>(),
+ FirebaseEventType.InsertOrUpdate, FirebaseEventSource.Offline));
+ else if (Database.Count > 0)
+ initialData = Database
+ .Where(kvp =>
+ !string.IsNullOrEmpty(kvp.Value.Data) && kvp.Value.Data != "null" && !kvp.Value.IsPartial)
+ .Select(kvp => new FirebaseEvent<T>(kvp.Key, kvp.Value.Deserialize<T>(),
+ FirebaseEventType.InsertOrUpdate, FirebaseEventSource.Offline))
+ .ToList()
+ .ToObservable();
+
+ observable = initialData
+ .Merge(subject)
+ .Merge(GetInitialPullObservable()
+ .RetryAfterDelay<IReadOnlyCollection<FirebaseObject<T>>, FirebaseException>(
+ childQuery.Client.Options.SyncPeriod,
+ ex => ex.StatusCode ==
+ HttpStatusCode
+ .OK) // OK implies the request couldn't complete due to network error.
+ .Select(e => ResetDatabaseFromInitial(e))
+ .SelectMany(e => e)
+ .Do(SetObjectFromInitialPull)
+ .Select(e => new FirebaseEvent<T>(e.Key, e.Object,
+ e.Object == null ? FirebaseEventType.Delete : FirebaseEventType.InsertOrUpdate,
+ FirebaseEventSource.OnlineInitial))
+ .Concat(Observable.Create<FirebaseEvent<T>>(observer =>
+ InitializeStreamingSubscription(observer))))
+ .Do(next => { }, e => observable = null, () => observable = null)
+ .Replay()
+ .RefCount();
+ }
+
+ return observable;
+ }
+
+ private IReadOnlyCollection<FirebaseObject<T>> ResetDatabaseFromInitial(
+ IReadOnlyCollection<FirebaseObject<T>> collection, bool onlyWhenInitialEverything = true)
+ {
+ if (onlyWhenInitialEverything && initialPullStrategy != InitialPullStrategy.Everything) return collection;
+
+ // items which are in local db, but not in the online collection
+ var extra = Once()
+ .Select(f => f.Key)
+ .Except(collection.Select(c => c.Key))
+ .Select(k => new FirebaseObject<T>(k, null));
+
+ return collection.Concat(extra).ToList();
+ }
+
+ private void SetObjectFromInitialPull(FirebaseObject<T> e)
+ {
+ // set object with no sync only if it doesn't exist yet
+ // and the InitialPullStrategy != Everything
+ // this attempts to deal with scenario when you are offline, have local changes and go online
+ // in this case having the InitialPullStrategy set to everything would basically purge all local changes
+ if (!Database.ContainsKey(e.Key) || Database[e.Key].SyncOptions == SyncOptions.None ||
+ Database[e.Key].SyncOptions == SyncOptions.Pull ||
+ initialPullStrategy != InitialPullStrategy.Everything)
+ Database[e.Key] = new OfflineEntry(e.Key, e.Object, 1, SyncOptions.None);
+ }
+
+ private IObservable<IReadOnlyCollection<FirebaseObject<T>>> GetInitialPullObservable()
+ {
+ FirebaseQuery query;
+ switch (initialPullStrategy)
+ {
+ case InitialPullStrategy.MissingOnly:
+ query = childQuery.OrderByKey().StartAt(() => GetLatestKey());
+ break;
+ case InitialPullStrategy.Everything:
+ query = childQuery;
+ break;
+ case InitialPullStrategy.None:
+ default:
+ return Observable.Empty<IReadOnlyCollection<FirebaseEvent<T>>>();
+ }
+
+ if (string.IsNullOrWhiteSpace(elementRoot))
+ return Observable.Defer(() => query.OnceAsync<T>().ToObservable());
+
+ // there is an element root, which indicates the target location is not a collection but a single element
+ return Observable.Defer(async () =>
+ Observable.Return(await query.OnceSingleAsync<T>())
+ .Select(e => new[] {new FirebaseObject<T>(elementRoot, e)}));
+ }
+
+ private IDisposable InitializeStreamingSubscription(IObserver<FirebaseEvent<T>> observer)
+ {
+ var completeDisposable = Disposable.Create(() => isSyncRunning = false);
+
+ switch (streamingOptions)
+ {
+ case StreamingOptions.LatestOnly:
+ // stream since the latest key
+ var queryLatest = childQuery.OrderByKey().StartAt(() => GetLatestKey());
+ firebaseSubscription =
+ new FirebaseSubscription<T>(observer, queryLatest, elementRoot, firebaseCache);
+ firebaseSubscription.ExceptionThrown += StreamingExceptionThrown;
+
+ return new CompositeDisposable(firebaseSubscription.Run(), completeDisposable);
+ case StreamingOptions.Everything:
+ // stream everything
+ var queryAll = childQuery;
+ firebaseSubscription = new FirebaseSubscription<T>(observer, queryAll, elementRoot, firebaseCache);
+ firebaseSubscription.ExceptionThrown += StreamingExceptionThrown;
+
+ return new CompositeDisposable(firebaseSubscription.Run(), completeDisposable);
+ }
+
+ return completeDisposable;
+ }
+
+ private void SetAndRaise(string key, OfflineEntry obj,
+ FirebaseEventSource eventSource = FirebaseEventSource.Offline)
+ {
+ Database[key] = obj;
+ subject.OnNext(new FirebaseEvent<T>(key, obj?.Deserialize<T>(),
+ string.IsNullOrEmpty(obj?.Data) || obj?.Data == "null"
+ ? FirebaseEventType.Delete
+ : FirebaseEventType.InsertOrUpdate, eventSource));
+ }
+
+ private async void SynchronizeThread()
+ {
+ while (isSyncRunning)
+ {
+ try
+ {
+ var validEntries = Database.Where(e => e.Value != null);
+ await PullEntriesAsync(validEntries.Where(kvp => kvp.Value.SyncOptions == SyncOptions.Pull));
+
+ if (pushChanges)
+ await PushEntriesAsync(validEntries.Where(kvp =>
+ kvp.Value.SyncOptions == SyncOptions.Put || kvp.Value.SyncOptions == SyncOptions.Patch));
+ }
+ catch (Exception ex)
+ {
+ SyncExceptionThrown?.Invoke(this, new ExceptionEventArgs(ex));
+ }
+
+ await Task.Delay(childQuery.Client.Options.SyncPeriod);
+ }
+ }
+
+ private string GetLatestKey()
+ {
+ var key = Database.OrderBy(o => o.Key, StringComparer.Ordinal).LastOrDefault().Key ?? string.Empty;
+
+ if (!string.IsNullOrWhiteSpace(key))
+ key = key.Substring(0, key.Length - 1) + (char) (key[key.Length - 1] + 1);
+
+ return key;
+ }
+
+ private async Task PushEntriesAsync(IEnumerable<KeyValuePair<string, OfflineEntry>> pushEntries)
+ {
+ var groups = pushEntries.GroupBy(pair => pair.Value.Priority).OrderByDescending(kvp => kvp.Key).ToList();
+
+ foreach (var group in groups)
+ {
+ var tasks = group.OrderBy(kvp => kvp.Value.IsPartial).Select(kvp =>
+ kvp.Value.IsPartial
+ ? ResetSyncAfterPush(PutHandler.SetAsync(childQuery, kvp.Key, kvp.Value), kvp.Key)
+ : ResetSyncAfterPush(PutHandler.SetAsync(childQuery, kvp.Key, kvp.Value), kvp.Key,
+ kvp.Value.Deserialize<T>()));
+
+ try
+ {
+ await Task.WhenAll(tasks).WithAggregateException();
+ }
+ catch (Exception ex)
+ {
+ SyncExceptionThrown?.Invoke(this, new ExceptionEventArgs(ex));
+ }
+ }
+ }
+
+ private async Task PullEntriesAsync(IEnumerable<KeyValuePair<string, OfflineEntry>> pullEntries)
+ {
+ var taskGroups = pullEntries.GroupBy(pair => pair.Value.Priority).OrderByDescending(kvp => kvp.Key);
+
+ foreach (var group in taskGroups)
+ {
+ var tasks = group.Select(pair =>
+ ResetAfterPull(
+ childQuery.Child(pair.Key == elementRoot ? string.Empty : pair.Key).OnceSingleAsync<T>(),
+ pair.Key, pair.Value));
+
+ try
+ {
+ await Task.WhenAll(tasks).WithAggregateException();
+ }
+ catch (Exception ex)
+ {
+ SyncExceptionThrown?.Invoke(this, new ExceptionEventArgs(ex));
+ }
+ }
+ }
+
+ private async Task ResetAfterPull(Task<T> task, string key, OfflineEntry entry)
+ {
+ await task;
+ SetAndRaise(key, new OfflineEntry(key, task.Result, entry.Priority, SyncOptions.None),
+ FirebaseEventSource.OnlinePull);
+ }
+
+ private async Task ResetSyncAfterPush(Task task, string key, T obj)
+ {
+ await ResetSyncAfterPush(task, key);
+
+ if (streamingOptions == StreamingOptions.None)
+ subject.OnNext(new FirebaseEvent<T>(key, obj,
+ obj == null ? FirebaseEventType.Delete : FirebaseEventType.InsertOrUpdate,
+ FirebaseEventSource.OnlinePush));
+ }
+
+ private async Task ResetSyncAfterPush(Task task, string key)
+ {
+ await task;
+ ResetSyncOptions(key);
+ }
+
+ private void ResetSyncOptions(string key)
+ {
+ var item = Database[key];
+
+ if (item.IsPartial)
+ {
+ Database.Remove(key);
+ }
+ else
+ {
+ item.SyncOptions = SyncOptions.None;
+ Database[key] = item;
+ }
+ }
+
+ private void StreamingExceptionThrown(object sender, ExceptionEventArgs<FirebaseException> e)
+ {
+ SyncExceptionThrown?.Invoke(this, new ExceptionEventArgs(e.Exception));
+ }
+
+ private Tuple<string, string, bool> GenerateFullKey<TProperty>(string key,
+ Expression<Func<T, TProperty>> propertyGetter, SyncOptions syncOptions)
+ {
+ var visitor = new MemberAccessVisitor();
+ visitor.Visit(propertyGetter);
+ var propertyType = typeof(TProperty).GetTypeInfo();
+ var prefix = key == string.Empty ? string.Empty : key + "/";
+
+ // primitive types
+ if (syncOptions == SyncOptions.Patch && (propertyType.IsPrimitive ||
+ Nullable.GetUnderlyingType(typeof(TProperty)) != null ||
+ typeof(TProperty) == typeof(string)))
+ return Tuple.Create(prefix + string.Join("/", visitor.PropertyNames.Skip(1).Reverse()),
+ visitor.PropertyNames.First(), true);
+
+ return Tuple.Create(prefix + string.Join("/", visitor.PropertyNames.Reverse()),
+ visitor.PropertyNames.First(), false);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/SetHandler.cs b/dsa/FireBase/Offline/SetHandler.cs
new file mode 100644
index 0000000..6314c3c
--- /dev/null
+++ b/dsa/FireBase/Offline/SetHandler.cs
@@ -0,0 +1,19 @@
+using System.Threading.Tasks;
+using Firebase.Database.Query;
+
+namespace Firebase.Database.Offline
+{
+ public class SetHandler<T> : ISetHandler<T>
+ {
+ public virtual async Task SetAsync(ChildQuery query, string key, OfflineEntry entry)
+ {
+ using (var child = query.Child(key))
+ {
+ if (entry.SyncOptions == SyncOptions.Put)
+ await child.PutAsync(entry.Data);
+ else
+ await child.PatchAsync(entry.Data);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/StreamingOptions.cs b/dsa/FireBase/Offline/StreamingOptions.cs
new file mode 100644
index 0000000..a420cbb
--- /dev/null
+++ b/dsa/FireBase/Offline/StreamingOptions.cs
@@ -0,0 +1,23 @@
+namespace Firebase.Database.Offline
+{
+ public enum StreamingOptions
+ {
+ /// <summary>
+ /// No realtime streaming.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Streaming of only new items - not the existing ones.
+ /// </summary>
+ LatestOnly,
+
+ /// <summary>
+ /// Streaming of all items. This will also pull all existing items on start, so be mindful about the number of items in
+ /// your DB.
+ /// When used, consider not setting the <see cref="InitialPullStrategy" /> to
+ /// <see cref="InitialPullStrategy.Everything" /> because you would pointlessly pull everything twice.
+ /// </summary>
+ Everything
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Offline/SyncOptions.cs b/dsa/FireBase/Offline/SyncOptions.cs
new file mode 100644
index 0000000..ca68d0a
--- /dev/null
+++ b/dsa/FireBase/Offline/SyncOptions.cs
@@ -0,0 +1,28 @@
+namespace Firebase.Database.Offline
+{
+ /// <summary>
+ /// Specifies type of sync requested for given data.
+ /// </summary>
+ public enum SyncOptions
+ {
+ /// <summary>
+ /// No sync needed for given data.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Data should be pulled from firebase.
+ /// </summary>
+ Pull,
+
+ /// <summary>
+ /// Data should be put to firebase.
+ /// </summary>
+ Put,
+
+ /// <summary>
+ /// Data should be patched in firebase.
+ /// </summary>
+ Patch
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/AuthQuery.cs b/dsa/FireBase/Query/AuthQuery.cs
new file mode 100644
index 0000000..2cfda3c
--- /dev/null
+++ b/dsa/FireBase/Query/AuthQuery.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Represents an auth parameter in firebase query, e.g. "?auth=xyz".
+ /// </summary>
+ public class AuthQuery : ParameterQuery
+ {
+ private readonly Func<string> tokenFactory;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The parent. </param>
+ /// <param name="tokenFactory"> The authentication token factory. </param>
+ /// <param name="client"> The owner. </param>
+ public AuthQuery(FirebaseQuery parent, Func<string> tokenFactory, FirebaseClient client) : base(parent,
+ () => client.Options.AsAccessToken ? "access_token" : "auth", client)
+ {
+ this.tokenFactory = tokenFactory;
+ }
+
+ /// <summary>
+ /// Build the url parameter value of this child.
+ /// </summary>
+ /// <param name="child"> The child of this child. </param>
+ /// <returns> The <see cref="string" />. </returns>
+ protected override string BuildUrlParameter(FirebaseQuery child)
+ {
+ return tokenFactory();
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/ChildQuery.cs b/dsa/FireBase/Query/ChildQuery.cs
new file mode 100644
index 0000000..014fe09
--- /dev/null
+++ b/dsa/FireBase/Query/ChildQuery.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Firebase query which references the child of current node.
+ /// </summary>
+ public class ChildQuery : FirebaseQuery
+ {
+ private readonly Func<string> pathFactory;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ChildQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The parent. </param>
+ /// <param name="pathFactory"> The path to the child node. </param>
+ /// <param name="client"> The owner. </param>
+ public ChildQuery(FirebaseQuery parent, Func<string> pathFactory, FirebaseClient client)
+ : base(parent, client)
+ {
+ this.pathFactory = pathFactory;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ChildQuery" /> class.
+ /// </summary>
+ /// <param name="client"> The client. </param>
+ /// <param name="pathFactory"> The path to the child node. </param>
+ public ChildQuery(FirebaseClient client, Func<string> pathFactory)
+ : this(null, pathFactory, client)
+ {
+ }
+
+ /// <summary>
+ /// Build the url segment of this child.
+ /// </summary>
+ /// <param name="child"> The child of this child. </param>
+ /// <returns> The <see cref="string" />. </returns>
+ protected override string BuildUrlSegment(FirebaseQuery child)
+ {
+ var s = pathFactory();
+
+ if (s != string.Empty && !s.EndsWith("/")) s += '/';
+
+ if (!(child is ChildQuery)) return s + ".json";
+
+ return s;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/FilterQuery.cs b/dsa/FireBase/Query/FilterQuery.cs
new file mode 100644
index 0000000..3434d1d
--- /dev/null
+++ b/dsa/FireBase/Query/FilterQuery.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Globalization;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Represents a firebase filtering query, e.g. "?LimitToLast=10".
+ /// </summary>
+ public class FilterQuery : ParameterQuery
+ {
+ private readonly Func<bool> boolValueFactory;
+ private readonly Func<double> doubleValueFactory;
+ private readonly Func<string> valueFactory;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FilterQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The parent. </param>
+ /// <param name="filterFactory"> The filter. </param>
+ /// <param name="valueFactory"> The value for filter. </param>
+ /// <param name="client"> The owning client. </param>
+ public FilterQuery(FirebaseQuery parent, Func<string> filterFactory, Func<string> valueFactory,
+ FirebaseClient client)
+ : base(parent, filterFactory, client)
+ {
+ this.valueFactory = valueFactory;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FilterQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The parent. </param>
+ /// <param name="filterFactory"> The filter. </param>
+ /// <param name="valueFactory"> The value for filter. </param>
+ /// <param name="client"> The owning client. </param>
+ public FilterQuery(FirebaseQuery parent, Func<string> filterFactory, Func<double> valueFactory,
+ FirebaseClient client)
+ : base(parent, filterFactory, client)
+ {
+ doubleValueFactory = valueFactory;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FilterQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The parent. </param>
+ /// <param name="filterFactory"> The filter. </param>
+ /// <param name="valueFactory"> The value for filter. </param>
+ /// <param name="client"> The owning client. </param>
+ public FilterQuery(FirebaseQuery parent, Func<string> filterFactory, Func<bool> valueFactory,
+ FirebaseClient client)
+ : base(parent, filterFactory, client)
+ {
+ boolValueFactory = valueFactory;
+ }
+
+ /// <summary>
+ /// The build url parameter.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <returns> Url parameter part of the resulting path. </returns>
+ protected override string BuildUrlParameter(FirebaseQuery child)
+ {
+ if (valueFactory != null)
+ {
+ if (valueFactory() == null) return "null";
+ return $"\"{valueFactory()}\"";
+ }
+
+ if (doubleValueFactory != null)
+ return doubleValueFactory().ToString(CultureInfo.InvariantCulture);
+ if (boolValueFactory != null) return $"{boolValueFactory().ToString().ToLower()}";
+
+ return string.Empty;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/FirebaseQuery.cs b/dsa/FireBase/Query/FirebaseQuery.cs
new file mode 100644
index 0000000..60d0289
--- /dev/null
+++ b/dsa/FireBase/Query/FirebaseQuery.cs
@@ -0,0 +1,314 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Firebase.Database.Http;
+using Firebase.Database.Streaming;
+using Newtonsoft.Json;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Represents a firebase query.
+ /// </summary>
+ public abstract class FirebaseQuery : IFirebaseQuery, IDisposable
+ {
+ protected readonly FirebaseQuery Parent;
+
+ private HttpClient client;
+ protected TimeSpan DEFAULT_HTTP_CLIENT_TIMEOUT = new TimeSpan(0, 0, 180);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FirebaseQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The parent of this query. </param>
+ /// <param name="client"> The owning client. </param>
+ protected FirebaseQuery(FirebaseQuery parent, FirebaseClient client)
+ {
+ Client = client;
+ Parent = parent;
+ }
+
+ /// <summary>
+ /// Disposes this instance.
+ /// </summary>
+ public void Dispose()
+ {
+ client?.Dispose();
+ }
+
+ /// <summary>
+ /// Gets the client.
+ /// </summary>
+ public FirebaseClient Client { get; }
+
+ /// <summary>
+ /// Queries the firebase server once returning collection of items.
+ /// </summary>
+ /// <param name="timeout"> Optional timeout value. </param>
+ /// <typeparam name="T"> Type of elements. </typeparam>
+ /// <returns> Collection of <see cref="FirebaseObject{T}" /> holding the entities returned by server. </returns>
+ public async Task<IReadOnlyCollection<FirebaseObject<T>>> OnceAsync<T>(TimeSpan? timeout = null)
+ {
+ var url = string.Empty;
+
+ try
+ {
+ url = await BuildUrlAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException("Couldn't build the url", string.Empty, string.Empty, HttpStatusCode.OK,
+ ex);
+ }
+
+ return await GetClient(timeout).GetObjectCollectionAsync<T>(url, Client.Options.JsonSerializerSettings)
+ .ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Starts observing this query watching for changes real time sent by the server.
+ /// </summary>
+ /// <typeparam name="T"> Type of elements. </typeparam>
+ /// <param name="elementRoot"> Optional custom root element of received json items. </param>
+ /// <returns> Observable stream of <see cref="FirebaseEvent{T}" />. </returns>
+ public IObservable<FirebaseEvent<T>> AsObservable<T>(
+ EventHandler<ExceptionEventArgs<FirebaseException>> exceptionHandler = null, string elementRoot = "")
+ {
+ return Observable.Create<FirebaseEvent<T>>(observer =>
+ {
+ var sub = new FirebaseSubscription<T>(observer, this, elementRoot, new FirebaseCache<T>());
+ sub.ExceptionThrown += exceptionHandler;
+ return sub.Run();
+ });
+ }
+
+ /// <summary>
+ /// Builds the actual URL of this query.
+ /// </summary>
+ /// <returns> The <see cref="string" />. </returns>
+ public async Task<string> BuildUrlAsync()
+ {
+ // if token factory is present on the parent then use it to generate auth token
+ if (Client.Options.AuthTokenAsyncFactory != null)
+ {
+ var token = await Client.Options.AuthTokenAsyncFactory().ConfigureAwait(false);
+ return this.WithAuth(token).BuildUrl(null);
+ }
+
+ return BuildUrl(null);
+ }
+
+ /*public async Task<IReadOnlyCollection<FirebaseObject<Object>>> OnceAsync(Type dataType, TimeSpan? timeout = null)
+ {
+ var url = string.Empty;
+
+ try
+ {
+ url = await this.BuildUrlAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException("Couldn't build the url", string.Empty, string.Empty, HttpStatusCode.OK, ex);
+ }
+
+ return await this.GetClient(timeout).GetObjectCollectionAsync(url, Client.Options.JsonSerializerSettings, dataType)
+ .ConfigureAwait(false);
+ }*/
+
+ /// <summary>
+ /// Assumes given query is pointing to a single object of type <typeparamref name="T" /> and retrieves it.
+ /// </summary>
+ /// <param name="timeout"> Optional timeout value. </param>
+ /// <typeparam name="T"> Type of elements. </typeparam>
+ /// <returns> Single object of type <typeparamref name="T" />. </returns>
+ public async Task<T> OnceSingleAsync<T>(TimeSpan? timeout = null)
+ {
+ var responseData = string.Empty;
+ var statusCode = HttpStatusCode.OK;
+ var url = string.Empty;
+
+ try
+ {
+ url = await BuildUrlAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException("Couldn't build the url", string.Empty, responseData, statusCode, ex);
+ }
+
+ try
+ {
+ var response = await GetClient(timeout).GetAsync(url).ConfigureAwait(false);
+ statusCode = response.StatusCode;
+ responseData = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ response.EnsureSuccessStatusCode();
+ response.Dispose();
+
+ return JsonConvert.DeserializeObject<T>(responseData, Client.Options.JsonSerializerSettings);
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException(url, string.Empty, responseData, statusCode, ex);
+ }
+ }
+
+ /// <summary>
+ /// Posts given object to repository.
+ /// </summary>
+ /// <param name="obj"> The object. </param>
+ /// <param name="generateKeyOffline"> Specifies whether the key should be generated offline instead of online. </param>
+ /// <param name="timeout"> Optional timeout value. </param>
+ /// <typeparam name="T"> Type of <see cref="obj" /> </typeparam>
+ /// <returns> Resulting firebase object with populated key. </returns>
+ public async Task<FirebaseObject<string>> PostAsync(string data, bool generateKeyOffline = true,
+ TimeSpan? timeout = null)
+ {
+ // post generates a new key server-side, while put can be used with an already generated local key
+ if (generateKeyOffline)
+ {
+ var key = FirebaseKeyGenerator.Next();
+ await new ChildQuery(this, () => key, Client).PutAsync(data).ConfigureAwait(false);
+
+ return new FirebaseObject<string>(key, data);
+ }
+
+ var c = GetClient(timeout);
+ var sendData = await SendAsync(c, data, HttpMethod.Post).ConfigureAwait(false);
+ var result = JsonConvert.DeserializeObject<PostResult>(sendData, Client.Options.JsonSerializerSettings);
+
+ return new FirebaseObject<string>(result.Name, data);
+ }
+
+ /// <summary>
+ /// Patches data at given location instead of overwriting them.
+ /// </summary>
+ /// <param name="obj"> The object. </param>
+ /// <param name="timeout"> Optional timeout value. </param>
+ /// <typeparam name="T"> Type of <see cref="obj" /> </typeparam>
+ /// <returns> The <see cref="Task" />. </returns>
+ public async Task PatchAsync(string data, TimeSpan? timeout = null)
+ {
+ var c = GetClient(timeout);
+
+ await this.Silent().SendAsync(c, data, new HttpMethod("PATCH")).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Sets or overwrites data at given location.
+ /// </summary>
+ /// <param name="obj"> The object. </param>
+ /// <param name="timeout"> Optional timeout value. </param>
+ /// <typeparam name="T"> Type of <see cref="obj" /> </typeparam>
+ /// <returns> The <see cref="Task" />. </returns>
+ public async Task PutAsync(string data, TimeSpan? timeout = null)
+ {
+ var c = GetClient(timeout);
+
+ await this.Silent().SendAsync(c, data, HttpMethod.Put).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Deletes data from given location.
+ /// </summary>
+ /// <param name="timeout"> Optional timeout value. </param>
+ /// <returns> The <see cref="Task" />. </returns>
+ public async Task DeleteAsync(TimeSpan? timeout = null)
+ {
+ var c = GetClient(timeout);
+ var url = string.Empty;
+ var responseData = string.Empty;
+ var statusCode = HttpStatusCode.OK;
+
+ try
+ {
+ url = await BuildUrlAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException("Couldn't build the url", string.Empty, responseData, statusCode, ex);
+ }
+
+ try
+ {
+ var result = await c.DeleteAsync(url).ConfigureAwait(false);
+ statusCode = result.StatusCode;
+ responseData = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ result.EnsureSuccessStatusCode();
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException(url, string.Empty, responseData, statusCode, ex);
+ }
+ }
+
+ /// <summary>
+ /// Build the url segment of this child.
+ /// </summary>
+ /// <param name="child"> The child of this query. </param>
+ /// <returns> The <see cref="string" />. </returns>
+ protected abstract string BuildUrlSegment(FirebaseQuery child);
+
+ private string BuildUrl(FirebaseQuery child)
+ {
+ var url = BuildUrlSegment(child);
+
+ if (Parent != null) url = Parent.BuildUrl(this) + url;
+
+ return url;
+ }
+
+ private HttpClient GetClient(TimeSpan? timeout = null)
+ {
+ if (client == null) client = new HttpClient();
+
+ if (!timeout.HasValue)
+ client.Timeout = DEFAULT_HTTP_CLIENT_TIMEOUT;
+ else
+ client.Timeout = timeout.Value;
+
+ return client;
+ }
+
+ private async Task<string> SendAsync(HttpClient client, string data, HttpMethod method)
+ {
+ var responseData = string.Empty;
+ var statusCode = HttpStatusCode.OK;
+ var requestData = data;
+ var url = string.Empty;
+
+ try
+ {
+ url = await BuildUrlAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException("Couldn't build the url", requestData, responseData, statusCode, ex);
+ }
+
+ var message = new HttpRequestMessage(method, url)
+ {
+ Content = new StringContent(requestData)
+ };
+
+ try
+ {
+ var result = await client.SendAsync(message).ConfigureAwait(false);
+ statusCode = result.StatusCode;
+ responseData = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ result.EnsureSuccessStatusCode();
+
+ return responseData;
+ }
+ catch (Exception ex)
+ {
+ throw new FirebaseException(url, requestData, responseData, statusCode, ex);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/IFirebaseQuery.cs b/dsa/FireBase/Query/IFirebaseQuery.cs
new file mode 100644
index 0000000..0da4b15
--- /dev/null
+++ b/dsa/FireBase/Query/IFirebaseQuery.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Firebase.Database.Streaming;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// The FirebaseQuery interface.
+ /// </summary>
+ public interface IFirebaseQuery
+ {
+ /// <summary>
+ /// Gets the owning client of this query.
+ /// </summary>
+ FirebaseClient Client { get; }
+
+ /// <summary>
+ /// Retrieves items which exist on the location specified by this query instance.
+ /// </summary>
+ /// <param name="timeout"> Optional timeout value. </param>
+ /// <typeparam name="T"> Type of the items. </typeparam>
+ /// <returns> Collection of <see cref="FirebaseObject{T}" />. </returns>
+ Task<IReadOnlyCollection<FirebaseObject<T>>> OnceAsync<T>(TimeSpan? timeout = null);
+
+ /// <summary>
+ /// Returns current location as an observable which allows to real-time listening to events from the firebase server.
+ /// </summary>
+ /// <typeparam name="T"> Type of the items. </typeparam>
+ /// <returns> Cold observable of <see cref="FirebaseEvent{T}" />. </returns>
+ IObservable<FirebaseEvent<T>> AsObservable<T>(
+ EventHandler<ExceptionEventArgs<FirebaseException>> exceptionHandler, string elementRoot = "");
+
+ /// <summary>
+ /// Builds the actual url of this query.
+ /// </summary>
+ /// <returns> The <see cref="string" />. </returns>
+ Task<string> BuildUrlAsync();
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/OrderQuery.cs b/dsa/FireBase/Query/OrderQuery.cs
new file mode 100644
index 0000000..302d1a3
--- /dev/null
+++ b/dsa/FireBase/Query/OrderQuery.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Represents a firebase ordering query, e.g. "?OrderBy=Foo".
+ /// </summary>
+ public class OrderQuery : ParameterQuery
+ {
+ private readonly Func<string> propertyNameFactory;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OrderQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The query parent. </param>
+ /// <param name="propertyNameFactory"> The property name. </param>
+ /// <param name="client"> The owning client. </param>
+ public OrderQuery(ChildQuery parent, Func<string> propertyNameFactory, FirebaseClient client)
+ : base(parent, () => "orderBy", client)
+ {
+ this.propertyNameFactory = propertyNameFactory;
+ }
+
+ /// <summary>
+ /// The build url parameter.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <returns> The <see cref="string" />. </returns>
+ protected override string BuildUrlParameter(FirebaseQuery child)
+ {
+ return $"\"{propertyNameFactory()}\"";
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/ParameterQuery.cs b/dsa/FireBase/Query/ParameterQuery.cs
new file mode 100644
index 0000000..572224c
--- /dev/null
+++ b/dsa/FireBase/Query/ParameterQuery.cs
@@ -0,0 +1,43 @@
+using System;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Represents a parameter in firebase query, e.g. "?data=foo".
+ /// </summary>
+ public abstract class ParameterQuery : FirebaseQuery
+ {
+ private readonly Func<string> parameterFactory;
+ private readonly string separator;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterQuery" /> class.
+ /// </summary>
+ /// <param name="parent"> The parent of this query. </param>
+ /// <param name="parameterFactory"> The parameter. </param>
+ /// <param name="client"> The owning client. </param>
+ protected ParameterQuery(FirebaseQuery parent, Func<string> parameterFactory, FirebaseClient client)
+ : base(parent, client)
+ {
+ this.parameterFactory = parameterFactory;
+ separator = Parent is ChildQuery ? "?" : "&";
+ }
+
+ /// <summary>
+ /// Build the url segment represented by this query.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <returns> The <see cref="string" />. </returns>
+ protected override string BuildUrlSegment(FirebaseQuery child)
+ {
+ return $"{separator}{parameterFactory()}={BuildUrlParameter(child)}";
+ }
+
+ /// <summary>
+ /// The build url parameter.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <returns> The <see cref="string" />. </returns>
+ protected abstract string BuildUrlParameter(FirebaseQuery child);
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/QueryExtensions.cs b/dsa/FireBase/Query/QueryExtensions.cs
new file mode 100644
index 0000000..df2edfc
--- /dev/null
+++ b/dsa/FireBase/Query/QueryExtensions.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Query extensions providing linq like syntax for firebase server methods.
+ /// </summary>
+ public static class QueryExtensions
+ {
+ /// <summary>
+ /// Adds an auth parameter to the query.
+ /// </summary>
+ /// <param name="node"> The child. </param>
+ /// <param name="token"> The auth token. </param>
+ /// <returns> The <see cref="AuthQuery" />. </returns>
+ internal static AuthQuery WithAuth(this FirebaseQuery node, string token)
+ {
+ return node.WithAuth(() => token);
+ }
+
+ /// <summary>
+ /// Appends print=silent to save bandwidth.
+ /// </summary>
+ /// <param name="node"> The child. </param>
+ /// <returns> The <see cref="SilentQuery" />. </returns>
+ internal static SilentQuery Silent(this FirebaseQuery node)
+ {
+ return new SilentQuery(node, node.Client);
+ }
+
+ /// <summary>
+ /// References a sub child of the existing node.
+ /// </summary>
+ /// <param name="node"> The child. </param>
+ /// <param name="path"> The path of sub child. </param>
+ /// <returns> The <see cref="ChildQuery" />. </returns>
+ public static ChildQuery Child(this ChildQuery node, string path)
+ {
+ return node.Child(() => path);
+ }
+
+ /// <summary>
+ /// Order data by given <see cref="propertyName" />. Note that this is used mainly for following filtering queries and
+ /// due to firebase implementation
+ /// the data may actually not be ordered.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <param name="propertyName"> The property name. </param>
+ /// <returns> The <see cref="OrderQuery" />. </returns>
+ public static OrderQuery OrderBy(this ChildQuery child, string propertyName)
+ {
+ return child.OrderBy(() => propertyName);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data greater or equal to the <see cref="value" />. This must be preceded by an OrderBy
+ /// query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="value"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery StartAt(this ParameterQuery child, string value)
+ {
+ return child.StartAt(() => value);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data lower or equal to the <see cref="value" />. This must be preceded by an OrderBy
+ /// query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="value"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EndAt(this ParameterQuery child, string value)
+ {
+ return child.EndAt(() => value);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data equal to the <see cref="value" />. This must be preceded by an OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="value"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EqualTo(this ParameterQuery child, string value)
+ {
+ return child.EqualTo(() => value);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data greater or equal to the <see cref="value" />. This must be preceded by an OrderBy
+ /// query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="value"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery StartAt(this ParameterQuery child, double value)
+ {
+ return child.StartAt(() => value);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data lower or equal to the <see cref="value" />. This must be preceded by an OrderBy
+ /// query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="value"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EndAt(this ParameterQuery child, double value)
+ {
+ return child.EndAt(() => value);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data equal to the <see cref="value" />. This must be preceded by an OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="value"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EqualTo(this ParameterQuery child, double value)
+ {
+ return child.EqualTo(() => value);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data equal to the <see cref="value" />. This must be preceded by an OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="value"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EqualTo(this ParameterQuery child, bool value)
+ {
+ return child.EqualTo(() => value);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data equal to null. This must be preceded by an OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EqualTo(this ParameterQuery child)
+ {
+ return child.EqualTo(() => null);
+ }
+
+ /// <summary>
+ /// Limits the result to first <see cref="count" /> items.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="count"> Number of elements. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery LimitToFirst(this ParameterQuery child, int count)
+ {
+ return child.LimitToFirst(() => count);
+ }
+
+ /// <summary>
+ /// Limits the result to last <see cref="count" /> items.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="count"> Number of elements. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery LimitToLast(this ParameterQuery child, int count)
+ {
+ return child.LimitToLast(() => count);
+ }
+
+ public static Task PutAsync<T>(this FirebaseQuery query, T obj)
+ {
+ return query.PutAsync(JsonConvert.SerializeObject(obj, query.Client.Options.JsonSerializerSettings));
+ }
+
+ public static Task PatchAsync<T>(this FirebaseQuery query, T obj)
+ {
+ return query.PatchAsync(JsonConvert.SerializeObject(obj, query.Client.Options.JsonSerializerSettings));
+ }
+
+ public static async Task<FirebaseObject<T>> PostAsync<T>(this FirebaseQuery query, T obj,
+ bool generateKeyOffline = true)
+ {
+ var result =
+ await query.PostAsync(JsonConvert.SerializeObject(obj, query.Client.Options.JsonSerializerSettings),
+ generateKeyOffline);
+
+ return new FirebaseObject<T>(result.Key, obj);
+ }
+
+ /// <summary>
+ /// Fan out given item to multiple locations at once. See
+ /// https://firebase.googleblog.com/2015/10/client-side-fan-out-for-data-consistency_73.html for details.
+ /// </summary>
+ /// <typeparam name="T"> Type of object to fan out. </typeparam>
+ /// <param name="query"> Current node. </param>
+ /// <param name="item"> Object to fan out. </param>
+ /// <param name="relativePaths"> Locations where to store the item. </param>
+ public static async Task FanOut<T>(this ChildQuery child, T item, params string[] relativePaths)
+ {
+ if (relativePaths == null) throw new ArgumentNullException(nameof(relativePaths));
+
+ var fanoutObject = new Dictionary<string, T>(relativePaths.Length);
+
+ foreach (var path in relativePaths) fanoutObject.Add(path, item);
+
+ await child.PatchAsync(fanoutObject);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/QueryFactoryExtensions.cs b/dsa/FireBase/Query/QueryFactoryExtensions.cs
new file mode 100644
index 0000000..71dae5c
--- /dev/null
+++ b/dsa/FireBase/Query/QueryFactoryExtensions.cs
@@ -0,0 +1,187 @@
+using System;
+
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Query extensions providing linq like syntax for firebase server methods.
+ /// </summary>
+ public static class QueryFactoryExtensions
+ {
+ /// <summary>
+ /// Adds an auth parameter to the query.
+ /// </summary>
+ /// <param name="node"> The child. </param>
+ /// <param name="tokenFactory"> The auth token. </param>
+ /// <returns> The <see cref="AuthQuery" />. </returns>
+ internal static AuthQuery WithAuth(this FirebaseQuery node, Func<string> tokenFactory)
+ {
+ return new AuthQuery(node, tokenFactory, node.Client);
+ }
+
+ /// <summary>
+ /// References a sub child of the existing node.
+ /// </summary>
+ /// <param name="node"> The child. </param>
+ /// <param name="pathFactory"> The path of sub child. </param>
+ /// <returns> The <see cref="ChildQuery" />. </returns>
+ public static ChildQuery Child(this ChildQuery node, Func<string> pathFactory)
+ {
+ return new ChildQuery(node, pathFactory, node.Client);
+ }
+
+ /// <summary>
+ /// Order data by given <see cref="propertyNameFactory" />. Note that this is used mainly for following filtering
+ /// queries and due to firebase implementation
+ /// the data may actually not be ordered.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <param name="propertyNameFactory"> The property name. </param>
+ /// <returns> The <see cref="OrderQuery" />. </returns>
+ public static OrderQuery OrderBy(this ChildQuery child, Func<string> propertyNameFactory)
+ {
+ return new OrderQuery(child, propertyNameFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Order data by $key. Note that this is used mainly for following filtering queries and due to firebase
+ /// implementation
+ /// the data may actually not be ordered.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <returns> The <see cref="OrderQuery" />. </returns>
+ public static OrderQuery OrderByKey(this ChildQuery child)
+ {
+ return child.OrderBy("$key");
+ }
+
+ /// <summary>
+ /// Order data by $value. Note that this is used mainly for following filtering queries and due to firebase
+ /// implementation
+ /// the data may actually not be ordered.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <returns> The <see cref="OrderQuery" />. </returns>
+ public static OrderQuery OrderByValue(this ChildQuery child)
+ {
+ return child.OrderBy("$value");
+ }
+
+ /// <summary>
+ /// Order data by $priority. Note that this is used mainly for following filtering queries and due to firebase
+ /// implementation
+ /// the data may actually not be ordered.
+ /// </summary>
+ /// <param name="child"> The child. </param>
+ /// <returns> The <see cref="OrderQuery" />. </returns>
+ public static OrderQuery OrderByPriority(this ChildQuery child)
+ {
+ return child.OrderBy("$priority");
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data greater or equal to the <see cref="valueFactory" />. This must be preceded by an
+ /// OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="valueFactory"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery StartAt(this ParameterQuery child, Func<string> valueFactory)
+ {
+ return new FilterQuery(child, () => "startAt", valueFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data lower or equal to the <see cref="valueFactory" />. This must be preceded by an
+ /// OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="valueFactory"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EndAt(this ParameterQuery child, Func<string> valueFactory)
+ {
+ return new FilterQuery(child, () => "endAt", valueFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data equal to the <see cref="valueFactory" />. This must be preceded by an OrderBy
+ /// query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="valueFactory"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EqualTo(this ParameterQuery child, Func<string> valueFactory)
+ {
+ return new FilterQuery(child, () => "equalTo", valueFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data greater or equal to the <see cref="valueFactory" />. This must be preceded by an
+ /// OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="valueFactory"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery StartAt(this ParameterQuery child, Func<double> valueFactory)
+ {
+ return new FilterQuery(child, () => "startAt", valueFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data lower or equal to the <see cref="valueFactory" />. This must be preceded by an
+ /// OrderBy query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="valueFactory"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EndAt(this ParameterQuery child, Func<double> valueFactory)
+ {
+ return new FilterQuery(child, () => "endAt", valueFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data equal to the <see cref="valueFactory" />. This must be preceded by an OrderBy
+ /// query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="valueFactory"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EqualTo(this ParameterQuery child, Func<double> valueFactory)
+ {
+ return new FilterQuery(child, () => "equalTo", valueFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Instructs firebase to send data equal to the <see cref="valueFactory" />. This must be preceded by an OrderBy
+ /// query.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="valueFactory"> Value to start at. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery EqualTo(this ParameterQuery child, Func<bool> valueFactory)
+ {
+ return new FilterQuery(child, () => "equalTo", valueFactory, child.Client);
+ }
+
+ /// <summary>
+ /// Limits the result to first <see cref="countFactory" /> items.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="countFactory"> Number of elements. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery LimitToFirst(this ParameterQuery child, Func<int> countFactory)
+ {
+ return new FilterQuery(child, () => "limitToFirst", () => countFactory(), child.Client);
+ }
+
+ /// <summary>
+ /// Limits the result to last <see cref="countFactory" /> items.
+ /// </summary>
+ /// <param name="child"> Current node. </param>
+ /// <param name="countFactory"> Number of elements. </param>
+ /// <returns> The <see cref="FilterQuery" />. </returns>
+ public static FilterQuery LimitToLast(this ParameterQuery child, Func<int> countFactory)
+ {
+ return new FilterQuery(child, () => "limitToLast", () => countFactory(), child.Client);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Query/SilentQuery.cs b/dsa/FireBase/Query/SilentQuery.cs
new file mode 100644
index 0000000..d09d38b
--- /dev/null
+++ b/dsa/FireBase/Query/SilentQuery.cs
@@ -0,0 +1,18 @@
+namespace Firebase.Database.Query
+{
+ /// <summary>
+ /// Appends print=silent to the url.
+ /// </summary>
+ public class SilentQuery : ParameterQuery
+ {
+ public SilentQuery(FirebaseQuery parent, FirebaseClient client)
+ : base(parent, () => "print", client)
+ {
+ }
+
+ protected override string BuildUrlParameter(FirebaseQuery child)
+ {
+ return "silent";
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Settings.StyleCop b/dsa/FireBase/Settings.StyleCop
new file mode 100644
index 0000000..833aa39
--- /dev/null
+++ b/dsa/FireBase/Settings.StyleCop
@@ -0,0 +1,77 @@
+<StyleCopSettings Version="105">
+ <GlobalSettings>
+ <CollectionProperty Name="RecognizedWords">
+ <Value>auth</Value>
+ <Value>firebase</Value>
+ <Value>json</Value>
+ <Value>linq</Value>
+ <Value>oauth</Value>
+ </CollectionProperty>
+ </GlobalSettings>
+ <Analyzers>
+ <Analyzer AnalyzerId="StyleCop.CSharp.DocumentationRules">
+ <Rules>
+ <Rule Name="FileMustHaveHeader">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileHeaderMustShowCopyright">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileHeaderMustHaveCopyrightText">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileHeaderMustContainFileName">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileHeaderFileNameDocumentationMustMatchFileName">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileHeaderMustHaveValidCompanyText">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="FileHeaderFileNameDocumentationMustMatchTypeName">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="DocumentationTextMustBeginWithACapitalLetter">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">True</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="DocumentationTextMustEndWithAPeriod">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">True</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings>
+ <BooleanProperty Name="IgnorePrivates">True</BooleanProperty>
+ <BooleanProperty Name="IgnoreInternals">True</BooleanProperty>
+ <BooleanProperty Name="IncludeFields">False</BooleanProperty>
+ </AnalyzerSettings>
+ </Analyzer>
+ <Analyzer AnalyzerId="StyleCop.CSharp.ReadabilityRules">
+ <Rules>
+ <Rule Name="DoNotUseRegions">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">True</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings />
+ </Analyzer>
+ </Analyzers>
+</StyleCopSettings> \ No newline at end of file
diff --git a/dsa/FireBase/Streaming/FirebaseCache.cs b/dsa/FireBase/Streaming/FirebaseCache.cs
new file mode 100644
index 0000000..66241e0
--- /dev/null
+++ b/dsa/FireBase/Streaming/FirebaseCache.cs
@@ -0,0 +1,181 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Firebase.Database.Http;
+using Newtonsoft.Json;
+
+namespace Firebase.Database.Streaming
+{
+ /// <summary>
+ /// The firebase cache.
+ /// </summary>
+ /// <typeparam name="T"> Type of top-level entities in the cache. </typeparam>
+ public class FirebaseCache<T> : IEnumerable<FirebaseObject<T>>
+ {
+ private readonly IDictionary<string, T> dictionary;
+ private readonly bool isDictionaryType;
+
+ private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings
+ {
+ ObjectCreationHandling = ObjectCreationHandling.Replace
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FirebaseCache{T}" /> class.
+ /// </summary>
+ public FirebaseCache()
+ : this(new Dictionary<string, T>())
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FirebaseCache{T}" /> class and populates it with existing data.
+ /// </summary>
+ /// <param name="existingItems"> The existing items. </param>
+ public FirebaseCache(IDictionary<string, T> existingItems)
+ {
+ dictionary = existingItems;
+ isDictionaryType = typeof(IDictionary).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo());
+ }
+
+ /// <summary>
+ /// The push data.
+ /// </summary>
+ /// <param name="path"> The path of incoming data, separated by slash. </param>
+ /// <param name="data"> The data in json format as returned by firebase. </param>
+ /// <returns> Collection of top-level entities which were affected by the push. </returns>
+ public IEnumerable<FirebaseObject<T>> PushData(string path, string data, bool removeEmptyEntries = true)
+ {
+ object obj = this.dictionary;
+ Action<object> primitiveObjSetter = null;
+ Action objDeleter = null;
+
+ var pathElements = path.Split(new[] {"/"},
+ removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None);
+
+ // first find where we should insert the data to
+ foreach (var element in pathElements)
+ if (obj is IDictionary)
+ {
+ // if it's a dictionary, then it's just a matter of inserting into it / accessing existing object by key
+ var dictionary = obj as IDictionary;
+ var valueType = obj.GetType().GenericTypeArguments[1];
+
+ primitiveObjSetter = d => dictionary[element] = d;
+ objDeleter = () => dictionary.Remove(element);
+
+ if (dictionary.Contains(element))
+ {
+ obj = dictionary[element];
+ }
+ else
+ {
+ dictionary[element] = CreateInstance(valueType);
+ obj = dictionary[element];
+ }
+ }
+ else
+ {
+ // if it's not a dictionary, try to find the property of current object with the matching name
+ var objParent = obj;
+ var property = objParent
+ .GetType()
+ .GetRuntimeProperties()
+ .First(p => p.Name.Equals(element, StringComparison.OrdinalIgnoreCase) ||
+ element == p.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName);
+
+ objDeleter = () => property.SetValue(objParent, null);
+ primitiveObjSetter = d => property.SetValue(objParent, d);
+ obj = property.GetValue(obj);
+ if (obj == null)
+ {
+ obj = CreateInstance(property.PropertyType);
+ property.SetValue(objParent, obj);
+ }
+ }
+
+ // if data is null (=empty string) delete it
+ if (string.IsNullOrWhiteSpace(data) || data == "null")
+ {
+ var key = pathElements[0];
+ var target = dictionary[key];
+
+ objDeleter();
+
+ yield return new FirebaseObject<T>(key, target);
+ yield break;
+ }
+
+ // now insert the data
+ if (obj is IDictionary && !isDictionaryType)
+ {
+ // insert data into dictionary and return it as a collection of FirebaseObject
+ var dictionary = obj as IDictionary;
+ var valueType = obj.GetType().GenericTypeArguments[1];
+ var objectCollection = data.GetObjectCollection(valueType);
+
+ foreach (var item in objectCollection)
+ {
+ dictionary[item.Key] = item.Object;
+
+ // top level dictionary changed
+ if (!pathElements.Any()) yield return new FirebaseObject<T>(item.Key, (T) item.Object);
+ }
+
+ // nested dictionary changed
+ if (pathElements.Any())
+ {
+ this.dictionary[pathElements[0]] = this.dictionary[pathElements[0]];
+ yield return new FirebaseObject<T>(pathElements[0], this.dictionary[pathElements[0]]);
+ }
+ }
+ else
+ {
+ // set the data on a property of the given object
+ var valueType = obj.GetType();
+
+ // firebase sends strings without double quotes
+ var targetObject = valueType == typeof(string)
+ ? data
+ : JsonConvert.DeserializeObject(data, valueType);
+
+ if ((valueType.GetTypeInfo().IsPrimitive || valueType == typeof(string)) && primitiveObjSetter != null)
+ // handle primitive (value) types separately
+ primitiveObjSetter(targetObject);
+ else
+ JsonConvert.PopulateObject(data, obj, serializerSettings);
+
+ dictionary[pathElements[0]] = dictionary[pathElements[0]];
+ yield return new FirebaseObject<T>(pathElements[0], dictionary[pathElements[0]]);
+ }
+ }
+
+ public bool Contains(string key)
+ {
+ return dictionary.Keys.Contains(key);
+ }
+
+ private object CreateInstance(Type type)
+ {
+ if (type == typeof(string))
+ return string.Empty;
+ return Activator.CreateInstance(type);
+ }
+
+ #region IEnumerable
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<FirebaseObject<T>> GetEnumerator()
+ {
+ return dictionary.Select(p => new FirebaseObject<T>(p.Key, p.Value)).GetEnumerator();
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Streaming/FirebaseEvent.cs b/dsa/FireBase/Streaming/FirebaseEvent.cs
new file mode 100644
index 0000000..1761a72
--- /dev/null
+++ b/dsa/FireBase/Streaming/FirebaseEvent.cs
@@ -0,0 +1,37 @@
+namespace Firebase.Database.Streaming
+{
+ /// <summary>
+ /// Firebase event which hold <see cref="EventType" /> and the object affected by the event.
+ /// </summary>
+ /// <typeparam name="T"> Type of object affected by the event. </typeparam>
+ public class FirebaseEvent<T> : FirebaseObject<T>
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FirebaseEvent{T}" /> class.
+ /// </summary>
+ /// <param name="key"> The key of the object. </param>
+ /// <param name="obj"> The object. </param>
+ /// <param name="eventType"> The event type. </param>
+ public FirebaseEvent(string key, T obj, FirebaseEventType eventType, FirebaseEventSource eventSource)
+ : base(key, obj)
+ {
+ EventType = eventType;
+ EventSource = eventSource;
+ }
+
+ /// <summary>
+ /// Gets the source of the event.
+ /// </summary>
+ public FirebaseEventSource EventSource { get; }
+
+ /// <summary>
+ /// Gets the event type.
+ /// </summary>
+ public FirebaseEventType EventType { get; }
+
+ public static FirebaseEvent<T> Empty(FirebaseEventSource source)
+ {
+ return new FirebaseEvent<T>(string.Empty, default(T), FirebaseEventType.InsertOrUpdate, source);
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Streaming/FirebaseEventSource.cs b/dsa/FireBase/Streaming/FirebaseEventSource.cs
new file mode 100644
index 0000000..b1385ca
--- /dev/null
+++ b/dsa/FireBase/Streaming/FirebaseEventSource.cs
@@ -0,0 +1,38 @@
+namespace Firebase.Database.Streaming
+{
+ /// <summary>
+ /// Specifies the origin of given <see cref="FirebaseEvent{T}" />
+ /// </summary>
+ public enum FirebaseEventSource
+ {
+ /// <summary>
+ /// Event comes from an offline source.
+ /// </summary>
+ Offline,
+
+ /// <summary>
+ /// Event comes from online source fetched during initial pull (valid only for RealtimeDatabase).
+ /// </summary>
+ OnlineInitial,
+
+ /// <summary>
+ /// Event comes from online source received thru active stream.
+ /// </summary>
+ OnlineStream,
+
+ /// <summary>
+ /// Event comes from online source being fetched manually.
+ /// </summary>
+ OnlinePull,
+
+ /// <summary>
+ /// Event raised after successful online push (valid only for RealtimeDatabase which isn't streaming).
+ /// </summary>
+ OnlinePush,
+
+ /// <summary>
+ /// Event comes from an online source.
+ /// </summary>
+ Online = OnlineInitial | OnlinePull | OnlinePush | OnlineStream
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Streaming/FirebaseEventType.cs b/dsa/FireBase/Streaming/FirebaseEventType.cs
new file mode 100644
index 0000000..7606331
--- /dev/null
+++ b/dsa/FireBase/Streaming/FirebaseEventType.cs
@@ -0,0 +1,18 @@
+namespace Firebase.Database.Streaming
+{
+ /// <summary>
+ /// The type of event.
+ /// </summary>
+ public enum FirebaseEventType
+ {
+ /// <summary>
+ /// Item was inserted or updated.
+ /// </summary>
+ InsertOrUpdate,
+
+ /// <summary>
+ /// Item was deleted.
+ /// </summary>
+ Delete
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Streaming/FirebaseServerEventType.cs b/dsa/FireBase/Streaming/FirebaseServerEventType.cs
new file mode 100644
index 0000000..79c816d
--- /dev/null
+++ b/dsa/FireBase/Streaming/FirebaseServerEventType.cs
@@ -0,0 +1,15 @@
+namespace Firebase.Database.Streaming
+{
+ internal enum FirebaseServerEventType
+ {
+ Put,
+
+ Patch,
+
+ KeepAlive,
+
+ Cancel,
+
+ AuthRevoked
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Streaming/FirebaseSubscription.cs b/dsa/FireBase/Streaming/FirebaseSubscription.cs
new file mode 100644
index 0000000..fb0f403
--- /dev/null
+++ b/dsa/FireBase/Streaming/FirebaseSubscription.cs
@@ -0,0 +1,217 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using Firebase.Database.Query;
+using Newtonsoft.Json.Linq;
+
+namespace Firebase.Database.Streaming
+{
+ /// <summary>
+ /// The firebase subscription.
+ /// </summary>
+ /// <typeparam name="T"> Type of object to be streaming back to the called. </typeparam>
+ internal class FirebaseSubscription<T> : IDisposable
+ {
+ private static readonly HttpClient http;
+ private readonly FirebaseCache<T> cache;
+ private readonly CancellationTokenSource cancel;
+ private readonly FirebaseClient client;
+ private readonly string elementRoot;
+ private readonly IObserver<FirebaseEvent<T>> observer;
+ private readonly IFirebaseQuery query;
+
+ static FirebaseSubscription()
+ {
+ var handler = new HttpClientHandler
+ {
+ AllowAutoRedirect = true,
+ MaxAutomaticRedirections = 10,
+ CookieContainer = new CookieContainer()
+ };
+
+ var httpClient = new HttpClient(handler, true);
+
+ httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
+
+ http = httpClient;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FirebaseSubscription{T}" /> class.
+ /// </summary>
+ /// <param name="observer"> The observer. </param>
+ /// <param name="query"> The query. </param>
+ /// <param name="cache"> The cache. </param>
+ public FirebaseSubscription(IObserver<FirebaseEvent<T>> observer, IFirebaseQuery query, string elementRoot,
+ FirebaseCache<T> cache)
+ {
+ this.observer = observer;
+ this.query = query;
+ this.elementRoot = elementRoot;
+ cancel = new CancellationTokenSource();
+ this.cache = cache;
+ client = query.Client;
+ }
+
+ public void Dispose()
+ {
+ cancel.Cancel();
+ }
+
+ public event EventHandler<ExceptionEventArgs<FirebaseException>> ExceptionThrown;
+
+ public IDisposable Run()
+ {
+ Task.Run(() => ReceiveThread());
+
+ return this;
+ }
+
+ private async void ReceiveThread()
+ {
+ while (true)
+ {
+ var url = string.Empty;
+ var line = string.Empty;
+ var statusCode = HttpStatusCode.OK;
+
+ try
+ {
+ cancel.Token.ThrowIfCancellationRequested();
+
+ // initialize network connection
+ url = await query.BuildUrlAsync().ConfigureAwait(false);
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ var serverEvent = FirebaseServerEventType.KeepAlive;
+
+ var client = GetHttpClient();
+ var response = await client
+ .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancel.Token)
+ .ConfigureAwait(false);
+
+ statusCode = response.StatusCode;
+ response.EnsureSuccessStatusCode();
+
+ using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
+ using (var reader = this.client.Options.SubscriptionStreamReaderFactory(stream))
+ {
+ while (true)
+ {
+ cancel.Token.ThrowIfCancellationRequested();
+
+ line = reader.ReadLine()?.Trim();
+
+ if (string.IsNullOrWhiteSpace(line)) continue;
+
+ var tuple = line.Split(new[] {':'}, 2, StringSplitOptions.RemoveEmptyEntries)
+ .Select(s => s.Trim()).ToArray();
+
+ switch (tuple[0].ToLower())
+ {
+ case "event":
+ serverEvent = ParseServerEvent(serverEvent, tuple[1]);
+ break;
+ case "data":
+ ProcessServerData(url, serverEvent, tuple[1]);
+ break;
+ }
+
+ if (serverEvent == FirebaseServerEventType.AuthRevoked)
+ // auth token no longer valid, reconnect
+ break;
+ }
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex) when (statusCode != HttpStatusCode.OK)
+ {
+ observer.OnError(new FirebaseException(url, string.Empty, line, statusCode, ex));
+ Dispose();
+ break;
+ }
+ catch (Exception ex)
+ {
+ ExceptionThrown?.Invoke(this,
+ new ExceptionEventArgs<FirebaseException>(new FirebaseException(url, string.Empty, line,
+ statusCode, ex)));
+
+ await Task.Delay(2000).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private FirebaseServerEventType ParseServerEvent(FirebaseServerEventType serverEvent, string eventName)
+ {
+ switch (eventName)
+ {
+ case "put":
+ serverEvent = FirebaseServerEventType.Put;
+ break;
+ case "patch":
+ serverEvent = FirebaseServerEventType.Patch;
+ break;
+ case "keep-alive":
+ serverEvent = FirebaseServerEventType.KeepAlive;
+ break;
+ case "cancel":
+ serverEvent = FirebaseServerEventType.Cancel;
+ break;
+ case "auth_revoked":
+ serverEvent = FirebaseServerEventType.AuthRevoked;
+ break;
+ }
+
+ return serverEvent;
+ }
+
+ private void ProcessServerData(string url, FirebaseServerEventType serverEvent, string serverData)
+ {
+ switch (serverEvent)
+ {
+ case FirebaseServerEventType.Put:
+ case FirebaseServerEventType.Patch:
+ var result = JObject.Parse(serverData);
+ var path = result["path"].ToString();
+ var data = result["data"].ToString();
+
+ // If an elementRoot parameter is provided, but it's not in the cache, it was already deleted. So we can return an empty object.
+ if (string.IsNullOrWhiteSpace(elementRoot) || !cache.Contains(elementRoot))
+ if (path == "/" && data == string.Empty)
+ {
+ observer.OnNext(FirebaseEvent<T>.Empty(FirebaseEventSource.OnlineStream));
+ return;
+ }
+
+ var eventType = string.IsNullOrWhiteSpace(data)
+ ? FirebaseEventType.Delete
+ : FirebaseEventType.InsertOrUpdate;
+
+ var items = cache.PushData(elementRoot + path, data);
+
+ foreach (var i in items.ToList())
+ observer.OnNext(new FirebaseEvent<T>(i.Key, i.Object, eventType,
+ FirebaseEventSource.OnlineStream));
+
+ break;
+ case FirebaseServerEventType.KeepAlive:
+ break;
+ case FirebaseServerEventType.Cancel:
+ observer.OnError(new FirebaseException(url, string.Empty, serverData, HttpStatusCode.Unauthorized));
+ Dispose();
+ break;
+ }
+ }
+
+ private HttpClient GetHttpClient()
+ {
+ return http;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/FireBase/Streaming/NonBlockingStreamReader.cs b/dsa/FireBase/Streaming/NonBlockingStreamReader.cs
new file mode 100644
index 0000000..8228e32
--- /dev/null
+++ b/dsa/FireBase/Streaming/NonBlockingStreamReader.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using System.Text;
+
+namespace Firebase.Database.Streaming
+{
+ /// <summary>
+ /// When a regular <see cref="StreamReader" /> is used in a UWP app its <see cref="StreamReader.ReadLine" /> method
+ /// tends to take a long
+ /// time for data larger then 2 KB. This extremly simple implementation of <see cref="TextReader" /> can be used
+ /// instead to boost performance
+ /// in your UWP app. Use <see cref="FirebaseOptions" /> to inject an instance of this class into your
+ /// <see cref="FirebaseClient" />.
+ /// </summary>
+ public class NonBlockingStreamReader : TextReader
+ {
+ private const int DefaultBufferSize = 16000;
+ private readonly byte[] buffer;
+ private readonly int bufferSize;
+
+ private readonly Stream stream;
+
+ private string cachedData;
+
+ public NonBlockingStreamReader(Stream stream, int bufferSize = DefaultBufferSize)
+ {
+ this.stream = stream;
+ this.bufferSize = bufferSize;
+ buffer = new byte[bufferSize];
+
+ cachedData = string.Empty;
+ }
+
+ public override string ReadLine()
+ {
+ var currentString = TryGetNewLine();
+
+ while (currentString == null)
+ {
+ var read = stream.Read(buffer, 0, bufferSize);
+ var str = Encoding.UTF8.GetString(buffer, 0, read);
+
+ cachedData += str;
+ currentString = TryGetNewLine();
+ }
+
+ return currentString;
+ }
+
+ private string TryGetNewLine()
+ {
+ var newLine = cachedData.IndexOf('\n');
+
+ if (newLine >= 0)
+ {
+ var r = cachedData.Substring(0, newLine + 1);
+ cachedData = cachedData.Remove(0, r.Length);
+ return r.Trim();
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/dsa/NUnitTestProject1/Auxiliary/Calculator/ArgumentTests.cs b/dsa/NUnitTestProject1/Auxiliary/Calculator/ArgumentTests.cs
new file mode 100644
index 0000000..3e8dbeb
--- /dev/null
+++ b/dsa/NUnitTestProject1/Auxiliary/Calculator/ArgumentTests.cs
@@ -0,0 +1,59 @@
+using DSALib.Auxiliary.Calculator;
+using Moq;
+using NUnit.Framework;
+
+namespace NUnitTest.Auxiliary.Calculator
+{
+ [TestFixture]
+ public class ArgumentTests
+ {
+ private MockRepository mockRepository;
+
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.mockRepository = new MockRepository(MockBehavior.Strict);
+
+
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ this.mockRepository.VerifyAll();
+ }
+
+ private Argument CreateArgument()
+ {
+ return new Argument("3");
+ }
+
+ [Test]
+ public void Solve_StateUnderTest_ExpectedBehavior()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateArgument();
+
+ // Act
+ var result = unitUnderTest.Solve();
+
+ // Assert
+ Assert.AreEqual(3, result);
+ }
+
+ [Test]
+ public void ToString_StateUnderTest_ExpectedBehavior()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateArgument();
+
+ // Act
+ var result = unitUnderTest.ToString();
+
+ // Assert
+ Assert.AreEqual("3", result);
+ }
+ }
+}
diff --git a/dsa/NUnitTestProject1/Auxiliary/Calculator/StringSolverTests.cs b/dsa/NUnitTestProject1/Auxiliary/Calculator/StringSolverTests.cs
new file mode 100644
index 0000000..998a78b
--- /dev/null
+++ b/dsa/NUnitTestProject1/Auxiliary/Calculator/StringSolverTests.cs
@@ -0,0 +1,105 @@
+using DSALib.Auxiliary.Calculator;
+using Moq;
+using NUnit.Framework;
+
+namespace NUnitTest.Auxiliary.Calculator
+{
+ [TestFixture]
+ public class StringSolverTests
+ {
+ private MockRepository mockRepository;
+
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.mockRepository = new MockRepository(MockBehavior.Strict);
+
+
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ this.mockRepository.VerifyAll();
+ }
+
+ private StringSolver CreateStringSolver(string input)
+ {
+ return new StringSolver(input);
+ }
+
+ [Test]
+ public void Solve_StateUnderTest_ExpectedBehavior()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateStringSolver("1+1");
+
+ // Act
+ var result = unitUnderTest.Solve();
+
+ // Assert
+ Assert.AreEqual(2,result);
+ }
+
+ [Test]
+ public void Solve_mult()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateStringSolver("1+1-4*6+2");
+
+ // Act
+ var result = unitUnderTest.Solve();
+
+ // Assert
+ Assert.AreEqual(-20, result);
+ }
+
+ [Test]
+ public void Solve_braces()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateStringSolver("1+(1-4)*6+2");
+
+ // Act
+ var result = unitUnderTest.Solve();
+
+ // Assert
+ Assert.AreEqual(-15, result);
+ }
+
+ [Test]
+ public void Solve_wrong_braces()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateStringSolver("1+)(1-4)*6+2");
+
+ // Act
+ Assert.Throws<System.ArgumentException>(() =>unitUnderTest.Solve(), "Invalid brace sequence");
+ }
+
+ [Test, MaxTime(200)]
+ public void Solve_braces_timeout()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateStringSolver("1+(1-(4)*6+2");
+
+ // Act
+ Assert.Throws<System.ArgumentException>(() => unitUnderTest.Solve(), "Invalid brace sequence");
+ }
+
+ [Test]
+ public void ToString_StateUnderTest_ExpectedBehavior()
+ {
+ // Arrange
+ var unitUnderTest = this.CreateStringSolver("3+-4");
+
+ // Act
+ var result = unitUnderTest.ToString();
+
+ // Assert
+ Assert.AreEqual("(0+3+-4)", result);
+ }
+ }
+}
diff --git a/dsa/NUnitTestProject1/Auxiliary/DiceTests.cs b/dsa/NUnitTestProject1/Auxiliary/DiceTests.cs
new file mode 100644
index 0000000..6b37492
--- /dev/null
+++ b/dsa/NUnitTestProject1/Auxiliary/DiceTests.cs
@@ -0,0 +1,71 @@
+using DSALib.Auxiliary;
+using Moq;
+using NUnit.Framework;
+using System;
+
+namespace NUnitTest.Auxiliary
+{
+ [TestFixture]
+ public class DiceTests
+ {
+ private MockRepository mockRepository;
+
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ this.mockRepository = new MockRepository(MockBehavior.Strict);
+
+
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ this.mockRepository.VerifyAll();
+ }
+
+ private void CreateDice()
+ {}
+
+ [Test]
+ public void Roll_StateUnderTest_ExpectedBehavior()
+ {
+ // Arrange
+ int d = 20;
+
+ // Act
+ var result = Dice.Roll(d);
+
+ // Assert
+ Assert.True(result > 0 && result < d+1);
+ }
+
+ [Test]
+ public void Roll_StateUnderTest_ExpectedBehavior1()
+ {
+ // Arrange
+ string input = "w";
+
+ // Act
+ Assert.Throws<ArgumentException>( () => Dice.Roll(input));
+ }
+
+ [Test]
+ public void Roll_zero_dice()
+ {
+ // Arrange
+ int count = 0;
+ int d = 2;
+
+ // Act
+ var result = Dice.Roll(
+ count,
+ d);
+
+ // Assert
+ Assert.AreEqual(0, result);
+ }
+ }
+}
diff --git a/dsa/NUnitTestProject1/NUnitTest.csproj b/dsa/NUnitTestProject1/NUnitTest.csproj
new file mode 100644
index 0000000..7b3b1ea
--- /dev/null
+++ b/dsa/NUnitTestProject1/NUnitTest.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp2.2</TargetFramework>
+
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Moq" Version="4.5.28" />
+ <PackageReference Include="nunit" Version="3.11.0" />
+ <PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\DSALib\DSALib.csproj" />
+ </ItemGroup>
+
+</Project>