Compare commits

..

23 Commits

Author SHA1 Message Date
morbalint 34eaa3de37 update grafana SDK 2024-02-23 20:14:53 +01:00
morbalint ed53c25ea1 release 2e 2024-02-23 18:31:39 +01:00
morbalint aca949c614 2e feat basic PDF render 2024-02-23 16:44:54 +01:00
morbalint 636dcb92b3 feat 2e save to DB
not fully tested, but seems to work overall
2024-02-20 14:55:08 +01:00
morbalint 41e894cd42 feat: 2e save character initial implementation WIP
2e is hidden behind url navigation hiding, so hoping that nobody found it yet.

Note: DB migration make this commit a bit risky!
2024-02-14 17:34:20 +01:00
morbalint 8b3fa2fefa rename name to nev 2024-02-14 12:03:53 +01:00
morbalint b50a54f92b fix multiclass level up display 2024-02-14 11:34:11 +01:00
morbalint 9476c1f44c fix fegyverek
fix data
fix megterheltseg
2024-02-14 11:33:55 +01:00
morbalint 35b12524ab feat: multiclassing almost works in 2e 2024-02-13 23:57:50 +01:00
morbalint 2fe8fb05e7 upgrade GHA actions to node20 in ci.yml 2024-01-24 22:08:18 +01:00
morbalint 540c665a14 [be] optional open telemetry console logging
staging logs are full :(
2024-01-24 16:42:25 +01:00
morbalint 8f32daf337 [fe] [2e] add basic szintlepes 2024-01-24 16:38:16 +01:00
morbalint 978618d1b1 [be] open telemetry: only log to console when no other exporter exists 2024-01-23 13:31:22 +01:00
morbalint 65132fc7c6 add open telemetry and refactor setup 2024-01-23 13:10:16 +01:00
morbalint 8abe228d89 [fe] improve first time save UX 2024-01-22 12:51:40 +01:00
morbalint cc9a83607c use the official discord button on registration page as well
and disable the not yet approved external login providers on the registration page
2024-01-21 15:25:37 +01:00
morbalint 8092210ec8 [fe] fix unused import 2024-01-21 14:53:41 +01:00
morbalint fb8964062b use official discord button and disable other not yet approved external login providers 2024-01-21 14:51:17 +01:00
morbalint 55eeae5693 change save response
- no need for "pop up" access in browser
- more visibility (the notification is still ugly)
2024-01-21 14:09:01 +01:00
morbalint a1f694e30c frontend deps upgrade 2024-01-20 10:15:08 +01:00
morbalint fc5ce6d1ba fix save redirect url 2024-01-19 15:38:06 +01:00
morbalint dd86e8bd47 add CI status badge README.md 2024-01-19 12:01:33 +01:00
dependabot[bot] 0cd83000ab Bump follow-redirects from 1.15.3 to 1.15.4 in /Kemkas.Web/frontend (#1)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-19 11:28:03 +01:00
87 changed files with 4248 additions and 1026 deletions
+4 -4
View File
@@ -11,14 +11,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Node.js 18.x - name: Setup Node.js 18.x
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18.x node-version: 18.x
- name: Cache NPM packages - name: Cache NPM packages
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
key: npm-${{ hashFiles('./Kemkas.Web/frontend/yarn.lock') }} key: npm-${{ hashFiles('./Kemkas.Web/frontend/yarn.lock') }}
path: | path: |
@@ -40,7 +40,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v3
- name: Install doctl - name: Install doctl
uses: digitalocean/action-doctl@v2 uses: digitalocean/action-doctl@v2
@@ -75,7 +75,7 @@ jobs:
- docker-build - docker-build
if: ${{ github.event_name == 'push' || ( github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged ) }} if: ${{ github.event_name == 'push' || ( github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged ) }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install doctl - name: Install doctl
uses: digitalocean/action-doctl@v2 uses: digitalocean/action-doctl@v2
with: with:
@@ -65,7 +65,8 @@
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div> <div>
<p> <p>
@foreach (var provider in Model.ExternalLogins!) <button type="submit" class="btn" name="provider" value="Discord" title="Log in using your Discord account"><img height="24px" alt="Discord" src="/img/discord-logo-blue.svg" /></button>
@foreach (var provider in Model.ExternalLogins!.Where(el => el.Name != "Discord"))
{ {
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
} }
@@ -22,11 +22,13 @@ namespace Kemkas.Web.Areas.Identity.Pages.Account
{ {
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<LoginModel> _logger; private readonly ILogger<LoginModel> _logger;
private readonly IWebHostEnvironment _environment;
public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger) public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IWebHostEnvironment environment)
{ {
_signInManager = signInManager; _signInManager = signInManager;
_logger = logger; _logger = logger;
_environment = environment;
} }
/// <summary> /// <summary>
@@ -98,6 +100,12 @@ namespace Kemkas.Web.Areas.Identity.Pages.Account
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
// Disable not yet approved external login providers in production
if (!_environment.IsDevelopment())
{
ExternalLogins = ExternalLogins.Where(el => el.Name == "Discord").ToList();
}
ReturnUrl = returnUrl; ReturnUrl = returnUrl;
} }
@@ -107,6 +115,12 @@ namespace Kemkas.Web.Areas.Identity.Pages.Account
returnUrl ??= Url.Content("~/"); returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
// Disable not yet approved external login providers in production
if (!_environment.IsDevelopment())
{
ExternalLogins = ExternalLogins.Where(el => el.Name == "Discord").ToList();
}
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
@@ -49,7 +49,8 @@
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div> <div>
<p> <p>
@foreach (var provider in Model.ExternalLogins!) <button type="submit" class="btn" name="provider" value="Discord" title="Log in using your Discord account"><img height="24px" alt="Discord" src="/img/discord-logo-blue.svg" /></button>
@foreach (var provider in Model.ExternalLogins!.Where(el => el.Name != "Discord"))
{ {
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
} }
@@ -30,13 +30,15 @@ namespace Kemkas.Web.Areas.Identity.Pages.Account
private readonly IUserEmailStore<ApplicationUser> _emailStore; private readonly IUserEmailStore<ApplicationUser> _emailStore;
private readonly ILogger<RegisterModel> _logger; private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender; private readonly IEmailSender _emailSender;
private readonly IWebHostEnvironment _environment;
public RegisterModel( public RegisterModel(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
IUserStore<ApplicationUser> userStore, IUserStore<ApplicationUser> userStore,
SignInManager<ApplicationUser> signInManager, SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger, ILogger<RegisterModel> logger,
IEmailSender emailSender) IEmailSender emailSender,
IWebHostEnvironment environment)
{ {
_userManager = userManager; _userManager = userManager;
_userStore = userStore; _userStore = userStore;
@@ -44,6 +46,7 @@ namespace Kemkas.Web.Areas.Identity.Pages.Account
_signInManager = signInManager; _signInManager = signInManager;
_logger = logger; _logger = logger;
_emailSender = emailSender; _emailSender = emailSender;
_environment = environment;
} }
/// <summary> /// <summary>
@@ -105,12 +108,22 @@ namespace Kemkas.Web.Areas.Identity.Pages.Account
{ {
ReturnUrl = returnUrl; ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
// Disable not yet approved external login providers in production
if (!_environment.IsDevelopment())
{
ExternalLogins = ExternalLogins.Where(el => el.Name == "Discord").ToList();
}
} }
public async Task<IActionResult> OnPostAsync(string returnUrl = null) public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{ {
returnUrl ??= Url.Content("~/"); returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
// Disable not yet approved external login providers in production
if (!_environment.IsDevelopment())
{
ExternalLogins = ExternalLogins.Where(el => el.Name == "Discord").ToList();
}
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
return Page(); return Page();
+10
View File
@@ -0,0 +1,10 @@
namespace Kemkas.Web.Config;
public class ExternalAuthOptions
{
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
public bool IsValid() => ClientId != null && ClientSecret != null;
}
@@ -0,0 +1,84 @@
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace Kemkas.Web.Config;
public static class WebApplicationBuilderExtensions
{
public static void AddAuth(this WebApplicationBuilder builder)
{
var externalAuthConfigSection = builder.Configuration.GetSection("Authentication");
var authenticationBuilder = builder.Services.AddAuthentication();
var googleAuthOptions = new ExternalAuthOptions();
externalAuthConfigSection.GetSection("Google").Bind(googleAuthOptions);
if (googleAuthOptions.IsValid())
{
authenticationBuilder.AddGoogle(options =>
{
options.ClientId = googleAuthOptions.ClientId!;
options.ClientSecret = googleAuthOptions.ClientSecret!;
});
}
var facebookAuthOptions = new ExternalAuthOptions();
externalAuthConfigSection.GetSection("Facebook").Bind(facebookAuthOptions);
if (facebookAuthOptions.IsValid())
{
authenticationBuilder.AddFacebook(options =>
{
options.ClientId = facebookAuthOptions.ClientId!;
options.ClientSecret = facebookAuthOptions.ClientSecret!;
});
}
var discordAuthOptions = new ExternalAuthOptions();
externalAuthConfigSection.GetSection("Discord").Bind(discordAuthOptions);
if (discordAuthOptions.IsValid())
{
authenticationBuilder.AddDiscord(options =>
{
options.ClientId = discordAuthOptions.ClientId!;
options.ClientSecret = discordAuthOptions.ClientSecret!;
});
}
}
public static void AddOpenTelemetry(this WebApplicationBuilder builder)
{
var otlResourceBuilder = ResourceBuilder.CreateDefault().AddService("kemkas-api");
var openTelemetryEndpoint = builder.Configuration.GetSection("monitoring")["url"];
var consoleEnabled = builder.Configuration.GetSection("monitoring")["console"] == "true";
builder.Services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.SetResourceBuilder(otlResourceBuilder)
.AddAspNetCoreInstrumentation();
if (openTelemetryEndpoint != null)
{
tracerProviderBuilder.AddOtlpExporter(opt => opt.Endpoint = new Uri(openTelemetryEndpoint));
}
else if (consoleEnabled)
{
tracerProviderBuilder.AddConsoleExporter();
}
})
.WithMetrics(meterProviderBuilder =>
{
meterProviderBuilder
.SetResourceBuilder(otlResourceBuilder)
.AddAspNetCoreInstrumentation();
if (openTelemetryEndpoint != null)
{
meterProviderBuilder.AddOtlpExporter(opt => opt.Endpoint = new Uri(openTelemetryEndpoint));
}
else if (consoleEnabled)
{
meterProviderBuilder.AddConsoleExporter();
}
});
}
}
@@ -0,0 +1,90 @@
using System.ComponentModel.DataAnnotations;
using Kemkas.Web.Db.Models;
using Kemkas.Web.Services.FirstEdition.Character;
using Kemkas.Web.Services.Shared;
using Kemkas.Web.ViewModels;
using Microsoft.AspNetCore.Mvc;
namespace Kemkas.Web.Controllers;
/// <summary>
/// First edition character endpoints
/// </summary>
/// <param name="validationService"></param>
/// <param name="dtoToDbModelService"></param>
/// <param name="dbModelToDto1EService"></param>
/// <param name="persistenceService"></param>
[ApiController]
[Route("[controller]")]
public class Character1EController(
ICharacterValidationService validationService,
ICharacter1EDtoToDbModelService dtoToDbModelService,
ICharacterDbModelToDto1EService dbModelToDto1EService,
ICharacterPersistenceService persistenceService)
: ControllerBase
{
[HttpPost]
public async Task<ActionResult<Guid>> StoreNewCharacter([FromBody] Character1eDto dto, [FromQuery]bool isPublic = true)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var errors = validationService.Validate(dto);
if (errors != null)
{
return BadRequest(errors);
}
V1Karakter model;
try
{
model = dtoToDbModelService.Convert(dto);
}
catch (ValidationException ex)
{
return BadRequest(ex.Message);
}
var id = await persistenceService.StoreNewCharacter1E(model, isPublic);
return id;
}
[HttpGet("{id:guid}")]
public async Task<ActionResult<Character1eDto>> GetCharacterById([FromRoute] Guid id)
{
var entity = await persistenceService.GetCharacter1EById(id);
if (entity is null)
{
return NotFound();
}
return dbModelToDto1EService.Convert(entity);
}
[HttpPost("{id:guid}")]
public async Task<ActionResult<Guid>> UpdateCharacter([FromRoute] Guid id, [FromBody] Character1eDto dto, [FromQuery]bool isPublic = true)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var errors = validationService.Validate(dto);
if (errors != null)
{
return BadRequest(errors);
}
var original = await persistenceService.GetCharacter1EById(id, tracking: true);
if (original is null)
{
return NotFound();
}
dtoToDbModelService.Update(original, dto);
await persistenceService.UpdateCharacter1E(original, isPublic);
return id;
}
}
@@ -0,0 +1,89 @@
using System.ComponentModel.DataAnnotations;
using Kemkas.Web.Db.Models;
using Kemkas.Web.Services.SecondEdition.Character;
using Kemkas.Web.Services.Shared;
using Kemkas.Web.ViewModels;
using Microsoft.AspNetCore.Mvc;
namespace Kemkas.Web.Controllers;
[ApiController]
[Route("[controller]")]
public class Character2EController(
ICharacter2EDtoToDbModelService dtoToDbModelService,
ICharacterPersistenceService persistenceService,
ICharacterDbModelToDto2EService dbModelToDtoService
) : ControllerBase
{
[HttpPost]
public async Task<ActionResult<Guid>> StoreNewCharacter([FromBody] Character2eDto dto, [FromQuery]bool isPublic = true)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// var errors = validationService.Validate(dto);
// if (errors != null)
// {
// return BadRequest(errors);
// }
V2Karakter model;
try
{
model = dtoToDbModelService.Convert(dto);
}
catch (ValidationException ex)
{
return BadRequest(ex.Message);
}
var id = await persistenceService.StoreNewCharacter2E(model, isPublic);
return id;
}
[HttpGet("{id:guid}")]
public async Task<ActionResult<Character2eDto>> GetCharacterById([FromRoute] Guid id)
{
var entity = await persistenceService.GetCharacter2EById(id);
if (entity is null)
{
return NotFound();
}
return dbModelToDtoService.Convert(entity);
}
[HttpPost("{id:guid}")]
public async Task<ActionResult<Guid>> UpdateCharacter([FromRoute] Guid id, [FromBody] Character2eDto dto, [FromQuery]bool isPublic = true)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// var errors = validationService.Validate(dto);
// if (errors != null)
// {
// return BadRequest(errors);
// }
var original = await persistenceService.GetCharacter2EById(id, tracking: true);
if (original is null)
{
return NotFound();
}
try
{
dtoToDbModelService.Update(original, dto);
}
catch (ValidationException ex)
{
return BadRequest(ex.Message);
}
await persistenceService.UpdateCharacter2E(original, isPublic);
return id;
}
}
+7 -73
View File
@@ -1,85 +1,19 @@
using System.ComponentModel.DataAnnotations; using Kemkas.Web.Services.FirstEdition.Character;
using Kemkas.Web.Db.Models; using Kemkas.Web.Services.Shared;
using Kemkas.Web.Services.Character;
using Kemkas.Web.ViewModels; using Kemkas.Web.ViewModels;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Kemkas.Web.Controllers; namespace Kemkas.Web.Controllers;
/// <summary>
/// Shared first and second edition character endpoints
/// </summary>
/// <param name="persistenceService"></param>
[ApiController] [ApiController]
[Route("[controller]")] [Route("[controller]")]
public class CharacterController( public class CharacterController(ICharacterPersistenceService persistenceService) : ControllerBase
ICharacterValidationService validationService,
ICharacterDtoToDbModelService dtoToDbModelService,
ICharacterDbModelToDtoService dbModelToDtoService,
ICharacterPersistenceService persistenceService)
: ControllerBase
{ {
[HttpPost]
public async Task<ActionResult<Guid>> StoreNewCharacter([FromBody] CharacterDto dto, [FromQuery]bool isPublic = true)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var errors = validationService.Validate(dto);
if (errors != null)
{
return BadRequest(errors);
}
V1Karakter model;
try
{
model = dtoToDbModelService.Convert(dto);
}
catch (ValidationException ex)
{
return BadRequest(ex.Message);
}
var id = await persistenceService.StoreNewCharacter(model, isPublic);
return id;
}
[HttpGet("{id:guid}")]
public async Task<ActionResult<CharacterDto>> GetCharacterById([FromRoute] Guid id)
{
var entity = await persistenceService.GetCharacterById(id);
if (entity is null)
{
return NotFound();
}
return dbModelToDtoService.Convert(entity);
}
[HttpPost("{id:guid}")]
public async Task<ActionResult<Guid>> UpdateCharacter([FromRoute] Guid id, [FromBody] CharacterDto dto, [FromQuery]bool isPublic = true)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var errors = validationService.Validate(dto);
if (errors != null)
{
return BadRequest(errors);
}
var original = await persistenceService.GetCharacterById(id, tracking: true);
if (original is null)
{
return NotFound();
}
dtoToDbModelService.Update(original, dto);
await persistenceService.UpdateCharacter(original, isPublic);
return id;
}
[HttpGet] [HttpGet]
[Authorize] [Authorize]
public async Task<List<CharacterListItemDto>> GetAllCharacters() public async Task<List<CharacterListItemDto>> GetAllCharacters()
@@ -1,35 +0,0 @@
using Kemkas.Web.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Kemkas.Web.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(
index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
+11
View File
@@ -13,6 +13,8 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, Applicati
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
// 1st edition character
public DbSet<V1Karakter> Karakterek { get; set; } public DbSet<V1Karakter> Karakterek { get; set; }
public DbSet<V1KarakterKepzettseg> KarakterKepzettsegek { get; set; } public DbSet<V1KarakterKepzettseg> KarakterKepzettsegek { get; set; }
@@ -21,4 +23,13 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, Applicati
public DbSet<V1Felszereles> Felszerelesek { get; set; } public DbSet<V1Felszereles> Felszerelesek { get; set; }
// 2nd edition character
public DbSet<V2Karakter> Karakterek2E { get; set; }
public DbSet<V2KarakterKepzettseg> KarakterKepzettsegek2E { get; set; }
public DbSet<V2Szintlepes> Szintlepesek2E { get; set; }
public DbSet<V2Felszereles> Felszerelesek2E { get; set; }
} }
+19 -2
View File
@@ -1,6 +1,6 @@
namespace Kemkas.Web.Db.Enums; namespace Kemkas.Web.Db.Enums;
public enum Faj public enum Faj1E
{ {
Ember = 0, Ember = 0,
Amazon = 1, Amazon = 1,
@@ -14,4 +14,21 @@ public enum Faj
Felszerzet = 9, Felszerzet = 9,
Gnom = 10, Gnom = 10,
Torpe = 11, Torpe = 11,
} }
public enum Faj2E
{
Ember = 0,
Amazon = 1,
Birodalmi = 2,
Nomad = 3,
Eszaki = 4,
Osember = 5,
Elf = 6,
Felelf = 7,
Felork = 8,
Felszerzet = 9,
Gnom = 10,
Torpe = 11,
}
+66 -26
View File
@@ -2,42 +2,82 @@ namespace Kemkas.Web.Db.Enums;
public static class FajExtensions public static class FajExtensions
{ {
public static Faj Convert(string faj) public static Faj1E Convert1E(string faj)
{ {
return faj switch return faj switch
{ {
"f_ember" => Faj.Ember, "f_ember" => Faj1E.Ember,
"f_amazon" => Faj.Amazon, "f_amazon" => Faj1E.Amazon,
"f_birodalmi" => Faj.Birodalmi, "f_birodalmi" => Faj1E.Birodalmi,
"f_etuniai" => Faj.Etuniai, "f_etuniai" => Faj1E.Etuniai,
"f_eszaki" => Faj.Eszaki, "f_eszaki" => Faj1E.Eszaki,
"f_osember" => Faj.Osember, "f_osember" => Faj1E.Osember,
"f_elf" => Faj.Elf, "f_elf" => Faj1E.Elf,
"f_felelf" => Faj.Felelf, "f_felelf" => Faj1E.Felelf,
"f_felork" => Faj.Felork, "f_felork" => Faj1E.Felork,
"f_felszerzet" => Faj.Felszerzet, "f_felszerzet" => Faj1E.Felszerzet,
"f_gnom" => Faj.Gnom, "f_gnom" => Faj1E.Gnom,
"f_torpe" => Faj.Torpe, "f_torpe" => Faj1E.Torpe,
_ => throw new Exception("invalid faj: " + faj)
};
}
public static Faj2E Convert2E(string faj)
{
return faj switch
{
"f_2e_ember" => Faj2E.Ember,
"f_2e_amazon" => Faj2E.Amazon,
"f_2e_birodalmi" => Faj2E.Birodalmi,
"f_2e_nomad" => Faj2E.Nomad,
"f_2e_eszaki" => Faj2E.Eszaki,
"f_2e_osember" => Faj2E.Osember,
"f_2e_elf" => Faj2E.Elf,
"f_2e_felelf" => Faj2E.Felelf,
"f_2e_felork" => Faj2E.Felork,
"f_2e_felszerzet" => Faj2E.Felszerzet,
"f_2e_gnom" => Faj2E.Gnom,
"f_2e_torpe" => Faj2E.Torpe,
_ => throw new Exception("invalid faj: " + faj) _ => throw new Exception("invalid faj: " + faj)
}; };
} }
public static string Convert(this Faj faj) public static string Convert(this Faj1E faj)
{ {
return faj switch return faj switch
{ {
Faj.Ember => "f_ember", Faj1E.Ember => "f_ember",
Faj.Amazon => "f_amazon", Faj1E.Amazon => "f_amazon",
Faj.Birodalmi => "f_birodalmi", Faj1E.Birodalmi => "f_birodalmi",
Faj.Etuniai => "f_etuniai", Faj1E.Etuniai => "f_etuniai",
Faj.Eszaki => "f_eszaki", Faj1E.Eszaki => "f_eszaki",
Faj.Osember => "f_osember", Faj1E.Osember => "f_osember",
Faj.Elf => "f_elf", Faj1E.Elf => "f_elf",
Faj.Felelf => "f_felelf", Faj1E.Felelf => "f_felelf",
Faj.Felork => "f_felork", Faj1E.Felork => "f_felork",
Faj.Felszerzet => "f_felszerzet", Faj1E.Felszerzet => "f_felszerzet",
Faj.Gnom => "f_gnom", Faj1E.Gnom => "f_gnom",
Faj.Torpe => "f_torpe", Faj1E.Torpe => "f_torpe",
_ => throw new Exception("invalid faj: " + faj)
};
}
public static string Convert(this Faj2E faj)
{
return faj switch
{
Faj2E.Ember => "f_2e_ember",
Faj2E.Amazon => "f_2e_amazon",
Faj2E.Birodalmi => "f_2e_birodalmi",
Faj2E.Nomad => "f_2e_nomad",
Faj2E.Eszaki => "f_2e_eszaki",
Faj2E.Osember => "f_2e_osember",
Faj2E.Elf => "f_2e_elf",
Faj2E.Felelf => "f_2e_felelf",
Faj2E.Felork => "f_2e_felork",
Faj2E.Felszerzet => "f_2e_felszerzet",
Faj2E.Gnom => "f_2e_gnom",
Faj2E.Torpe => "f_2e_torpe",
_ => throw new Exception("invalid faj: " + faj) _ => throw new Exception("invalid faj: " + faj)
}; };
} }
+37 -1
View File
@@ -1,6 +1,6 @@
namespace Kemkas.Web.Db.Enums; namespace Kemkas.Web.Db.Enums;
public enum Kepzettseg public enum Kepzettseg1E
{ {
Alkimia = 1, Alkimia = 1,
Alcazas = 2, Alcazas = 2,
@@ -31,4 +31,40 @@ public enum Kepzettseg
Varazslatismeret = 27, Varazslatismeret = 27,
Zarnyitas = 28, Zarnyitas = 28,
Zsebmetszes = 29, Zsebmetszes = 29,
}
// offset by 200 to make it more different from 1e karakter kepzettsegek
// I don't plan to mix them, but let's make it obvious which one is which.
// But it could be useful in one-time character conversions in the future.
public enum Kepzettseg2E
{
Alkimia = 201,
Alcazas = 202,
Allatidomitas = 203,
Csapdak = 204,
Csillagjoslas = 205,
Egyensulyozas = 206,
Eloadas = 207,
Ertekbecsles = 208,
Gyogyitas = 209,
Hajozas = 210,
Hallgatozas = 211,
Hamisitas = 212,
JelekOlvasasa = 213,
Herbalizmus = 601,
Historia = 602,
Lovaglas = 215,
Maszas = 216,
Megfigyeles = 217,
Meregkeveres = 218,
Mesterseg = 219,
Nyomkereses = 220,
Okkultizmus = 666, // inside joke
Osonas = 221,
Szabadulomuveszet = 223,
Ugras = 225,
Uszas = 226,
Vadonjaras = 603,
Zarnyitas = 228,
Zsebmetszes = 229,
} }
+134 -60
View File
@@ -4,76 +4,150 @@ namespace Kemkas.Web.Db.Enums;
public static class KepzettsegExtensions public static class KepzettsegExtensions
{ {
public static Kepzettseg Convert(string kepzettseg) public static Kepzettseg1E Convert1E(string kepzettseg)
{ {
return kepzettseg switch return kepzettseg switch
{ {
"k_alkimia" => Kepzettseg.Alkimia, "k_alkimia" => Kepzettseg1E.Alkimia,
"k_alcazas" => Kepzettseg.Alcazas, "k_alcazas" => Kepzettseg1E.Alcazas,
"k_allatidomitas" => Kepzettseg.Allatidomitas, "k_allatidomitas" => Kepzettseg1E.Allatidomitas,
"k_csapdak" => Kepzettseg.Csapdak, "k_csapdak" => Kepzettseg1E.Csapdak,
"k_csillagjoslas" => Kepzettseg.Csillagjoslas, "k_csillagjoslas" => Kepzettseg1E.Csillagjoslas,
"k_egyensulyozas" => Kepzettseg.Egyensulyozas, "k_egyensulyozas" => Kepzettseg1E.Egyensulyozas,
"k_eloadas" => Kepzettseg.Eloadas, "k_eloadas" => Kepzettseg1E.Eloadas,
"k_ertekbecsles" => Kepzettseg.Ertekbecsles, "k_ertekbecsles" => Kepzettseg1E.Ertekbecsles,
"k_gyogyitas" => Kepzettseg.Gyogyitas, "k_gyogyitas" => Kepzettseg1E.Gyogyitas,
"k_hajozas" => Kepzettseg.Hajozas, "k_hajozas" => Kepzettseg1E.Hajozas,
"k_hallgatozas" => Kepzettseg.Hallgatozas, "k_hallgatozas" => Kepzettseg1E.Hallgatozas,
"k_hamisitas" => Kepzettseg.Hamisitas, "k_hamisitas" => Kepzettseg1E.Hamisitas,
"k_jelek_olvasasa" => Kepzettseg.JelekOlvasasa, "k_jelek_olvasasa" => Kepzettseg1E.JelekOlvasasa,
"k_koncentracio" => Kepzettseg.Koncentracio, "k_koncentracio" => Kepzettseg1E.Koncentracio,
"k_lovaglas" => Kepzettseg.Lovaglas, "k_lovaglas" => Kepzettseg1E.Lovaglas,
"k_maszas" => Kepzettseg.Maszas, "k_maszas" => Kepzettseg1E.Maszas,
"k_megfigyeles" => Kepzettseg.Megfigyeles, "k_megfigyeles" => Kepzettseg1E.Megfigyeles,
"k_meregkeveres" => Kepzettseg.Meregkeveres, "k_meregkeveres" => Kepzettseg1E.Meregkeveres,
"k_mesterseg" => Kepzettseg.Mesterseg, "k_mesterseg" => Kepzettseg1E.Mesterseg,
"k_nyomkereses" => Kepzettseg.Nyomkereses, "k_nyomkereses" => Kepzettseg1E.Nyomkereses,
"k_osonas" => Kepzettseg.Osonas, "k_osonas" => Kepzettseg1E.Osonas,
"k_rejtozes" => Kepzettseg.Rejtozes, "k_rejtozes" => Kepzettseg1E.Rejtozes,
"k_szabadulomuveszet" => Kepzettseg.Szabadulomuveszet, "k_szabadulomuveszet" => Kepzettseg1E.Szabadulomuveszet,
"k_tudas" => Kepzettseg.Tudas, "k_tudas" => Kepzettseg1E.Tudas,
"k_ugras" => Kepzettseg.Ugras, "k_ugras" => Kepzettseg1E.Ugras,
"k_uszas" => Kepzettseg.Uszas, "k_uszas" => Kepzettseg1E.Uszas,
"k_varazslatismeret" => Kepzettseg.Varazslatismeret, "k_varazslatismeret" => Kepzettseg1E.Varazslatismeret,
"k_zarnyitas" => Kepzettseg.Zarnyitas, "k_zarnyitas" => Kepzettseg1E.Zarnyitas,
"k_zsebmetszes" => Kepzettseg.Zsebmetszes, "k_zsebmetszes" => Kepzettseg1E.Zsebmetszes,
_ => throw new ValidationException("invalid kepzettseg: " + kepzettseg),
};
}
public static Kepzettseg2E Convert2E(string kepzettseg)
{
return kepzettseg switch
{
"k_alkimia" => Kepzettseg2E.Alkimia,
"k_alcazas" => Kepzettseg2E.Alcazas,
"k_allatidomitas" => Kepzettseg2E.Allatidomitas,
"k_csapdak" => Kepzettseg2E.Csapdak,
"k_csillagjoslas" => Kepzettseg2E.Csillagjoslas,
"k_egyensulyozas" => Kepzettseg2E.Egyensulyozas,
"k_eloadas" => Kepzettseg2E.Eloadas,
"k_ertekbecsles" => Kepzettseg2E.Ertekbecsles,
"k_gyogyitas" => Kepzettseg2E.Gyogyitas,
"k_hajozas" => Kepzettseg2E.Hajozas,
"k_hallgatozas" => Kepzettseg2E.Hallgatozas,
"k_hamisitas" => Kepzettseg2E.Hamisitas,
"k_herbalizmus" => Kepzettseg2E.Herbalizmus,
"k_historia" => Kepzettseg2E.Historia,
"k_jelek_olvasasa" => Kepzettseg2E.JelekOlvasasa,
"k_lovaglas" => Kepzettseg2E.Lovaglas,
"k_maszas" => Kepzettseg2E.Maszas,
"k_megfigyeles" => Kepzettseg2E.Megfigyeles,
"k_meregkeveres" => Kepzettseg2E.Meregkeveres,
"k_mesterseg" => Kepzettseg2E.Mesterseg,
"k_nyomkereses" => Kepzettseg2E.Nyomkereses,
"k_okkultizmus" => Kepzettseg2E.Okkultizmus,
"k_osonas" => Kepzettseg2E.Osonas,
"k_szabadulomuveszet" => Kepzettseg2E.Szabadulomuveszet,
"k_ugras" => Kepzettseg2E.Ugras,
"k_uszas" => Kepzettseg2E.Uszas,
"k_vadonjaras" => Kepzettseg2E.Vadonjaras,
"k_zarnyitas" => Kepzettseg2E.Zarnyitas,
"k_zsebmetszes" => Kepzettseg2E.Zsebmetszes,
_ => throw new ValidationException("invalid kepzettseg: " + kepzettseg), _ => throw new ValidationException("invalid kepzettseg: " + kepzettseg),
}; };
} }
public static string Convert(this Kepzettseg kepzettseg) public static string Convert(this Kepzettseg1E kepzettseg)
{ {
return kepzettseg switch return kepzettseg switch
{ {
Kepzettseg.Alkimia => "k_alkimia", Kepzettseg1E.Alkimia => "k_alkimia",
Kepzettseg.Alcazas => "k_alcazas", Kepzettseg1E.Alcazas => "k_alcazas",
Kepzettseg.Allatidomitas => "k_allatidomitas", Kepzettseg1E.Allatidomitas => "k_allatidomitas",
Kepzettseg.Csapdak => "k_csapdak", Kepzettseg1E.Csapdak => "k_csapdak",
Kepzettseg.Csillagjoslas => "k_csillagjoslas", Kepzettseg1E.Csillagjoslas => "k_csillagjoslas",
Kepzettseg.Egyensulyozas => "k_egyensulyozas", Kepzettseg1E.Egyensulyozas => "k_egyensulyozas",
Kepzettseg.Eloadas => "k_eloadas", Kepzettseg1E.Eloadas => "k_eloadas",
Kepzettseg.Ertekbecsles => "k_ertekbecsles", Kepzettseg1E.Ertekbecsles => "k_ertekbecsles",
Kepzettseg.Gyogyitas => "k_gyogyitas", Kepzettseg1E.Gyogyitas => "k_gyogyitas",
Kepzettseg.Hajozas => "k_hajozas", Kepzettseg1E.Hajozas => "k_hajozas",
Kepzettseg.Hallgatozas => "k_hallgatozas", Kepzettseg1E.Hallgatozas => "k_hallgatozas",
Kepzettseg.Hamisitas => "k_hamisitas", Kepzettseg1E.Hamisitas => "k_hamisitas",
Kepzettseg.JelekOlvasasa => "k_jelek_olvasasa", Kepzettseg1E.JelekOlvasasa => "k_jelek_olvasasa",
Kepzettseg.Koncentracio => "k_koncentracio", Kepzettseg1E.Koncentracio => "k_koncentracio",
Kepzettseg.Lovaglas => "k_lovaglas", Kepzettseg1E.Lovaglas => "k_lovaglas",
Kepzettseg.Maszas => "k_maszas", Kepzettseg1E.Maszas => "k_maszas",
Kepzettseg.Megfigyeles => "k_megfigyeles", Kepzettseg1E.Megfigyeles => "k_megfigyeles",
Kepzettseg.Meregkeveres => "k_meregkeveres", Kepzettseg1E.Meregkeveres => "k_meregkeveres",
Kepzettseg.Mesterseg => "k_mesterseg", Kepzettseg1E.Mesterseg => "k_mesterseg",
Kepzettseg.Nyomkereses => "k_nyomkereses", Kepzettseg1E.Nyomkereses => "k_nyomkereses",
Kepzettseg.Osonas => "k_osonas", Kepzettseg1E.Osonas => "k_osonas",
Kepzettseg.Rejtozes => "k_rejtozes", Kepzettseg1E.Rejtozes => "k_rejtozes",
Kepzettseg.Szabadulomuveszet => "k_szabadulomuveszet", Kepzettseg1E.Szabadulomuveszet => "k_szabadulomuveszet",
Kepzettseg.Tudas => "k_tudas", Kepzettseg1E.Tudas => "k_tudas",
Kepzettseg.Ugras => "k_ugras", Kepzettseg1E.Ugras => "k_ugras",
Kepzettseg.Uszas => "k_uszas", Kepzettseg1E.Uszas => "k_uszas",
Kepzettseg.Varazslatismeret => "k_varazslatismeret", Kepzettseg1E.Varazslatismeret => "k_varazslatismeret",
Kepzettseg.Zarnyitas => "k_zarnyitas", Kepzettseg1E.Zarnyitas => "k_zarnyitas",
Kepzettseg.Zsebmetszes => "k_zsebmetszes", Kepzettseg1E.Zsebmetszes => "k_zsebmetszes",
_ => throw new ValidationException("invalid kepzettseg: " + kepzettseg),
};
}
public static string Convert(this Kepzettseg2E kepzettseg)
{
return kepzettseg switch
{
Kepzettseg2E.Alkimia => "k_alkimia",
Kepzettseg2E.Alcazas => "k_alcazas",
Kepzettseg2E.Allatidomitas => "k_allatidomitas",
Kepzettseg2E.Csapdak => "k_csapdak",
Kepzettseg2E.Csillagjoslas => "k_csillagjoslas",
Kepzettseg2E.Egyensulyozas => "k_egyensulyozas",
Kepzettseg2E.Eloadas => "k_eloadas",
Kepzettseg2E.Ertekbecsles => "k_ertekbecsles",
Kepzettseg2E.Gyogyitas => "k_gyogyitas",
Kepzettseg2E.Hajozas => "k_hajozas",
Kepzettseg2E.Hallgatozas => "k_hallgatozas",
Kepzettseg2E.Hamisitas => "k_hamisitas",
Kepzettseg2E.Herbalizmus => "k_herbalizmus",
Kepzettseg2E.Historia => "k_historia",
Kepzettseg2E.JelekOlvasasa => "k_jelek_olvasasa",
Kepzettseg2E.Lovaglas => "k_lovaglas",
Kepzettseg2E.Maszas => "k_maszas",
Kepzettseg2E.Megfigyeles => "k_megfigyeles",
Kepzettseg2E.Meregkeveres => "k_meregkeveres",
Kepzettseg2E.Mesterseg => "k_mesterseg",
Kepzettseg2E.Nyomkereses => "k_nyomkereses",
Kepzettseg2E.Okkultizmus => "k_okkultizmus",
Kepzettseg2E.Osonas => "k_osonas",
Kepzettseg2E.Szabadulomuveszet => "k_szabadulomuveszet",
Kepzettseg2E.Ugras => "k_ugras",
Kepzettseg2E.Uszas => "k_uszas",
Kepzettseg2E.Vadonjaras => "k_vadonjaras",
Kepzettseg2E.Zarnyitas => "k_zarnyitas",
Kepzettseg2E.Zsebmetszes => "k_zsebmetszes",
_ => throw new ValidationException("invalid kepzettseg: " + kepzettseg), _ => throw new ValidationException("invalid kepzettseg: " + kepzettseg),
}; };
} }
+18 -1
View File
@@ -1,6 +1,6 @@
namespace Kemkas.Web.Db.Enums; namespace Kemkas.Web.Db.Enums;
public enum Osztaly public enum Osztaly1E
{ {
Error = 0, // for unset enum value mapping Error = 0, // for unset enum value mapping
Harcos = 1, Harcos = 1,
@@ -12,4 +12,21 @@ public enum Osztaly
Tolvaj = 7, Tolvaj = 7,
Varazslo = 8, Varazslo = 8,
Illuzionista = 9, Illuzionista = 9,
}
public enum Osztaly2E
{
Error = 0, // for unset enum value mapping
Harcos = 1,
Ijasz = 2,
Amazon = 3,
Tengeresz = 4,
Barbar = 5,
Pap = 6,
Tolvaj = 7,
Varazslo = 8,
Illuzionista = 9,
Dalnok = 10,
Druida = 11,
Vandor = 12,
} }
+60 -20
View File
@@ -4,36 +4,76 @@ namespace Kemkas.Web.Db.Enums;
public static class OsztalyExtensions public static class OsztalyExtensions
{ {
public static Osztaly Convert(string osztaly) public static Osztaly1E Convert1E(string osztaly)
{ {
return osztaly switch return osztaly switch
{ {
"o_harcos" => Osztaly.Harcos, "o_harcos" => Osztaly1E.Harcos,
"o_ijasz" => Osztaly.Ijasz, "o_ijasz" => Osztaly1E.Ijasz,
"o_amazon" => Osztaly.Amazon, "o_amazon" => Osztaly1E.Amazon,
"o_kaloz" => Osztaly.Kaloz, "o_kaloz" => Osztaly1E.Kaloz,
"o_barbar" => Osztaly.Barbar, "o_barbar" => Osztaly1E.Barbar,
"o_pap" => Osztaly.Pap, "o_pap" => Osztaly1E.Pap,
"o_tolvaj" => Osztaly.Tolvaj, "o_tolvaj" => Osztaly1E.Tolvaj,
"o_varazslo" => Osztaly.Varazslo, "o_varazslo" => Osztaly1E.Varazslo,
"o_illuzionista" => Osztaly.Illuzionista, "o_illuzionista" => Osztaly1E.Illuzionista,
_ => throw new ValidationException("unknown osztaly: " + osztaly) _ => throw new ValidationException("unknown osztaly: " + osztaly)
}; };
} }
public static string Convert(this Osztaly osztaly) public static Osztaly2E Convert2E(string osztaly)
{ {
return osztaly switch return osztaly switch
{ {
Osztaly.Harcos => "o_harcos", "o_2e_harcos" => Osztaly2E.Harcos,
Osztaly.Ijasz => "o_ijasz", "o_2e_ijasz" => Osztaly2E.Ijasz,
Osztaly.Amazon => "o_amazon", "o_2e_amazon" => Osztaly2E.Amazon,
Osztaly.Kaloz => "o_kaloz", "o_2e_tengeresz" => Osztaly2E.Tengeresz,
Osztaly.Barbar => "o_barbar", "o_2e_barbar" => Osztaly2E.Barbar,
Osztaly.Pap => "o_pap", "o_2e_pap" => Osztaly2E.Pap,
Osztaly.Tolvaj => "o_tolvaj", "o_2e_tolvaj" => Osztaly2E.Tolvaj,
Osztaly.Varazslo => "o_varazslo", "o_2e_varazslo" => Osztaly2E.Varazslo,
Osztaly.Illuzionista => "o_illuzionista", "o_2e_illuzionista" => Osztaly2E.Illuzionista,
"o_2e_dalnok" => Osztaly2E.Dalnok,
"o_2e_druida" => Osztaly2E.Druida,
"o_2e_vandor" => Osztaly2E.Vandor,
_ => throw new ValidationException("unknown osztaly: " + osztaly)
};
}
public static string Convert(this Osztaly1E osztaly)
{
return osztaly switch
{
Osztaly1E.Harcos => "o_harcos",
Osztaly1E.Ijasz => "o_ijasz",
Osztaly1E.Amazon => "o_amazon",
Osztaly1E.Kaloz => "o_kaloz",
Osztaly1E.Barbar => "o_barbar",
Osztaly1E.Pap => "o_pap",
Osztaly1E.Tolvaj => "o_tolvaj",
Osztaly1E.Varazslo => "o_varazslo",
Osztaly1E.Illuzionista => "o_illuzionista",
_ => throw new ValidationException("invalid osztaly!")
};
}
public static string Convert(this Osztaly2E osztaly)
{
return osztaly switch
{
Osztaly2E.Harcos => "o_2e_harcos",
Osztaly2E.Ijasz => "o_2e_ijasz",
Osztaly2E.Amazon => "o_2e_amazon",
Osztaly2E.Tengeresz => "o_2e_tengeresz",
Osztaly2E.Barbar => "o_2e_barbar",
Osztaly2E.Pap => "o_2e_pap",
Osztaly2E.Tolvaj => "o_2e_tolvaj",
Osztaly2E.Varazslo => "o_2e_varazslo",
Osztaly2E.Illuzionista => "o_2e_illuzionista",
Osztaly2E.Dalnok => "o_2e_dalnok",
Osztaly2E.Druida => "o_2e_druida",
Osztaly2E.Vandor => "o_2e_vandor",
_ => throw new ValidationException("invalid osztaly!") _ => throw new ValidationException("invalid osztaly!")
}; };
} }
@@ -0,0 +1,690 @@
// <auto-generated />
using System;
using Kemkas.Web.Db;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Kemkas.Web.Db.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240214163223_AddSecondEditionModels")]
partial class AddSecondEditionModels
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Kemkas.Web.Db.Models.ApplicationRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Kemkas.Web.Db.Models.ApplicationUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1Felszereles", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsFegyver")
.HasColumnType("boolean");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("Felszerelesek");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1Karakter", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte>("Bolcsesseg")
.HasColumnType("smallint");
b.Property<byte>("Egeszseg")
.HasColumnType("smallint");
b.Property<byte>("Ero")
.HasColumnType("smallint");
b.Property<int>("Faj")
.HasColumnType("integer");
b.Property<byte>("Intelligencia")
.HasColumnType("smallint");
b.Property<bool>("IsPublic")
.HasColumnType("boolean");
b.Property<string>("Isten")
.HasColumnType("text");
b.Property<byte>("Jellem")
.HasColumnType("smallint");
b.Property<byte>("Karizma")
.HasColumnType("smallint");
b.Property<double?>("Kor")
.HasColumnType("double precision");
b.Property<string>("Nem")
.HasColumnType("text");
b.Property<string>("Nev")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Osztaly")
.HasColumnType("integer");
b.Property<int?>("OwnerUserId")
.HasColumnType("integer");
b.Property<string>("Pajzs")
.HasColumnType("text");
b.Property<string>("Pancel")
.HasColumnType("text");
b.Property<byte>("Szint")
.HasColumnType("smallint");
b.Property<byte>("Ugyesseg")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("OwnerUserId");
b.ToTable("Karakterek");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1KarakterKepzettseg", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsTolvajKepzettseg")
.HasColumnType("boolean");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<int>("Kepzettseg")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("KarakterKepzettsegek");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1Szintlepes", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FegyverSpecializacio")
.HasColumnType("text");
b.Property<byte>("HpRoll")
.HasColumnType("smallint");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<byte>("KarakterSzint")
.HasColumnType("smallint");
b.Property<byte?>("TulajdonsagNoveles")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("Szintlepesek");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Felszereles", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsFegyver")
.HasColumnType("boolean");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<string>("TargyId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("Felszerelesek2E");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Karakter", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte>("Bolcsesseg")
.HasColumnType("smallint");
b.Property<byte>("Egeszseg")
.HasColumnType("smallint");
b.Property<byte>("Ero")
.HasColumnType("smallint");
b.Property<int>("Faj")
.HasColumnType("integer");
b.Property<byte>("Intelligencia")
.HasColumnType("smallint");
b.Property<bool>("IsPublic")
.HasColumnType("boolean");
b.Property<string>("Isten")
.HasColumnType("text");
b.Property<byte>("Jellem")
.HasColumnType("smallint");
b.Property<byte>("Karizma")
.HasColumnType("smallint");
b.Property<double?>("Kor")
.HasColumnType("double precision");
b.Property<string>("Nem")
.HasColumnType("text");
b.Property<string>("Nev")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("OwnerUserId")
.HasColumnType("integer");
b.Property<string>("Pajzs")
.HasColumnType("text");
b.Property<string>("Pancel")
.HasColumnType("text");
b.Property<byte>("Szint")
.HasColumnType("smallint");
b.Property<byte>("Ugyesseg")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("OwnerUserId");
b.ToTable("Karakterek2E");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2KarakterKepzettseg", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsTolvajKepzettseg")
.HasColumnType("boolean");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<int>("Kepzettseg")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("KarakterKepzettsegek2E");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Szintlepes", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FegyverSpecializacio")
.HasColumnType("text");
b.Property<byte>("HpRoll")
.HasColumnType("smallint");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<byte>("KarakterSzint")
.HasColumnType("smallint");
b.Property<int>("Osztaly")
.HasColumnType("integer");
b.Property<int?>("TolvajExtraKepzettseg")
.HasColumnType("integer");
b.Property<byte?>("TulajdonsagNoveles")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("Szintlepesek2E");
});
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("FriendlyName")
.HasColumnType("text");
b.Property<string>("Xml")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DataProtectionKeys");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<int>("RoleId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ProviderKey")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<int>", b =>
{
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<int>("RoleId")
.HasColumnType("integer");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
{
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1Felszereles", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V1Karakter", "Karakter")
.WithMany("Felszereles")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1Karakter", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationUser", "OwnerUser")
.WithMany()
.HasForeignKey("OwnerUserId");
b.Navigation("OwnerUser");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1KarakterKepzettseg", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V1Karakter", "Karakter")
.WithMany("KarakterKepzettsegek")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1Szintlepes", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V1Karakter", "Karakter")
.WithMany("Szintlepesek")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Felszereles", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V2Karakter", "Karakter")
.WithMany("Felszereles")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Karakter", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationUser", "OwnerUser")
.WithMany()
.HasForeignKey("OwnerUserId");
b.Navigation("OwnerUser");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2KarakterKepzettseg", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V2Karakter", "Karakter")
.WithMany("KarakterKepzettsegek")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Szintlepes", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V2Karakter", "Karakter")
.WithMany("Szintlepesek")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<int>", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Kemkas.Web.Db.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V1Karakter", b =>
{
b.Navigation("Felszereles");
b.Navigation("KarakterKepzettsegek");
b.Navigation("Szintlepesek");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Karakter", b =>
{
b.Navigation("Felszereles");
b.Navigation("KarakterKepzettsegek");
b.Navigation("Szintlepesek");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,148 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Kemkas.Web.Db.Migrations
{
/// <inheritdoc />
public partial class AddSecondEditionModels : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Karakterek2E",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
IsPublic = table.Column<bool>(type: "boolean", nullable: false),
OwnerUserId = table.Column<int>(type: "integer", nullable: true),
Nev = table.Column<string>(type: "text", nullable: false),
Nem = table.Column<string>(type: "text", nullable: true),
Kor = table.Column<double>(type: "double precision", nullable: true),
Jellem = table.Column<byte>(type: "smallint", nullable: false),
Isten = table.Column<string>(type: "text", nullable: true),
Faj = table.Column<int>(type: "integer", nullable: false),
Ero = table.Column<byte>(type: "smallint", nullable: false),
Ugyesseg = table.Column<byte>(type: "smallint", nullable: false),
Egeszseg = table.Column<byte>(type: "smallint", nullable: false),
Intelligencia = table.Column<byte>(type: "smallint", nullable: false),
Bolcsesseg = table.Column<byte>(type: "smallint", nullable: false),
Karizma = table.Column<byte>(type: "smallint", nullable: false),
Szint = table.Column<byte>(type: "smallint", nullable: false),
Pancel = table.Column<string>(type: "text", nullable: true),
Pajzs = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Karakterek2E", x => x.Id);
table.ForeignKey(
name: "FK_Karakterek2E_AspNetUsers_OwnerUserId",
column: x => x.OwnerUserId,
principalTable: "AspNetUsers",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Felszerelesek2E",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
KarakterId = table.Column<Guid>(type: "uuid", nullable: false),
TargyId = table.Column<string>(type: "text", nullable: false),
IsFegyver = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Felszerelesek2E", x => x.Id);
table.ForeignKey(
name: "FK_Felszerelesek2E_Karakterek2E_KarakterId",
column: x => x.KarakterId,
principalTable: "Karakterek2E",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "KarakterKepzettsegek2E",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
KarakterId = table.Column<Guid>(type: "uuid", nullable: false),
Kepzettseg = table.Column<int>(type: "integer", nullable: false),
IsTolvajKepzettseg = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_KarakterKepzettsegek2E", x => x.Id);
table.ForeignKey(
name: "FK_KarakterKepzettsegek2E_Karakterek2E_KarakterId",
column: x => x.KarakterId,
principalTable: "Karakterek2E",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Szintlepesek2E",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
KarakterId = table.Column<Guid>(type: "uuid", nullable: false),
KarakterSzint = table.Column<byte>(type: "smallint", nullable: false),
Osztaly = table.Column<int>(type: "integer", nullable: false),
HpRoll = table.Column<byte>(type: "smallint", nullable: false),
TulajdonsagNoveles = table.Column<byte>(type: "smallint", nullable: true),
FegyverSpecializacio = table.Column<string>(type: "text", nullable: true),
TolvajExtraKepzettseg = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Szintlepesek2E", x => x.Id);
table.ForeignKey(
name: "FK_Szintlepesek2E_Karakterek2E_KarakterId",
column: x => x.KarakterId,
principalTable: "Karakterek2E",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Felszerelesek2E_KarakterId",
table: "Felszerelesek2E",
column: "KarakterId");
migrationBuilder.CreateIndex(
name: "IX_Karakterek2E_OwnerUserId",
table: "Karakterek2E",
column: "OwnerUserId");
migrationBuilder.CreateIndex(
name: "IX_KarakterKepzettsegek2E_KarakterId",
table: "KarakterKepzettsegek2E",
column: "KarakterId");
migrationBuilder.CreateIndex(
name: "IX_Szintlepesek2E_KarakterId",
table: "Szintlepesek2E",
column: "KarakterId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Felszerelesek2E");
migrationBuilder.DropTable(
name: "KarakterKepzettsegek2E");
migrationBuilder.DropTable(
name: "Szintlepesek2E");
migrationBuilder.DropTable(
name: "Karakterek2E");
}
}
}
@@ -259,6 +259,150 @@ namespace Kemkas.Web.Db.Migrations
b.ToTable("Szintlepesek"); b.ToTable("Szintlepesek");
}); });
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Felszereles", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsFegyver")
.HasColumnType("boolean");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<string>("TargyId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("Felszerelesek2E");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Karakter", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte>("Bolcsesseg")
.HasColumnType("smallint");
b.Property<byte>("Egeszseg")
.HasColumnType("smallint");
b.Property<byte>("Ero")
.HasColumnType("smallint");
b.Property<int>("Faj")
.HasColumnType("integer");
b.Property<byte>("Intelligencia")
.HasColumnType("smallint");
b.Property<bool>("IsPublic")
.HasColumnType("boolean");
b.Property<string>("Isten")
.HasColumnType("text");
b.Property<byte>("Jellem")
.HasColumnType("smallint");
b.Property<byte>("Karizma")
.HasColumnType("smallint");
b.Property<double?>("Kor")
.HasColumnType("double precision");
b.Property<string>("Nem")
.HasColumnType("text");
b.Property<string>("Nev")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("OwnerUserId")
.HasColumnType("integer");
b.Property<string>("Pajzs")
.HasColumnType("text");
b.Property<string>("Pancel")
.HasColumnType("text");
b.Property<byte>("Szint")
.HasColumnType("smallint");
b.Property<byte>("Ugyesseg")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("OwnerUserId");
b.ToTable("Karakterek2E");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2KarakterKepzettseg", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("IsTolvajKepzettseg")
.HasColumnType("boolean");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<int>("Kepzettseg")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("KarakterKepzettsegek2E");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Szintlepes", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FegyverSpecializacio")
.HasColumnType("text");
b.Property<byte>("HpRoll")
.HasColumnType("smallint");
b.Property<Guid>("KarakterId")
.HasColumnType("uuid");
b.Property<byte>("KarakterSzint")
.HasColumnType("smallint");
b.Property<int>("Osztaly")
.HasColumnType("integer");
b.Property<int?>("TolvajExtraKepzettseg")
.HasColumnType("integer");
b.Property<byte?>("TulajdonsagNoveles")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("KarakterId");
b.ToTable("Szintlepesek2E");
});
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -427,6 +571,48 @@ namespace Kemkas.Web.Db.Migrations
b.Navigation("Karakter"); b.Navigation("Karakter");
}); });
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Felszereles", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V2Karakter", "Karakter")
.WithMany("Felszereles")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Karakter", b =>
{
b.HasOne("Kemkas.Web.Db.Models.ApplicationUser", "OwnerUser")
.WithMany()
.HasForeignKey("OwnerUserId");
b.Navigation("OwnerUser");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2KarakterKepzettseg", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V2Karakter", "Karakter")
.WithMany("KarakterKepzettsegek")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Szintlepes", b =>
{
b.HasOne("Kemkas.Web.Db.Models.V2Karakter", "Karakter")
.WithMany("Szintlepesek")
.HasForeignKey("KarakterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Karakter");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{ {
b.HasOne("Kemkas.Web.Db.Models.ApplicationRole", null) b.HasOne("Kemkas.Web.Db.Models.ApplicationRole", null)
@@ -486,6 +672,15 @@ namespace Kemkas.Web.Db.Migrations
b.Navigation("Szintlepesek"); b.Navigation("Szintlepesek");
}); });
modelBuilder.Entity("Kemkas.Web.Db.Models.V2Karakter", b =>
{
b.Navigation("Felszereles");
b.Navigation("KarakterKepzettsegek");
b.Navigation("Szintlepesek");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }
+5 -2
View File
@@ -4,6 +4,9 @@ using Kemkas.Web.Db.Enums;
namespace Kemkas.Web.Db.Models; namespace Kemkas.Web.Db.Models;
/// <summary>
/// 1st edition character sheet
/// </summary>
public class V1Karakter public class V1Karakter
{ {
[Key] public Guid Id { get; set; } [Key] public Guid Id { get; set; }
@@ -15,8 +18,8 @@ public class V1Karakter
public double? Kor { get; set; } public double? Kor { get; set; }
public Jellem Jellem { get; set; } = Jellem.Semleges; public Jellem Jellem { get; set; } = Jellem.Semleges;
public string? Isten { get; set; } public string? Isten { get; set; }
public Faj Faj { get; set; } = Faj.Ember; public Faj1E Faj { get; set; } = Faj1E.Ember;
public Osztaly Osztaly { get; set; } public Osztaly1E Osztaly { get; set; }
public byte Ero { get; set; } public byte Ero { get; set; }
public byte Ugyesseg { get; set; } public byte Ugyesseg { get; set; }
public byte Egeszseg { get; set; } public byte Egeszseg { get; set; }
+1 -1
View File
@@ -9,7 +9,7 @@ public class V1KarakterKepzettseg
[Key] [Key]
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid KarakterId { get; set; } public Guid KarakterId { get; set; }
public Kepzettseg Kepzettseg { get; set; } public Kepzettseg1E Kepzettseg { get; set; }
public bool IsTolvajKepzettseg { get; set; } = false; public bool IsTolvajKepzettseg { get; set; } = false;
[ForeignKey(nameof(KarakterId))] [ForeignKey(nameof(KarakterId))]
+2
View File
@@ -19,6 +19,8 @@ public class V1Szintlepes
public byte HpRoll { get; set; } public byte HpRoll { get; set; }
public Tulajdonsag? TulajdonsagNoveles { get; set; } public Tulajdonsag? TulajdonsagNoveles { get; set; }
// used for both Kaloz/Tengeresz and Harcos
public string? FegyverSpecializacio { get; set; } public string? FegyverSpecializacio { get; set; }
[ForeignKey(nameof(KarakterId))] [ForeignKey(nameof(KarakterId))]
+17
View File
@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Kemkas.Web.Db.Models;
public class V2Felszereles
{
[Key]
public Guid Id { get; set; }
public Guid KarakterId { get; set; }
public string TargyId { get; set; }
public bool IsFegyver { get; set; } = true;
[ForeignKey(nameof(KarakterId))]
public virtual V2Karakter Karakter { get; set; }
}
+44
View File
@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Kemkas.Web.Db.Enums;
namespace Kemkas.Web.Db.Models;
/// <summary>
/// 2nd edition character sheet
/// </summary>
public class V2Karakter
{
[Key] public Guid Id { get; set; }
public bool IsPublic { get; set; } = false;
public int? OwnerUserId { get; set; }
public string Nev { get; set; }
public string? Nem { get; set; }
public double? Kor { get; set; }
public Jellem Jellem { get; set; } = Jellem.Semleges;
public string? Isten { get; set; }
public Faj2E Faj { get; set; } = Faj2E.Ember;
public byte Ero { get; set; }
public byte Ugyesseg { get; set; }
public byte Egeszseg { get; set; }
public byte Intelligencia { get; set; }
public byte Bolcsesseg { get; set; }
public byte Karizma { get; set; }
public byte Szint { get; set; } = 1;
public string? Pancel { get; set; }
public string? Pajzs { get; set; }
[ForeignKey(nameof(OwnerUserId))]
public ApplicationUser? OwnerUser { get; set; }
[InverseProperty(nameof(V2KarakterKepzettseg.Karakter))]
public virtual IEnumerable<V2KarakterKepzettseg> KarakterKepzettsegek { get; set; }
[InverseProperty(nameof(V2Szintlepes.Karakter))]
public virtual IEnumerable<V2Szintlepes> Szintlepesek { get; set; }
[InverseProperty(nameof(V2Felszereles.Karakter))]
public virtual IEnumerable<V2Felszereles> Felszereles { get; set; }
}
@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Kemkas.Web.Db.Enums;
namespace Kemkas.Web.Db.Models;
public class V2KarakterKepzettseg
{
[Key]
public Guid Id { get; set; }
public Guid KarakterId { get; set; }
public Kepzettseg2E Kepzettseg { get; set; }
public bool IsTolvajKepzettseg { get; set; } = false;
[ForeignKey(nameof(KarakterId))]
public virtual V2Karakter Karakter { get; set; }
}
+29
View File
@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Kemkas.Web.Db.Enums;
namespace Kemkas.Web.Db.Models;
public class V2Szintlepes
{
[Key]
public Guid Id { get; set; }
public Guid KarakterId { get; set; }
// this is also used for ordering the level ups
public byte KarakterSzint { get; set; }
public Osztaly2E Osztaly { get; set; }
public byte HpRoll { get; set; }
public Tulajdonsag? TulajdonsagNoveles { get; set; }
// used for both Kaloz/Tengeresz and Harcos
public string? FegyverSpecializacio { get; set; }
// for the 9th level Tolvaj specialty
public Kepzettseg2E? TolvajExtraKepzettseg { get; set; }
[ForeignKey(nameof(KarakterId))]
public virtual V2Karakter Karakter { get; set; }
}
+9
View File
@@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.Discord" Version="8.0.0" /> <PackageReference Include="AspNet.Security.OAuth.Discord" Version="8.0.0" />
<PackageReference Include="Grpc.Core" Version="2.46.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.0" />
@@ -36,6 +37,10 @@
</PackageReference> </PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -47,6 +52,10 @@
<None Remove="$(SpaRoot)**" /> <None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" /> <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Services\FirstEdition\" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') "> <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed --> <!-- Ensure Node.js is installed -->
+14 -21
View File
@@ -6,11 +6,15 @@ using Kemkas.Web.Config;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Kemkas.Web.Db; using Kemkas.Web.Db;
using Kemkas.Web.Db.Models; using Kemkas.Web.Db.Models;
using Kemkas.Web.Services.Character; using Kemkas.Web.Services.FirstEdition.Character;
using Kemkas.Web.Services.Identity; using Kemkas.Web.Services.Identity;
using Kemkas.Web.Services.SecondEdition.Character;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
// using OpenTelemetry.Metrics;
// using OpenTelemetry.Resources;
// using OpenTelemetry.Trace;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -34,25 +38,7 @@ builder.Services.AddSingleton<IEmailSender, MailgunEmailSender>();
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>(); .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthentication() builder.AddAuth();
.AddGoogle(options =>
{
var section = builder.Configuration.GetSection("Authentication:Google");
options.ClientId = section["ClientId"];
options.ClientSecret = section["ClientSecret"];
})
.AddFacebook(options =>
{
var section = builder.Configuration.GetSection("Authentication:Facebook");
options.ClientId = section["ClientId"];
options.ClientSecret = section["ClientSecret"];
})
.AddDiscord(options =>
{
var section = builder.Configuration.GetSection("Authentication:Discord");
options.ClientId = section["ClientId"];
options.ClientSecret = section["ClientSecret"];
});
builder.Services.AddControllersWithViews().AddJsonOptions(options => builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{ {
@@ -62,7 +48,12 @@ builder.Services.AddControllersWithViews().AddJsonOptions(options =>
}); });
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
builder.Services.AddCharacterServices(); builder.AddOpenTelemetry();
builder.Services.AddHealthChecks();
builder.Services.AddCharacter1EServices();
builder.Services.AddCharacter2EServices();
var app = builder.Build(); var app = builder.Build();
app.UsePathBase("/api"); app.UsePathBase("/api");
@@ -93,6 +84,8 @@ else
app.UseHsts(); // TODO: check if this should be handled by proxy app.UseHsts(); // TODO: check if this should be handled by proxy
} }
app.UseHealthChecks("/health");
await using (var scope = app.Services.CreateAsyncScope()) await using (var scope = app.Services.CreateAsyncScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
@@ -1,88 +0,0 @@
using Kemkas.Web.Db;
using Kemkas.Web.Db.Enums;
using Kemkas.Web.Db.Models;
using Kemkas.Web.Services.Identity;
using Kemkas.Web.ViewModels;
using Microsoft.EntityFrameworkCore;
namespace Kemkas.Web.Services.Character;
public interface ICharacterPersistenceService
{
public Task<Guid> StoreNewCharacter(V1Karakter karakter, bool isPublic = false);
public Task<V1Karakter?> GetCharacterById(Guid id, bool tracking = false);
public Task<List<CharacterListItemDto>> GetAllCharactersOfUser();
public Task UpdateCharacter(V1Karakter karakter, bool isPublic = false);
}
public class CharacterPersistenceService(
ApplicationDbContext dbContext,
ICurrentUserService currentUserService
) : ICharacterPersistenceService
{
public async Task<Guid> StoreNewCharacter(V1Karakter karakter, bool isPublic = false)
{
var currentUser = await currentUserService.GetCurrentUser();
karakter.OwnerUserId = currentUser?.Id;
karakter.Id = Guid.Empty;
karakter.IsPublic = isPublic;
dbContext.Karakterek.Add(karakter);
await dbContext.SaveChangesAsync();
return karakter.Id;
}
public async Task<V1Karakter?> GetCharacterById(Guid id, bool tracking = false)
{
IQueryable<V1Karakter> queriable = dbContext.Karakterek.Include(x => x.KarakterKepzettsegek)
.Include(x => x.Szintlepesek)
.Include(x => x.Felszereles);
queriable = tracking ? queriable.AsTracking() : queriable.AsNoTracking();
var entity = await queriable
.FirstOrDefaultAsync(x => x.Id == id);
if (entity?.OwnerUserId is null || entity.IsPublic)
{
return entity;
}
var currentUser = await currentUserService.GetCurrentUser();
return entity.OwnerUserId == currentUser?.Id ? entity : null;
}
public async Task<List<CharacterListItemDto>> GetAllCharactersOfUser()
{
var currentUser = await currentUserService.GetCurrentUser();
if (currentUser is null)
{
return [];
}
var characters = await dbContext.Karakterek
.Where(k => k.OwnerUserId == currentUser.Id)
.Select(k => new CharacterListItemDto
{
Id = k.Id,
Name = k.Nev,
Szint = k.Szint,
Faj = k.Faj.Convert(),
Osztaly = k.Osztaly.Convert(),
Edition = "1e"
})
.ToListAsync();
return characters;
}
public async Task UpdateCharacter(V1Karakter karakter, bool isPublic = false)
{
karakter.IsPublic = isPublic;
dbContext.Karakterek.Attach(karakter);
await dbContext.SaveChangesAsync();
}
}
@@ -3,18 +3,18 @@ using Kemkas.Web.Db.Enums;
using Kemkas.Web.Db.Models; using Kemkas.Web.Db.Models;
using Kemkas.Web.ViewModels; using Kemkas.Web.ViewModels;
namespace Kemkas.Web.Services.Character; namespace Kemkas.Web.Services.FirstEdition.Character;
public interface ICharacterDtoToDbModelService public interface ICharacter1EDtoToDbModelService
{ {
public V1Karakter Convert(CharacterDto dto); public V1Karakter Convert(Character1eDto dto);
public void Update(V1Karakter original, CharacterDto dto); public void Update(V1Karakter original, Character1eDto dto);
} }
public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService public class Character1EDtoToDbModelService : ICharacter1EDtoToDbModelService
{ {
public V1Karakter Convert(CharacterDto dto) public V1Karakter Convert(Character1eDto dto)
{ {
var karakter = new V1Karakter var karakter = new V1Karakter
{ {
@@ -23,8 +23,8 @@ public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService
Kor = dto.Kor, Kor = dto.Kor,
Jellem = JellemExtensions.Convert(dto.Jellem), Jellem = JellemExtensions.Convert(dto.Jellem),
Isten = dto.Isten, Isten = dto.Isten,
Faj = FajExtensions.Convert(dto.Faj), Faj = FajExtensions.Convert1E(dto.Faj),
Osztaly = OsztalyExtensions.Convert(dto.Osztaly), Osztaly = OsztalyExtensions.Convert1E(dto.Osztaly),
Ero = dto.Tulajdonsagok.Ero, Ero = dto.Tulajdonsagok.Ero,
Ugyesseg = dto.Tulajdonsagok.Ugy, Ugyesseg = dto.Tulajdonsagok.Ugy,
Egeszseg = dto.Tulajdonsagok.Egs, Egeszseg = dto.Tulajdonsagok.Egs,
@@ -32,10 +32,10 @@ public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService
Bolcsesseg = dto.Tulajdonsagok.Bol, Bolcsesseg = dto.Tulajdonsagok.Bol,
Karizma = dto.Tulajdonsagok.Kar, Karizma = dto.Tulajdonsagok.Kar,
Szint = dto.Szint, Szint = dto.Szint,
Pajzs = dto.Felszereles.PajzsId,
Pancel = dto.Felszereles.PancelId
}; };
karakter.KarakterKepzettsegek = ConvertKepzettsegek(dto, karakter); karakter.KarakterKepzettsegek = ConvertKepzettsegek(dto, karakter);
karakter.Pajzs = dto.Felszereles.PajzsId;
karakter.Pancel = dto.Felszereles.PancelId;
karakter.Felszereles = dto.Felszereles.FegyverIds.Select(x => new V1Felszereles karakter.Felszereles = dto.Felszereles.FegyverIds.Select(x => new V1Felszereles
{ {
Karakter = karakter, Karakter = karakter,
@@ -47,15 +47,15 @@ public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService
return karakter; return karakter;
} }
public void Update(V1Karakter original, CharacterDto dto) public void Update(V1Karakter original, Character1eDto dto)
{ {
original.Nev = dto.Name; original.Nev = dto.Name;
original.Nem = dto.Nem; original.Nem = dto.Nem;
original.Kor = dto.Kor; original.Kor = dto.Kor;
original.Jellem = JellemExtensions.Convert(dto.Jellem); original.Jellem = JellemExtensions.Convert(dto.Jellem);
original.Isten = dto.Isten; original.Isten = dto.Isten;
original.Faj = FajExtensions.Convert(dto.Faj); original.Faj = FajExtensions.Convert1E(dto.Faj);
original.Osztaly = OsztalyExtensions.Convert(dto.Osztaly); original.Osztaly = OsztalyExtensions.Convert1E(dto.Osztaly);
original.Ero = dto.Tulajdonsagok.Ero; original.Ero = dto.Tulajdonsagok.Ero;
original.Ugyesseg = dto.Tulajdonsagok.Ugy; original.Ugyesseg = dto.Tulajdonsagok.Ugy;
original.Egeszseg = dto.Tulajdonsagok.Egs; original.Egeszseg = dto.Tulajdonsagok.Egs;
@@ -76,7 +76,7 @@ public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService
} }
// TODO: this is unnecessary duplication of business logic!! // TODO: this is unnecessary duplication of business logic!!
private static List<V1Szintlepes> ConvertSzintLepes(CharacterDto dto, V1Karakter karakter) private static List<V1Szintlepes> ConvertSzintLepes(Character1eDto dto, V1Karakter karakter)
{ {
if (dto.HpRolls.Count < dto.Szint - 2) if (dto.HpRolls.Count < dto.Szint - 2)
{ {
@@ -103,15 +103,15 @@ public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService
// OsztalySzint = 1, // OsztalySzint = 1,
HpRoll = karakter.Osztaly switch // TODO: duplication of logic!! HpRoll = karakter.Osztaly switch // TODO: duplication of logic!!
{ {
Osztaly.Barbar => 12, Osztaly1E.Barbar => 12,
Osztaly.Amazon or Osztaly.Harcos or Osztaly.Kaloz or Osztaly.Ijasz => 10, Osztaly1E.Amazon or Osztaly1E.Harcos or Osztaly1E.Kaloz or Osztaly1E.Ijasz => 10,
Osztaly.Pap => 8, Osztaly1E.Pap => 8,
Osztaly.Tolvaj => 6, Osztaly1E.Tolvaj => 6,
Osztaly.Varazslo or Osztaly.Illuzionista => 4, Osztaly1E.Varazslo or Osztaly1E.Illuzionista => 4,
_ => throw new ArgumentOutOfRangeException(nameof(karakter.Osztaly), karakter.Osztaly, _ => throw new ArgumentOutOfRangeException(nameof(karakter.Osztaly), karakter.Osztaly,
"invalid osztaly!") "invalid osztaly!")
}, },
FegyverSpecializacio = karakter.Osztaly == Osztaly.Harcos ? dto.HarcosSpecializaciok[0] : null, FegyverSpecializacio = karakter.Osztaly == Osztaly1E.Harcos ? dto.HarcosSpecializaciok[0] : null,
}; };
return Enumerable.Range(2, count: dto.Szint - 1).Select(szint => return Enumerable.Range(2, count: dto.Szint - 1).Select(szint =>
@@ -131,13 +131,13 @@ public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService
}).Prepend(level1).ToList(); }).Prepend(level1).ToList();
} }
private static List<V1KarakterKepzettseg> ConvertKepzettsegek(CharacterDto dto, V1Karakter karakter) private static List<V1KarakterKepzettseg> ConvertKepzettsegek(Character1eDto dto, V1Karakter karakter)
{ {
var kepzettsegek = dto.Kepzettsegek.Select(x => new V1KarakterKepzettseg var kepzettsegek = dto.Kepzettsegek.Select(x => new V1KarakterKepzettseg
{ {
Karakter = karakter, Karakter = karakter,
IsTolvajKepzettseg = false, IsTolvajKepzettseg = false,
Kepzettseg = KepzettsegExtensions.Convert(x), Kepzettseg = KepzettsegExtensions.Convert1E(x),
}).ToList(); }).ToList();
if (dto.Tolvajkepzettsegek != null) if (dto.Tolvajkepzettsegek != null)
{ {
@@ -145,7 +145,7 @@ public class CharacterDtoToDbModelService : ICharacterDtoToDbModelService
{ {
Karakter = karakter, Karakter = karakter,
IsTolvajKepzettseg = true, IsTolvajKepzettseg = true,
Kepzettseg = KepzettsegExtensions.Convert(x), Kepzettseg = KepzettsegExtensions.Convert1E(x),
})); }));
} }
return kepzettsegek; return kepzettsegek;
@@ -2,18 +2,18 @@ using Kemkas.Web.Db.Enums;
using Kemkas.Web.Db.Models; using Kemkas.Web.Db.Models;
using Kemkas.Web.ViewModels; using Kemkas.Web.ViewModels;
namespace Kemkas.Web.Services.Character; namespace Kemkas.Web.Services.FirstEdition.Character;
public interface ICharacterDbModelToDtoService public interface ICharacterDbModelToDto1EService
{ {
public CharacterDto Convert(V1Karakter entity); public Character1eDto Convert(V1Karakter entity);
} }
public class CharacterDbModelToDtoService : ICharacterDbModelToDtoService public class CharacterDbModelToDto1EService : ICharacterDbModelToDto1EService
{ {
public CharacterDto Convert(V1Karakter entity) public Character1eDto Convert(V1Karakter entity)
{ {
return new CharacterDto return new Character1eDto
{ {
Name = entity.Nev, Name = entity.Nev,
Nem = entity.Nem, Nem = entity.Nem,
@@ -36,8 +36,8 @@ public class CharacterDbModelToDtoService : ICharacterDbModelToDtoService
Szint = entity.Szint, Szint = entity.Szint,
HpRolls = entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Skip(1).Select(x => x.HpRoll).ToList(), HpRolls = entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Skip(1).Select(x => x.HpRoll).ToList(),
TulajdonsagNovelesek = entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(x => x.TulajdonsagNoveles).Where(x => x is not null).Cast<Tulajdonsag>().Select(x => x.Convert()).ToList(), TulajdonsagNovelesek = entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(x => x.TulajdonsagNoveles).Where(x => x is not null).Cast<Tulajdonsag>().Select(x => x.Convert()).ToList(),
HarcosSpecializaciok = entity.Osztaly == Osztaly.Harcos ? entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(x => x.FegyverSpecializacio).Where(x => x != null).Cast<string>().ToList() : [], HarcosSpecializaciok = entity.Osztaly == Osztaly1E.Harcos ? entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(x => x.FegyverSpecializacio).Where(x => x != null).Cast<string>().ToList() : [],
KalozKritikus = entity.Osztaly == Osztaly.Kaloz ? entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(x => x.FegyverSpecializacio).Where(x => x != null).Cast<string>().ToList() : [], KalozKritikus = entity.Osztaly == Osztaly1E.Kaloz ? entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(x => x.FegyverSpecializacio).Where(x => x != null).Cast<string>().ToList() : [],
Felszereles = new KarakterFelszerelesDto Felszereles = new KarakterFelszerelesDto
{ {
PajzsId = entity.Pajzs, PajzsId = entity.Pajzs,
@@ -1,16 +1,17 @@
using Kemkas.Web.Services.Identity; using Kemkas.Web.Services.Identity;
using Kemkas.Web.Services.Shared;
namespace Kemkas.Web.Services.Character; namespace Kemkas.Web.Services.FirstEdition.Character;
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddCharacterServices(this IServiceCollection serviceCollection) public static IServiceCollection AddCharacter1EServices(this IServiceCollection serviceCollection)
{ {
serviceCollection.AddScoped<ICurrentUserService, CurrentUserService>(); serviceCollection.AddScoped<ICurrentUserService, CurrentUserService>();
serviceCollection.AddTransient<ICharacterValidationService, CharacterValidationService>(); serviceCollection.AddTransient<ICharacterValidationService, CharacterValidationService>();
serviceCollection.AddTransient<ICharacterDtoToDbModelService, CharacterDtoToDbModelService>(); serviceCollection.AddTransient<ICharacter1EDtoToDbModelService, Character1EDtoToDbModelService>();
serviceCollection.AddTransient<ICharacterDbModelToDtoService, CharacterDbModelToDtoService>(); serviceCollection.AddTransient<ICharacterDbModelToDto1EService, CharacterDbModelToDto1EService>();
serviceCollection.AddScoped<ICharacterPersistenceService, CharacterPersistenceService>(); serviceCollection.AddScoped<ICharacterPersistenceService, CharacterPersistenceService>();
return serviceCollection; return serviceCollection;
} }
@@ -1,15 +1,15 @@
using Kemkas.Web.ViewModels; using Kemkas.Web.ViewModels;
namespace Kemkas.Web.Services.Character; namespace Kemkas.Web.Services.FirstEdition.Character;
public interface ICharacterValidationService public interface ICharacterValidationService
{ {
public string? Validate(CharacterDto dto); public string? Validate(Character1eDto dto);
} }
public class CharacterValidationService : ICharacterValidationService public class CharacterValidationService : ICharacterValidationService
{ {
public string? Validate(CharacterDto dto) public string? Validate(Character1eDto dto)
{ {
// TODO: add validation!! // TODO: add validation!!
return null; return null;
@@ -0,0 +1,113 @@
using Kemkas.Web.Db.Enums;
using Kemkas.Web.Db.Models;
using Kemkas.Web.ViewModels;
namespace Kemkas.Web.Services.SecondEdition.Character;
public interface ICharacter2EDtoToDbModelService
{
public V2Karakter Convert(Character2eDto dto);
public void Update(V2Karakter original, Character2eDto dto);
}
public class Character2EDtoToDbModelService : ICharacter2EDtoToDbModelService
{
public V2Karakter Convert(Character2eDto dto)
{
var karakter = new V2Karakter
{
Nev = dto.Nev,
Nem = dto.Nem,
Kor = dto.Kor,
Isten = dto.Isten,
Jellem = JellemExtensions.Convert(dto.Jellem),
Faj = FajExtensions.Convert2E(dto.Faj),
Ero = dto.Tulajdonsagok.Ero,
Ugyesseg = dto.Tulajdonsagok.Ugy,
Egeszseg = dto.Tulajdonsagok.Egs,
Intelligencia = dto.Tulajdonsagok.Int,
Bolcsesseg = dto.Tulajdonsagok.Bol,
Karizma = dto.Tulajdonsagok.Kar,
Szint = dto.Szint,
Pajzs = dto.Felszereles.PancelId,
Pancel = dto.Felszereles.PancelId
};
karakter.KarakterKepzettsegek = ConvertKarakterKepzettsegek(dto, karakter);
karakter.Felszereles = dto.Felszereles.FegyverIds.Select(x => new V2Felszereles
{
Karakter = karakter,
IsFegyver = true,
TargyId = x,
}).ToList();
karakter.Szintlepesek = dto.Szintlepesek.Select((x, i) => new V2Szintlepes
{
Karakter = karakter,
Osztaly = OsztalyExtensions.Convert2E(x.Osztaly),
KarakterSzint = (byte)(i + 1),
HpRoll = x.HProll,
FegyverSpecializacio = x.HarcosFegyver ?? x.KalozKritikus,
TulajdonsagNoveles = x.TulajdonsagNoveles == null ? null : TulajdonsagExtensions.Convert(x.TulajdonsagNoveles),
TolvajExtraKepzettseg = x.TolvajExtraKepzettseg == null ? null : KepzettsegExtensions.Convert2E(x.TolvajExtraKepzettseg),
}).ToList();
return karakter;
}
public void Update(V2Karakter original, Character2eDto dto)
{
original.Nev = dto.Nev;
original.Nem = dto.Nem;
original.Kor = dto.Kor;
original.Isten = dto.Isten;
original.Jellem = JellemExtensions.Convert(dto.Jellem);
original.Faj = FajExtensions.Convert2E(dto.Faj);
original.Ero = dto.Tulajdonsagok.Ero;
original.Ugyesseg = dto.Tulajdonsagok.Ugy;
original.Egeszseg = dto.Tulajdonsagok.Egs;
original.Intelligencia = dto.Tulajdonsagok.Int;
original.Bolcsesseg = dto.Tulajdonsagok.Bol;
original.Karizma = dto.Tulajdonsagok.Kar;
original.Szint = dto.Szint;
original.Pajzs = dto.Felszereles.PajzsId;
original.Pancel = dto.Felszereles.PancelId;
original.KarakterKepzettsegek = ConvertKarakterKepzettsegek(dto, original);
original.Felszereles = dto.Felszereles.FegyverIds.Select(x => new V2Felszereles
{
Karakter = original,
IsFegyver = true,
TargyId = x,
}).ToList();
original.Szintlepesek = dto.Szintlepesek.Select((x, i) => new V2Szintlepes
{
Karakter = original,
Osztaly = OsztalyExtensions.Convert2E(x.Osztaly),
KarakterSzint = (byte)(i + 1),
HpRoll = x.HProll,
FegyverSpecializacio = x.HarcosFegyver ?? x.KalozKritikus,
TulajdonsagNoveles = x.TulajdonsagNoveles == null ? null : TulajdonsagExtensions.Convert(x.TulajdonsagNoveles),
TolvajExtraKepzettseg = x.TolvajExtraKepzettseg == null ? null : KepzettsegExtensions.Convert2E(x.TolvajExtraKepzettseg),
}).ToList();
}
private static List<V2KarakterKepzettseg> ConvertKarakterKepzettsegek(Character2eDto dto, V2Karakter karakter)
{
var kepzettsegek = dto.Kepzettsegek.Select(x => new V2KarakterKepzettseg
{
Karakter = karakter,
IsTolvajKepzettseg = false,
Kepzettseg = KepzettsegExtensions.Convert2E(x),
}).ToList();
if (dto.Tolvajkepzettsegek != null)
{
kepzettsegek.AddRange(dto.Tolvajkepzettsegek.Select(x => new V2KarakterKepzettseg
{
Karakter = karakter,
IsTolvajKepzettseg = true,
Kepzettseg = KepzettsegExtensions.Convert2E(x),
}));
}
return kepzettsegek;
}
}
@@ -0,0 +1,54 @@
using Kemkas.Web.Db.Enums;
using Kemkas.Web.Db.Models;
using Kemkas.Web.ViewModels;
namespace Kemkas.Web.Services.SecondEdition.Character;
public interface ICharacterDbModelToDto2EService
{
public Character2eDto Convert(V2Karakter entity);
}
public class CharacterDbModelToDto2EService : ICharacterDbModelToDto2EService
{
public Character2eDto Convert(V2Karakter entity)
{
return new Character2eDto
{
Nev = entity.Nev,
Nem = entity.Nem,
Kor = entity.Kor,
Jellem = entity.Jellem.Convert(),
Isten = entity.Isten,
Faj = entity.Faj.Convert(),
Tulajdonsagok = new KarakterTulajdonsagokDto
{
Ero = entity.Ero,
Ugy = entity.Ugyesseg,
Egs = entity.Egeszseg,
Int = entity.Intelligencia,
Bol = entity.Bolcsesseg,
Kar = entity.Karizma,
},
Kepzettsegek = entity.KarakterKepzettsegek.Where(x => !x.IsTolvajKepzettseg).Select(x => x.Kepzettseg.Convert()).ToList(),
Tolvajkepzettsegek = entity.KarakterKepzettsegek.Where(x => x.IsTolvajKepzettseg).Select(x => x.Kepzettseg.Convert()).ToList(),
Szint = entity.Szint,
Felszereles = new KarakterFelszerelesDto
{
PajzsId = entity.Pajzs,
PancelId = entity.Pancel,
FegyverIds = entity.Felszereles.Where(x => x.IsFegyver).Select(x => x.TargyId).ToList(),
},
IsPublic = entity.IsPublic,
Szintlepesek = entity.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(sz => new Szintlepes
{
Osztaly = sz.Osztaly.Convert(),
KalozKritikus = sz.Osztaly == Osztaly2E.Tengeresz ? sz.FegyverSpecializacio : null,
HarcosFegyver = sz.Osztaly == Osztaly2E.Harcos ? sz.FegyverSpecializacio : null,
TulajdonsagNoveles = sz.TulajdonsagNoveles?.Convert(),
HProll = sz.HpRoll,
TolvajExtraKepzettseg = sz.TolvajExtraKepzettseg?.Convert()
}).ToList()
};
}
}
@@ -0,0 +1,11 @@
namespace Kemkas.Web.Services.SecondEdition.Character;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCharacter2EServices(this IServiceCollection serviceCollection)
{
serviceCollection.AddTransient<ICharacter2EDtoToDbModelService, Character2EDtoToDbModelService>();
serviceCollection.AddTransient<ICharacterDbModelToDto2EService, CharacterDbModelToDto2EService>();
return serviceCollection;
}
}
@@ -0,0 +1,147 @@
using Kemkas.Web.Db;
using Kemkas.Web.Db.Enums;
using Kemkas.Web.Db.Models;
using Kemkas.Web.Services.Identity;
using Kemkas.Web.ViewModels;
using Microsoft.EntityFrameworkCore;
namespace Kemkas.Web.Services.Shared;
public interface ICharacterPersistenceService
{
public Task<Guid> StoreNewCharacter1E(V1Karakter karakter, bool isPublic = false);
public Task<Guid> StoreNewCharacter2E(V2Karakter karakter, bool isPublic = false);
public Task<V1Karakter?> GetCharacter1EById(Guid id, bool tracking = false);
public Task<V2Karakter?> GetCharacter2EById(Guid id, bool tracking = false);
public Task<List<CharacterListItemDto>> GetAllCharactersOfUser();
public Task UpdateCharacter1E(V1Karakter karakter, bool isPublic = false);
public Task UpdateCharacter2E(V2Karakter karakter, bool isPublic = false);
}
public class CharacterPersistenceService(
ApplicationDbContext dbContext,
ICurrentUserService currentUserService
) : ICharacterPersistenceService
{
public async Task<Guid> StoreNewCharacter1E(V1Karakter karakter, bool isPublic = false)
{
var currentUser = await currentUserService.GetCurrentUser();
karakter.OwnerUserId = currentUser?.Id;
karakter.Id = Guid.Empty;
karakter.IsPublic = isPublic;
dbContext.Karakterek.Add(karakter);
await dbContext.SaveChangesAsync();
return karakter.Id;
}
public async Task<Guid> StoreNewCharacter2E(V2Karakter karakter, bool isPublic = false)
{
var currentUser = await currentUserService.GetCurrentUser();
karakter.OwnerUserId = currentUser?.Id;
karakter.Id = Guid.Empty;
karakter.IsPublic = isPublic;
dbContext.Karakterek2E.Add(karakter);
await dbContext.SaveChangesAsync();
return karakter.Id;
}
public async Task<V1Karakter?> GetCharacter1EById(Guid id, bool tracking = false)
{
IQueryable<V1Karakter> queryable = dbContext.Karakterek.Include(x => x.KarakterKepzettsegek)
.Include(x => x.Szintlepesek)
.Include(x => x.Felszereles);
queryable = tracking ? queryable.AsTracking() : queryable.AsNoTracking();
var entity = await queryable
.FirstOrDefaultAsync(x => x.Id == id);
if (entity?.OwnerUserId is null || entity.IsPublic)
{
return entity;
}
var currentUser = await currentUserService.GetCurrentUser();
return entity.OwnerUserId == currentUser?.Id ? entity : null;
}
public async Task<V2Karakter?> GetCharacter2EById(Guid id, bool tracking = false)
{
IQueryable<V2Karakter> queryable = dbContext.Karakterek2E.Include(x => x.KarakterKepzettsegek)
.Include(x => x.Szintlepesek)
.Include(x => x.Felszereles);
queryable = tracking ? queryable.AsTracking() : queryable.AsNoTracking();
var entity = await queryable
.FirstOrDefaultAsync(x => x.Id == id);
if (entity?.OwnerUserId is null || entity.IsPublic)
{
return entity;
}
var currentUser = await currentUserService.GetCurrentUser();
return entity.OwnerUserId == currentUser?.Id ? entity : null;
}
public async Task<List<CharacterListItemDto>> GetAllCharactersOfUser()
{
var currentUser = await currentUserService.GetCurrentUser();
if (currentUser is null)
{
return [];
}
var characters = await dbContext.Karakterek
.Where(k => k.OwnerUserId == currentUser.Id)
.Select(k => new CharacterListItemDto
{
Id = k.Id,
Name = k.Nev,
Szint = k.Szint,
Faj = k.Faj.Convert(),
Osztaly = k.Osztaly.Convert(),
Edition = "1e"
})
.ToListAsync();
var characters2E = await dbContext.Karakterek2E.Include(k => k.Szintlepesek)
.Where(k => k.OwnerUserId == currentUser.Id)
.Select(k => new CharacterListItemDto
{
Id = k.Id,
Name = k.Nev,
Szint = k.Szint,
Faj = k.Faj.Convert(),
Osztaly = k.Szintlepesek.OrderBy(x => x.KarakterSzint).Select(x => x.Osztaly).First().Convert(),
Edition = "2e"
})
.ToListAsync();
characters.AddRange(characters2E);
return characters;
}
public async Task UpdateCharacter1E(V1Karakter karakter, bool isPublic = false)
{
karakter.IsPublic = isPublic;
dbContext.Karakterek.Attach(karakter);
await dbContext.SaveChangesAsync();
}
public async Task UpdateCharacter2E(V2Karakter karakter, bool isPublic = false)
{
karakter.IsPublic = isPublic;
dbContext.Karakterek2E.Attach(karakter);
await dbContext.SaveChangesAsync();
}
}
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace Kemkas.Web.ViewModels; namespace Kemkas.Web.ViewModels;
public struct CharacterDto public struct Character1eDto
{ {
[Required] [Required]
[JsonPropertyName("name")] [JsonPropertyName("name")]
+46
View File
@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Kemkas.Web.ViewModels;
public struct Character2eDto
{
[Required]
[JsonPropertyName("nev")]
public string Nev { get; set; }
[JsonPropertyName("nem")]
public string? Nem { get; set; }
[JsonPropertyName("kor")]
public double? Kor { get; set; }
[JsonPropertyName("jellem")]
public string Jellem { get; set; }
[JsonPropertyName("isten")]
public string? Isten { get; set; }
[JsonPropertyName("faj")]
public string Faj { get; set; }
[JsonPropertyName("tulajdonsagok")]
public KarakterTulajdonsagokDto Tulajdonsagok { get; set; }
[JsonPropertyName("kepzettsegek")]
public IList<string> Kepzettsegek { get; set; }
[JsonPropertyName("tolvajkepzettsegek")]
public IList<string>? Tolvajkepzettsegek { get; set; }
[JsonPropertyName("szint")]
public byte Szint { get; set; }
[JsonPropertyName("szintlepesek")]
public IList<Szintlepes> Szintlepesek { get; set; }
[JsonPropertyName("felszereles")]
public KarakterFelszerelesDto Felszereles { get; set; }
public bool? IsPublic { get; set; }
}
@@ -2,6 +2,9 @@ using System.Text.Json.Serialization;
namespace Kemkas.Web.ViewModels; namespace Kemkas.Web.ViewModels;
/// <summary>
/// currently the same for 1st edition and 2nd edition, be cautious!
/// </summary>
public struct KarakterFelszerelesDto public struct KarakterFelszerelesDto
{ {
[JsonPropertyName("pancelID")] [JsonPropertyName("pancelID")]
@@ -2,6 +2,9 @@ using System.Text.Json.Serialization;
namespace Kemkas.Web.ViewModels; namespace Kemkas.Web.ViewModels;
/// <summary>
/// currently the same for 1st edition and 2nd edition, be cautious!
/// </summary>
public struct KarakterTulajdonsagokDto public struct KarakterTulajdonsagokDto
{ {
[JsonPropertyName("t_ero")] [JsonPropertyName("t_ero")]
+24
View File
@@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
namespace Kemkas.Web.ViewModels;
public struct Szintlepes
{
[JsonPropertyName("osztaly")]
public string Osztaly { get; set; }
[JsonPropertyName("HProll")]
public byte HProll { get; set; }
[JsonPropertyName("tolvajExtraKepzettseg")]
public string? TolvajExtraKepzettseg { get; set; }
[JsonPropertyName("harcosFegyver")]
public string? HarcosFegyver { get; set; }
[JsonPropertyName("kalozKritikus")]
public string? KalozKritikus { get; set; }
[JsonPropertyName("tulajdonsagNoveles")]
public string? TulajdonsagNoveles { get; set; }
}
-12
View File
@@ -1,12 +0,0 @@
namespace Kemkas.Web.ViewModels;
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
+1 -1
View File
@@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@grafana/faro-web-sdk": "^1.2.2", "@grafana/faro-web-sdk": "^1.3.9",
"@pdf-lib/fontkit": "^1.1.1", "@pdf-lib/fontkit": "^1.1.1",
"@testing-library/jest-dom": "^6.1.4", "@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
Binary file not shown.
Binary file not shown.
+6 -6
View File
@@ -3,7 +3,7 @@ import './App.css';
import {Faro} from "@grafana/faro-web-sdk"; import {Faro} from "@grafana/faro-web-sdk";
import {createBrowserRouter, redirect, RouterProvider} from "react-router-dom"; import {createBrowserRouter, redirect, RouterProvider} from "react-router-dom";
import CreateCharacter from "./first-edition/pages/CreateCharacter"; import CreateCharacter from "./first-edition/pages/CreateCharacter";
import CharacterList from "./first-edition/pages/CharacterList"; import CharacterList from "./shared/pages/CharacterList";
import ErrorBoundary from "./shared/ErrorBoundary"; import ErrorBoundary from "./shared/ErrorBoundary";
import Header from "./shared/Header"; import Header from "./shared/Header";
import Footer from "./shared/Footer"; import Footer from "./shared/Footer";
@@ -42,7 +42,7 @@ function App(props: {faro?: Faro}) {
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: "/", path: "/",
loader: () => fetchedUser.data == null ? redirect("/1e/karakter") : redirect("/karaktereim") loader: () => fetchedUser.data == null ? redirect("/2e/karakter") : redirect("/karaktereim")
}, },
{ {
path: "/karaktereim", path: "/karaktereim",
@@ -62,7 +62,7 @@ function App(props: {faro?: Faro}) {
{ {
path: "/1e/karakter/:id", path: "/1e/karakter/:id",
element: <CreateCharacter faro={props.faro}/>, element: <CreateCharacter faro={props.faro}/>,
loader: args => fetch(`${window.location.origin}/api/Character/${args.params.id}`), loader: args => fetch(`${window.location.origin}/api/Character1E/${args.params.id}`),
ErrorBoundary: ErrorBoundary, ErrorBoundary: ErrorBoundary,
}, },
{ {
@@ -71,13 +71,13 @@ function App(props: {faro?: Faro}) {
}, },
{ {
path: "/2e/karakter", path: "/2e/karakter",
element: <CreateCharacter2E />, element: <CreateCharacter2E faro={props.faro}/>,
ErrorBoundary: ErrorBoundary, ErrorBoundary: ErrorBoundary,
}, },
{ {
path: "/2e/karakter/:id", path: "/2e/karakter/:id",
element: <CreateCharacter faro={props.faro}/>, element: <CreateCharacter2E faro={props.faro}/>,
loader: args => fetch(`${window.location.origin}/api/Character/${args.params.id}`), loader: args => fetch(`${window.location.origin}/api/Character2E/${args.params.id}`),
ErrorBoundary: ErrorBoundary, ErrorBoundary: ErrorBoundary,
}, },
]); ]);
@@ -2,7 +2,7 @@ import {KarakterInputs} from "../domain-models/karakter";
import axios from "axios"; import axios from "axios";
export async function StoreNewCharacter(karakter: KarakterInputs, isPublic: boolean = false) { export async function StoreNewCharacter(karakter: KarakterInputs, isPublic: boolean = false) {
let response = await axios.post(`${window.location.origin}/api/Character/`, karakter, { let response = await axios.post(`${window.location.origin}/api/Character1E/`, karakter, {
withCredentials: true, withCredentials: true,
params: { params: {
isPublic, isPublic,
@@ -17,7 +17,7 @@ export async function StoreNewCharacter(karakter: KarakterInputs, isPublic: bool
} }
export async function UpdateCharacter(id: string, karakter: KarakterInputs, isPublic: boolean = false) { export async function UpdateCharacter(id: string, karakter: KarakterInputs, isPublic: boolean = false) {
let response = await axios.post(`${window.location.origin}/api/Character/${id}`, karakter, { let response = await axios.post(`${window.location.origin}/api/Character1E/${id}`, karakter, {
withCredentials: true, withCredentials: true,
params: { params: {
isPublic, isPublic,
@@ -30,4 +30,3 @@ export async function UpdateCharacter(id: string, karakter: KarakterInputs, isPu
throw Error(response.statusText) throw Error(response.statusText)
} }
} }
@@ -1,26 +0,0 @@
import * as React from "react";
import {Osztaly, OsztalyLabel} from "../domain-models/osztaly";
import {Faj, FajLabel} from "../domain-models/faj";
export interface CharacterListItemDto {
id: string,
name: string,
szint: number,
faj: Faj,
osztaly: Osztaly,
edition: "1e" | "2e"
}
function CharacterListItem(props: {
character: CharacterListItemDto,
}) {
const {character:c} = props
return <>
<td><a href={`/${c.edition}/karakter/${c.id}`}>{c.name}</a></td>
<td>{c.szint}. szintű</td>
<td>{FajLabel(c.faj)}</td>
<td>{OsztalyLabel(c.osztaly)}</td>
</>
}
export default CharacterListItem;
@@ -1,35 +0,0 @@
import * as React from "react";
import {useLoaderData} from "react-router-dom";
import CharacterListItem, {CharacterListItemDto} from "../components/CharacterListItem";
function CharacterList(props: {}) {
const characters = useLoaderData() as CharacterListItemDto[];
return <div className="container">
<div className="container-fluid p-5 bg-black text-white text-center">
<h1>Karakterek</h1>
</div>
<div className="d-grid gap-2 m-5">
<a className="btn btn-dark btn-lg" href="/" type="button">Új karakter létrehozása</a>
</div>
<table className="table mt-3">
<thead>
<tr>
<th scope="col">Név</th>
<th scope="col">Szint</th>
<th scope="col">Faj</th>
<th scope="col">Osztály</th>
</tr>
</thead>
<tbody>
{characters.map(c => (
<tr key={c.id}>
<CharacterListItem character={c}/>
</tr>
))}
</tbody>
</table>
</div>
}
export default CharacterList;
@@ -21,14 +21,15 @@ import {CalculateMasodlagosErtekek} from "../domain-models/masodlagos_ertekek";
import {KarakterInputToPdfView} from "../pdf/karakter_pdf_view"; import {KarakterInputToPdfView} from "../pdf/karakter_pdf_view";
import JellemSelector from "../components/JellemSelector"; import JellemSelector from "../components/JellemSelector";
import {CanLevelUp, LevelDown, LevelUp} from "../domain-models/level"; import {CanLevelUp, LevelDown, LevelUp} from "../domain-models/level";
import {Card, OverlayTrigger, Toast, ToastContainer} from "react-bootstrap"; import {Button, Card, InputGroup, Modal, OverlayTrigger, Toast, ToastContainer} from "react-bootstrap";
import Form from "react-bootstrap/Form";
import LevelUps from "../components/LevelUps"; import LevelUps from "../components/LevelUps";
import HarcosFegyverSpecializacio from "../components/HarcosFegyverSpecializacio"; import HarcosFegyverSpecializacio from "../components/HarcosFegyverSpecializacio";
import Felszereles from '../components/Felszereles'; import Felszereles from '../components/Felszereles';
import { KarakterFelszereles } from '../domain-models/felszereles'; import { KarakterFelszereles } from '../domain-models/felszereles';
import {Faro} from "@grafana/faro-web-sdk"; import {Faro} from "@grafana/faro-web-sdk";
import {StoreNewCharacter, UpdateCharacter} from "../api/character.api"; import {StoreNewCharacter, UpdateCharacter} from "../api/character.api";
import {useLoaderData, useParams} from "react-router-dom"; import {useLoaderData, useNavigate, useParams} from "react-router-dom";
import saveOverlayTooltip from "../components/SaveOverlayTooltip"; import saveOverlayTooltip from "../components/SaveOverlayTooltip";
import {UserContext} from "../../shared/contexts/UserContext"; import {UserContext} from "../../shared/contexts/UserContext";
import {KarakterTulajdonsagok} from "../domain-models/tulajdonsag"; import {KarakterTulajdonsagok} from "../domain-models/tulajdonsag";
@@ -39,7 +40,6 @@ function CreateCharacterPage(props: {
const {faro} = props const {faro} = props
const loaderData = useLoaderData() as KarakterInputs & { isPublic: boolean } | undefined; const loaderData = useLoaderData() as KarakterInputs & { isPublic: boolean } | undefined;
const initialIsPublic = loaderData?.isPublic ?? false;
let initialKarakterInputs = KarakterDefaults; let initialKarakterInputs = KarakterDefaults;
if (loaderData != null) { if (loaderData != null) {
const { isPublic: t1, ...t2 } = loaderData; const { isPublic: t1, ...t2 } = loaderData;
@@ -47,6 +47,10 @@ function CreateCharacterPage(props: {
} }
const { id } = useParams(); const { id } = useParams();
const fetchedUser = useContext(UserContext); const fetchedUser = useContext(UserContext);
const initialIsPublic = loaderData?.isPublic ?? fetchedUser?.data == null;
const navigate = useNavigate();
let [karakter, changeKarakter] = useState(initialKarakterInputs) let [karakter, changeKarakter] = useState(initialKarakterInputs)
@@ -64,9 +68,25 @@ function CreateCharacterPage(props: {
const levelDown = () => LevelDown(karakter, changeKarakter) const levelDown = () => LevelDown(karakter, changeKarakter)
const canLevelUp = CanLevelUp(karakter) const canLevelUp = CanLevelUp(karakter)
let [showSaveModal, setShowSaveModal] = useState(false);
let [newId, setNewId] = useState(null as string | null)
let [showSaved, setShowSaved] = useState(false); let [showSaved, setShowSaved] = useState(false);
let [isPublic, setIsPublic] = useState(initialIsPublic); let [isPublic, setIsPublic] = useState(initialIsPublic);
const newCharacterUrl = () => `${window.location.origin}/1e/karakter/${newId}`
const handleSaveModalCopyAndClose = async () => {
await navigator.clipboard.writeText(newCharacterUrl());
handleSaveModalClose()
}
const handleSaveModalClose = () => {
setShowSaveModal(false);
navigate(`/1e/karakter/${newId}`)
}
const hideSaved = () => setShowSaved(false); const hideSaved = () => setShowSaved(false);
const onSaveClicked = async () => { const onSaveClicked = async () => {
@@ -77,11 +97,9 @@ function CreateCharacterPage(props: {
is_public: isPublic.toString(), is_public: isPublic.toString(),
}) })
if (id == null) { if (id == null) {
let newId = await StoreNewCharacter(karakter, isPublic); let recievedId = await StoreNewCharacter(karakter, isPublic);
const win = window.open(`/${newId}`, '_blank'); setNewId(recievedId)
if (win != null) { setShowSaveModal(true);
win.focus();
}
} else { } else {
await UpdateCharacter(id, karakter, isPublic); await UpdateCharacter(id, karakter, isPublic);
setShowSaved(true); setShowSaved(true);
@@ -98,9 +116,31 @@ function CreateCharacterPage(props: {
</div> </div>
<ToastContainer className="position-fixed" position="top-end"> <ToastContainer className="position-fixed" position="top-end">
<Toast show={showSaved} onClose={hideSaved} bg="success"> <Toast show={showSaved} onClose={hideSaved} bg="success">
<Toast.Header><strong>Karakter mentve!</strong></Toast.Header> <Toast.Body className="text-light">Karakter mentve!</Toast.Body>
</Toast> </Toast>
</ToastContainer> </ToastContainer>
<Modal show={showSaveModal} onHide={handleSaveModalClose}>
<Modal.Header closeButton>
<Modal.Title>Karakter mentve!</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Karaktered az alábbi {isPublic ? 'publikus' : 'privát'} URLen érhető el.</p>
<p><a href={newCharacterUrl()}>{newCharacterUrl()}</a></p>
<InputGroup className="mb-3">
<Form.Control id="input" value={newCharacterUrl()} />
<Button variant={"outline-dark"} onClick={() => {
let copyText = document.querySelector("#input") as any;
copyText?.select()
document.execCommand("copy");
}}>Másolás</Button>
</InputGroup>
</Modal.Body>
<Modal.Footer>
<Button variant="danger" onClick={handleSaveModalCopyAndClose}>
Másolás és bezárás
</Button>
</Modal.Footer>
</Modal>
<div className='p-3'> <div className='p-3'>
<form onSubmit={async (event) => event.preventDefault()}> <form onSubmit={async (event) => event.preventDefault()}>
<div className='row'> <div className='row'>
@@ -46,7 +46,6 @@ export async function CreatePDF(karakter: KarakterPdfView) {
const fontBytes = await fetch('/Merienda-Regular.ttf').then(res => res.arrayBuffer()) const fontBytes = await fetch('/Merienda-Regular.ttf').then(res => res.arrayBuffer())
const pdfFont = await pdfDoc.embedFont(fontBytes) const pdfFont = await pdfDoc.embedFont(fontBytes)
// const pdfFont = await pdfDoc.embedFont(StandardFonts.TimesNewRoman)
const fontSizeBase = 12 const fontSizeBase = 12
const page = pdfDoc.getPage(0) const page = pdfDoc.getPage(0)
@@ -0,0 +1,32 @@
import axios from "axios";
import {Karakter2E} from "../domain-models/karakter2E";
export async function StoreNewCharacter2E(karakter: Karakter2E, isPublic: boolean = false) {
let response = await axios.post(`${window.location.origin}/api/Character2E/`, karakter, {
withCredentials: true,
params: {
isPublic,
}
})
if (response.status < 300){
return response.data as string
}
else {
throw Error(response.statusText)
}
}
export async function UpdateCharacter2E(id: string, karakter: Karakter2E, isPublic: boolean = false) {
let response = await axios.post(`${window.location.origin}/api/Character2E/${id}`, karakter, {
withCredentials: true,
params: {
isPublic,
}
})
if (response.status < 300){
return response.data as string
}
else {
throw Error(response.statusText)
}
}
@@ -0,0 +1,90 @@
import React from "react";
import {Karakter2E} from "../domain-models/karakter2E";
import {SignedNumberToText} from "../../shared/components/Helpers";
import {Mentok, MentoTipus, MentoTipusLabel, MentokTotal} from "../domain-models/mentok";
import {BaseHP, HP} from "../domain-models/masodlagos_ertekek";
import {MultiClassTamadasBonusz} from "../domain-models/tamadas_bonusz";
import {Osztaly2E} from "../domain-models/osztaly2E";
function MentoNovekedes(props : {tipus: MentoTipus, ujMentok: Mentok, regiMentok: Mentok}) {
const {tipus, ujMentok, regiMentok} = props
if (ujMentok[tipus] === regiMentok[tipus]) {
return null
}
return <div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>{MentoTipusLabel(tipus)} mentő növekedés{' '}</label>
<span className='col col-form-label'>{SignedNumberToText(ujMentok[tipus] - regiMentok[tipus])}</span>
</div>;
}
function UjMento(props : {tipus: MentoTipus, ujMentok: Mentok, regiMentok: Mentok}) {
const {tipus, ujMentok, regiMentok} = props
if (ujMentok[tipus] === regiMentok[tipus]) {
return null
}
return <div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>{MentoTipusLabel(tipus)} mentő új értéke{' '}</label>
<span className='col col-form-label'>{SignedNumberToText(ujMentok[tipus])}</span>
</div>;
}
function BasicNewLevel(props: {
karakter: Pick<Karakter2E, 'tulajdonsagok' | 'szintlepesek'>
szint: number
osztaly: Osztaly2E
changeRolledHP: (hp: number) => void,
}) {
const {karakter, szint, osztaly, changeRolledHP} = props
const regiMentok = MentokTotal({...karakter, szintlepesek: karakter.szintlepesek.slice(0, szint - 1)})
const ujMentok = MentokTotal({...karakter, szintlepesek: karakter.szintlepesek.slice(0, szint)})
const regiTB = MultiClassTamadasBonusz(karakter.szintlepesek.slice(0, szint-1))
const ujTB = MultiClassTamadasBonusz(karakter.szintlepesek.slice(0, szint))
if (regiTB.length < ujTB.length) {
regiTB.push(0)
}
const TBdiff = ujTB.map((x, i) => x - regiTB[i])
const isTBincreased = TBdiff.length > 0 && TBdiff.some(x => x > 0)
return <>
<div className='row'>
<div className='col-lg-6'>
<div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>Plusz dobott HP (d{BaseHP(osztaly)}){' '}</label>
<div className='col-sm-2 col'>
<input
className='form-control'
maxLength={2}
type='number'
value={karakter.szintlepesek[szint-1].HProll || 1}
min={1}
max={12}
onChange={e => changeRolledHP(Number(e.target.value))} />
</div>
</div>
{ isTBincreased && <div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>Támadás bónusz növekedés{' '}</label>
<span className='col col-form-label'>{TBdiff.map(SignedNumberToText).join(" / ")}</span>
</div>}
<MentoNovekedes tipus={'kitartas'} ujMentok={ujMentok} regiMentok={regiMentok} />
<MentoNovekedes tipus={'reflex'} ujMentok={ujMentok} regiMentok={regiMentok} />
<MentoNovekedes tipus={'akaratero'} ujMentok={ujMentok} regiMentok={regiMentok} />
</div>
<div className='col-lg-6'>
<div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>Új HP{' '}</label>
<span className='col col-form-label'>{HP({...karakter, szintlepesek: karakter.szintlepesek.slice(0, szint)})}</span>
</div>
{isTBincreased && <div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>Új Támadás bónusz{' '}</label>
<span className='col col-form-label'>{ujTB.map(SignedNumberToText).join(" / ")}</span>
</div>}
<UjMento tipus={'kitartas'} ujMentok={ujMentok} regiMentok={regiMentok} />
<UjMento tipus={'reflex'} ujMentok={ujMentok} regiMentok={regiMentok} />
<UjMento tipus={'akaratero'} ujMentok={ujMentok} regiMentok={regiMentok} />
</div>
</div>
</>
}
export default BasicNewLevel
@@ -0,0 +1,87 @@
import React from "react";
import {Fegyver} from "../domain-models/felszereles";
export function FegyverFlags(fegyver: Fegyver): string {
const flags = [] as string[]
if (fegyver.Megterheltseg) {
flags.push("M")
}
if (fegyver.Harcos) {
flags.push("H")
}
if (fegyver.Masfelkezes) {
flags.push("1½K")
}
if (fegyver.Ketkezes) {
if (fegyver.Type === "kozelharci") {
flags.push("2K")
} else {
if (fegyver.KetkezesBonusz) {
flags.push("2KB")
} else {
flags.push("2KN")
}
}
}
if (fegyver.HarciManover) {
flags.push("+2 HM")
}
if (fegyver.Alakzat){
flags.push("AL")
}
if (fegyver.Hosszu) {
flags.push("HO")
}
if (fegyver.Kabito) {
flags.push("KO")
}
if (fegyver.LovasRoham) {
flags.push("LR")
}
if (fegyver.PancelToro) {
flags.push("PT")
}
if (fegyver.PajzsZuzo) {
flags.push("PZ")
}
if (fegyver.Rohamtoro) {
flags.push("RT")
}
return flags.join(' ');
}
export function FegyverLabel(fegyver: Fegyver): string {
const serializedFlags = FegyverFlags(fegyver) + (fegyver.Megjegyzes != null ? " " + fegyver.Megjegyzes : "");
const sizeLabel = fegyver.Size < 1 && fegyver.Size > 0 ? ` [1/${Math.round(1 / fegyver.Size)}]` : (fegyver.Size > 1 ? ` [${fegyver.Size}]` : '');
return `${fegyver.Name}${sizeLabel} | ${serializedFlags.length > 0 ? serializedFlags + " |" : ''}${
fegyver.DamageMultiplier > 1 ? `${fegyver.DamageMultiplier}*` : ''}${
fegyver.NumberOfDamageDice}d${fegyver.DamageDice}${fegyver.DamageBonus > 0 ? ` +${fegyver.DamageBonus}` : ''} ${
fegyver.CritRangeStart < 20 ? `${fegyver.CritRangeStart}-20` : ''}x${fegyver.CritMultiplier}${
fegyver.Price > 0 ? ` | ár: ${fegyver.Price} at ` : ''}${
fegyver.Range > 10 ? ` | táv: ${fegyver.Range}` : ''}`
}
function FegyverSelector(props: {fegyverek: Fegyver[], selectedId: string, onChange: (id: string) => void}) {
const { fegyverek, selectedId, onChange } = props
return <>
<select className='col form-select' value={selectedId}
onChange={e => onChange(e.target.value)}>
<optgroup label="Közelharci">
{fegyverek.filter(f => f.Type === 'kozelharci').map(f => <option
key={f.Id} value={f.Id}>{FegyverLabel(f)}</option>)}
</optgroup>
<optgroup label="Közelharci, dobható">
{fegyverek.filter(f => f.Type === 'dobhato').map(f => <option
key={f.Id} value={f.Id}>{FegyverLabel(f)}</option>)}
</optgroup>
<optgroup label="Távolsági">
{fegyverek.filter(f => f.Type === 'lofegyver').map(f => <option
key={f.Id} value={f.Id}>{FegyverLabel(f)}</option>)}
</optgroup>
</select>
</>
}
export default FegyverSelector
@@ -5,64 +5,7 @@ import {AllowedPajzsTypes, AllowedPancelTypes} from '../domain-models/allowed-pa
import fegyverek from '../domain-models/fegyver.json' import fegyverek from '../domain-models/fegyver.json'
import {arraySetN} from "../../util"; import {arraySetN} from "../../util";
import {AllowedFegyver} from "../domain-models/allowed-fegyver"; import {AllowedFegyver} from "../domain-models/allowed-fegyver";
import FegyverSelector from "./FegyverSelector";
export function FegyverLabel(fegyver: Fegyver): string {
const flags = [] as string[]
if (fegyver.Harcos) {
flags.push("H")
}
if (fegyver.Masfelkezes) {
flags.push("1½K")
}
if (fegyver.Ketkezes) {
if (fegyver.Type === "kozelharci") {
flags.push("2K")
} else {
if (fegyver.KetkezesBonusz) {
flags.push("2KB")
} else {
flags.push("2KN")
}
}
}
if (fegyver.HarciManover) {
flags.push("+2 HM")
}
if (fegyver.Alakzat){
flags.push("AL")
}
if (fegyver.Hosszu) {
flags.push("HO")
}
if (fegyver.Kabito) {
flags.push("KO")
}
if (fegyver.LovasRoham) {
flags.push("LR")
}
if (fegyver.PancelToro) {
flags.push("PT")
}
if (fegyver.PajzsZuzo) {
flags.push("PZ")
}
if (fegyver.Rohamtoro) {
flags.push("RT")
}
// if (fegyver.EroBonusz) {
// flags.push("ER")
// }
if (fegyver.Megjegyzes != null && fegyver.Megjegyzes.length > 0) {
flags.push(fegyver.Megjegyzes)
}
const serializedFlags = flags.reduce((p,c) => `${p}${c} `, '');
const sizeLabel = fegyver.Size < 1 && fegyver.Size > 0 ? ` [1/${Math.round(1 / fegyver.Size)}]` : (fegyver.Size > 1 ? ` [${fegyver.Size}]` : '');
return `${fegyver.Name}${sizeLabel} ${serializedFlags.length > 0 ? "| " + serializedFlags : ''}| ${
fegyver.DamageMultiplier > 1 ? `${fegyver.DamageMultiplier}*` : ''}${fegyver.NumberOfDamageDice}d${fegyver.DamageDice}${fegyver.DamageBonus > 0 ? ` +${fegyver.DamageBonus}` : ''} ${
fegyver.CritRangeStart < 20 ? `${fegyver.CritRangeStart}-20` : ''}x${fegyver.CritMultiplier}${
fegyver.Price > 0 ? ` | ár: ${fegyver.Price} at ` : ''}${
fegyver.Range > 10 ? ` | táv: ${fegyver.Range}` : ''}`
}
function Felszereles(props: {felszereles: KarakterFelszereles, changeFelszereles: (felszereles: KarakterFelszereles) => void, osztaly: Osztaly2E}) { function Felszereles(props: {felszereles: KarakterFelszereles, changeFelszereles: (felszereles: KarakterFelszereles) => void, osztaly: Osztaly2E}) {
const { felszereles, changeFelszereles, osztaly } = props; const { felszereles, changeFelszereles, osztaly } = props;
@@ -116,21 +59,7 @@ function Felszereles(props: {felszereles: KarakterFelszereles, changeFelszereles
{valasztottFegyverek.map(({idx, fegyver}) => ( {valasztottFegyverek.map(({idx, fegyver}) => (
<div key={`fegyver-${idx}`} className='row m-2'> <div key={`fegyver-${idx}`} className='row m-2'>
<label className='col-md-2 col-sm-3 col-form-label'>Fegyver</label> <label className='col-md-2 col-sm-3 col-form-label'>Fegyver</label>
<select className='col form-select' value={fegyver.Id} <FegyverSelector fegyverek={allowedFegyverek} selectedId={fegyver.Id} onChange={(fegyverId) => changeFegyver(idx, fegyverId)} />
onChange={e => changeFegyver(idx, e.target.value)}>
<optgroup label="Közelharci">
{allowedFegyverek.filter(f => f.Type === 'kozelharci').map(f => <option
key={f.Id} value={f.Id}>{FegyverLabel(f)}</option>)}
</optgroup>
<optgroup label="Közelharci, dobható">
{allowedFegyverek.filter(f => f.Type === 'dobhato').map(f => <option
key={f.Id} value={f.Id}>{FegyverLabel(f)}</option>)}
</optgroup>
<optgroup label="Távolsági">
{allowedFegyverek.filter(f => f.Type === 'lofegyver').map(f => <option
key={f.Id} value={f.Id}>{FegyverLabel(f)}</option>)}
</optgroup>
</select>
<button className='col-md-2 col-sm-3 btn btn-outline-dark btn-lg ms-2' type='button' <button className='col-md-2 col-sm-3 btn btn-outline-dark btn-lg ms-2' type='button'
onClick={() => changeFelszereles({ onClick={() => changeFelszereles({
...felszereles, ...felszereles,
@@ -0,0 +1,32 @@
import React from "react";
import {GetFegyver} from "../domain-models/felszereles";
import FegyverSelector from "./FegyverSelector";
import {AllowedFegyver} from "../domain-models/allowed-fegyver";
import {Osztaly2E} from "../domain-models/osztaly2E";
function HarcosFegyverSpecializacio(props: {
specialization: string,
existingSpecializations: (string | undefined)[],
changeSpecialization: (specialization: string) => void
szint: number
}) {
const {
specialization,
existingSpecializations,
changeSpecialization,
szint
} = props
const fegyver = GetFegyver(specialization)
const availableFegyverek = AllowedFegyver(Osztaly2E.Harcos)
const pickableFegyverek = availableFegyverek.filter(f => f.Id === specialization || existingSpecializations.filter(x => x === f.Id).length < (szint < 9 ? 1 : 2))
return <>
<div className='row m-2'>
<label className='col-md-2 col-sm-3 col-form-label' >Fegyver specializáció</label>
<FegyverSelector fegyverek={pickableFegyverek} selectedId={specialization} onChange={changeSpecialization} />
{fegyver == null && <span className='form-field-error'>Ismeretlen fegyver: {specialization}</span>}
</div>
</>
}
export default HarcosFegyverSpecializacio
@@ -0,0 +1,28 @@
import React from "react";
import {Osztaly2E} from "../domain-models/osztaly2E";
import {AllowedFegyver} from "../domain-models/allowed-fegyver";
import FegyverSelector from "./FegyverSelector";
function KalozKritikus(props: {
kritFegyverId: string,
existingKrits: (string | undefined)[],
changeKrit: (krit: string) => void,
szint: number
}) {
const {
kritFegyverId,
existingKrits,
changeKrit,
szint
} = props
const fegyverek = AllowedFegyver(Osztaly2E.Tengeresz).filter(f => f.Id === kritFegyverId || existingKrits.filter(x => x === f.Id).length < (szint < 9 ? 1 : 2))
return <>
<div className='row m-2'>
<label className='col-md-2 col-sm-3 col-form-label'>Kritikus</label>
<FegyverSelector fegyverek={fegyverek} selectedId={kritFegyverId} onChange={changeKrit} />
</div>
</>
}
export default KalozKritikus
@@ -0,0 +1,147 @@
import React from "react";
import {ChangeOsztalyAtSzint, Karakter2E} from "../domain-models/karakter2E";
import BasicNewLevel from "./BasicNewLevel";
import TulajdonsagNoveles from "./TulajdonsagNoveles";
import {Tulajdonsag2E, TulajdonsagokTotal} from "../domain-models/tulajdonsag2E";
import {arraySetN} from "../../util";
import {Osztaly2E} from "../domain-models/osztaly2E";
import KalozKritikus from "./KalozKritikus";
import HarcosFegyverSpecializacio from "./HarcosFegyverSpec";
import MultiClassOsztalySelector2E from "./MultiClassOsztalySelector2E";
import {getClassLevels} from "../domain-models/szintlepes";
function LevelUp(props: {
szint: number,
osztaly: Osztaly2E,
karakter: Karakter2E,
changeTulajdonsagNoveles: (t: Tulajdonsag2E) => void,
changeRolledHP: (rolledHP: number) => void,
changeHarcosSpec: (spec: string) => void,
changeKalozKritikus: (krit: string) => void,
changeOsztaly: (osztaly: Osztaly2E) => void
}) {
const {
szint,
osztaly,
karakter,
changeTulajdonsagNoveles,
changeRolledHP,
changeHarcosSpec,
changeKalozKritikus,
changeOsztaly
} = props
const classLevels = getClassLevels(karakter.szintlepesek)
const harcosSzint = classLevels[Osztaly2E.Harcos]
const kalozSzint = classLevels[Osztaly2E.Tengeresz]
const specialization = karakter.szintlepesek[szint-1].harcosFegyver
const krit = karakter.szintlepesek[szint-1].kalozKritikus
return <div>
<div className='row mt-3'>
<h5 className='col align-self-center'>{szint}. Szint</h5>
</div>
{osztaly !== Osztaly2E.Druida &&
<MultiClassOsztalySelector2E
faj={karakter.faj}
osztaly={osztaly}
changeOsztaly={changeOsztaly}
osztalySzint={classLevels[osztaly]}
/> }
{szint % 4 === 0 &&
<TulajdonsagNoveles
tulajdonsagok={karakter.tulajdonsagok}
tulajdonsagNoveles={karakter.szintlepesek[szint-1].tulajdonsagNoveles!}
changeTulajdonsagNoveles={changeTulajdonsagNoveles}
/> }
<BasicNewLevel
szint={szint}
karakter={karakter}
osztaly={osztaly}
changeRolledHP={changeRolledHP}
/>
{osztaly === Osztaly2E.Harcos && harcosSzint % 2 === 1 &&
<HarcosFegyverSpecializacio
specialization={specialization!}
existingSpecializations={karakter.szintlepesek.map(x => x.harcosFegyver)}
changeSpecialization={changeHarcosSpec}
szint={harcosSzint}
/>
}
{osztaly === Osztaly2E.Tengeresz && kalozSzint % 3 === 0 &&
<KalozKritikus
kritFegyverId={krit!}
existingKrits={karakter.szintlepesek.map(x => x.kalozKritikus)}
changeKrit={changeKalozKritikus}
szint={kalozSzint}
/>
}
</div>
}
export function LevelUps(props: {karakter: Karakter2E, changeKarakter: (inputs: Karakter2E) => void}){
const {karakter, changeKarakter} = props;
const tulajdonsagokAtSzint = (szint: number) => TulajdonsagokTotal({...karakter, szintlepesek: karakter.szintlepesek.slice(0, szint)})
const changeRolledHPAtSzint = (szint: number) => (newHProll: number) => {
changeKarakter({...karakter, szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {...karakter.szintlepesek[szint-1], HProll: newHProll})})
console.log(karakter)
}
const changeTulajdonsagNovelesAtSzint = (szint: number) => {
return (t: Tulajdonsag2E) => {
changeKarakter({...karakter,
szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {
...karakter.szintlepesek[szint-1],
tulajdonsagNoveles: t
})
})
};
}
const changeHarcosSpecializacioAtSzint = (szint: number) => {
return (spec: string) => {
changeKarakter({
...karakter,
szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {
...karakter.szintlepesek[szint-1],
harcosFegyver: spec
})
})
};
}
const changeKalozKritikusAtSzint = (szint: number) => {
return (krit: string) => {
changeKarakter({
...karakter,
szintlepesek: arraySetN(karakter.szintlepesek, szint-1, {
...karakter.szintlepesek[szint-1],
kalozKritikus: krit
})
})
};
}
const levels = karakter.szintlepesek.map((_, i) => i+1);
return <>
{levels.slice(1).map(szint =>
<LevelUp
key={`level-up-${szint}`}
szint={szint}
osztaly={karakter.szintlepesek[szint-1].osztaly}
karakter={{...karakter, szintlepesek: karakter.szintlepesek.slice(0, szint) ,tulajdonsagok: tulajdonsagokAtSzint(szint)}}
changeTulajdonsagNoveles={changeTulajdonsagNovelesAtSzint(szint)}
changeRolledHP={changeRolledHPAtSzint(szint)}
changeHarcosSpec={changeHarcosSpecializacioAtSzint(szint)}
changeKalozKritikus={changeKalozKritikusAtSzint(szint)}
changeOsztaly={(o) => changeKarakter(ChangeOsztalyAtSzint(karakter, o, szint))}
/>
)}
</>
}
export default LevelUps
@@ -0,0 +1,54 @@
import {Faj2E, SzintKorlatokFajokra} from "../domain-models/faj2E";
import {Osztaly2E, OsztalyLabel} from "../domain-models/osztaly2E";
import React from "react";
function MultiClassOsztalySelector2E(props: {
faj: Faj2E,
osztaly: Osztaly2E,
changeOsztaly: (o: Osztaly2E) => void
osztalySzint: number
}) {
const {faj, osztaly, changeOsztaly, osztalySzint} = props
return <>
<div className='row'>
<div className="col-lg-6">
<div className="row m-md-2">
<label className='col-8 col-sm-6 col-form-label'>Osztály</label>
<div className="col">
<select className="col form-select" value={osztaly}
onChange={(e) => changeOsztaly(e.target.value as Osztaly2E)}>
<optgroup label='Harcos'>
<option value={Osztaly2E.Harcos}>{OsztalyLabel(Osztaly2E.Harcos)}</option>
{SzintKorlatokFajokra(faj)[Osztaly2E.Amazon] > 0 && (
<option value={Osztaly2E.Amazon}>{OsztalyLabel(Osztaly2E.Amazon)}</option>)}
<option value={Osztaly2E.Barbar}>{OsztalyLabel(Osztaly2E.Barbar)}</option>
<option value={Osztaly2E.Ijasz}>{OsztalyLabel(Osztaly2E.Ijasz)}</option>
<option value={Osztaly2E.Tengeresz}>{OsztalyLabel(Osztaly2E.Tengeresz)}</option>
</optgroup>
<option value={Osztaly2E.Pap}>{OsztalyLabel(Osztaly2E.Pap)}</option>
<option value={Osztaly2E.Tolvaj}>{OsztalyLabel(Osztaly2E.Tolvaj)}</option>
<optgroup label='Varázsló'>
<option value={Osztaly2E.Varazslo}>{OsztalyLabel(Osztaly2E.Varazslo)}</option>
<option value={Osztaly2E.Illuzionista}>{OsztalyLabel(Osztaly2E.Illuzionista)}</option>
</optgroup>
<optgroup label='Opcionális'>
<option value={Osztaly2E.Dalnok}>{OsztalyLabel(Osztaly2E.Dalnok)}</option>
{/* Druida nem lehet tobb osztalyu karakter! */}
<option value={Osztaly2E.Vandor}>{OsztalyLabel(Osztaly2E.Vandor)}</option>
</optgroup>
</select>
</div>
</div>
</div>
<div className="col-lg-6">
<div className="row m-2">
<label className='col-sm-6 col-8 col-form-label'>Új {OsztalyLabel(osztaly)} szint{' '}</label>
<span className='col col-form-label'>{osztalySzint}</span>
</div>
</div>
</div>
</>
}
export default MultiClassOsztalySelector2E
@@ -9,8 +9,8 @@ import {
import {Faj2E, SzintKorlatokFajokra} from "../domain-models/faj2E"; import {Faj2E, SzintKorlatokFajokra} from "../domain-models/faj2E";
function OsztalySelector2E(props: { function OsztalySelector2E(props: {
currentFaj: Faj2E, currentFaj: Faj2E,
currentOsztaly: Osztaly2E, currentOsztaly: Osztaly2E,
changeOsztaly: (newOsztaly: Osztaly2E) => void changeOsztaly: (newOsztaly: Osztaly2E) => void
}) { }) {
const {currentFaj, currentOsztaly, changeOsztaly} = props; const {currentFaj, currentOsztaly, changeOsztaly} = props;
@@ -2,7 +2,7 @@ import React from "react";
import {Faj2E, FajLabel} from "../domain-models/faj2E"; import {Faj2E, FajLabel} from "../domain-models/faj2E";
import {Modifier} from "../domain-models/tulajdonsag2E"; import {Modifier} from "../domain-models/tulajdonsag2E";
import {SignedNumberToText} from "../../shared/helpers"; import {SignedNumberToText} from "../../shared/components/Helpers";
function fajiModositoText(faj: Faj2E, szamolas: (f: Faj2E) => number) : string { function fajiModositoText(faj: Faj2E, szamolas: (f: Faj2E) => number) : string {
@@ -0,0 +1,37 @@
import React from "react";
import {
KarakterTulajdonsagok,
Modifier,
Tulajdonsag2E,
TulajdonsagIDs,
TulajdonsagLabel
} from "../domain-models/tulajdonsag2E";
import {SignedNumberToText} from "../../shared/components/Helpers";
function TulajdonsagNoveles(props: {tulajdonsagok: KarakterTulajdonsagok, tulajdonsagNoveles: Tulajdonsag2E, changeTulajdonsagNoveles: (tulajdonsag: Tulajdonsag2E) => void}) {
const {tulajdonsagok, tulajdonsagNoveles, changeTulajdonsagNoveles} = props
return <>
<div className='row'>
<div className='col-lg-6'>
<div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>Tulajdonság növelés{' '}</label>
<div className='col'>
<select className='form-select' value={tulajdonsagNoveles} onChange={(e) => changeTulajdonsagNoveles(e.target.value as Tulajdonsag2E)}>
{TulajdonsagIDs.filter(t => t === tulajdonsagNoveles || tulajdonsagok[t] < 18).map(t => (
<option value={t} key={t}>{TulajdonsagLabel(t)}</option>
))}
</select>
</div>
</div>
</div>
<div className='col-lg-6'>
<div className='row m-2'>
<label className='col-sm-6 col-8 col-form-label'>Új {TulajdonsagLabel(tulajdonsagNoveles)}{' '}</label>
<span className='col col-form-label'>{tulajdonsagok[tulajdonsagNoveles]} ({SignedNumberToText(Modifier(tulajdonsagok[tulajdonsagNoveles]))})</span>
</div>
</div>
</div>
</>
}
export default TulajdonsagNoveles
@@ -487,7 +487,7 @@
"Harcos": true, "Harcos": true,
"CsakNemHarcos": false, "CsakNemHarcos": false,
"Masfelkezes": false, "Masfelkezes": false,
"Ketkezes": false, "Ketkezes": true,
"KetkezesBonusz": false, "KetkezesBonusz": false,
"HarciManover": false, "HarciManover": false,
"Alakzat": false, "Alakzat": false,
@@ -4,37 +4,129 @@ import {Osztaly2E} from "./osztaly2E";
import {KepzettsegId} from "./kepzettsegek2E"; import {KepzettsegId} from "./kepzettsegek2E";
import {JellemID} from "./jellem"; import {JellemID} from "./jellem";
import {DefaultFelszereles, KarakterFelszereles} from "./felszereles"; import {DefaultFelszereles, KarakterFelszereles} from "./felszereles";
import {Szintlepes} from "./szintlepes";
import {AllowedFegyver} from "./allowed-fegyver";
import {BaseHP} from "./masodlagos_ertekek";
import {dAny} from "../../shared/domain-models/kockak";
export interface Karakter2E { export interface Karakter2E {
name: string, nev: string,
nem?: string, nem?: string,
kor: number, kor: number,
isten?: string, isten?: string,
jellem: JellemID, jellem: JellemID,
tulajdonsagok: KarakterTulajdonsagok tulajdonsagok: KarakterTulajdonsagok
faj: Faj2E faj: Faj2E
osztaly: Osztaly2E
harcosSpecializaciok: string[],
kalozKritikus: string[],
kepzettsegek: KepzettsegId[] kepzettsegek: KepzettsegId[]
tolvajKepzettsegek?: KepzettsegId[] tolvajKepzettsegek?: KepzettsegId[]
szint: number, szint: number,
hpRolls: number[]
felszereles: KarakterFelszereles, felszereles: KarakterFelszereles,
szintlepesek: Szintlepes[]
} }
export const DefaultKarakter: Karakter2E = { export const DefaultKarakter: Karakter2E = {
name: 'Névtelen Kalandozó', nev: 'Névtelen Kalandozó',
kor: 20, kor: 20,
jellem: 'S', jellem: 'S',
tulajdonsagok: TulajdonsagDefaults, tulajdonsagok: TulajdonsagDefaults,
faj: Faj2E.Ember, faj: Faj2E.Ember,
osztaly: Osztaly2E.Tengeresz,
harcosSpecializaciok: [],
kalozKritikus: [],
kepzettsegek: [], kepzettsegek: [],
tolvajKepzettsegek: [], tolvajKepzettsegek: [],
szint: 1, szint: 1,
hpRolls: [],
felszereles: DefaultFelszereles, felszereles: DefaultFelszereles,
} szintlepesek: [{
osztaly: Osztaly2E.Tengeresz,
HProll: 10,
}]
}
function setOsztalyForSzintLepes(szintlepesek: Szintlepes[], osztaly: Osztaly2E, szint: number): Szintlepes[] {
const prev = szintlepesek.slice(0, szint-1)
const current : Szintlepes = {
...szintlepesek[szint-1],
osztaly,
harcosFegyver: undefined,
kalozKritikus: undefined
}
const tail = szintlepesek.slice(szint).map(x => {return {...x}})
let harcosSzint = prev.filter(x => x.osztaly === Osztaly2E.Harcos).length
// TODO: only pick specializations from valid levels
const harcosSpecializaciok = szintlepesek
.map(x => x.harcosFegyver)
.filter(x => x != null)
const harcosSpecAtSzint = (hSzint: number): (string | undefined) => {
if (hSzint % 2 === 1) {
const specIndex = (hSzint+1)/2 -1
if (harcosSpecializaciok.length > specIndex ) {
return harcosSpecializaciok[specIndex]
} else {
return AllowedFegyver(Osztaly2E.Harcos)[specIndex].Id
}
}
else {
return undefined
}
}
if (osztaly === Osztaly2E.Harcos) {
harcosSzint += 1
current.harcosFegyver = harcosSpecAtSzint(harcosSzint)
}
for (let i = 0; i < tail.length; i++) {
if (tail[i].osztaly === Osztaly2E.Harcos) {
harcosSzint++
tail[i].harcosFegyver = harcosSpecAtSzint(harcosSzint)
}
}
let kalozSzint = prev.filter(x => x.osztaly === Osztaly2E.Tengeresz).length
const existingKalozKrits = szintlepesek
.map(x => x.kalozKritikus)
.filter(x => x != null)
const kalozKritAtSzint = (kSzint: number): (string | undefined) => {
if (kSzint % 3 === 0) {
const specIndex = kSzint/3 -1
if (existingKalozKrits.length > specIndex ) {
return existingKalozKrits[specIndex]
} else {
return AllowedFegyver(Osztaly2E.Tengeresz)[specIndex].Id
}
}
else {
return undefined
}
}
if (osztaly === Osztaly2E.Tengeresz) {
kalozSzint++
current.kalozKritikus = kalozKritAtSzint(szint)
}
for (let i = 0; i < tail.length; i++) {
if (tail[i].osztaly === Osztaly2E.Tengeresz) {
kalozSzint++
tail[i].kalozKritikus = kalozKritAtSzint(kalozSzint)
}
}
const HD = BaseHP(osztaly)
if (szint === 1) {
current.HProll = HD
} else {
current.HProll = dAny(HD)
console.log(`Level ${szint} HP roll on d${HD} is: ${current.HProll} due to class change`)
}
return [...prev, current, ...tail]
}
export function ChangeLvl1Osztaly(karakter: Karakter2E, osztaly: Osztaly2E): Karakter2E {
return {
...karakter,
szintlepesek: [{...karakter.szintlepesek[0], HProll: BaseHP(osztaly) , osztaly}, ...karakter.szintlepesek.slice(1)]
}
}
export function ChangeOsztalyAtSzint(karakter: Karakter2E, osztaly: Osztaly2E, szint: number): Karakter2E {
return {
...karakter,
szintlepesek: setOsztalyForSzintLepes(karakter.szintlepesek, osztaly, szint)
}
}
@@ -209,12 +209,12 @@ export const Kepzettsegek: Record<KepzettsegId, Kepzettseg> =
(acc: Record<KepzettsegId, Kepzettseg>, k) => { acc[k.Id] = k; return acc; }, (acc: Record<KepzettsegId, Kepzettseg>, k) => { acc[k.Id] = k; return acc; },
{} as Record<KepzettsegId, Kepzettseg>); {} as Record<KepzettsegId, Kepzettseg>);
export function AvailableKezpettsegList(osztaly: Osztaly2E): Kepzettseg[] { export function AvailableKezpettsegList(...osztalyok: Osztaly2E[]): Kepzettseg[] {
return KepzettsegLista.filter(k => k.Osztalyok == null || k.Osztalyok.includes(osztaly)) return KepzettsegLista.filter(k => k.Osztalyok == null || k.Osztalyok.some(o => osztalyok.includes(o)))
} }
export function GetNumberOfKepzettsegek(t_int: number, faj: Faj2E, max: number = 11) { export function GetNumberOfKepzettsegek(t_int: number, faj: Faj2E, max: number = 11) {
//max = 3 + 1 + 3 + 4 // 3 base, 1 human, 3 from max ability modifier, 4 from Thief //max = 3 + 1 + 3 + 5 = 12 // 3 base, 1 human, 3 from max ability modifier, 5 from Thief at 9th level
let numberOfKepzettseg = 3 + Modifier(t_int) + (faj === Faj2E.Ember ? 1 : 0) let numberOfKepzettseg = 3 + Modifier(t_int) + (faj === Faj2E.Ember ? 1 : 0)
if (numberOfKepzettseg < 1) { if (numberOfKepzettseg < 1) {
@@ -228,18 +228,20 @@ export function GetNumberOfKepzettsegek(t_int: number, faj: Faj2E, max: number =
return numberOfKepzettseg; return numberOfKepzettseg;
} }
export function SetDefaultTolvajKepzettsegek(karakter: Pick<Karakter2E, 'osztaly' | 'tolvajKepzettsegek'>, changeTolvajKepzettsegek: (tolvajKepzettsegek?: KepzettsegId[]) => void) { export function SetDefaultTolvajKepzettsegek(karakter: Pick<Karakter2E, 'szintlepesek' | 'tolvajKepzettsegek'>, changeTolvajKepzettsegek: (tolvajKepzettsegek?: KepzettsegId[]) => void) {
if (karakter.osztaly !== Osztaly2E.Tolvaj && karakter.tolvajKepzettsegek != null) { const hasTolvajSzint = karakter.szintlepesek.some(szint => szint.osztaly === Osztaly2E.Tolvaj)
if (!hasTolvajSzint && karakter.tolvajKepzettsegek != null) {
changeTolvajKepzettsegek(undefined) changeTolvajKepzettsegek(undefined)
} }
if (karakter.osztaly === Osztaly2E.Tolvaj && (karakter.tolvajKepzettsegek == null || karakter.tolvajKepzettsegek.length === 0)) { if (hasTolvajSzint && (karakter.tolvajKepzettsegek == null || karakter.tolvajKepzettsegek.length === 0)) {
const tolvajKepzettsegek = TolvajKepzettsegList.slice(0, 4).map(x => x.Id) const tolvajKepzettsegek = TolvajKepzettsegList.slice(0, 4).map(x => x.Id)
changeTolvajKepzettsegek(tolvajKepzettsegek) changeTolvajKepzettsegek(tolvajKepzettsegek)
} }
} }
export function GetKepzettsegListaN(karakter: Pick<Karakter2E, 'osztaly' | 'faj' | 'kepzettsegek' | 'tolvajKepzettsegek'>) { export function GetKepzettsegListaN(karakter: Pick<Karakter2E, 'szintlepesek' | 'faj' | 'kepzettsegek' | 'tolvajKepzettsegek'>) {
const availableKepzettsegList = AvailableKezpettsegList(karakter.osztaly) const osztalyok = new Set(karakter.szintlepesek.map(x => x.osztaly))
const availableKepzettsegList = AvailableKezpettsegList(...osztalyok)
return (n: number): Kepzettseg[] => { return (n: number): Kepzettseg[] => {
let available = [...availableKepzettsegList] let available = [...availableKepzettsegList]
if (n === 0) { if (n === 0) {
@@ -249,7 +251,8 @@ export function GetKepzettsegListaN(karakter: Pick<Karakter2E, 'osztaly' | 'faj'
if (karakter.faj === Faj2E.Nomad) { if (karakter.faj === Faj2E.Nomad) {
return [Kepzettsegek.k_lovaglas] return [Kepzettsegek.k_lovaglas]
} }
if (karakter.faj === Faj2E.Birodalmi && karakter.osztaly !== Osztaly2E.Tolvaj) { // TODO birodalmi tolvaj, tolvajképzettségek!
if (karakter.faj === Faj2E.Birodalmi) {
return [Kepzettsegek.k_alkimia, Kepzettsegek.k_meregkeveres, Kepzettsegek.k_okkultizmus] return [Kepzettsegek.k_alkimia, Kepzettsegek.k_meregkeveres, Kepzettsegek.k_okkultizmus]
} }
if (karakter.faj === Faj2E.Torpe) { if (karakter.faj === Faj2E.Torpe) {
@@ -258,30 +261,38 @@ export function GetKepzettsegListaN(karakter: Pick<Karakter2E, 'osztaly' | 'faj'
if (karakter.faj === Faj2E.Elf || karakter.faj === Faj2E.Felelf) { if (karakter.faj === Faj2E.Elf || karakter.faj === Faj2E.Felelf) {
return [Kepzettsegek.k_vadonjaras] return [Kepzettsegek.k_vadonjaras]
} }
if (karakter.osztaly === Osztaly2E.Druida || karakter.osztaly === Osztaly2E.Vandor) { if (osztalyok.has(Osztaly2E.Druida) || osztalyok.has(Osztaly2E.Vandor)) {
return [Kepzettsegek.k_vadonjaras] return [Kepzettsegek.k_vadonjaras]
} }
if (karakter.osztaly === Osztaly2E.Dalnok) { if (osztalyok.has(Osztaly2E.Dalnok)) {
return [Kepzettsegek.k_eloadas] return [Kepzettsegek.k_eloadas]
} }
} }
if (n === 1) { if (n === 1) {
if (karakter.osztaly === Osztaly2E.Dalnok if ((osztalyok.has(Osztaly2E.Vandor) || osztalyok.has(Osztaly2E.Druida))
&& [Faj2E.Eszaki, Faj2E.Nomad, Faj2E.Birodalmi, Faj2E.Torpe, Faj2E.Elf, Faj2E.Felelf].includes(karakter.faj)) { && karakter.kepzettsegek[0] !== Kepzettsegek.k_vadonjaras.Id) {
return [Kepzettsegek.k_eloadas]
}
if ([Osztaly2E.Druida, Osztaly2E.Vandor].includes(karakter.osztaly)
&& [Faj2E.Eszaki, Faj2E.Nomad, Faj2E.Birodalmi, Faj2E.Torpe].includes(karakter.faj)) {
return [Kepzettsegek.k_vadonjaras] return [Kepzettsegek.k_vadonjaras]
} }
if (osztalyok.has(Osztaly2E.Dalnok)
&& karakter.kepzettsegek[0] !== Kepzettsegek.k_eloadas.Id) {
return [Kepzettsegek.k_eloadas]
}
}
if (n === 2) { // maybe write a better abstraction?
if (osztalyok.has(Osztaly2E.Dalnok)
&& karakter.kepzettsegek[0] !== Kepzettsegek.k_eloadas.Id
&& karakter.kepzettsegek[1] !== Kepzettsegek.k_eloadas.Id) {
return [Kepzettsegek.k_eloadas]
}
} }
const kepzettsegekWithoutN = [...karakter.kepzettsegek.slice(0, n), ...karakter.kepzettsegek.slice(n + 1)] const kepzettsegekWithoutN = [...karakter.kepzettsegek.slice(0, n), ...karakter.kepzettsegek.slice(n + 1)]
return available.filter(x => !kepzettsegekWithoutN.includes(x.Id) && !karakter.tolvajKepzettsegek?.includes(x.Id)) return available.filter(x => !kepzettsegekWithoutN.includes(x.Id) && !karakter.tolvajKepzettsegek?.includes(x.Id))
} }
} }
export function SetDefaultKepzettsegek(karakter: Pick<Karakter2E, 'osztaly' | 'faj' | 'tulajdonsagok' | 'kepzettsegek' | 'tolvajKepzettsegek'>, changeKepzettsegek: (k: KepzettsegId[]) => void) { export function SetDefaultKepzettsegek(karakter: Pick<Karakter2E, 'szintlepesek' | 'faj' | 'tulajdonsagok' | 'kepzettsegek' | 'tolvajKepzettsegek'>, changeKepzettsegek: (k: KepzettsegId[]) => void) {
const availableKepzettsegList = AvailableKezpettsegList(karakter.osztaly) const osztalyok = karakter.szintlepesek.map(x => x.osztaly)
const availableKepzettsegList = AvailableKezpettsegList(...osztalyok)
const numberOfKepzettsegek = GetNumberOfKepzettsegek(karakter.tulajdonsagok.t_int, karakter.faj, availableKepzettsegList.length) const numberOfKepzettsegek = GetNumberOfKepzettsegek(karakter.tulajdonsagok.t_int, karakter.faj, availableKepzettsegList.length)
let kepzettsegek = [...karakter.kepzettsegek] let kepzettsegek = [...karakter.kepzettsegek]
@@ -14,7 +14,7 @@ export interface MasodlagosErtekekView {
Mentok: Mentok Mentok: Mentok
} }
export type KarakterpickForMasodlagosErtekek = Pick<Karakter2E, 'osztaly' | 'hpRolls' | 'tulajdonsagok' | 'szint' | 'faj' | 'felszereles'> export type KarakterpickForMasodlagosErtekek = Pick<Karakter2E, 'tulajdonsagok' | 'szint' | 'szintlepesek' | 'faj' | 'felszereles'>
export function BaseHP(osztaly: Osztaly2E) { export function BaseHP(osztaly: Osztaly2E) {
let base = 4; let base = 4;
@@ -45,15 +45,34 @@ export function BaseHP(osztaly: Osztaly2E) {
return base return base
} }
export function HP(karakter: Pick<Karakter2E, 'osztaly' | 'tulajdonsagok' | 'hpRolls'>): number { export function HP(karakter: Pick<Karakter2E, 'tulajdonsagok' | 'szintlepesek'>): number {
const egeszsegModifier = Modifier(karakter.tulajdonsagok.t_egs) const egeszsegModifier = Modifier(karakter.tulajdonsagok.t_egs)
return BaseHP(karakter.osztaly) + egeszsegModifier + karakter.hpRolls return karakter.szintlepesek
// https://lfg.hu/forum/topic/15079-kard-es-magia/page/219/#comment-2218333 // https://lfg.hu/forum/topic/15079-kard-es-magia/page/219/#comment-2218333
.map(hp => Math.max(1, hp + egeszsegModifier)) .map(szint => Math.max(1, szint.HProll + egeszsegModifier))
.reduce((sum, val) => sum + val, 0) .reduce((sum, val) => sum + val, 0)
} }
export function VO(karakter: Pick<Karakter2E, 'tulajdonsagok' | 'faj' | 'osztaly' | 'szint' | 'felszereles'>): number { export function VOegyeb(karakter: Pick<Karakter2E, 'faj'| 'szintlepesek'>): number {
const osztalyok = new Set(karakter.szintlepesek.map(x => x.osztaly))
let vo = 0;
if (osztalyok.has(Osztaly2E.Tengeresz)) {
const kalozSzintek = karakter.szintlepesek.filter(x => x.osztaly === Osztaly2E.Tengeresz).length
vo += Math.floor(kalozSzintek / 3)
}
if (osztalyok.has(Osztaly2E.Amazon)) {
const amazonSzintek = karakter.szintlepesek.filter(x => x.osztaly === Osztaly2E.Amazon).length
vo += Math.floor(amazonSzintek / 2)
}
if (karakter.faj === Faj2E.Felszerzet || karakter.faj === Faj2E.Gnom){
vo += 1
}
return vo;
}
export function VO(karakter: Pick<Karakter2E, 'tulajdonsagok' | 'faj' | 'szintlepesek' | 'szint' | 'felszereles'>): number {
const osztalyok = new Set(karakter.szintlepesek.map(x => x.osztaly))
const pancel = GetPancel(karakter.felszereles.pancelID) const pancel = GetPancel(karakter.felszereles.pancelID)
const pancelVO = pancel?.VO ?? 0 const pancelVO = pancel?.VO ?? 0
const pajzsVO = GetPajzs(karakter.felszereles.pajzsID)?.VO ?? 0 const pajzsVO = GetPajzs(karakter.felszereles.pajzsID)?.VO ?? 0
@@ -62,18 +81,12 @@ export function VO(karakter: Pick<Karakter2E, 'tulajdonsagok' | 'faj' | 'osztaly
if (pancel) { if (pancel) {
abilityVO = Math.min(MaxAbilityVO(pancel.Type), abilityVO) abilityVO = Math.min(MaxAbilityVO(pancel.Type), abilityVO)
} }
if (karakter.osztaly === Osztaly2E.Tengeresz) { if (osztalyok.has(Osztaly2E.Amazon)){
vo += Math.floor(karakter.szint / 3)
}
if (karakter.osztaly === Osztaly2E.Amazon) {
if (karakter.tulajdonsagok.t_kar > karakter.tulajdonsagok.t_ugy) { if (karakter.tulajdonsagok.t_kar > karakter.tulajdonsagok.t_ugy) {
abilityVO = Modifier(karakter.tulajdonsagok.t_kar) abilityVO = Modifier(karakter.tulajdonsagok.t_kar)
} }
vo += Math.floor(karakter.szint / 2)
}
if (karakter.faj === Faj2E.Felszerzet || karakter.faj === Faj2E.Gnom){
vo += 1
} }
vo += VOegyeb(karakter)
vo += abilityVO vo += abilityVO
return vo return vo
@@ -1,6 +1,7 @@
import {Osztaly2E} from "./osztaly2E"; import {Osztaly2E} from "./osztaly2E";
import {Karakter2E} from "./karakter2E"; import {Karakter2E} from "./karakter2E";
import {KarakterTulajdonsagok, TulajdonsagModosito} from "./tulajdonsag2E"; import {KarakterTulajdonsagok, TulajdonsagModosito} from "./tulajdonsag2E";
import {Szintlepes} from "./szintlepes";
export interface Mentok { export interface Mentok {
kitartas: number kitartas: number
@@ -73,8 +74,26 @@ export function MentoModositok(tulajdonsagModositok: KarakterTulajdonsagok) {
} }
} }
export function MentokTotal(karakter: Pick<Karakter2E, 'osztaly' | 'szint' | 'tulajdonsagok'>) { export function MultiClassMentokAlap(szintlepesek: Szintlepes[]) : Mentok {
const alap = MentokAlap(karakter.osztaly, karakter.szint) const osztalyok = new Set(szintlepesek.map(x => x.osztaly))
let mentok = {
kitartas: 0,
reflex: 0,
akaratero: 0
}
for (const osztaly of osztalyok) {
const szint = szintlepesek.filter(x => x.osztaly === osztaly).length
const uj_mentok = MentokAlap(osztaly, szint)
mentok.akaratero += uj_mentok.akaratero
mentok.reflex += uj_mentok.reflex
mentok.kitartas += uj_mentok.kitartas
}
return mentok
}
export function MentokTotal(karakter: Pick<Karakter2E, 'szintlepesek' | 'tulajdonsagok'>) {
const alap = MultiClassMentokAlap(karakter.szintlepesek)
const modositok = MentoModositok(TulajdonsagModosito(karakter.tulajdonsagok)) const modositok = MentoModositok(TulajdonsagModosito(karakter.tulajdonsagok))
return { return {
kitartas: alap.kitartas + modositok.kitartas, kitartas: alap.kitartas + modositok.kitartas,
@@ -0,0 +1,102 @@
import {Tulajdonsag2E, TulajdonsagIDs, TulajdonsagokTotal} from "./tulajdonsag2E";
import {KepzettsegId} from "./kepzettsegek2E";
import {Osztaly2E} from "./osztaly2E";
import {Karakter2E} from "./karakter2E";
import {BaseHP} from "./masodlagos_ertekek";
import {dAny} from "../../shared/domain-models/kockak";
import {AllowedFegyver} from "./allowed-fegyver";
export interface Szintlepes {
osztaly: Osztaly2E;
HProll: number;
// only at level 5 and 9
tolvajExtraKepzettseg?: KepzettsegId;
// every odd level
harcosFegyver?: string;
// every 3rd level
kalozKritikus?: string;
tulajdonsagNoveles?: Tulajdonsag2E;
}
export function PickableSpecializationFilter(existingSpecilizations: (string | undefined)[], szint: number) {
return (fegyver: {Id: string}) => existingSpecilizations.filter(x => x === fegyver.Id).length < (szint < 9 ? 1 : 2)
}
export function LevelUp(karakter: Karakter2E, changeKarakter: (input: Karakter2E) => void) {
const osztaly = karakter.szintlepesek[0].osztaly
const tulajdonsagokTotal = TulajdonsagokTotal(karakter)
const szint = karakter.szint + 1
const dice = BaseHP(osztaly)
const roll = dAny(dice)
console.log(`Level ${szint} HP roll on d${dice} is: ${roll}`)
const szintlepes: Szintlepes = {
osztaly: osztaly,
HProll: roll
}
if (szint % 4 === 0) {
for (const tulajdonsag of TulajdonsagIDs) {
if (tulajdonsagokTotal[tulajdonsag] < 18) {
szintlepes.tulajdonsagNoveles = tulajdonsag;
break;
}
}
}
if(osztaly === Osztaly2E.Harcos) {
const harcosSzint = karakter.szintlepesek.filter(x => x.osztaly === Osztaly2E.Harcos).length + 1
if (harcosSzint % 2 === 1) {
const harcosSpecializaciok = karakter.szintlepesek.map(x => x.harcosFegyver)
let specializationFilter = PickableSpecializationFilter(harcosSpecializaciok, harcosSzint)
szintlepes.harcosFegyver = AllowedFegyver(Osztaly2E.Harcos).filter(specializationFilter)[0].Id
}
}
if(osztaly === Osztaly2E.Tengeresz) {
const kalozSzint = karakter.szintlepesek.filter(x => x.osztaly === Osztaly2E.Tengeresz).length + 1
if (kalozSzint % 3 === 0) {
let kalozKritikus = karakter.szintlepesek.map(x => x.kalozKritikus)
let specializationFilter = PickableSpecializationFilter(kalozKritikus, kalozSzint)
szintlepes.kalozKritikus = AllowedFegyver(Osztaly2E.Tengeresz).filter(specializationFilter)[0].Id
}
}
changeKarakter({...karakter, szint, szintlepesek: [...karakter.szintlepesek, szintlepes]})
}
export function LevelDown(karakter: Karakter2E, changeKarakter: (input: Karakter2E) => void) {
if (karakter.szintlepesek.length > 1) {
changeKarakter({...karakter, szint: karakter.szint - 1, szintlepesek: karakter.szintlepesek.slice(0, -1)})
}
else {
console.error(`Can't level down character: ${karakter}`)
}
}
export type ClassLevels = {
[key in Osztaly2E]: number;
};
const defaultClassLevels: ClassLevels = {
[Osztaly2E.Amazon]: 0,
[Osztaly2E.Barbar]: 0,
[Osztaly2E.Ijasz]: 0,
[Osztaly2E.Harcos]: 0,
[Osztaly2E.Tengeresz]: 0,
[Osztaly2E.Tolvaj]: 0,
[Osztaly2E.Pap]: 0,
[Osztaly2E.Varazslo]: 0,
[Osztaly2E.Illuzionista]: 0,
[Osztaly2E.Dalnok]: 0,
[Osztaly2E.Druida]: 0,
[Osztaly2E.Vandor]: 0,
}
export function getClassLevels(szintlepesek: Szintlepes[]) : ClassLevels {
return szintlepesek.map(x => x.osztaly).reduce((classLevels, osztaly) => {
classLevels[osztaly]++
return classLevels
}, {...defaultClassLevels})
}
@@ -1,6 +1,7 @@
import {Osztaly2E} from "./osztaly2E"; import {Osztaly2E} from "./osztaly2E";
import {Karakter2E} from "./karakter2E"; import {Karakter2E} from "./karakter2E";
import {Modifier} from "./tulajdonsag2E"; import {Modifier} from "./tulajdonsag2E";
import {Szintlepes} from "./szintlepes";
/// Finally second edition doesn't mess with the extra attack of high level clerics rouges and wizards! /// Finally second edition doesn't mess with the extra attack of high level clerics rouges and wizards!
function ExtraAttack(attack: number): number[] { function ExtraAttack(attack: number): number[] {
@@ -10,6 +11,31 @@ function ExtraAttack(attack: number): number[] {
return [Math.min(attack, 10), ...ExtraAttack(attack - 5)] return [Math.min(attack, 10), ...ExtraAttack(attack - 5)]
} }
export function TamadasBonuszBase(osztaly: Osztaly2E, szint: number = 1) : number {
let base = 0
switch (osztaly){
case Osztaly2E.Barbar:
case Osztaly2E.Amazon:
case Osztaly2E.Ijasz:
case Osztaly2E.Tengeresz:
case Osztaly2E.Harcos:
case Osztaly2E.Vandor:
base = szint;
break;
case Osztaly2E.Pap:
case Osztaly2E.Tolvaj:
case Osztaly2E.Dalnok:
case Osztaly2E.Druida:
base = Math.floor(szint * 2 / 3);
break;
case Osztaly2E.Varazslo:
case Osztaly2E.Illuzionista:
base = Math.floor(szint / 2);
break;
}
return base
}
export function TamadasBonusz(osztaly: Osztaly2E, szint: number = 1) : number[] { export function TamadasBonusz(osztaly: Osztaly2E, szint: number = 1) : number[] {
let base = 0 let base = 0
switch (osztaly){ switch (osztaly){
@@ -35,12 +61,23 @@ export function TamadasBonusz(osztaly: Osztaly2E, szint: number = 1) : number[]
return ExtraAttack(base) return ExtraAttack(base)
} }
export function KozelharciTB(karakter : Pick<Karakter2E, 'szint' | 'tulajdonsagok' | 'osztaly'>): number[] {
const base = TamadasBonusz(karakter.osztaly, karakter.szint); export function MultiClassTamadasBonusz(szintlepesek: Szintlepes[]) : number[]{
const osztalyok = new Set(szintlepesek.map(x => x.osztaly))
let TBB = 0
for (const osztaly of osztalyok) {
const szint = szintlepesek.filter(x => x.osztaly === osztaly).length
TBB += TamadasBonuszBase(osztaly, szint)
}
return ExtraAttack(TBB)
}
export function KozelharciTB(karakter : Pick<Karakter2E, 'szintlepesek' | 'tulajdonsagok'>): number[] {
const base = MultiClassTamadasBonusz(karakter.szintlepesek);
let modifier = Modifier(karakter.tulajdonsagok.t_ero) let modifier = Modifier(karakter.tulajdonsagok.t_ero)
return base.map(v => v + modifier) return base.map(v => v + modifier)
} }
export function CelzoTB(karakter : Pick<Karakter2E, 'szint' | 'tulajdonsagok' | 'osztaly'>): number[] { export function CelzoTB(karakter : Pick<Karakter2E, 'szintlepesek' | 'tulajdonsagok'>): number[] {
let base = TamadasBonusz(karakter.osztaly, karakter.szint) let base = MultiClassTamadasBonusz(karakter.szintlepesek)
return base.map(v => v + Modifier(karakter.tulajdonsagok.t_ugy)) return base.map(v => v + Modifier(karakter.tulajdonsagok.t_ugy))
} }
@@ -1,4 +1,6 @@
import {d6} from "../../shared/domain-models/kockak"; import {d6} from "../../shared/domain-models/kockak";
import {Karakter2E} from "./karakter2E";
import {TulajdonsagokFajjal} from "./faj2E";
export enum Tulajdonsag2E { export enum Tulajdonsag2E {
Ero = 't_ero', Ero = 't_ero',
@@ -73,4 +75,16 @@ export function TulajdonsagModosito(tulajdonsagok: KarakterTulajdonsagok): Karak
response[key] = Modifier(tulajdonsagok[key]) response[key] = Modifier(tulajdonsagok[key])
} }
return response return response
}
export function TulajdonsagokTotal(karakter: Pick<Karakter2E, 'tulajdonsagok' | 'faj' | 'szintlepesek'>): KarakterTulajdonsagok {
const level1 = TulajdonsagokFajjal(karakter.tulajdonsagok, karakter.faj)
return {
t_ero: level1.t_ero + karakter.szintlepesek.filter(s => s.tulajdonsagNoveles === 't_ero').length,
t_ugy: level1.t_ugy + karakter.szintlepesek.filter(s => s.tulajdonsagNoveles === 't_ugy').length,
t_egs: level1.t_egs + karakter.szintlepesek.filter(s => s.tulajdonsagNoveles === 't_egs').length,
t_int: level1.t_int + karakter.szintlepesek.filter(s => s.tulajdonsagNoveles === 't_int').length,
t_bol: level1.t_bol + karakter.szintlepesek.filter(s => s.tulajdonsagNoveles === 't_bol').length,
t_kar: level1.t_kar + karakter.szintlepesek.filter(s => s.tulajdonsagNoveles === 't_kar').length,
}
} }
@@ -1,7 +1,7 @@
import React, {useState} from 'react' import React, {useContext, useState} from 'react'
import {useParams} from "react-router-dom"; import {useLoaderData, useNavigate, useParams} from "react-router-dom";
import {Toast, ToastContainer} from "react-bootstrap"; import {Button, InputGroup, Modal, OverlayTrigger, Toast, ToastContainer} from "react-bootstrap";
import {DefaultKarakter} from "../domain-models/karakter2E"; import {ChangeLvl1Osztaly, DefaultKarakter, Karakter2E} from "../domain-models/karakter2E";
import {KarakterTulajdonsagok} from "../domain-models/tulajdonsag2E"; import {KarakterTulajdonsagok} from "../domain-models/tulajdonsag2E";
import FajSelector2E from "../components/FajSelector2E"; import FajSelector2E from "../components/FajSelector2E";
import Tulajdonsagok2E from "../components/Tulajdonsagok2E"; import Tulajdonsagok2E from "../components/Tulajdonsagok2E";
@@ -20,12 +20,37 @@ import MasodlagosErtekek from "../components/MasodlagosErtekek";
import {CalculateMasodlagosErtekek} from "../domain-models/masodlagos_ertekek"; import {CalculateMasodlagosErtekek} from "../domain-models/masodlagos_ertekek";
import Felszereles from "../components/Felszereles"; import Felszereles from "../components/Felszereles";
import {KarakterFelszereles} from "../domain-models/felszereles"; import {KarakterFelszereles} from "../domain-models/felszereles";
import LevelUps from "../components/LevelUps";
import {LevelDown, LevelUp} from "../domain-models/szintlepes";
import HarcosFegyverSpecializacio from "../components/HarcosFegyverSpec";
import {arraySetN} from "../../util";
import saveOverlayTooltip from "../../first-edition/components/SaveOverlayTooltip";
import {UserContext} from "../../shared/contexts/UserContext";
import {
StoreNewCharacter2E,
UpdateCharacter2E
} from "../api/character.api";
import {Faro} from "@grafana/faro-web-sdk";
import Form from "react-bootstrap/Form";
import {CreatePDF} from "../pdf/character.pdf";
import {KarakterInputToPdfView} from "../pdf/karakter_pdf_view";
function CreateCharacter2E(props: {}) { function CreateCharacter2E(props: {
faro?: Faro
}) {
const {faro} = props
const { id } = useParams(); const { id } = useParams();
const fetchedUser = useContext(UserContext);
const loaderData = useLoaderData() as Karakter2E & { isPublic: boolean } | undefined;
let initialKarakterInputs = DefaultKarakter;
if (loaderData != null) {
const { isPublic: t1, ...t2 } = loaderData;
initialKarakterInputs = t2
}
const initialIsPublic = loaderData?.isPublic ?? fetchedUser?.data == null;
const navigate = useNavigate();
const [karakter, setKarakter] = useState(DefaultKarakter) const [karakter, setKarakter] = useState(initialKarakterInputs)
const tulajdonsagokFajjal = TulajdonsagokFajjal(karakter.tulajdonsagok, karakter.faj) const tulajdonsagokFajjal = TulajdonsagokFajjal(karakter.tulajdonsagok, karakter.faj)
const changeKepzettseg = (k: KepzettsegId[]) => setKarakter({...karakter, kepzettsegek: k}) const changeKepzettseg = (k: KepzettsegId[]) => setKarakter({...karakter, kepzettsegek: k})
const changeTolvajKepzettseg = (tk?: KepzettsegId[]) => setKarakter({...karakter, tolvajKepzettsegek: tk}) const changeTolvajKepzettseg = (tk?: KepzettsegId[]) => setKarakter({...karakter, tolvajKepzettsegek: tk})
@@ -33,9 +58,44 @@ function CreateCharacter2E(props: {}) {
SetDefaultKepzettsegek({...karakter, tulajdonsagok: tulajdonsagokFajjal}, changeKepzettseg) SetDefaultKepzettsegek({...karakter, tulajdonsagok: tulajdonsagokFajjal}, changeKepzettseg)
const changeFelszereles = (f: KarakterFelszereles) => setKarakter({...karakter, felszereles: f}) const changeFelszereles = (f: KarakterFelszereles) => setKarakter({...karakter, felszereles: f})
let [showSaveModal, setShowSaveModal] = useState(false);
let [newId, setNewId] = useState(null as string | null)
let [showSaved, setShowSaved] = useState(false); let [showSaved, setShowSaved] = useState(false);
let [isPublic, setIsPublic] = useState(initialIsPublic);
const newCharacterUrl = () => `${window.location.origin}/2e/karakter/${newId}`
const handleSaveModalCopyAndClose = async () => {
await navigator.clipboard.writeText(newCharacterUrl());
handleSaveModalClose()
}
const handleSaveModalClose = () => {
setShowSaveModal(false);
navigate(`/2e/karakter/${newId}`)
}
const hideSaved = () => setShowSaved(false); const hideSaved = () => setShowSaved(false);
const onSaveClicked = async () => {
faro?.api.pushEvent('character_stored', {
osztaly: karakter.szintlepesek[0].osztaly.toString(),
szint: karakter.szint.toString(),
faj: karakter.faj.toString(),
is_public: isPublic.toString(),
edition: "2e",
})
if (id == null) {
let recievedId = await StoreNewCharacter2E(karakter, isPublic);
setNewId(recievedId)
setShowSaveModal(true);
} else {
await UpdateCharacter2E(id, karakter, isPublic);
setShowSaved(true);
setTimeout(() => {
hideSaved();
}, 5000)
}
}
return <div> return <div>
<div className='container-fluid p-5 bg-black text-white text-center'> <div className='container-fluid p-5 bg-black text-white text-center'>
@@ -46,6 +106,28 @@ function CreateCharacter2E(props: {}) {
<Toast.Header><strong>Karakter mentve!</strong></Toast.Header> <Toast.Header><strong>Karakter mentve!</strong></Toast.Header>
</Toast> </Toast>
</ToastContainer> </ToastContainer>
<Modal show={showSaveModal} onHide={handleSaveModalClose}>
<Modal.Header closeButton>
<Modal.Title>Karakter mentve!</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Karaktered az alábbi {isPublic ? 'publikus' : 'privát'} URLen érhető el.</p>
<p><a href={newCharacterUrl()}>{newCharacterUrl()}</a></p>
<InputGroup className="mb-3">
<Form.Control id="input" value={newCharacterUrl()} />
<Button variant={"outline-dark"} onClick={() => {
let copyText = document.querySelector("#input") as any;
copyText?.select()
document.execCommand("copy");
}}>Másolás</Button>
</InputGroup>
</Modal.Body>
<Modal.Footer>
<Button variant="danger" onClick={handleSaveModalCopyAndClose}>
Másolás és bezárás
</Button>
</Modal.Footer>
</Modal>
<div className='p-3'> <div className='p-3'>
<form onSubmit={async (event) => event.preventDefault()}> <form onSubmit={async (event) => event.preventDefault()}>
<div className='row'> <div className='row'>
@@ -54,9 +136,9 @@ function CreateCharacter2E(props: {}) {
<div className='row m-2'> <div className='row m-2'>
<label className='col-md-2 col-sm-3 col-form-label'>Név</label> <label className='col-md-2 col-sm-3 col-form-label'>Név</label>
<input className='col form-control' <input className='col form-control'
value={karakter.name} value={karakter.nev}
onChange={(e) => setKarakter({...karakter, name: e.target.value})}/> onChange={(e) => setKarakter({...karakter, nev: e.target.value})}/>
{!karakter.name && {!karakter.nev &&
<span className='form-field-error'>A karaktered nem mászkálhat névtelenül a világban!</span>} <span className='form-field-error'>A karaktered nem mászkálhat névtelenül a világban!</span>}
</div> </div>
<div className='row m-2'> <div className='row m-2'>
@@ -99,13 +181,21 @@ function CreateCharacter2E(props: {}) {
</div> </div>
<OsztalySelector2E <OsztalySelector2E
currentFaj={karakter.faj} currentFaj={karakter.faj}
currentOsztaly={karakter.osztaly} currentOsztaly={karakter.szintlepesek[0].osztaly}
changeOsztaly={osztaly => setKarakter({...karakter, osztaly})} changeOsztaly={o => setKarakter(ChangeLvl1Osztaly(karakter, o))}
/> />
{karakter.osztaly === Osztaly2E.Harcos && {karakter.szintlepesek[0].osztaly === Osztaly2E.Harcos &&
<div className='mb-6'> <HarcosFegyverSpecializacio
TODO: Harcos fegyver specializaciok!! specialization={karakter.szintlepesek[0].harcosFegyver || 'kard_hosszu'}
</div>} szint={1}
changeSpecialization={(spec) => setKarakter({...karakter,
szintlepesek: arraySetN(karakter.szintlepesek, 0, {
...karakter.szintlepesek[0],
harcosFegyver: spec
})
})}
existingSpecializations={karakter.szintlepesek.map(x => x.harcosFegyver)}
/>}
<KarakterKepzettsegek <KarakterKepzettsegek
getKepzettsegListaN={GetKepzettsegListaN(karakter)} getKepzettsegListaN={GetKepzettsegListaN(karakter)}
numberOfKepzettsegek={GetNumberOfKepzettsegek(tulajdonsagokFajjal.t_int, karakter.faj)} numberOfKepzettsegek={GetNumberOfKepzettsegek(tulajdonsagokFajjal.t_int, karakter.faj)}
@@ -114,22 +204,90 @@ function CreateCharacter2E(props: {}) {
tolvajKepzettsegek={karakter.tolvajKepzettsegek || []} tolvajKepzettsegek={karakter.tolvajKepzettsegek || []}
changeTolvajKepzettsegek={changeTolvajKepzettseg} changeTolvajKepzettsegek={changeTolvajKepzettseg}
/> />
<hr /> <hr/>
{/* 1. szinten */} {/* 1. szinten */}
<MasodlagosErtekek {...CalculateMasodlagosErtekek({ <MasodlagosErtekek {...CalculateMasodlagosErtekek({
...karakter, ...karakter,
tulajdonsagok: tulajdonsagokFajjal, tulajdonsagok: tulajdonsagokFajjal,
szint: 1, szint: 1,
hpRolls: [] szintlepesek: karakter.szintlepesek.slice(0, 1)
})} /> })} />
<hr /> <hr/>
<Felszereles osztaly={karakter.osztaly} felszereles={karakter.felszereles} <LevelUps karakter={karakter} changeKarakter={setKarakter}/>
{karakter.szint > 1 && <hr/>}
<div className='d-grid gap-2 m-5'>
{karakter.szint < 12 &&
<button className='btn btn-dark btn-lg' type='button'
onClick={() => LevelUp(karakter, setKarakter)}>Szintlépés!
</button>}
{karakter.szint > 1 &&
<button className='btn btn-dark btn-lg' type='button'
onClick={() => LevelDown(karakter, setKarakter)}>Visszalépés!
</button>}
</div>
<hr/>
<Felszereles osztaly={karakter.szintlepesek[0].osztaly} felszereles={karakter.felszereles}
changeFelszereles={changeFelszereles}/> changeFelszereles={changeFelszereles}/>
<hr/> <hr/>
<div className='row'>
<h5 className='col align-self-center'>Véglegesítés</h5>
</div>
{fetchedUser.data != null &&
<div className='row m-2'>
<label className='col-md-2 col-sm-3 col-form-label'>Publikus karakterlap?</label>
<select className="col form-select" value={isPublic.toString()}
onChange={(event) => setIsPublic(event.target.value === "true")}>
<option value="true">Igen</option>
<option value="false">Nem</option>
</select>
</div>
}
<div className="row">
<div className="col-6">
<div className='d-grid gap-2 m-5'>
{fetchedUser.data == null
? <OverlayTrigger
placement='top'
overlay={saveOverlayTooltip}
delay={0}
defaultShow={false}
flip={false}
>
<button
className='btn btn-danger btn-lg'
type='button'
onClick={onSaveClicked}
>
Mentés
</button>
</OverlayTrigger>
: <button
className='btn btn-danger btn-lg'
type='button'
onClick={onSaveClicked}
>
Mentés
</button>
}
</div>
</div>
<div className="col-6">
<div className='d-grid gap-2 m-5'>
<button className='btn btn-danger btn-lg' type='button' onClick={async () => {
faro?.api.pushEvent('character_created', {
osztaly: karakter.szintlepesek[0].osztaly,
szint: karakter.szint.toString(),
faj: karakter.faj.toString(),
})
await CreatePDF(KarakterInputToPdfView(karakter))
}}>PDF</button>
</div>
</div>
</div>
</form> </form>
</div> </div>
<hr/>
</div> </div>
} }
@@ -0,0 +1,90 @@
import {KarakterPdfView} from "./karakter_pdf_view";
import {PDFDocument} from "pdf-lib";
import fontkit from "@pdf-lib/fontkit";
import download from "downloadjs";
import {SignedNumberToText} from "../../shared/components/Helpers";
export async function CreatePDF(karakter: KarakterPdfView) {
const existingPdfBytes = await fetch('/kem2karakterlap_form.pdf').then(res => res.arrayBuffer())
const pdfDoc = await PDFDocument.load(existingPdfBytes)
pdfDoc.registerFontkit(fontkit)
const fontBytes = await fetch('/Milonga-Regular.ttf').then(res => res.arrayBuffer())
const pdfFont = await pdfDoc.embedFont(fontBytes)
const form = pdfDoc.getForm()
const osztalyok = karakter.Osztaly.length > 1 ? karakter.Osztaly.map(x => `${x.osztaly} (${x.szint})`).join(", ") : karakter.Osztaly[0].osztaly
form.getTextField("nev_faj_osztaly").setText(`${karakter.Nev}, ${karakter.Faj} ${osztalyok}`)
form.getTextField("szint").setText((karakter.SzintTotal.length > 1 ? " " : " ") + karakter.SzintTotal)
form.getTextField("jellem").setText(karakter.Jellem)
form.getTextField("istenseg").setText(karakter.Isten)
form.getTextField("vo").setText(karakter.VO.toString())
form.getTextField("pancel").setText(" " + karakter.PancelVO)
form.getTextField("pajzs").setText(" " + karakter.PajzsVO)
form.getTextField("egyeb_vo").setText(" " + karakter.EgyebVO)
form.getTextField("tul_ero").setText(` ${karakter.Tulajdonsagok.t_ero}`)
form.getTextField("tul_ero_mod").setText(SignedNumberToText(karakter.TulajdonsagModositok.t_ero))
form.getTextField("tul_ugy").setText(` ${karakter.Tulajdonsagok.t_ugy}`)
form.getTextField("tul_ugy_mod").setText(SignedNumberToText(karakter.TulajdonsagModositok.t_ugy))
form.getTextField("tul_egs").setText(` ${karakter.Tulajdonsagok.t_egs}`)
form.getTextField("tul_egs_mod").setText(SignedNumberToText(karakter.TulajdonsagModositok.t_egs))
form.getTextField("tul_int").setText(` ${karakter.Tulajdonsagok.t_int}`)
form.getTextField("tul_int_mod").setText(SignedNumberToText(karakter.TulajdonsagModositok.t_int))
form.getTextField("tul_bol").setText(` ${karakter.Tulajdonsagok.t_bol}`)
form.getTextField("tul_bol_mod").setText(SignedNumberToText(karakter.TulajdonsagModositok.t_bol))
form.getTextField("tul_kar").setText(` ${karakter.Tulajdonsagok.t_kar}`)
form.getTextField("tul_kar_mod").setText(SignedNumberToText(karakter.TulajdonsagModositok.t_kar))
form.getTextField("hp_rolls").setText(karakter.HPszintenkent.join(", "))
form.getTextField("hp_aktualis").setText(karakter.HP.toString())
form.getTextField("kitartas_alap").setText(" " + karakter.MentokAlap.kitartas.toString())
form.getTextField("kitartas_mod").setText(" " + SignedNumberToText(karakter.MentoModositok.kitartas))
form.getTextField("kitartas_sum").setText(" " + karakter.MentokModositokkal.kitartas.toString())
form.getTextField("reflex_alap").setText(" " + karakter.MentokAlap.reflex.toString())
form.getTextField("reflex_mod").setText(" " + SignedNumberToText(karakter.MentoModositok.reflex))
form.getTextField("reflex_sum").setText(" " + karakter.MentokModositokkal.reflex.toString())
form.getTextField("akaratero_alap").setText(" " + karakter.MentokAlap.akaratero.toString())
form.getTextField("akaratero_mod").setText(" " + SignedNumberToText(karakter.MentoModositok.akaratero))
form.getTextField("akaratero_sum").setText(" " + karakter.MentokModositokkal.akaratero.toString())
form.getTextField("kezdemenyezes").setText(" " + SignedNumberToText(karakter.Kezdemenyezes))
form.getTextField("tamadas_bonusz_alap").setText(karakter.TamadasBonuszAlap.join("/"))
form.getTextField("tamadas_bonusz_ero").setText(" " + SignedNumberToText(karakter.TulajdonsagModositok.t_ero))
form.getTextField("tamadas_bonusz_kozelharc").setText(karakter.KozelharciTB.join("/"))
form.getTextField("tamadas_bonusz_ugy").setText(" " + SignedNumberToText(karakter.TulajdonsagModositok.t_ugy))
form.getTextField("tamadas_bonusz_tavolsagi").setText(karakter.CelzoTB.join("/"))
for (let i = 0; i < karakter.Kepzettsegek.length; i++) {
const kepzettseg = karakter.Kepzettsegek[i]
form.getTextField(`kepzettseg_${i}_nev`).setText(kepzettseg.nev)
form.getTextField(`kepzettseg_${i}_alap`).setText(kepzettseg.alap)
form.getTextField(`kepzettseg_${i}_bonusz`).setText(kepzettseg.bonusz)
form.getTextField(`kepzettseg_${i}_osszes`).setText(kepzettseg.osszes)
}
for (let i = 0; i < karakter.Fegyverek.length; i++) {
form.getTextField(`fegyver_${i+1}`).setText(karakter.Fegyverek[i])
}
for (let i = 0; i < karakter.FelszerelesViselt.length && i < 8; i++) {
form.getTextField(`viselt_${i}`).setText(karakter.FelszerelesViselt[i])
}
for (let i = 0; i < karakter.FelszerelesCipelt.length && i < 8; i++) {
form.getTextField(`cipelt_${i}`).setText(karakter.FelszerelesCipelt[i])
}
for (let i = 0; i < karakter.FelszerelesAprosagok.length && i < 8; i++) {
form.getTextField(`aprosagok_${i}`).setText(karakter.FelszerelesAprosagok[i])
}
form.updateFieldAppearances(pdfFont)
const pdfBytes = await pdfDoc.save();
download(pdfBytes, `${karakter.Nev}_${karakter.SzintTotal}.pdf`, "application/pdf");
}
@@ -0,0 +1,176 @@
import {Modifier, TulajdonsagokTotal} from "../domain-models/tulajdonsag2E";
import {Mentok, MentoModositok, MultiClassMentokAlap} from "../domain-models/mentok";
import {CalculateMasodlagosErtekek, VOegyeb} from "../domain-models/masodlagos_ertekek";
import {FajLabel} from "../domain-models/faj2E";
import {Osztaly2E, OsztalyLabel} from "../domain-models/osztaly2E";
import {CelzoTB, KozelharciTB, MultiClassTamadasBonusz} from "../domain-models/tamadas_bonusz";
import {Karakter2E} from "../domain-models/karakter2E";
import {GetJellem} from "../domain-models/jellem";
import {GetFegyver, GetPajzs, GetPancel} from '../domain-models/felszereles'
import {
KarakterTulajdonsagok,
TulajdonsagModosito
} from "../domain-models/tulajdonsag2E";
import {SignedNumberToText} from "../../shared/components/Helpers";
import {Kepzettseg, Kepzettsegek} from "../domain-models/kepzettsegek2E";
import {getClassLevels} from "../domain-models/szintlepes";
import {FegyverFlags} from "../components/FegyverSelector";
interface KepzettsegPdfView {
nev: string,
alap: string,
bonusz: string,
osszes: string
}
export interface KarakterPdfView {
Nev: string
Isten: string
Jellem: string
Faj: string
Osztaly: {osztaly: string, szint: number}[]
Tulajdonsagok: KarakterTulajdonsagok,
TulajdonsagModositok: KarakterTulajdonsagok,
SzintTotal: string
HP: number
HPszintenkent: number[]
VO: number
PancelVO: string
PajzsVO: string
EgyebVO: string
Kezdemenyezes: number
TamadasBonuszAlap: number[]
KozelharciTB: number[]
CelzoTB: number[]
MentokAlap: Mentok
MentoModositok: Mentok
MentokModositokkal: Mentok,
Kepzettsegek: KepzettsegPdfView[],
Fegyverek: string[],
FelszerelesViselt: string[],
FelszerelesCipelt: string[],
FelszerelesAprosagok: string[],
}
export function KarakterInputToPdfView(karakter: Karakter2E): KarakterPdfView {
const tulajdonsagok = TulajdonsagokTotal(karakter)
karakter = {...karakter, tulajdonsagok}
const tulajdonsagModositok = TulajdonsagModosito(tulajdonsagok)
const masodlagosErtekek = CalculateMasodlagosErtekek(karakter)
const pancel = GetPancel(karakter.felszereles.pancelID)
const pajzs = GetPajzs(karakter.felszereles.pajzsID)
const kezdemenyezes = tulajdonsagModositok.t_ugy + (karakter.szintlepesek.some(x => x.osztaly === Osztaly2E.Tolvaj) ? 4 : 0)
const mentok = MultiClassMentokAlap(karakter.szintlepesek)
const mentoModositok = MentoModositok(tulajdonsagModositok)
const osztalyok = new Array(...(new Set(karakter.szintlepesek.map(x => x.osztaly))))
const osztaly = osztalyok.map(x => ({
osztaly: OsztalyLabel(x),
szint: karakter.szintlepesek.filter(sz => sz.osztaly === x).length
}))
const classLevels = getClassLevels(karakter.szintlepesek)
let felszereles = [] as string[]
if(pancel != null){
felszereles = felszereles.concat(FelszerelesToPdfView(pancel))
}
if(pajzs != null){
felszereles = felszereles.concat(FelszerelesToPdfView(pajzs))
}
felszereles = felszereles.concat(karakter.felszereles.fegyverIDk.map(x => {
const fegyver = GetFegyver(x)
if (fegyver == null) {
console.error("unknown fegyver: " + x)
return []
}
return FelszerelesToPdfView(fegyver)
}).reduce((acc, item) => acc.concat(item), [] as string[]))
return {
Faj: FajLabel(karakter.faj).toLowerCase(),
Isten: karakter.isten || "",
Jellem: GetJellem(karakter.jellem).Label,
Nev: karakter.nev,
Tulajdonsagok: karakter.tulajdonsagok,
Osztaly: osztaly,
SzintTotal: karakter.szint.toString(),
TulajdonsagModositok: tulajdonsagModositok,
HP: masodlagosErtekek.HP,
HPszintenkent: karakter.szintlepesek.map(x => x.HProll),
Kezdemenyezes: kezdemenyezes,
TamadasBonuszAlap: MultiClassTamadasBonusz(karakter.szintlepesek),
KozelharciTB: KozelharciTB(karakter),
CelzoTB: CelzoTB(karakter),
MentoModositok: mentoModositok,
MentokAlap: mentok,
MentokModositokkal: masodlagosErtekek.Mentok,
VO: masodlagosErtekek.VO,
PajzsVO: SignedNumberToText(pajzs?.VO),
PancelVO: SignedNumberToText(pancel?.VO),
EgyebVO: SignedNumberToText(VOegyeb(karakter)),
Kepzettsegek:
karakter.kepzettsegek.map(k =>
mapKepzettsegToPdfView(Kepzettsegek[k], tulajdonsagok, karakter.szint))
.concat(karakter.tolvajKepzettsegek?.map(k =>
mapKepzettsegToPdfView(Kepzettsegek[k], tulajdonsagok, classLevels[Osztaly2E.Tolvaj])) || []),
Fegyverek: karakter.felszereles.fegyverIDk.slice(0, 4).map(fid => {
const fegyver = GetFegyver(fid)
if (fegyver == null) {
console.error("unknown fegyver: " + fid)
return ""
}
const serializedFlags = FegyverFlags(fegyver);
const critStart = fegyver.CritRangeStart - karakter.szintlepesek.filter(x => x.kalozKritikus === fegyver.Id).length
const damageBonus = fegyver.DamageBonus +
(fegyver.KetkezesBonusz ? 1 : 0) +
(fegyver.Type !== "lofegyver" || fegyver.EroBonusz ? tulajdonsagModositok.t_ero : 0) +
(karakter.szintlepesek.filter(x => x.harcosFegyver === fegyver.Id).length * 2)
return `${fegyver.Name} ${serializedFlags.length > 0 ? serializedFlags + " " : ''} ${
fegyver.DamageMultiplier > 1 ? `${fegyver.DamageMultiplier}*` : ''}${
fegyver.NumberOfDamageDice}d${fegyver.DamageDice}${damageBonus !== 0 ? `${
SignedNumberToText(damageBonus)}` : ''} ${
critStart < 20 ? `${critStart}-20` : '20'}x${fegyver.CritMultiplier}`
}),
FelszerelesViselt: felszereles,
FelszerelesCipelt: [],
FelszerelesAprosagok: []
}
}
interface FelszerelesPdf {
Name: string,
Size: number,
}
function FelszerelesToPdfView(item: FelszerelesPdf): string[] {
const ans = [item.Name]
for (let i = 1; i < item.Size; i ++) {
ans.push("+")
}
return ans
}
function mapKepzettsegToPdfView(kepzettseg: Kepzettseg, tulajdonsagok: KarakterTulajdonsagok, szint: number): KepzettsegPdfView {
const tulajdonsagErtek = kepzettseg.Tulajdonsag.reduce((maxTulajdonsagErtek, currentTulajdonsag) =>
tulajdonsagok[currentTulajdonsag] > maxTulajdonsagErtek
? tulajdonsagok[currentTulajdonsag]
: maxTulajdonsagErtek,
tulajdonsagok[kepzettseg.Tulajdonsag[0]])
return {
nev: kepzettseg.Name,
alap: szint.toString(),
bonusz: SignedNumberToText(Modifier(tulajdonsagErtek)),
osszes: (szint + Modifier(tulajdonsagErtek)).toString(),
}
}
@@ -0,0 +1,19 @@
import * as React from "react";
import {CharacterListItemViewModel} from "../pages/CharacterList";
function CharacterListItem(props: {
character: CharacterListItemViewModel,
}) {
const {character:c} = props
return <>
<td><a href={`/${c.edition}/karakter/${c.id}`}>{c.name}</a></td>
<td>{c.szint}. szintű</td>
<td>{c.faj}</td>
<td>{c.osztaly}</td>
<td>{c.edition}</td>
</>
}
export default CharacterListItem;
@@ -1,3 +1,3 @@
export function SignedNumberToText(val: number) : string { export function SignedNumberToText(val?: number) : string {
return val === 0 ? " 0" : (val > 0 ? '+'+val : val.toString()) return (val === null || val === undefined) ? "" : (val === 0 ? " 0" : (val > 0 ? '+'+val : val.toString()))
} }
@@ -1,3 +0,0 @@
export function SignedNumberToText(val: number) : string {
return val === 0 ? " 0" : (val > 0 ? '+'+val : val.toString())
}
@@ -0,0 +1,101 @@
import * as React from "react";
import {useLoaderData} from "react-router-dom";
import CharacterListItem from "../components/CharacterListItem";
import {Faj, FajLabel as FajLabel1E} from "../../first-edition/domain-models/faj";
import {Faj2E, FajLabel as FajLabel2E} from "../../second-edition/domain-models/faj2E";
import {Osztaly, OsztalyLabel as OsztalyLabel1E} from "../../first-edition/domain-models/osztaly";
import {Osztaly2E, OsztalyLabel as OsztalyLabel2E} from "../../second-edition/domain-models/osztaly2E";
export interface CharacterListItemViewModel {
id: string,
name: string,
szint: number,
faj: string,
osztaly: string,
edition: "1e" | "2e"
}
interface CharacterListItemDto1E {
id: string,
name: string,
szint: number,
faj: Faj,
osztaly: Osztaly,
edition: "1e"
}
interface CharacterListItemDto2E {
id: string,
name: string,
szint: number,
faj: Faj2E,
osztaly: Osztaly2E,
edition: "2e"
}
type CharacterListItemDto = CharacterListItemDto1E | CharacterListItemDto2E;
function mapDtoToViewModel(dto: CharacterListItemDto): CharacterListItemViewModel {
if (dto.edition === "1e") {
return {
id: dto.id,
name: dto.name,
edition: dto.edition,
szint: dto.szint,
faj: FajLabel1E(dto.faj),
osztaly: OsztalyLabel1E(dto.osztaly),
}
}
else {
return {
id: dto.id,
name: dto.name,
edition: dto.edition,
szint: dto.szint,
faj: FajLabel2E(dto.faj),
osztaly: OsztalyLabel2E(dto.osztaly),
}
}
}
function CharacterList(props: {}) {
const characters = useLoaderData() as CharacterListItemDto[];
return <div className="container">
<div className="container-fluid p-5 bg-black text-white text-center">
<h1>Karakterek</h1>
</div>
<div className="row">
<div className="col-6">
<div className="d-grid gap-1 m-3 m-lg-5">
<a className="btn btn-dark btn-lg" href="/2e/karakter/" type="button">Új 2e karakter létrehozása</a>
</div>
</div>
<div className="col-6">
<div className="d-grid gap-1 m-3 m-lg-5">
<a className="btn btn-dark btn-lg" href="/1e/karakter/" type="button">Új 1e karakter létrehozása</a>
</div>
</div>
</div>
<table className="table mt-3">
<thead>
<tr>
<th scope="col">Név</th>
<th scope="col">Szint</th>
<th scope="col">Faj</th>
<th scope="col">Osztály</th>
<th scope="col">Kiadás</th>
</tr>
</thead>
<tbody>
{characters.map(c => (
<tr key={c.id}>
<CharacterListItem character={mapDtoToViewModel(c)}/>
</tr>
))}
</tbody>
</table>
</div>
}
export default CharacterList;
File diff suppressed because it is too large Load Diff
+4
View File
@@ -91,5 +91,9 @@ http {
proxy_pass https://backend; proxy_pass https://backend;
proxy_pass_request_headers on; proxy_pass_request_headers on;
} }
location /img/ {
proxy_pass https://backend;
proxy_pass_request_headers on;
}
} }
} }
+158
View File
@@ -8,6 +8,16 @@
"resolved": "8.0.0", "resolved": "8.0.0",
"contentHash": "+MtOHPctq1QT1c03h+7g4oBWtZ/buQYUJg4AR0DG+qjmD2cQRMGWXC1J6/ITeCc6xa5BcXzJmt1L/8pVXIB4kw==" "contentHash": "+MtOHPctq1QT1c03h+7g4oBWtZ/buQYUJg4AR0DG+qjmD2cQRMGWXC1J6/ITeCc6xa5BcXzJmt1L/8pVXIB4kw=="
}, },
"Grpc.Core": {
"type": "Direct",
"requested": "[2.46.6, )",
"resolved": "2.46.6",
"contentHash": "ZoRg3KmOJ2urTF4+u3H0b1Yv10xzz2Y/flFWS2tnRmj8dbKLeiJaSRqu4LOBD3ova90evqLkVZ85kUkC4JT4lw==",
"dependencies": {
"Grpc.Core.Api": "2.46.6",
"System.Memory": "4.5.3"
}
},
"Microsoft.AspNetCore.Authentication.Facebook": { "Microsoft.AspNetCore.Authentication.Facebook": {
"type": "Direct", "type": "Direct",
"requested": "[8.0.0, )", "requested": "[8.0.0, )",
@@ -124,6 +134,78 @@
"Npgsql.EntityFrameworkCore.PostgreSQL": "1.1.0" "Npgsql.EntityFrameworkCore.PostgreSQL": "1.1.0"
} }
}, },
"OpenTelemetry.Exporter.Console": {
"type": "Direct",
"requested": "[1.7.0, )",
"resolved": "1.7.0",
"contentHash": "9mcwpArzG7+5cLnG6marWdVD1TyoVR03WAjShvxADqgsEeuzLMClTlgXi41bTCAgxjYyiHV5l60fJwXTekiJdQ==",
"dependencies": {
"OpenTelemetry": "1.7.0",
"System.Text.Encodings.Web": "4.7.2",
"System.Text.Json": "4.7.2"
}
},
"OpenTelemetry.Exporter.OpenTelemetryProtocol": {
"type": "Direct",
"requested": "[1.7.0, )",
"resolved": "1.7.0",
"contentHash": "ULGTxg4HdnJD+CIuiD15Uov4xVVqwpbNGFd075J3MT3lLM9XcwU4YRz7CCNmqIlehYaiZrGhUurXrI2LgZZL1Q==",
"dependencies": {
"Google.Protobuf": "[3.22.5, 4.0.0)",
"Grpc.Net.Client": "[2.52.0, 3.0.0)",
"OpenTelemetry": "1.7.0"
}
},
"OpenTelemetry.Extensions.Hosting": {
"type": "Direct",
"requested": "[1.7.0, )",
"resolved": "1.7.0",
"contentHash": "MbB7CWWqb7xHK0jTF9Gtvw/eLWdaKqzkE1XAwLe05xyskHuwJWAbZFax4nGLA71YkMWQNO5iPIBlirvYXOLMlg==",
"dependencies": {
"Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
"Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
"OpenTelemetry": "1.7.0"
}
},
"OpenTelemetry.Instrumentation.AspNetCore": {
"type": "Direct",
"requested": "[1.7.0, )",
"resolved": "1.7.0",
"contentHash": "K/mjCmuR9zgrL6fMC6sbnX/y6BOJJFI3hp42O+tz8nT/w6sxbfjrLcU+fVwfct3lsnohSKdPfrq6fSrxAF5ezw==",
"dependencies": {
"OpenTelemetry.Api.ProviderBuilderExtensions": "1.7.0"
}
},
"Google.Protobuf": {
"type": "Transitive",
"resolved": "3.22.5",
"contentHash": "tTMtDZPbLxJew8pk7NBdqhLqC4OipfkZdwPuCEUNr2AoDo1siUGcxFqJK0wDewTL8ge5Cjrb16CToMPxBUHMGA=="
},
"Grpc.Core.Api": {
"type": "Transitive",
"resolved": "2.52.0",
"contentHash": "SQiPyBczG4vKPmI6Fd+O58GcxxDSFr6nfRAJuBDUNj+PgdokhjWJvZE/La1c09AkL2FVm/jrDloG89nkzmVF7A==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"Grpc.Net.Client": {
"type": "Transitive",
"resolved": "2.52.0",
"contentHash": "hWVH9g/Nnjz40ni//2S8UIOyEmhueQREoZIkD0zKHEPqLxXcNlbp4eebXIOicZtkwDSx0TFz9NpkbecEDn6rBw==",
"dependencies": {
"Grpc.Net.Common": "2.52.0",
"Microsoft.Extensions.Logging.Abstractions": "3.0.3"
}
},
"Grpc.Net.Common": {
"type": "Transitive",
"resolved": "2.52.0",
"contentHash": "di9qzpdx525IxumZdYmu6sG2y/gXJyYeZ1ruFUzB9BJ1nj4kU1/dTAioNCMt1VLRvNVDqh8S8B1oBdKhHJ4xRg==",
"dependencies": {
"Grpc.Core.Api": "2.52.0"
}
},
"Humanizer.Core": { "Humanizer.Core": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.14.1", "resolved": "2.14.1",
@@ -265,6 +347,15 @@
"Microsoft.Extensions.Primitives": "8.0.0" "Microsoft.Extensions.Primitives": "8.0.0"
} }
}, },
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": { "Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.0", "resolved": "8.0.0",
@@ -273,6 +364,14 @@
"Microsoft.Extensions.Primitives": "8.0.0" "Microsoft.Extensions.Primitives": "8.0.0"
} }
}, },
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
}
},
"Microsoft.Extensions.DependencyInjection": { "Microsoft.Extensions.DependencyInjection": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.0", "resolved": "8.0.0",
@@ -371,6 +470,21 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
} }
}, },
"Microsoft.Extensions.Logging.Configuration": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "ixXXV0G/12g6MXK65TLngYN9V5hQQRuV+fZi882WIoVJT7h5JvoYoxTEwCgdqwLjSneqh1O+66gM8sMr9z/rsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
}
},
"Microsoft.Extensions.Options": { "Microsoft.Extensions.Options": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.0", "resolved": "8.0.0",
@@ -380,6 +494,18 @@
"Microsoft.Extensions.Primitives": "8.0.0" "Microsoft.Extensions.Primitives": "8.0.0"
} }
}, },
"Microsoft.Extensions.Options.ConfigurationExtensions": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
"Microsoft.Extensions.Configuration.Binder": "8.0.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Extensions.Options": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0"
}
},
"Microsoft.Extensions.Primitives": { "Microsoft.Extensions.Primitives": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.0", "resolved": "8.0.0",
@@ -472,6 +598,33 @@
"Microsoft.Extensions.Logging.Abstractions": "8.0.0" "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
} }
}, },
"OpenTelemetry": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "HNmOJg++4FtEJGCIn1dCJcDkHRLNuLKU047owrGXUOfz/C/c5oZHOPKrowKVOy2jOOh/F9+HDshzeOk5NnQEZg==",
"dependencies": {
"Microsoft.Extensions.Logging": "8.0.0",
"Microsoft.Extensions.Logging.Configuration": "8.0.0",
"OpenTelemetry.Api.ProviderBuilderExtensions": "1.7.0"
}
},
"OpenTelemetry.Api": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "w5uDHl1Nm7R5igWu1QjewpdPzJSavua8NTI3+iThOdRNHD3cabox8JeIJ8LpBSXGHARSNFMJZ/Xr5EGRinMTMg==",
"dependencies": {
"System.Diagnostics.DiagnosticSource": "8.0.0"
}
},
"OpenTelemetry.Api.ProviderBuilderExtensions": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "fHLmJcRJk1dAwddjBgvqi/Grmr04hPo1DoFwTa6hxtSvAFOXPU56Xe9Sh2VXqz6Gstzp6TCju8z0Sob1t7BzSg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"OpenTelemetry.Api": "1.7.0"
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.3.0", "resolved": "4.3.0",
@@ -887,6 +1040,11 @@
"System.Threading": "4.3.0" "System.Threading": "4.3.0"
} }
}, },
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Net.Http": { "System.Net.Http": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.3.0", "resolved": "4.3.0",
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508.67 96.36"><g fill="#5865f2"><path d="M170.85,20.2h27.3q9.87,0,16.7,3.08a22.5,22.5,0,0,1,10.21,8.58,23.34,23.34,0,0,1,3.4,12.56A23.24,23.24,0,0,1,224.93,57a23.94,23.94,0,0,1-10.79,8.92q-7.24,3.3-17.95,3.29H170.85Zm25.06,36.54q6.65,0,10.22-3.32a11.8,11.8,0,0,0,3.57-9.07,11.5,11.5,0,0,0-3.18-8.5q-3.2-3.18-9.63-3.19h-8.54V56.74Z"/><path d="M269.34,69.13a37,37,0,0,1-10.22-4.27V53.24a27.77,27.77,0,0,0,9.2,4.38,39.31,39.31,0,0,0,11.17,1.71,8.71,8.71,0,0,0,3.82-.66c.86-.44,1.29-1,1.29-1.58a2.37,2.37,0,0,0-.7-1.75,6.15,6.15,0,0,0-2.73-1.19l-8.4-1.89q-7.22-1.68-10.25-4.65a10.39,10.39,0,0,1-3-7.81,10.37,10.37,0,0,1,2.66-7.07,17.13,17.13,0,0,1,7.56-4.65,36,36,0,0,1,11.48-1.65A43.27,43.27,0,0,1,292,27.69a30.25,30.25,0,0,1,8.12,3.22v11a30,30,0,0,0-7.6-3.11,34,34,0,0,0-8.85-1.16q-6.58,0-6.58,2.24a1.69,1.69,0,0,0,1,1.58,16.14,16.14,0,0,0,3.74,1.08l7,1.26Q295.65,45,299,48t3.36,8.78a11.61,11.61,0,0,1-5.57,10.12Q291.26,70.61,281,70.6A46.41,46.41,0,0,1,269.34,69.13Z"/><path d="M318.9,67.66a21,21,0,0,1-9.07-8,21.59,21.59,0,0,1-3-11.34,20.62,20.62,0,0,1,3.15-11.27,21.16,21.16,0,0,1,9.24-7.8,34.25,34.25,0,0,1,14.56-2.84q10.5,0,17.43,4.41V43.65a21.84,21.84,0,0,0-5.7-2.73,22.65,22.65,0,0,0-7-1.05q-6.51,0-10.19,2.38a7.15,7.15,0,0,0-.1,12.43q3.57,2.41,10.36,2.41a23.91,23.91,0,0,0,6.9-1,25.71,25.71,0,0,0,5.84-2.49V66a34,34,0,0,1-17.85,4.62A32.93,32.93,0,0,1,318.9,67.66Z"/><path d="M368.64,67.66a21.77,21.77,0,0,1-9.25-8,21.14,21.14,0,0,1-3.18-11.41A20.27,20.27,0,0,1,359.39,37a21.42,21.42,0,0,1,9.21-7.74,38.17,38.17,0,0,1,28.7,0,21.25,21.25,0,0,1,9.17,7.7,20.41,20.41,0,0,1,3.15,11.27,21.29,21.29,0,0,1-3.15,11.41,21.51,21.51,0,0,1-9.2,8,36.32,36.32,0,0,1-28.63,0Zm21.27-12.42a9.12,9.12,0,0,0,2.56-6.76,8.87,8.87,0,0,0-2.56-6.68,9.53,9.53,0,0,0-7-2.49,9.67,9.67,0,0,0-7,2.49,8.9,8.9,0,0,0-2.55,6.68,9.15,9.15,0,0,0,2.55,6.76,9.53,9.53,0,0,0,7,2.55A9.4,9.4,0,0,0,389.91,55.24Z"/><path d="M451.69,29V44.14a12.47,12.47,0,0,0-6.93-1.75c-3.73,0-6.61,1.14-8.61,3.4s-3,5.77-3,10.53V69.2H416V28.25h16.8v13q1.4-7.14,4.52-10.53a10.38,10.38,0,0,1,8-3.4A11.71,11.71,0,0,1,451.69,29Z"/><path d="M508.67,18.8V69.2H491.52V60a16.23,16.23,0,0,1-6.62,7.88A20.81,20.81,0,0,1,474,70.6a18.11,18.11,0,0,1-10.15-2.83A18.6,18.6,0,0,1,457.11,60a25.75,25.75,0,0,1-2.34-11.17,24.87,24.87,0,0,1,2.48-11.55,19.43,19.43,0,0,1,7.21-8,19.85,19.85,0,0,1,10.61-2.87q12.24,0,16.45,10.64V18.8ZM489,55a8.83,8.83,0,0,0,2.63-6.62A8.42,8.42,0,0,0,489,42a11,11,0,0,0-13.89,0,8.55,8.55,0,0,0-2.59,6.47A8.67,8.67,0,0,0,475.14,55,9.42,9.42,0,0,0,482,57.51,9.56,9.56,0,0,0,489,55Z"/><path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/><ellipse cx="242.92" cy="24.93" rx="8.55" ry="7.68"/><path d="M234.36,37.9a22.08,22.08,0,0,0,17.11,0V69.42H234.36Z"/></g></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+1
View File
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=karakter/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kemkas/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=Kemkas/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+6 -4
View File
@@ -6,6 +6,12 @@ Kard és Mágia karakter alkotó segédlet (https://app.kemkas.hu)
\[HU] Kérlek nézd el a kevert angol kifejezeséket, ugyan a szerepáték nyelve magyar, de a programozás szakmai nyelve angol. \[HU] Kérlek nézd el a kevert angol kifejezeséket, ugyan a szerepáték nyelve magyar, de a programozás szakmai nyelve angol.
## Deployment
See deployment repo at: [https://github.com/morbalint/kemkas-deployment](https://github.com/morbalint/kemkas-deployment)
![CI workflow](https://github.com/morbalint/kemkas/actions/workflows/ci.yml/badge.svg)
## Coding readmes ## Coding readmes
For frontend only see: [Frontend README](./Kemkas.Web/frontend/README.md) For frontend only see: [Frontend README](./Kemkas.Web/frontend/README.md)
@@ -18,7 +24,3 @@ For the first time setup:
3. spin up the proxy from docker compose (needs the SSL certs) 3. spin up the proxy from docker compose (needs the SSL certs)
Later start the docker compose first and the project launch settings after the DB is up and running. Later start the docker compose first and the project launch settings after the DB is up and running.
## Deployment
See deployment repo at: [https://github.com/morbalint/kemkas-deployment](https://github.com/morbalint/kemkas-deployment)
+10 -1
View File
@@ -13,12 +13,21 @@
## Features ## Features
- multiclass characters - multiclass characters
- 2e -ben félig működik
- druidáknál tiltani kell
- PDF-ben varazshasznalok
- tolvaj 9ik szintű képessége
## Epics ## Epics
- Backend - Backend
- Varazslatok - Varazslatok
- Felszerelés - Felszerelés
- KéM 2.0-ra felkészülés - 1e-ben fel van már véve az adat. Gergő neki veselkedik.
- 2e-ben teljesen hiányzik
- KéM 2e
- frontend oldalon úgy, ahogy megvan
- PDF hiányzik
- backend hiányzik
- Klikkelésre dobás - Klikkelésre dobás