v0.6 prerelease commit with some minor fixes
Release CI / build-and-upload-mod (push) Canceled after 0s
Release CI / build-and-upload-mod (release) Canceled after 0s
Java CI / build (push) Successful in 4m54s
.NET+Docker CI/CD / Unit and Integration tests (push) Successful in 44s
.NET+Docker CI/CD / Push Docker image to ghcr.io (push) Successful in 1m45s
Release CI / build-and-upload-mod (push) Canceled after 0s
Release CI / build-and-upload-mod (release) Canceled after 0s
Java CI / build (push) Successful in 4m54s
.NET+Docker CI/CD / Unit and Integration tests (push) Successful in 44s
.NET+Docker CI/CD / Push Docker image to ghcr.io (push) Successful in 1m45s
Signed-off-by: Dmitri Shimanski <yawaflua@perseverance.yawaflua.tech>
This commit is contained in:
committed by
Dmitri Shimanski
parent
b1bc688d83
commit
d41cd8f079
@@ -32,12 +32,63 @@ SPMega - клиентский Fabric-мод с банковым UI для раб
|
||||
`Вы не владелец карты. Часть функций может быть ограничена.`
|
||||
###### позже это станет ограничением для синхронизации карт/истории с облаком
|
||||
|
||||
## Сборка (PowerShell)
|
||||
## Поддерживаемые версии
|
||||
|
||||
- Minecraft 1.21.1 (Java 21)
|
||||
- Minecraft 1.21.11 (Java 21)
|
||||
- Minecraft 26.1.x (сборка на 26.1.2, Java 25)
|
||||
- Minecraft 26.2.x (сборка на 26.2, Java 25)
|
||||
|
||||
Версии собираются из общего дерева исходников через Stonecutter. Зависимости
|
||||
для каждой версии находятся в `stonecutter.properties.toml`, а условные участки
|
||||
исходников помечены директивами `/*? ... */`.
|
||||
|
||||
## Сборка
|
||||
|
||||
Все поддерживаемые версии:
|
||||
|
||||
```bash
|
||||
./gradlew buildAndCollect
|
||||
```
|
||||
|
||||
Готовые JAR-файлы появятся в `build/libs/<версия-мода>/`.
|
||||
|
||||
Одна версия:
|
||||
|
||||
```bash
|
||||
./gradlew :1.21.1:build
|
||||
./gradlew :26.2.x:build
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat build
|
||||
.\gradlew.bat buildAndCollect
|
||||
```
|
||||
|
||||
## Публикация на Modrinth
|
||||
|
||||
Один универсальный JAR для этих версий использовать нельзя: сборки 1.21.x и
|
||||
26.x имеют несовместимые Minecraft/Fabric API. Вместо ручной загрузки четырёх
|
||||
файлов Gradle создаёт отдельную запись Modrinth для каждой версии:
|
||||
|
||||
```bash
|
||||
export MODRINTH_TOKEN="ваш-токен"
|
||||
export MODRINTH_PROJECT_ID="id-или-slug-проекта"
|
||||
./gradlew modrinth
|
||||
```
|
||||
|
||||
В PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:MODRINTH_TOKEN="ваш-токен"
|
||||
$env:MODRINTH_PROJECT_ID="id-или-slug-проекта"
|
||||
.\gradlew.bat modrinth
|
||||
```
|
||||
|
||||
Тип релиза задаётся свойством `modrinth_version_type` в `gradle.properties`:
|
||||
`release`, `beta` или `alpha`.
|
||||
|
||||
## Дорожная карта
|
||||
|
||||
- [ ] Синхронизация карт/истории с облаком
|
||||
|
||||
@@ -3,7 +3,9 @@ using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using SpMega.Backend.Persistent.Database;
|
||||
using SpMega.Backend.Services;
|
||||
|
||||
namespace SpMega.Backend.Authetication;
|
||||
|
||||
@@ -11,28 +13,39 @@ public class JwtAuthHandler(
|
||||
IOptionsMonitor<JwtAuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
AppDbContext dbContext) : AuthenticationHandler<JwtAuthenticationSchemeOptions>(options, logger, encoder)
|
||||
AppDbContext dbContext,
|
||||
TokenService tokenService) : AuthenticationHandler<JwtAuthenticationSchemeOptions>(options, logger, encoder)
|
||||
{
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (!Request.Headers.ContainsKey("Authorization") || !Request.Headers.Authorization.ToString().StartsWith("Bearer "))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var token = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
var authorization = Request.Headers.Authorization.ToString();
|
||||
if (string.IsNullOrWhiteSpace(authorization))
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid authorization header format");
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
const string bearerPrefix = "Bearer ";
|
||||
if (!authorization.StartsWith(bearerPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid authorization header format.");
|
||||
}
|
||||
|
||||
var token = authorization[bearerPrefix.Length..].Trim();
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid authorization header format.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var user = await dbContext.Users.FirstOrDefaultAsync(k => k.Token == token);
|
||||
var validatedToken = tokenService.ValidateAccessToken(token);
|
||||
var user = await dbContext.Users.FirstOrDefaultAsync(
|
||||
candidate => candidate.Id == validatedToken.UserId && !candidate.IsDeleted,
|
||||
Context.RequestAborted);
|
||||
|
||||
if (user == null)
|
||||
if (user == null || !string.Equals(user.Username, validatedToken.UserName, StringComparison.Ordinal))
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid or expired token");
|
||||
return AuthenticateResult.Fail("Invalid token subject.");
|
||||
}
|
||||
|
||||
var claims = new List<Claim>
|
||||
@@ -40,25 +53,39 @@ public class JwtAuthHandler(
|
||||
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new(ClaimTypes.Name, user.Username),
|
||||
new("UserId", user.Id.ToString()),
|
||||
new("Token", token),
|
||||
new(ClaimTypes.Role, "default")
|
||||
};
|
||||
|
||||
var claimsIdentity = new ClaimsIdentity(claims, Scheme.Name);
|
||||
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
|
||||
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
|
||||
Context.Items["User"] = user;
|
||||
Context.Items["@me"] = user;
|
||||
Context.Items["user"] = user;
|
||||
Context.Items[TokenService.HttpContextItemKey] = validatedToken;
|
||||
|
||||
var ticket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
|
||||
|
||||
return AuthenticateResult.Success(ticket);
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (SecurityTokenException exception)
|
||||
{
|
||||
Logger.LogError(ex, "Error during JWT authentication");
|
||||
return AuthenticateResult.Fail("Authentication error occurred");
|
||||
Logger.LogWarning("JWT authentication rejected: {Reason}", exception.Message);
|
||||
return AuthenticateResult.Fail("Invalid or expired token.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.LogError(exception, "Error during JWT authentication");
|
||||
return AuthenticateResult.Fail("Authentication error occurred.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
|
||||
{
|
||||
Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
Response.ContentType = "application/json";
|
||||
Response.Headers.WWWAuthenticate = $"{Scheme.Name} error=\"invalid_token\"";
|
||||
Response.Headers["X-SPMega-Reauthenticate"] = "true";
|
||||
return Response.WriteAsJsonAsync(new
|
||||
{
|
||||
code = "reauthentication_required",
|
||||
message = "The access token is missing, invalid, expired, or older than two days."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,35 @@ public class AuthController(AppDbContext dbContext, TokenService tokenService, I
|
||||
|
||||
}
|
||||
|
||||
[HttpPost("refresh")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> RefreshAccessTokenAsync()
|
||||
{
|
||||
if (HttpContext.Items[TokenService.HttpContextItemKey] is not ValidatedAccessToken accessToken ||
|
||||
HttpContext.Items["@me"] is not User user)
|
||||
{
|
||||
return Unauthorized(new { code = "reauthentication_required" });
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
if (accessToken.ExpiresAt - now > TokenService.RefreshThreshold)
|
||||
{
|
||||
return Ok(new { refreshed = false, expiresAt = accessToken.ExpiresAt });
|
||||
}
|
||||
|
||||
if (accessToken.AuthenticatedAt.Add(TokenService.MaximumAuthenticationAge) - now <= TokenService.RefreshThreshold)
|
||||
{
|
||||
return Unauthorized(new { code = "reauthentication_required" });
|
||||
}
|
||||
|
||||
var token = tokenService.GenerateAccessToken(user.Username, user.Id, accessToken.AuthenticatedAt);
|
||||
user.Token = token;
|
||||
user.UpdatedAt = DateTime.UtcNow;
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
return Ok(new { refreshed = true, token });
|
||||
}
|
||||
|
||||
[HttpPut("cards")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> AddCardAsync([FromBody] AddCardBody body)
|
||||
@@ -145,11 +174,6 @@ public class AuthController(AppDbContext dbContext, TokenService tokenService, I
|
||||
throw new Exception("Its not ur card");
|
||||
}
|
||||
|
||||
var balanceResp = await SendRequest("/public/card", new("Bearer", Base64BearerToken));
|
||||
var balance = (int?)JsonNode.Parse(balanceResp)?["balance"];
|
||||
|
||||
|
||||
|
||||
var card = me.cards.First(k => k.id == body.id);
|
||||
var existingCard = user.Cards.FirstOrDefault(k => k.Id.ToString() == card.id);
|
||||
if (existingCard == null)
|
||||
@@ -157,7 +181,6 @@ public class AuthController(AppDbContext dbContext, TokenService tokenService, I
|
||||
{
|
||||
Id = Guid.Parse(card.id),
|
||||
Name = card.name,
|
||||
Balance = balance ?? -1,
|
||||
SpworldsID = card.number,
|
||||
Token = Base64BearerToken,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
@@ -168,7 +191,6 @@ public class AuthController(AppDbContext dbContext, TokenService tokenService, I
|
||||
existingCard.Name = card.name;
|
||||
existingCard.SpworldsID = card.number;
|
||||
existingCard.Token = Base64BearerToken;
|
||||
existingCard.Balance = balance ?? -1;
|
||||
existingCard.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
}
|
||||
@@ -236,4 +258,3 @@ public class AuthController(AppDbContext dbContext, TokenService tokenService, I
|
||||
}
|
||||
|
||||
public record AddCardBody(string id, string token);
|
||||
|
||||
|
||||
@@ -65,10 +65,6 @@ public class TransactionsController(AppDbContext context, ILogger<TransactionsCo
|
||||
return BadRequest(new { error = "Card not found" });
|
||||
}
|
||||
|
||||
if (cardToUse.Balance != -1 && cardToUse.Balance < body.amount)
|
||||
{
|
||||
return BadRequest(new { error = "Insufficient balance" });
|
||||
}
|
||||
|
||||
|
||||
var shortId = Program.GenerateRandomString(2);
|
||||
@@ -110,8 +106,6 @@ public class TransactionsController(AppDbContext context, ILogger<TransactionsCo
|
||||
{
|
||||
throw new Exception("Failed to create transaction: " + resp);
|
||||
}
|
||||
cardToUse.Balance = balance.Value;
|
||||
|
||||
|
||||
} catch (Exception exception)
|
||||
{
|
||||
|
||||
@@ -12,8 +12,6 @@ public class Card
|
||||
public string? ShortId { get; set; } = "";
|
||||
public bool? WebhookConnected { get; set; } = false;
|
||||
|
||||
public int? Balance { get; set; } = -1;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
@@ -5,36 +6,120 @@ using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace SpMega.Backend.Services;
|
||||
|
||||
public sealed record ValidatedAccessToken(
|
||||
Guid UserId,
|
||||
string UserName,
|
||||
DateTimeOffset AuthenticatedAt,
|
||||
DateTimeOffset ExpiresAt);
|
||||
|
||||
public class TokenService(IConfiguration conf)
|
||||
{
|
||||
private const int AccessTokenExpirationMinutes = 24;
|
||||
public const string HttpContextItemKey = "ValidatedAccessToken";
|
||||
public static readonly TimeSpan AccessTokenLifetime = TimeSpan.FromHours(24);
|
||||
public static readonly TimeSpan MaximumAuthenticationAge = TimeSpan.FromDays(2);
|
||||
public static readonly TimeSpan RefreshThreshold = TimeSpan.FromHours(1);
|
||||
|
||||
private static readonly TimeSpan ClockSkew = TimeSpan.FromMinutes(1);
|
||||
private readonly string _issuer = conf["JWT:Issuer"] ?? conf["JWT__Issuer"] ?? "spmega.il.yawaflua.tech";
|
||||
private readonly string _secret = conf["JWT:Secret"] ?? conf["JWT__Secret"] ?? throw new InvalidOperationException("JWT__Secret is not configured.");
|
||||
|
||||
public string GenerateAccessToken(string userName, Guid uuid)
|
||||
{
|
||||
return GenerateAccessToken(userName, uuid, DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
public string GenerateAccessToken(string userName, Guid uuid, DateTimeOffset authenticatedAt)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var maximumExpiration = authenticatedAt.Add(MaximumAuthenticationAge);
|
||||
var expiration = now.Add(AccessTokenLifetime);
|
||||
if (expiration > maximumExpiration)
|
||||
{
|
||||
expiration = maximumExpiration;
|
||||
}
|
||||
|
||||
if (expiration <= now)
|
||||
{
|
||||
throw new SecurityTokenExpiredException("The original authentication is older than two days.");
|
||||
}
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(JwtRegisteredClaimNames.Name, userName),
|
||||
new (JwtRegisteredClaimNames.UniqueName, uuid.ToString()),
|
||||
new(JwtRegisteredClaimNames.AuthTime, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()),
|
||||
new(JwtRegisteredClaimNames.UniqueName, uuid.ToString()),
|
||||
new(JwtRegisteredClaimNames.AuthTime, authenticatedAt.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture)),
|
||||
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
};
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_secret);
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler { MapInboundClaims = false };
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddHours(AccessTokenExpirationMinutes),
|
||||
Expires = expiration.UtcDateTime,
|
||||
Issuer = _issuer,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
|
||||
IssuedAt = DateTime.UtcNow
|
||||
SigningCredentials = new SigningCredentials(
|
||||
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret)),
|
||||
SecurityAlgorithms.HmacSha256),
|
||||
IssuedAt = now.UtcDateTime
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
|
||||
return tokenHandler.WriteToken(token);
|
||||
return tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
}
|
||||
}
|
||||
|
||||
public ValidatedAccessToken ValidateAccessToken(string token)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler { MapInboundClaims = false };
|
||||
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = _issuer,
|
||||
ValidateAudience = false,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret)),
|
||||
ValidateLifetime = true,
|
||||
RequireExpirationTime = true,
|
||||
RequireSignedTokens = true,
|
||||
ClockSkew = ClockSkew,
|
||||
ValidAlgorithms = [SecurityAlgorithms.HmacSha256]
|
||||
}, out var validatedToken);
|
||||
|
||||
if (validatedToken is not JwtSecurityToken jwtToken ||
|
||||
!string.Equals(jwtToken.Header.Alg, SecurityAlgorithms.HmacSha256, StringComparison.Ordinal))
|
||||
{
|
||||
throw new SecurityTokenValidationException("Unsupported JWT signing algorithm.");
|
||||
}
|
||||
|
||||
var userName = principal.FindFirst(JwtRegisteredClaimNames.Name)?.Value;
|
||||
var uniqueName = principal.FindFirst(JwtRegisteredClaimNames.UniqueName)?.Value;
|
||||
var authTimeValue = principal.FindFirst(JwtRegisteredClaimNames.AuthTime)?.Value;
|
||||
var expirationValue = principal.FindFirst(JwtRegisteredClaimNames.Exp)?.Value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(userName) ||
|
||||
!Guid.TryParse(uniqueName, out var userId) ||
|
||||
!long.TryParse(authTimeValue, NumberStyles.None, CultureInfo.InvariantCulture, out var authTimeSeconds) ||
|
||||
!long.TryParse(expirationValue, NumberStyles.None, CultureInfo.InvariantCulture, out var expirationSeconds))
|
||||
{
|
||||
throw new SecurityTokenValidationException("Required JWT claims are missing or invalid.");
|
||||
}
|
||||
|
||||
DateTimeOffset authenticatedAt;
|
||||
DateTimeOffset expiresAt;
|
||||
try
|
||||
{
|
||||
authenticatedAt = DateTimeOffset.FromUnixTimeSeconds(authTimeSeconds);
|
||||
expiresAt = DateTimeOffset.FromUnixTimeSeconds(expirationSeconds);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException exception)
|
||||
{
|
||||
throw new SecurityTokenValidationException("JWT timestamps are invalid.", exception);
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
if (authenticatedAt > now.Add(ClockSkew) || now - authenticatedAt > MaximumAuthenticationAge)
|
||||
{
|
||||
throw new SecurityTokenExpiredException("Reauthentication is required.");
|
||||
}
|
||||
|
||||
return new ValidatedAccessToken(userId, userName, authenticatedAt, expiresAt);
|
||||
}
|
||||
}
|
||||
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
plugins {
|
||||
id 'fabric-loom' version '1.14.10'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
base {
|
||||
archivesName = project.archives_base_name
|
||||
}
|
||||
|
||||
loom {
|
||||
splitEnvironmentSourceSets()
|
||||
|
||||
mods {
|
||||
"spmega" {
|
||||
sourceSet sourceSets.main
|
||||
sourceSet sourceSets.client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fabricApi {
|
||||
configureDataGeneration {
|
||||
client = true
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url "https://maven.shedaniel.me/" }
|
||||
maven { url "https://maven.terraformersmc.com/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
modImplementation("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") {
|
||||
exclude(group: "net.fabricmc.fabric-api")
|
||||
}
|
||||
|
||||
modCompileOnly "com.terraformersmc:modmenu:11.0.3"
|
||||
|
||||
include(implementation("org.xerial:sqlite-jdbc:3.46.1.3"))
|
||||
include(clientImplementation("com.google.zxing:core:${project.zxing_version}"))
|
||||
include(clientImplementation("com.google.zxing:javase:${project.zxing_version}"))
|
||||
}
|
||||
|
||||
processResources {
|
||||
inputs.property "version", project.version
|
||||
inputs.property "minecraft_version", project.minecraft_version
|
||||
inputs.property "loader_version", project.loader_version
|
||||
inputs.property "cloth_config_version", project.cloth_config_version
|
||||
filteringCharset "UTF-8"
|
||||
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand "version": project.version,
|
||||
"minecraft_version": project.minecraft_version,
|
||||
"loader_version": project.loader_version,
|
||||
"cloth_config_version": project.cloth_config_version
|
||||
}
|
||||
}
|
||||
|
||||
def targetJavaVersion = 21
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||
// this fixes some edge cases with special characters not displaying correctly
|
||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||
// If Javadoc is generated, this must be specified in that task too.
|
||||
it.options.encoding = "UTF-8"
|
||||
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
||||
it.options.release.set(targetJavaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
|
||||
if (JavaVersion.current() < javaVersion) {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||
}
|
||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||
// if it is present.
|
||||
// If you remove this line, sources will not be generated.
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
from("LICENSE") {
|
||||
rename { "${it}_${project.archives_base_name}" }
|
||||
}
|
||||
}
|
||||
|
||||
// configure the maven publication
|
||||
publishing {
|
||||
publications {
|
||||
create("mavenJava", MavenPublication) {
|
||||
artifactId = project.archives_base_name
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
|
||||
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
|
||||
repositories {
|
||||
// Add repositories to publish to here.
|
||||
// Notice: This block does NOT have the same function as the block in the top level.
|
||||
// The repositories here will be used for publishing your artifact, not for
|
||||
// retrieving dependencies.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
plugins {
|
||||
id("dev.kikugie.loom-back-compat")
|
||||
id("com.modrinth.minotaur")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
version = "${property("mod_version")}+${sc.current.version}"
|
||||
group = property("maven_group") as String
|
||||
|
||||
base {
|
||||
archivesName = property("archives_base_name") as String
|
||||
}
|
||||
|
||||
val requiredJava = if (sc.current.parsed >= "26.1") JavaVersion.VERSION_25 else JavaVersion.VERSION_21
|
||||
val fabricVersion: String = sc.properties["dependencies.fabric_version"]
|
||||
val clothConfigVersion: String = sc.properties["dependencies.cloth_config_version"]
|
||||
val modMenuVersion: String = sc.properties["dependencies.modmenu_version"]
|
||||
val minecraftCompat: String = sc.properties["metadata.minecraft_compat"]
|
||||
val loaderCompat: String = sc.properties["metadata.loader_compat"]
|
||||
val modrinthGameVersions = when (sc.current.project) {
|
||||
"1.21.1" -> listOf("1.21.1")
|
||||
"1.21.11" -> listOf("1.21.11")
|
||||
"26.1.x" -> listOf("26.1", "26.1.1", "26.1.2")
|
||||
"26.2.x" -> listOf("26.2")
|
||||
else -> error("Missing Modrinth game versions for ${sc.current.project}")
|
||||
}
|
||||
|
||||
loom {
|
||||
splitEnvironmentSourceSets()
|
||||
interfaceInjection {
|
||||
enableDependencyInterfaceInjection = false
|
||||
}
|
||||
|
||||
mods {
|
||||
create("spmega") {
|
||||
sourceSet(sourceSets.main.get())
|
||||
sourceSet(sourceSets["client"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fabricApi {
|
||||
configureDataGeneration {
|
||||
client = true
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://maven.shedaniel.me/")
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven("https://api.modrinth.com/maven") { name = "Modrinth" }
|
||||
}
|
||||
filter { includeGroup("maven.modrinth") }
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:${sc.current.version}")
|
||||
loomx.applyMojangMappings()
|
||||
|
||||
modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}")
|
||||
if (sc.current.parsed >= "26.1") {
|
||||
modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricVersion")
|
||||
} else {
|
||||
listOf(
|
||||
"fabric-api-base",
|
||||
"fabric-data-generation-api-v1",
|
||||
"fabric-events-interaction-v0",
|
||||
"fabric-key-binding-api-v1",
|
||||
"fabric-lifecycle-events-v1",
|
||||
"fabric-message-api-v1",
|
||||
"fabric-networking-api-v1",
|
||||
"fabric-rendering-v1",
|
||||
"fabric-screen-api-v1"
|
||||
).forEach { module ->
|
||||
modImplementation(fabricApi.module(module, fabricVersion))
|
||||
}
|
||||
}
|
||||
|
||||
modImplementation("me.shedaniel.cloth:cloth-config-fabric:$clothConfigVersion") {
|
||||
exclude(group = "net.fabricmc.fabric-api")
|
||||
}
|
||||
modCompileOnly("maven.modrinth:mOgUt4GM:$modMenuVersion")
|
||||
|
||||
val sqlite = "org.xerial:sqlite-jdbc:${property("sqlite_version")}"
|
||||
val zxingCore = "com.google.zxing:core:${property("zxing_version")}"
|
||||
val zxingJavase = "com.google.zxing:javase:${property("zxing_version")}"
|
||||
implementation(sqlite)
|
||||
include(sqlite)
|
||||
add("clientImplementation", zxingCore)
|
||||
add("clientImplementation", zxingJavase)
|
||||
include(zxingCore)
|
||||
include(zxingJavase)
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
val values: Map<String, String> = mapOf(
|
||||
"version" to project.version.toString(),
|
||||
"minecraft_version" to minecraftCompat,
|
||||
"loader_version" to loaderCompat,
|
||||
"java_version" to requiredJava.majorVersion,
|
||||
"cloth_config_version" to clothConfigVersion
|
||||
)
|
||||
values.forEach(inputs::property)
|
||||
filteringCharset = "UTF-8"
|
||||
filesMatching("fabric.mod.json") { expand(values) }
|
||||
}
|
||||
|
||||
withType<JavaCompile>().configureEach {
|
||||
options.encoding = "UTF-8"
|
||||
options.release = requiredJava.majorVersion.toInt()
|
||||
}
|
||||
|
||||
jar {
|
||||
from(rootProject.file("LICENSE.txt")) {
|
||||
rename { "${it}_${project.property("archives_base_name")}" }
|
||||
}
|
||||
}
|
||||
|
||||
register<Copy>("buildAndCollect") {
|
||||
group = "build"
|
||||
description = "Builds this Minecraft version and collects its distributable JAR"
|
||||
from(loomx.modJar.flatMap { it.archiveFile })
|
||||
into(rootProject.layout.buildDirectory.dir("libs/${project.property("mod_version")}"))
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = requiredJava
|
||||
targetCompatibility = requiredJava
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(requiredJava.majorVersion)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
artifactId = property("archives_base_name") as String
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modrinth {
|
||||
token.set(providers.environmentVariable("MODRINTH_TOKEN"))
|
||||
projectId.set(providers.environmentVariable("MODRINTH_PROJECT_ID"))
|
||||
versionNumber.set(project.version.toString())
|
||||
versionName.set("SPMega ${project.version}")
|
||||
versionType.set(property("modrinth_version_type") as String)
|
||||
uploadFile.set(loomx.modJar)
|
||||
gameVersions.addAll(modrinthGameVersions)
|
||||
loaders.add("fabric")
|
||||
dependencies {
|
||||
required.project("fabric-api")
|
||||
optional.project("cloth-config")
|
||||
optional.project("modmenu")
|
||||
}
|
||||
}
|
||||
+8
-10
@@ -1,16 +1,14 @@
|
||||
# Done to increase the memory available to gradle.
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
# Fabric Properties
|
||||
# check these on https://modmuss50.me/fabric.html
|
||||
minecraft_version=1.21.11
|
||||
yarn_mappings=1.21.11+build.4
|
||||
loader_version=0.18.1
|
||||
# Shared Fabric properties. Per-Minecraft-version dependencies are in
|
||||
# stonecutter.properties.toml.
|
||||
loader_version=0.19.3
|
||||
loomx.loom_version=1.17-SNAPSHOT
|
||||
# Mod Properties
|
||||
mod_version=0.5-beta
|
||||
mod_version=0.6-prerelease
|
||||
maven_group=git.yawaflua.tech
|
||||
archives_base_name=SPMega
|
||||
# Dependencies
|
||||
# check this on https://modmuss50.me/fabric.html
|
||||
fabric_version=0.141.1+1.21.11
|
||||
cloth_config_version=21.11.153
|
||||
modrinth_version_type=beta
|
||||
# Bundled dependencies
|
||||
sqlite_version=3.46.1.3
|
||||
zxing_version=3.5.3
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
name = 'Fabric'
|
||||
url = 'https://maven.fabricmc.net/'
|
||||
}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven("https://maven.fabricmc.net/") { name = "Fabric" }
|
||||
maven("https://maven.kikugie.dev/releases") { name = "KikuGie Releases" }
|
||||
maven("https://maven.kikugie.dev/snapshots") { name = "KikuGie Snapshots" }
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.kikugie.stonecutter") version "0.9.7"
|
||||
id("dev.kikugie.loom-back-compat") version "0.4"
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
|
||||
stonecutter {
|
||||
create(rootProject) {
|
||||
versions("1.21.1", "1.21.11")
|
||||
version("26.1.x", "26.1.2")
|
||||
version("26.2.x", "26.2")
|
||||
vcsVersion = "1.21.11"
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "SPMega"
|
||||
@@ -3,10 +3,9 @@ package git.yawaflua.tech.spmega.client;
|
||||
import git.yawaflua.tech.spmega.client.ui.UiNotifications;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.text.ClickEvent;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -38,7 +37,7 @@ public class ChatListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void handleMessage(Text message) {
|
||||
private void handleMessage(Component message) {
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
@@ -52,9 +51,9 @@ public class ChatListener {
|
||||
String tokenId = clickValues.get(0);
|
||||
String cardId = clickValues.get(1);
|
||||
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client.player != null) {
|
||||
String playerUuid = client.player.getUuidAsString();
|
||||
String playerUuid = client.player.getStringUUID();
|
||||
|
||||
BankUiService.instance().addCardAsync(cardId, tokenId, playerUuid)
|
||||
.thenAccept(msg -> {
|
||||
@@ -68,7 +67,7 @@ public class ChatListener {
|
||||
.exceptionally(exception -> {
|
||||
if (client != null) {
|
||||
client.execute(() -> {
|
||||
notifications.show(Text.literal("Ошибка добавления карты: " + exception.getMessage()));
|
||||
notifications.show(Component.literal("Ошибка добавления карты: " + exception.getMessage()));
|
||||
});
|
||||
}
|
||||
return null;
|
||||
@@ -80,7 +79,7 @@ public class ChatListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void collectClickEventValues(Text text, List<String> values) {
|
||||
private void collectClickEventValues(Component text, List<String> values) {
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
@@ -98,12 +97,13 @@ public class ChatListener {
|
||||
}
|
||||
}
|
||||
|
||||
for (Text sibling : text.getSiblings()) {
|
||||
for (Component sibling : text.getSiblings()) {
|
||||
collectClickEventValues(sibling, values);
|
||||
}
|
||||
}
|
||||
|
||||
private String getEventValue(ClickEvent clickEvent) {
|
||||
/*? if mc_1_21_11 {*/
|
||||
if (clickEvent instanceof ClickEvent.CopyToClipboard(String value)) {
|
||||
return value;
|
||||
} else if (clickEvent instanceof ClickEvent.RunCommand(String command1)) {
|
||||
@@ -118,5 +118,8 @@ public class ChatListener {
|
||||
return String.valueOf(page);
|
||||
}
|
||||
return "";
|
||||
/*?} else {*/
|
||||
// return clickEvent.getValue();
|
||||
/*?}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import git.yawaflua.tech.spmega.client.ui.service.BackendAuthenticator;
|
||||
import me.shedaniel.clothconfig2.api.ConfigBuilder;
|
||||
import me.shedaniel.clothconfig2.api.ConfigCategory;
|
||||
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class ModMenuIntegration implements ModMenuApi {
|
||||
@Override
|
||||
@@ -19,10 +19,10 @@ public class ModMenuIntegration implements ModMenuApi {
|
||||
return parent -> {
|
||||
ConfigBuilder builder = ConfigBuilder.create()
|
||||
.setParentScreen(parent)
|
||||
.setTitle(Text.translatable("title.spmega.config"));
|
||||
.setTitle(Component.translatable("title.spmega.config"));
|
||||
|
||||
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
|
||||
ConfigCategory general = builder.getOrCreateCategory(Text.translatable("category.spmega.general"));
|
||||
ConfigCategory general = builder.getOrCreateCategory(Component.translatable("category.spmega.general"));
|
||||
|
||||
ModConfig current = SPMega.getConfig();
|
||||
if (current == null) {
|
||||
@@ -39,27 +39,27 @@ public class ModMenuIntegration implements ModMenuApi {
|
||||
final int[] telemetryIntervalSeconds = {current.telemetryIntervalSeconds()};
|
||||
final boolean[] telemetryCollectSystemInfo = {current.telemetryCollectSystemInfo()};
|
||||
|
||||
general.addEntry(entryBuilder.startStrField(Text.translatable("option.spmega.api_domain"), current.apiDomain())
|
||||
general.addEntry(entryBuilder.startStrField(Component.translatable("option.spmega.api_domain"), current.apiDomain())
|
||||
.setDefaultValue(ModConfig.DEFAULT_API_DOMAIN)
|
||||
.setSaveConsumer(newValue -> apiDomain[0] = newValue)
|
||||
.build());
|
||||
|
||||
general.addEntry(entryBuilder.startStrField(Text.translatable("option.spmega.api_token"), current.apiToken())
|
||||
general.addEntry(entryBuilder.startStrField(Component.translatable("option.spmega.api_token"), current.apiToken())
|
||||
.setDefaultValue(ModConfig.DEFAULT_API_TOKEN)
|
||||
.setSaveConsumer(newValue -> apiToken[0] = newValue)
|
||||
.build());
|
||||
|
||||
general.addEntry(entryBuilder.startBooleanToggle(Text.translatable("option.spmega.allow_backend"), current.allowBackend())
|
||||
general.addEntry(entryBuilder.startBooleanToggle(Component.translatable("option.spmega.allow_backend"), current.allowBackend())
|
||||
.setDefaultValue(ModConfig.ALLOW_BACKEND)
|
||||
.setSaveConsumer(newValue -> allowBackend[0] = newValue)
|
||||
.build());
|
||||
|
||||
boolean isLoggedIn = !current.apiToken().isEmpty() && !current.apiToken().equals(ModConfig.DEFAULT_API_TOKEN);
|
||||
general.addEntry(entryBuilder.startBooleanToggle(Text.literal("Авторизоваться").formatted(Formatting.RED), isLoggedIn)
|
||||
.setTooltip(Text.literal("Запустить процесс авторизации через Mojang и ваш бэкенд"))
|
||||
general.addEntry(entryBuilder.startBooleanToggle(Component.literal("Авторизоваться").withStyle(ChatFormatting.RED), isLoggedIn)
|
||||
.setTooltip(Component.literal("Запустить процесс авторизации через Mojang и ваш бэкенд"))
|
||||
.setSaveConsumer(newValue -> {
|
||||
if (newValue) {
|
||||
BackendAuthenticator.authenticateAsync(MinecraftClient.getInstance())
|
||||
BackendAuthenticator.authenticateAsync(Minecraft.getInstance())
|
||||
.thenAccept(authenticated -> {
|
||||
if (authenticated && SPMega.getConfig() != null) {
|
||||
apiToken[0] = SPMega.getConfig().apiToken();
|
||||
@@ -71,46 +71,46 @@ public class ModMenuIntegration implements ModMenuApi {
|
||||
})
|
||||
.build());
|
||||
|
||||
general.addEntry(entryBuilder.startBooleanToggle(Text.translatable("option.spmega.sign_quick_pay"), current.signQuickPayEnabled())
|
||||
general.addEntry(entryBuilder.startBooleanToggle(Component.translatable("option.spmega.sign_quick_pay"), current.signQuickPayEnabled())
|
||||
.setDefaultValue(ModConfig.DEFAULT_SIGN_QUICK_PAY_ENABLED)
|
||||
.setSaveConsumer(newValue -> signQuickPayEnabled[0] = newValue)
|
||||
.build());
|
||||
|
||||
general.addEntry(entryBuilder.startEnumSelector(
|
||||
Text.translatable("option.spmega.gps_position"),
|
||||
Component.translatable("option.spmega.gps_position"),
|
||||
GpsHudPosition.class,
|
||||
current.gpsPosition()
|
||||
)
|
||||
.setDefaultValue(ModConfig.DEFAULT_GPS_POSITION)
|
||||
.setEnumNameProvider(position -> Text.translatable("option.spmega.gps_position." + position.name().toLowerCase()))
|
||||
.setEnumNameProvider(position -> Component.translatable("option.spmega.gps_position." + position.name().toLowerCase()))
|
||||
.setSaveConsumer(newValue -> gpsPosition[0] = newValue)
|
||||
.build());
|
||||
|
||||
general.addEntry(entryBuilder.startEnumSelector(
|
||||
Text.translatable("option.spmega.notification_position"),
|
||||
Component.translatable("option.spmega.notification_position"),
|
||||
GpsHudPosition.class,
|
||||
current.notificationPosition()
|
||||
)
|
||||
.setDefaultValue(ModConfig.DEFAULT_NOTIFICATION_POSITION)
|
||||
.setEnumNameProvider(position -> Text.translatable(
|
||||
.setEnumNameProvider(position -> Component.translatable(
|
||||
"option.spmega.notification_position." + position.name().toLowerCase()))
|
||||
.setSaveConsumer(newValue -> notificationPosition[0] = newValue)
|
||||
.build());
|
||||
|
||||
// Telemetry settings
|
||||
ConfigCategory telemetry = builder.getOrCreateCategory(Text.translatable("category.spmega.telemetry"));
|
||||
ConfigCategory telemetry = builder.getOrCreateCategory(Component.translatable("category.spmega.telemetry"));
|
||||
|
||||
telemetry.addEntry(entryBuilder.startIntField(Text.translatable("option.spmega.telemetry_interval"), current.telemetryIntervalSeconds())
|
||||
telemetry.addEntry(entryBuilder.startIntField(Component.translatable("option.spmega.telemetry_interval"), current.telemetryIntervalSeconds())
|
||||
.setDefaultValue(ModConfig.DEFAULT_TELEMETRY_INTERVAL_SECONDS)
|
||||
.setMin(10)
|
||||
.setMax(600)
|
||||
.setTooltip(Text.translatable("tooltip.spmega.telemetry_interval"))
|
||||
.setTooltip(Component.translatable("tooltip.spmega.telemetry_interval"))
|
||||
.setSaveConsumer(newValue -> telemetryIntervalSeconds[0] = newValue)
|
||||
.build());
|
||||
|
||||
telemetry.addEntry(entryBuilder.startBooleanToggle(Text.translatable("option.spmega.telemetry_system_info"), current.telemetryCollectSystemInfo())
|
||||
telemetry.addEntry(entryBuilder.startBooleanToggle(Component.translatable("option.spmega.telemetry_system_info"), current.telemetryCollectSystemInfo())
|
||||
.setDefaultValue(ModConfig.DEFAULT_TELEMETRY_COLLECT_SYSTEM_INFO)
|
||||
.setTooltip(Text.translatable("tooltip.spmega.telemetry_system_info"))
|
||||
.setTooltip(Component.translatable("tooltip.spmega.telemetry_system_info"))
|
||||
.setSaveConsumer(newValue -> telemetryCollectSystemInfo[0] = newValue)
|
||||
.build());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package git.yawaflua.tech.spmega.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import git.yawaflua.tech.spmega.ModConfig;
|
||||
import git.yawaflua.tech.spmega.SPMega;
|
||||
import git.yawaflua.tech.spmega.client.qr.QRCodeScanner;
|
||||
@@ -11,20 +12,24 @@ import git.yawaflua.tech.spmega.client.ui.service.BackendAuthenticator;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
|
||||
import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
|
||||
/*? if mc_26 {*/
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry;
|
||||
import net.minecraft.resources.Identifier;
|
||||
/*?} else {*/
|
||||
/*import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
|
||||
*//*?}*/
|
||||
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
|
||||
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
|
||||
import net.fabricmc.fabric.api.event.player.UseEntityCallback;
|
||||
import net.minecraft.block.entity.SignBlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import net.minecraft.entity.decoration.ItemFrameEntity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
@@ -32,9 +37,9 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class SPMegaClient implements ClientModInitializer {
|
||||
private static final Pattern ISOLATED_FIVE_DIGITS = Pattern.compile("(?<!\\d)(\\d{5})(?!\\d)");
|
||||
private static KeyBinding openBankMenuKeyBinding;
|
||||
private static KeyBinding scanQrKeyBinding;
|
||||
private static KeyBinding toggleGpsKeyBinding;
|
||||
private static KeyMapping openBankMenuKeyBinding;
|
||||
private static KeyMapping scanQrKeyBinding;
|
||||
private static KeyMapping toggleGpsKeyBinding;
|
||||
private static boolean wasPaymentShortcutPressed = false;
|
||||
|
||||
|
||||
@@ -46,15 +51,15 @@ public class SPMegaClient implements ClientModInitializer {
|
||||
return findFiveDigits(signBlockEntity.getBackText().getMessages(false));
|
||||
}
|
||||
|
||||
private static String extractCardNumber(ItemFrameEntity itemFrameEntity) {
|
||||
if (itemFrameEntity.getHeldItemStack().isEmpty()) {
|
||||
private static String extractCardNumber(ItemFrame itemFrameEntity) {
|
||||
if (itemFrameEntity.getItem().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return findFiveDigits(itemFrameEntity.getHeldItemStack().getName().getString());
|
||||
return findFiveDigits(itemFrameEntity.getItem().getHoverName().getString());
|
||||
}
|
||||
|
||||
private static String findFiveDigits(Text[] lines) {
|
||||
for (Text line : lines) {
|
||||
private static String findFiveDigits(Component[] lines) {
|
||||
for (Component line : lines) {
|
||||
String candidate = findFiveDigits(line.getString());
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
@@ -74,25 +79,41 @@ public class SPMegaClient implements ClientModInitializer {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
GpsHudRenderer.instance();
|
||||
openBankMenuKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
|
||||
|
||||
UiNotifications.instance().showMessage("Этот мод собирает телеметрию. Проверьте настройки.");
|
||||
|
||||
|
||||
openBankMenuKeyBinding = KeyMappingHelper.registerKeyMapping(new KeyMapping(
|
||||
"key.spmega.open_menu",
|
||||
InputUtil.Type.KEYSYM,
|
||||
InputConstants.Type.KEYSYM,
|
||||
GLFW.GLFW_KEY_P,
|
||||
KeyBinding.Category.GAMEPLAY
|
||||
/*? if mc_1_21_11 {*/
|
||||
KeyMapping.Category.GAMEPLAY
|
||||
/*?} else {*/
|
||||
// "key.categories.gameplay"
|
||||
/*?}*/
|
||||
));
|
||||
|
||||
scanQrKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
|
||||
scanQrKeyBinding = KeyMappingHelper.registerKeyMapping(new KeyMapping(
|
||||
"key.spmega.scan_qr",
|
||||
InputUtil.Type.KEYSYM,
|
||||
InputConstants.Type.KEYSYM,
|
||||
GLFW.GLFW_KEY_O,
|
||||
KeyBinding.Category.GAMEPLAY
|
||||
/*? if mc_1_21_11 {*/
|
||||
KeyMapping.Category.GAMEPLAY
|
||||
/*?} else {*/
|
||||
// "key.categories.gameplay"
|
||||
/*?}*/
|
||||
));
|
||||
|
||||
toggleGpsKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
|
||||
toggleGpsKeyBinding = KeyMappingHelper.registerKeyMapping(new KeyMapping(
|
||||
"key.spmega.toggle_gps",
|
||||
InputUtil.Type.KEYSYM,
|
||||
InputConstants.Type.KEYSYM,
|
||||
GLFW.GLFW_KEY_J,
|
||||
KeyBinding.Category.GAMEPLAY
|
||||
/*? if mc_1_21_11 {*/
|
||||
KeyMapping.Category.GAMEPLAY
|
||||
/*?} else {*/
|
||||
// "key.categories.gameplay"
|
||||
/*?}*/
|
||||
));
|
||||
|
||||
if (SPMega.getConfig() != null) {
|
||||
@@ -102,13 +123,14 @@ public class SPMegaClient implements ClientModInitializer {
|
||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||
PerformanceSampler.instance().tick();
|
||||
UiNotifications.instance().tick();
|
||||
while (openBankMenuKeyBinding.wasPressed()) {
|
||||
BackendAuthenticator.tickTokenRefresh(client);
|
||||
while (openBankMenuKeyBinding.consumeClick()) {
|
||||
UiOpeners.openMainMenu(client);
|
||||
}
|
||||
while (scanQrKeyBinding.wasPressed()) {
|
||||
while (scanQrKeyBinding.consumeClick()) {
|
||||
QRCodeScanner.scanQrCode(client);
|
||||
}
|
||||
while (toggleGpsKeyBinding.wasPressed()) {
|
||||
while (toggleGpsKeyBinding.consumeClick()) {
|
||||
GpsHudRenderer.instance().toggle();
|
||||
if (SPMega.getConfig() != null) {
|
||||
ModConfig current = SPMega.getConfig();
|
||||
@@ -126,15 +148,15 @@ public class SPMegaClient implements ClientModInitializer {
|
||||
SPMega.setConfig(updated);
|
||||
}
|
||||
String status = GpsHudRenderer.instance().isEnabled() ? "включен" : "выключен";
|
||||
UiNotifications.instance().show(Text.literal("GPS Ада " + status));
|
||||
UiNotifications.instance().show(Component.literal("GPS Ада " + status));
|
||||
}
|
||||
|
||||
if (client.player != null && client.options != null) {
|
||||
boolean isCombinationPressed = client.options.sprintKey.isPressed() && client.options.sneakKey.isPressed() && client.options.leftKey.isPressed();
|
||||
boolean isCombinationPressed = client.options.keySprint.isDown() && client.options.keyShift.isDown() && client.options.keyLeft.isDown();
|
||||
if (isCombinationPressed) {
|
||||
if (!wasPaymentShortcutPressed && client.currentScreen == null) {
|
||||
if (client.targetedEntity instanceof PlayerEntity targetedPlayer && targetedPlayer != client.player) {
|
||||
String recipient = targetedPlayer.getStringifiedName();
|
||||
if (!wasPaymentShortcutPressed && client.gui.screen() == null) {
|
||||
if (client.crosshairPickEntity instanceof Player targetedPlayer && targetedPlayer != client.player) {
|
||||
String recipient = targetedPlayer.getScoreboardName();
|
||||
UiOpeners.openPaymentMenu(client, recipient);
|
||||
}
|
||||
}
|
||||
@@ -145,31 +167,35 @@ public class SPMegaClient implements ClientModInitializer {
|
||||
}
|
||||
});
|
||||
|
||||
HudRenderCallback.EVENT.register((drawContext, tickDeltaManager) -> {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.currentScreen != null || client.textRenderer == null) {
|
||||
/*? if mc_26 {*/
|
||||
HudElementRegistry.addLast(Identifier.fromNamespaceAndPath("spmega", "main_hud"), (drawContext, tickDeltaManager) -> {
|
||||
/*?} else {*/
|
||||
/*HudRenderCallback.EVENT.register((drawContext, tickDeltaManager) -> {
|
||||
*//*?}*/
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client.gui.screen() != null || client.font == null) {
|
||||
return;
|
||||
}
|
||||
UiNotifications.instance().render(
|
||||
drawContext,
|
||||
client.textRenderer,
|
||||
client.getWindow().getScaledWidth(),
|
||||
client.getWindow().getScaledHeight()
|
||||
client.font,
|
||||
client.getWindow().getGuiScaledWidth(),
|
||||
client.getWindow().getGuiScaledHeight()
|
||||
);
|
||||
GpsHudRenderer.instance().render(
|
||||
drawContext,
|
||||
client.textRenderer,
|
||||
client.getWindow().getScaledWidth(),
|
||||
client.getWindow().getScaledHeight()
|
||||
client.font,
|
||||
client.getWindow().getGuiScaledWidth(),
|
||||
client.getWindow().getGuiScaledHeight()
|
||||
);
|
||||
});
|
||||
|
||||
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
|
||||
ScreenEvents.afterRender(screen).register((screenInstance, drawContext, mouseX, mouseY, tickDelta) -> {
|
||||
if (client.textRenderer == null) {
|
||||
ScreenEvents.afterExtract(screen).register((screenInstance, drawContext, mouseX, mouseY, tickDelta) -> {
|
||||
if (client.font == null) {
|
||||
return;
|
||||
}
|
||||
UiNotifications.instance().render(drawContext, client.textRenderer, scaledWidth, scaledHeight);
|
||||
UiNotifications.instance().render(drawContext, client.font, scaledWidth, scaledHeight);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -177,66 +203,61 @@ public class SPMegaClient implements ClientModInitializer {
|
||||
|
||||
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
|
||||
if (client.player != null) {
|
||||
BankUiService.instance().refreshOnServerJoinAsync(client.player.getUuidAsString());
|
||||
BankUiService.instance().refreshOnServerJoinAsync(client.player.getStringUUID());
|
||||
}
|
||||
});
|
||||
|
||||
UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
|
||||
if (!world.isClient()) {
|
||||
return ActionResult.PASS;
|
||||
if (!world.isClientSide()) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
if (!player.isSneaking()) {
|
||||
return ActionResult.PASS;
|
||||
if (!player.isShiftKeyDown()) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
if (SPMega.getConfig() == null || !SPMega.getConfig().signQuickPayEnabled()) {
|
||||
return ActionResult.PASS;
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
if (world.getBlockEntity(hitResult.getBlockPos()) instanceof SignBlockEntity signBlockEntity) {
|
||||
String cardNumber = extractCardNumber(signBlockEntity);
|
||||
if (cardNumber == null) {
|
||||
return ActionResult.PASS;
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
UiOpeners.openPaymentMenu(MinecraftClient.getInstance(), cardNumber);
|
||||
return ActionResult.SUCCESS;
|
||||
UiOpeners.openPaymentMenu(Minecraft.getInstance(), cardNumber);
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return ActionResult.PASS;
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
|
||||
if (!world.isClient()) {
|
||||
return ActionResult.PASS;
|
||||
if (!world.isClientSide()) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
if (!player.isSneaking()) {
|
||||
return ActionResult.PASS;
|
||||
if (!player.isShiftKeyDown()) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
if (SPMega.getConfig() == null || !SPMega.getConfig().signQuickPayEnabled()) {
|
||||
return ActionResult.PASS;
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
if (entity instanceof ItemFrameEntity itemFrameEntity) {
|
||||
if (entity instanceof ItemFrame itemFrameEntity) {
|
||||
String cardNumber = extractCardNumber(itemFrameEntity);
|
||||
if (cardNumber == null) {
|
||||
return ActionResult.PASS;
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
UiOpeners.openPaymentMenu(MinecraftClient.getInstance(), cardNumber);
|
||||
return ActionResult.SUCCESS;
|
||||
UiOpeners.openPaymentMenu(Minecraft.getInstance(), cardNumber);
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return ActionResult.PASS;
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
new ChatListener().register();
|
||||
WebhookNotificationPoller.instance().start();
|
||||
BackendAuthenticator.authenticateAsync(MinecraftClient.getInstance())
|
||||
.exceptionally(throwable -> {
|
||||
System.err.println("[SPMEGA] Error during async authenticate: " + throwable.getMessage());
|
||||
return null;
|
||||
});
|
||||
|
||||
// Telemetry init
|
||||
long clientInitStart = System.nanoTime();
|
||||
|
||||
@@ -3,13 +3,12 @@ package git.yawaflua.tech.spmega.client;
|
||||
import git.yawaflua.tech.spmega.client.ui.UiNotifications;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BackendAuthenticator;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public final class WebhookNotificationPoller {
|
||||
private static final WebhookNotificationPoller INSTANCE = new WebhookNotificationPoller();
|
||||
@@ -61,12 +60,12 @@ public final class WebhookNotificationPoller {
|
||||
System.err.println("[SPMEGA] Failed to poll webhook notifications: " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client == null || notifications.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
client.execute(() -> notifications.forEach(notification ->
|
||||
UiNotifications.instance().showQueued(Text.literal(format(notification)))));
|
||||
UiNotifications.instance().showQueued(Component.literal(format(notification)))));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@ import git.yawaflua.tech.spmega.client.telemetry.TelemetryCollector;
|
||||
import git.yawaflua.tech.spmega.client.telemetry.TelemetryEvent;
|
||||
import git.yawaflua.tech.spmega.client.ui.QRcodeAcceptScreen;
|
||||
import git.yawaflua.tech.spmega.client.ui.UiNotifications;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.util.ScreenshotRecorder;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.Screenshot;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public final class QRCodeScanner {
|
||||
private static final ExecutorService DECODER = Executors.newSingleThreadExecutor(runnable -> {
|
||||
@@ -29,64 +29,85 @@ public final class QRCodeScanner {
|
||||
private QRCodeScanner() {
|
||||
}
|
||||
|
||||
public static void scanQrCode(MinecraftClient client) {
|
||||
if (client == null || client.player == null || client.getFramebuffer() == null) {
|
||||
public static void scanQrCode(Minecraft client) {
|
||||
if (client == null || client.player == null || client.gameRenderer.mainRenderTarget() == null) {
|
||||
return;
|
||||
}
|
||||
UiNotifications notifications = UiNotifications.instance();
|
||||
int framebufferWidth = client.getWindow().getFramebufferWidth();
|
||||
int framebufferHeight = client.getWindow().getFramebufferHeight();
|
||||
int framebufferWidth = client.getWindow().getWidth();
|
||||
int framebufferHeight = client.getWindow().getHeight();
|
||||
if (framebufferWidth <= 0 || framebufferHeight <= 0) {
|
||||
client.player.sendMessage(Text.translatable("message.spmega.qr.capture_failed"), false);
|
||||
/*? if mc_26 {*/
|
||||
client.player.sendSystemMessage(Component.translatable("message.spmega.qr.capture_failed"));
|
||||
/*?} else {*/
|
||||
/*client.player.displayClientMessage(Component.translatable("message.spmega.qr.capture_failed"), false);
|
||||
*//*?}*/
|
||||
return;
|
||||
}
|
||||
|
||||
long startNs = System.nanoTime();
|
||||
try {
|
||||
ScreenshotRecorder.takeScreenshot(client.getFramebuffer(), nativeImage -> {
|
||||
try {
|
||||
if (nativeImage == null || nativeImage.getWidth() <= 0 || nativeImage.getHeight() <= 0) {
|
||||
client.execute(() -> {
|
||||
if (client.player != null) {
|
||||
notifications.show(Text.translatable("message.spmega.qr.capture_failed"));
|
||||
}
|
||||
});
|
||||
recordQrEvent(false, false, null, "capture_failed", startNs);
|
||||
return;
|
||||
}
|
||||
|
||||
int width = nativeImage.getWidth();
|
||||
int height = nativeImage.getHeight();
|
||||
int[] pixels = nativeImage.copyPixelsArgb();
|
||||
CompletableFuture.supplyAsync(() -> decodeQrCode(width, height, pixels), DECODER)
|
||||
.whenComplete((result, throwable) -> {
|
||||
client.execute(() -> showResult(client, notifications, result, throwable));
|
||||
recordQrEvent(
|
||||
throwable == null && result != null,
|
||||
System.nanoTime() - startNs > 50_000_000L,
|
||||
result,
|
||||
throwable == null ? null : throwable.getClass().getSimpleName() + ": " + throwable.getMessage(),
|
||||
startNs
|
||||
);
|
||||
});
|
||||
} catch (Exception exception) {
|
||||
client.execute(() -> notifications.show(Text.translatable("message.spmega.qr.error")));
|
||||
recordQrEvent(false, false, null,
|
||||
exception.getClass().getSimpleName() + ": " + exception.getMessage(), startNs);
|
||||
} finally {
|
||||
if (nativeImage != null) {
|
||||
nativeImage.close();
|
||||
}
|
||||
}
|
||||
/*? if mc_1_21_11 {*/
|
||||
Screenshot.takeScreenshot(client.gameRenderer.mainRenderTarget(), nativeImage -> {
|
||||
processCapturedImage(client, notifications, nativeImage, startNs);
|
||||
});
|
||||
/*?} else {*/
|
||||
// processCapturedImage(client, notifications, Screenshot.takeScreenshot(client.gameRenderer.mainRenderTarget()), startNs);
|
||||
/*?}*/
|
||||
} catch (Exception ex) {
|
||||
notifications.show(Text.translatable("message.spmega.qr.error"));
|
||||
notifications.show(Component.translatable("message.spmega.qr.error"));
|
||||
recordQrEvent(false, false, null, ex.getClass().getSimpleName() + ": " + ex.getMessage(), startNs);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processCapturedImage(
|
||||
Minecraft client,
|
||||
UiNotifications notifications,
|
||||
NativeImage nativeImage,
|
||||
long startNs
|
||||
) {
|
||||
try {
|
||||
if (nativeImage == null || nativeImage.getWidth() <= 0 || nativeImage.getHeight() <= 0) {
|
||||
client.execute(() -> {
|
||||
if (client.player != null) {
|
||||
notifications.show(Component.translatable("message.spmega.qr.capture_failed"));
|
||||
}
|
||||
});
|
||||
recordQrEvent(false, false, null, "capture_failed", startNs);
|
||||
return;
|
||||
}
|
||||
|
||||
int width = nativeImage.getWidth();
|
||||
int height = nativeImage.getHeight();
|
||||
/*? if mc_1_21_11 {*/
|
||||
int[] pixels = nativeImage.getPixels();
|
||||
/*?} else {*/
|
||||
// int[] pixels = nativeImage.getPixelsRGBA();
|
||||
/*?}*/
|
||||
CompletableFuture.supplyAsync(() -> decodeQrCode(width, height, pixels), DECODER)
|
||||
.whenComplete((result, throwable) -> {
|
||||
client.execute(() -> showResult(client, notifications, result, throwable));
|
||||
recordQrEvent(
|
||||
throwable == null && result != null,
|
||||
System.nanoTime() - startNs > 50_000_000L,
|
||||
result,
|
||||
throwable == null ? null : throwable.getClass().getSimpleName() + ": " + throwable.getMessage(),
|
||||
startNs
|
||||
);
|
||||
});
|
||||
} catch (Exception exception) {
|
||||
client.execute(() -> notifications.show(Component.translatable("message.spmega.qr.error")));
|
||||
recordQrEvent(false, false, null,
|
||||
exception.getClass().getSimpleName() + ": " + exception.getMessage(), startNs);
|
||||
} finally {
|
||||
if (nativeImage != null) {
|
||||
nativeImage.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void showResult(
|
||||
MinecraftClient client,
|
||||
Minecraft client,
|
||||
UiNotifications notifications,
|
||||
String result,
|
||||
Throwable throwable
|
||||
@@ -95,17 +116,21 @@ public final class QRCodeScanner {
|
||||
return;
|
||||
}
|
||||
if (throwable != null) {
|
||||
notifications.show(Text.translatable("message.spmega.qr.error"));
|
||||
notifications.show(Component.translatable("message.spmega.qr.error"));
|
||||
return;
|
||||
}
|
||||
if (result == null) {
|
||||
notifications.show(Text.translatable("message.spmega.qr.not_found"));
|
||||
notifications.show(Component.translatable("message.spmega.qr.not_found"));
|
||||
return;
|
||||
}
|
||||
|
||||
Text clickableLink = Text.literal(result).styled(style -> style.withInsertion(result));
|
||||
client.player.sendMessage(Text.translatable("message.spmega.qr.found_link", clickableLink), false);
|
||||
client.setScreen(new QRcodeAcceptScreen(result, client.currentScreen));
|
||||
Component clickableLink = Component.literal(result).withStyle(style -> style.withInsertion(result));
|
||||
/*? if mc_26 {*/
|
||||
client.player.sendSystemMessage(Component.translatable("message.spmega.qr.found_link", clickableLink));
|
||||
/*?} else {*/
|
||||
/*client.player.displayClientMessage(Component.translatable("message.spmega.qr.found_link", clickableLink), false);
|
||||
*//*?}*/
|
||||
client.gui.setScreen(new QRcodeAcceptScreen(result, client.gui.screen()));
|
||||
}
|
||||
|
||||
private static void recordQrEvent(boolean success, boolean didLag, String decodedUrl, String error, long startNs) {
|
||||
@@ -165,4 +190,3 @@ public final class QRCodeScanner {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package git.yawaflua.tech.spmega.client.telemetry;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BackendAuthenticator;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
@@ -53,6 +54,12 @@ public record InstrumentedHttpClient(HttpClient delegate) {
|
||||
} else {
|
||||
recordHttpEvent(uri, method, response.statusCode(), durationMs,
|
||||
true, null, fpsBefore, fpsAfter);
|
||||
if (response.statusCode() == 401
|
||||
&& response.headers().firstValue("X-SPMega-Reauthenticate")
|
||||
.map("true"::equalsIgnoreCase)
|
||||
.orElse(false)) {
|
||||
BackendAuthenticator.onReauthenticationRequired();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package git.yawaflua.tech.spmega.client.telemetry;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
public final class PerformanceSampler {
|
||||
private static final PerformanceSampler INSTANCE = new PerformanceSampler();
|
||||
@@ -23,7 +22,7 @@ public final class PerformanceSampler {
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
int fps = MinecraftClient.getInstance().getCurrentFps();
|
||||
int fps = Minecraft.getInstance().getFps();
|
||||
fpsSum.addAndGet(fps);
|
||||
fpsCount.incrementAndGet();
|
||||
fpsMin.updateAndGet(prev -> Math.min(prev, fps));
|
||||
@@ -31,7 +30,7 @@ public final class PerformanceSampler {
|
||||
}
|
||||
|
||||
public int currentFps() {
|
||||
return MinecraftClient.getInstance().getCurrentFps();
|
||||
return Minecraft.getInstance().getFps();
|
||||
}
|
||||
|
||||
public void emitSnapshot() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package git.yawaflua.tech.spmega.client.telemetry;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
@@ -16,6 +15,7 @@ import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
public final class SystemInfoCollector {
|
||||
private static final SystemInfoCollector INSTANCE = new SystemInfoCollector();
|
||||
@@ -58,7 +58,7 @@ public final class SystemInfoCollector {
|
||||
}
|
||||
|
||||
private void collectGpuOnRenderThread() {
|
||||
MinecraftClient.getInstance().execute(() -> {
|
||||
Minecraft.getInstance().execute(() -> {
|
||||
try {
|
||||
gpuRenderer.set(GL11.glGetString(GL11.GL_RENDERER));
|
||||
gpuVendor.set(GL11.glGetString(GL11.GL_VENDOR));
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
package git.yawaflua.tech.spmega.client.ui;
|
||||
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class AddCardScreen extends Screen {
|
||||
private final Screen parent;
|
||||
private final Consumer<String> onSubmitMessage;
|
||||
private final BankUiService bankUiService = BankUiService.instance();
|
||||
private final UiNotifications notifications = UiNotifications.instance();
|
||||
private TextFieldWidget cardIdField;
|
||||
private TextFieldWidget cardTokenField;
|
||||
private ButtonWidget addButton;
|
||||
private ButtonWidget cancelButton;
|
||||
private EditBox cardIdField;
|
||||
private EditBox cardTokenField;
|
||||
private Button addButton;
|
||||
private Button cancelButton;
|
||||
|
||||
public AddCardScreen(Screen parent, Consumer<String> onSubmitMessage) {
|
||||
super(Text.literal("Добавление карты"));
|
||||
super(Component.literal("Добавление карты"));
|
||||
this.parent = parent;
|
||||
this.onSubmitMessage = onSubmitMessage;
|
||||
}
|
||||
@@ -30,66 +29,74 @@ public class AddCardScreen extends Screen {
|
||||
int centerX = this.width / 2;
|
||||
int startY = this.height / 2 - 50;
|
||||
|
||||
cardIdField = new TextFieldWidget(this.textRenderer, centerX - 140, startY, 280, 20, Text.literal("Card ID"));
|
||||
cardIdField = new EditBox(this.font, centerX - 140, startY, 280, 20, Component.literal("Card ID"));
|
||||
cardIdField.setMaxLength(64);
|
||||
cardIdField.setPlaceholder(Text.literal("ID карты (UUID)"));
|
||||
this.addDrawableChild(cardIdField);
|
||||
cardIdField.setHint(Component.literal("ID карты (UUID)"));
|
||||
this.addRenderableWidget(cardIdField);
|
||||
|
||||
cardTokenField = new TextFieldWidget(this.textRenderer, centerX - 140, startY + 28, 280, 20, Text.literal("Card Token"));
|
||||
cardTokenField = new EditBox(this.font, centerX - 140, startY + 28, 280, 20, Component.literal("Card Token"));
|
||||
cardTokenField.setMaxLength(128);
|
||||
cardTokenField.setPlaceholder(Text.literal("Токен карты"));
|
||||
this.addDrawableChild(cardTokenField);
|
||||
cardTokenField.setHint(Component.literal("Токен карты"));
|
||||
this.addRenderableWidget(cardTokenField);
|
||||
|
||||
addButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("Добавить"), button -> submit())
|
||||
.dimensions(centerX - 140, startY + 60, 136, 20)
|
||||
addButton = this.addRenderableWidget(Button.builder(Component.literal("Добавить"), button -> submit())
|
||||
.bounds(centerX - 140, startY + 60, 136, 20)
|
||||
.build());
|
||||
|
||||
cancelButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("Отмена"), button -> this.close())
|
||||
.dimensions(centerX + 4, startY + 60, 136, 20)
|
||||
cancelButton = this.addRenderableWidget(Button.builder(Component.literal("Отмена"), button -> this.onClose())
|
||||
.bounds(centerX + 4, startY + 60, 136, 20)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
public void onClose() {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
/*? if mc_26 {*/
|
||||
public void extractRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
/*?} else {*/
|
||||
/*public void render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
*//*?}*/
|
||||
/*? if mc_26 {*/
|
||||
super.extractRenderState(context, mouseX, mouseY, delta);
|
||||
/*?} else {*/
|
||||
/*super.render(context, mouseX, mouseY, delta);
|
||||
*//*?}*/
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int startY = this.height / 2 - 50;
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 24, 0xFFFFFF);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Card ID (UUID):"), centerX - 140, startY - 10, 0xCCCCCC);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Card Token:"), centerX - 140, startY + 18, 0xCCCCCC);
|
||||
context.centeredText(this.font, this.title, centerX, 24, 0xFFFFFF);
|
||||
context.text(this.font, Component.literal("Card ID (UUID):"), centerX - 140, startY - 10, 0xCCCCCC);
|
||||
context.text(this.font, Component.literal("Card Token:"), centerX - 140, startY + 18, 0xCCCCCC);
|
||||
}
|
||||
|
||||
private void submit() {
|
||||
String cardId = cardIdField.getText().trim();
|
||||
String cardToken = cardTokenField.getText().trim();
|
||||
String cardId = cardIdField.getValue().trim();
|
||||
String cardToken = cardTokenField.getValue().trim();
|
||||
if (cardId.isEmpty() || cardToken.isEmpty()) {
|
||||
notifications.show(Text.literal("Укажи cardId и cardToken"));
|
||||
notifications.show(Component.literal("Укажи cardId и cardToken"));
|
||||
return;
|
||||
}
|
||||
|
||||
String playerUuid = this.client != null && this.client.player != null
|
||||
? this.client.player.getUuidAsString()
|
||||
String playerUuid = this.minecraft != null && this.minecraft.player != null
|
||||
? this.minecraft.player.getStringUUID()
|
||||
: "";
|
||||
|
||||
notifications.show(Text.literal("Проверка карты..."));
|
||||
notifications.show(Component.literal("Проверка карты..."));
|
||||
addButton.active = false;
|
||||
cancelButton.active = false;
|
||||
|
||||
bankUiService.addCardAsync(cardId, cardToken, playerUuid)
|
||||
.thenAccept(message -> {
|
||||
if (this.client == null) {
|
||||
if (this.minecraft == null) {
|
||||
return;
|
||||
}
|
||||
this.client.execute(() -> {
|
||||
this.minecraft.execute(() -> {
|
||||
notifications.showMessage(message);
|
||||
addButton.active = true;
|
||||
cancelButton.active = true;
|
||||
@@ -99,14 +106,14 @@ public class AddCardScreen extends Screen {
|
||||
}
|
||||
|
||||
if (message.startsWith("Карта добавлена") || message.startsWith("Вы не владелец карты")) {
|
||||
this.close();
|
||||
this.onClose();
|
||||
}
|
||||
});
|
||||
})
|
||||
.exceptionally(exception -> {
|
||||
if (this.client != null) {
|
||||
this.client.execute(() -> {
|
||||
notifications.show(Text.literal("Ошибка добавления карты: " + exception.getMessage()));
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.execute(() -> {
|
||||
notifications.show(Component.literal("Ошибка добавления карты: " + exception.getMessage()));
|
||||
addButton.active = true;
|
||||
cancelButton.active = true;
|
||||
});
|
||||
@@ -115,4 +122,3 @@ public class AddCardScreen extends Screen {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,25 +2,24 @@ package git.yawaflua.tech.spmega.client.ui;
|
||||
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.CardViewModel;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.ConfirmScreen;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.ConfirmScreen;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class CardScreen extends Screen {
|
||||
private final Screen parent;
|
||||
private final BankUiService bankUiService = BankUiService.instance();
|
||||
private final UiNotifications notifications = UiNotifications.instance();
|
||||
private final List<ButtonWidget> cardButtons = new ArrayList<>();
|
||||
private ButtonWidget historyButton;
|
||||
private ButtonWidget webhookButton;
|
||||
private final List<Button> cardButtons = new ArrayList<>();
|
||||
private Button historyButton;
|
||||
private Button webhookButton;
|
||||
|
||||
public CardScreen(Screen parent) {
|
||||
super(Text.literal("Управление картами"));
|
||||
super(Component.literal("Управление картами"));
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@@ -38,115 +37,123 @@ public class CardScreen extends Screen {
|
||||
for (int i = 0; i < cards.size(); i++) {
|
||||
final int index = i;
|
||||
CardViewModel card = cards.get(i);
|
||||
ButtonWidget cardButton = this.addDrawableChild(ButtonWidget.builder(Text.literal(card.title()), button -> {
|
||||
Button cardButton = this.addRenderableWidget(Button.builder(Component.literal(card.title()), button -> {
|
||||
bankUiService.setSelectedCardIndex(index);
|
||||
notifications.show(Text.literal("Выбрана карта " + card.title()));
|
||||
notifications.show(Component.literal("Выбрана карта " + card.title()));
|
||||
updateCardButtonStates();
|
||||
}).dimensions(leftX, startY + i * 24, columnWidth, 20).build());
|
||||
}).bounds(leftX, startY + i * 24, columnWidth, 20).build());
|
||||
cardButtons.add(cardButton);
|
||||
}
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Удалить"), button -> {
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Удалить"), button -> {
|
||||
bankUiService.removeSelectedCardAsync()
|
||||
.thenAccept(message -> {
|
||||
if (this.client != null) {
|
||||
this.client.execute(() -> {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.execute(() -> {
|
||||
notifications.showMessage(message);
|
||||
this.clearAndInit();
|
||||
this.rebuildWidgets();
|
||||
});
|
||||
}
|
||||
});
|
||||
}).dimensions(rightX, startY, columnWidth, 20).build());
|
||||
}).bounds(rightX, startY, columnWidth, 20).build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Обновить"), button -> {
|
||||
String playerUuid = this.client != null && this.client.player != null
|
||||
? this.client.player.getUuidAsString()
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Обновить"), button -> {
|
||||
String playerUuid = this.minecraft != null && this.minecraft.player != null
|
||||
? this.minecraft.player.getStringUUID()
|
||||
: "";
|
||||
bankUiService.refreshSelectedCardAsync(playerUuid)
|
||||
.thenAccept(message -> {
|
||||
if (this.client != null) {
|
||||
this.client.execute(() -> {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.execute(() -> {
|
||||
notifications.showMessage(message);
|
||||
this.clearAndInit();
|
||||
this.rebuildWidgets();
|
||||
});
|
||||
}
|
||||
});
|
||||
}).dimensions(rightX, startY + 24, columnWidth, 20).build());
|
||||
}).bounds(rightX, startY + 24, columnWidth, 20).build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Добавить новую"), button -> {
|
||||
if (this.client == null) {
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Добавить новую"), button -> {
|
||||
if (this.minecraft == null) {
|
||||
return;
|
||||
}
|
||||
this.client.setScreen(new AddCardScreen(this, message -> {
|
||||
this.minecraft.gui.setScreen(new AddCardScreen(this, message -> {
|
||||
notifications.showMessage(message);
|
||||
this.clearAndInit();
|
||||
this.rebuildWidgets();
|
||||
}));
|
||||
}).dimensions(rightX, startY + 48, columnWidth, 20).build());
|
||||
}).bounds(rightX, startY + 48, columnWidth, 20).build());
|
||||
|
||||
historyButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("История"), button -> {
|
||||
if (this.client == null) {
|
||||
historyButton = this.addRenderableWidget(Button.builder(Component.literal("История"), button -> {
|
||||
if (this.minecraft == null) {
|
||||
return;
|
||||
}
|
||||
CardViewModel selected = bankUiService.getSelectedCard();
|
||||
if (selected == null) {
|
||||
notifications.show(Text.literal("Сначала выбери или добавь карту"));
|
||||
notifications.show(Component.literal("Сначала выбери или добавь карту"));
|
||||
return;
|
||||
}
|
||||
this.client.setScreen(new TransactionHistoryScreen(this, selected.id(), selected.title()));
|
||||
}).dimensions(rightX, startY + 72, columnWidth, 20).build());
|
||||
this.minecraft.gui.setScreen(new TransactionHistoryScreen(this, selected.id(), selected.title()));
|
||||
}).bounds(rightX, startY + 72, columnWidth, 20).build());
|
||||
|
||||
webhookButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("Включить вебхуки"), button -> {
|
||||
if (this.client == null || bankUiService.getSelectedCard() == null) {
|
||||
notifications.show(Text.literal("Сначала выбери или добавь карту"));
|
||||
webhookButton = this.addRenderableWidget(Button.builder(Component.literal("Включить вебхуки"), button -> {
|
||||
if (this.minecraft == null || bankUiService.getSelectedCard() == null) {
|
||||
notifications.show(Component.literal("Сначала выбери или добавь карту"));
|
||||
return;
|
||||
}
|
||||
this.client.setScreen(new ConfirmScreen(confirmed -> {
|
||||
if (this.client == null) {
|
||||
this.minecraft.gui.setScreen(new ConfirmScreen(confirmed -> {
|
||||
if (this.minecraft == null) {
|
||||
return;
|
||||
}
|
||||
this.client.setScreen(this);
|
||||
this.minecraft.gui.setScreen(this);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
bankUiService.registerSelectedWebhookAsync().thenAccept(message -> this.client.execute(() -> {
|
||||
bankUiService.registerSelectedWebhookAsync().thenAccept(message -> this.minecraft.execute(() -> {
|
||||
notifications.showMessage(message);
|
||||
this.clearAndInit();
|
||||
this.rebuildWidgets();
|
||||
}));
|
||||
}, Text.literal("Включить обработку вебхуков?"),
|
||||
Text.literal("Если другой вебхук уже подключен к карте — он может затереться."),
|
||||
Text.literal("Включить"), Text.literal("Отмена")));
|
||||
}).dimensions(rightX, startY + 96, columnWidth, 20).build());
|
||||
}, Component.literal("Включить обработку вебхуков?"),
|
||||
Component.literal("Если другой вебхук уже подключен к карте — он может затереться."),
|
||||
Component.literal("Включить"), Component.literal("Отмена")));
|
||||
}).bounds(rightX, startY + 96, columnWidth, 20).build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Назад"), button -> this.close())
|
||||
.dimensions(rightX, startY + 144, columnWidth, 20)
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Назад"), button -> this.onClose())
|
||||
.bounds(rightX, startY + 144, columnWidth, 20)
|
||||
.build());
|
||||
|
||||
if (cards.isEmpty()) {
|
||||
notifications.show(Text.literal("Нет карт. Добавь карту через кнопку 'Добавить новую'."));
|
||||
notifications.show(Component.literal("Нет карт. Добавь карту через кнопку 'Добавить новую'."));
|
||||
}
|
||||
|
||||
updateCardButtonStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
public void onClose() {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
/*? if mc_26 {*/
|
||||
public void extractRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
/*?} else {*/
|
||||
/*public void render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
*//*?}*/
|
||||
/*? if mc_26 {*/
|
||||
super.extractRenderState(context, mouseX, mouseY, delta);
|
||||
/*?} else {*/
|
||||
/*super.render(context, mouseX, mouseY, delta);
|
||||
*//*?}*/
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int startY = this.height / 2 - 82;
|
||||
int leftX = centerX - 210;
|
||||
int rightX = centerX + 14;
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 24, 0xFFFFFF);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Список карт"), leftX, startY - 18, 0xBFBFBF);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Действия"), rightX, startY - 18, 0xBFBFBF);
|
||||
context.centeredText(this.font, this.title, centerX, 24, 0xFFFFFF);
|
||||
context.text(this.font, Component.literal("Список карт"), leftX, startY - 18, 0xBFBFBF);
|
||||
context.text(this.font, Component.literal("Действия"), rightX, startY - 18, 0xBFBFBF);
|
||||
}
|
||||
|
||||
private void updateCardButtonStates() {
|
||||
@@ -154,7 +161,7 @@ public class CardScreen extends Screen {
|
||||
List<CardViewModel> cards = bankUiService.getCards();
|
||||
|
||||
for (int i = 0; i < cardButtons.size(); i++) {
|
||||
ButtonWidget button = cardButtons.get(i);
|
||||
Button button = cardButtons.get(i);
|
||||
if (i >= cards.size()) {
|
||||
button.visible = false;
|
||||
button.active = false;
|
||||
@@ -164,7 +171,7 @@ public class CardScreen extends Screen {
|
||||
boolean selected = i == selectedIndex;
|
||||
String prefix = selected ? ">> " : "";
|
||||
String suffix = selected ? " <<" : "";
|
||||
button.setMessage(Text.literal(prefix + cards.get(i).title() + suffix));
|
||||
button.setMessage(Component.literal(prefix + cards.get(i).title() + suffix));
|
||||
button.active = !selected;
|
||||
}
|
||||
|
||||
@@ -175,7 +182,7 @@ public class CardScreen extends Screen {
|
||||
CardViewModel selected = bankUiService.getSelectedCard();
|
||||
boolean enabled = selected != null && selected.webhookEnabled();
|
||||
webhookButton.active = selected != null && !enabled;
|
||||
webhookButton.setMessage(Text.literal(enabled ? "Вебхуки включены" : "Включить вебхуки"));
|
||||
webhookButton.setMessage(Component.literal(enabled ? "Вебхуки включены" : "Включить вебхуки"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,6 @@ import com.google.gson.Gson;
|
||||
import git.yawaflua.tech.spmega.GpsHudPosition;
|
||||
import git.yawaflua.tech.spmega.SPMega;
|
||||
import git.yawaflua.tech.spmega.client.telemetry.InstrumentedHttpClient;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.util.ArrayList;
|
||||
@@ -17,6 +11,11 @@ import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public final class GpsHudRenderer {
|
||||
private static final GpsHudRenderer INSTANCE = new GpsHudRenderer();
|
||||
@@ -80,17 +79,17 @@ public final class GpsHudRenderer {
|
||||
.exceptionally(ignored -> null);
|
||||
}
|
||||
|
||||
public void render(DrawContext context, TextRenderer textRenderer, int width, int height) {
|
||||
public void render(GuiGraphicsExtractor context, Font textRenderer, int width, int height) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.world == null || client.player == null) {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client.level == null || client.player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.world.getRegistryKey() != World.NETHER) {
|
||||
if (client.level.dimension() != Level.NETHER) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,14 +123,14 @@ public final class GpsHudRenderer {
|
||||
|
||||
int padding = 6;
|
||||
int lineSpacing = 2;
|
||||
int textWidth = Math.max(textRenderer.getWidth(Text.literal(titleText)), textRenderer.getWidth(Text.literal(offsetText)));
|
||||
int textWidth = Math.max(textRenderer.width(Component.literal(titleText)), textRenderer.width(Component.literal(offsetText)));
|
||||
if (cityText != null) {
|
||||
textWidth = Math.max(textWidth, textRenderer.getWidth(Text.literal(cityText)));
|
||||
textWidth = Math.max(textWidth, textRenderer.width(Component.literal(cityText)));
|
||||
}
|
||||
|
||||
int boxWidth = textWidth + padding * 2 + 6;
|
||||
int linesCount = cityText != null ? 3 : 2;
|
||||
int boxHeight = textRenderer.fontHeight * linesCount + lineSpacing * (linesCount - 1) + padding * 2;
|
||||
int boxHeight = textRenderer.lineHeight * linesCount + lineSpacing * (linesCount - 1) + padding * 2;
|
||||
|
||||
GpsHudPosition position = GpsHudPosition.TOP_CENTER;
|
||||
if (SPMega.getConfig() != null) {
|
||||
@@ -177,10 +176,10 @@ public final class GpsHudRenderer {
|
||||
|
||||
int textX = boxX + padding + barWidth + 4;
|
||||
int textY = boxY + padding;
|
||||
context.drawTextWithShadow(textRenderer, Text.literal(titleText), textX, textY, 0xFFFFFFFF);
|
||||
context.drawTextWithShadow(textRenderer, Text.literal(offsetText), textX, textY + textRenderer.fontHeight + lineSpacing, 0xFFFFFFFF);
|
||||
context.text(textRenderer, Component.literal(titleText), textX, textY, 0xFFFFFFFF);
|
||||
context.text(textRenderer, Component.literal(offsetText), textX, textY + textRenderer.lineHeight + lineSpacing, 0xFFFFFFFF);
|
||||
if (cityText != null) {
|
||||
context.drawTextWithShadow(textRenderer, Text.literal(cityText), textX, textY + (textRenderer.fontHeight + lineSpacing) * 2, 0xFFFFFFFF);
|
||||
context.text(textRenderer, Component.literal(cityText), textX, textY + (textRenderer.lineHeight + lineSpacing) * 2, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package git.yawaflua.tech.spmega.client.ui;
|
||||
|
||||
import git.yawaflua.tech.spmega.client.qr.QRCodeScanner;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.awt.*;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class MainBankScreen extends Screen {
|
||||
private final Screen parent;
|
||||
|
||||
public MainBankScreen(Screen parent) {
|
||||
super(Text.literal("SPMega"));
|
||||
super(Component.literal("SPMega"));
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@@ -26,52 +25,60 @@ public class MainBankScreen extends Screen {
|
||||
int totalWidth = buttonWidth * 3 + gap * 2;
|
||||
int startX = centerX - totalWidth / 2;
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Карта"), button -> {
|
||||
this.client.setScreen(new CardScreen(this));
|
||||
}).dimensions(startX, y, buttonWidth, 20).build());
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Карта"), button -> {
|
||||
this.minecraft.gui.setScreen(new CardScreen(this));
|
||||
}).bounds(startX, y, buttonWidth, 20).build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Оплата"), button -> {
|
||||
this.client.setScreen(new PaymentScreen(this));
|
||||
}).dimensions(startX + buttonWidth + gap, y, buttonWidth, 20).build());
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Оплата"), button -> {
|
||||
this.minecraft.gui.setScreen(new PaymentScreen(this));
|
||||
}).bounds(startX + buttonWidth + gap, y, buttonWidth, 20).build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Закрыть"), button -> this.close())
|
||||
.dimensions(startX + (buttonWidth + gap) * 2, y, buttonWidth, 20)
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Закрыть"), button -> this.onClose())
|
||||
.bounds(startX + (buttonWidth + gap) * 2, y, buttonWidth, 20)
|
||||
.build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.translatable("button.spmega.scan_qr"), button -> {
|
||||
this.client.setScreen(null);
|
||||
QRCodeScanner.scanQrCode(this.client);
|
||||
}).dimensions(centerX - 60, y + 28, 120, 20).build());
|
||||
this.addRenderableWidget(Button.builder(Component.translatable("button.spmega.scan_qr"), button -> {
|
||||
this.minecraft.gui.setScreen(null);
|
||||
QRCodeScanner.scanQrCode(this.minecraft);
|
||||
}).bounds(centerX - 60, y + 28, 120, 20).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
public void onClose() {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
/*? if mc_26 {*/
|
||||
public void extractRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
/*?} else {*/
|
||||
/*public void render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
*//*?}*/
|
||||
/*? if mc_26 {*/
|
||||
super.extractRenderState(context, mouseX, mouseY, delta);
|
||||
/*?} else {*/
|
||||
/*super.render(context, mouseX, mouseY, delta);
|
||||
*//*?}*/
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 24, 0xFFFFFF);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal(greetingLine()), this.width / 2, 48, Color.WHITE.getRGB());
|
||||
context.centeredText(this.font, this.title, this.width / 2, 24, 0xFFFFFF);
|
||||
context.centeredText(this.font, Component.literal(greetingLine()), this.width / 2, 48, Color.WHITE.getRGB());
|
||||
}
|
||||
|
||||
private String greetingLine() {
|
||||
MinecraftClient minecraftClient = MinecraftClient.getInstance();
|
||||
String username = minecraftClient.getSession().getUsername();
|
||||
Minecraft minecraftClient = Minecraft.getInstance();
|
||||
String username = minecraftClient.getUser().getName();
|
||||
return "Доброе " + getTimeOfDay() + ", " + username;
|
||||
}
|
||||
|
||||
private String getTimeOfDay() {
|
||||
MinecraftClient minecraftClient = MinecraftClient.getInstance();
|
||||
if (minecraftClient.world == null) {
|
||||
Minecraft minecraftClient = Minecraft.getInstance();
|
||||
if (minecraftClient.level == null) {
|
||||
return "время суток";
|
||||
}
|
||||
|
||||
long dayTime = minecraftClient.world.getTimeOfDay() % 24000L;
|
||||
long dayTime = minecraftClient.level.getOverworldClockTime() % 24000L;
|
||||
if (dayTime < 6000) {
|
||||
return "утро";
|
||||
}
|
||||
|
||||
@@ -3,17 +3,16 @@ package git.yawaflua.tech.spmega.client.ui;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.CardViewModel;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.PaymentDraft;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class PaymentScreen extends Screen {
|
||||
private static final int MAX_RECIPIENT_OPTION_BUTTONS = 10;
|
||||
@@ -21,25 +20,25 @@ public class PaymentScreen extends Screen {
|
||||
private static final Pattern CARD_ID_PATTERN = Pattern.compile("\\d{5}");
|
||||
private final Screen parent;
|
||||
private final BankUiService bankUiService = BankUiService.instance();
|
||||
private final List<ButtonWidget> recipientCardOptionButtons = new ArrayList<>();
|
||||
private final List<Button> recipientCardOptionButtons = new ArrayList<>();
|
||||
private final List<String> recipientCardOptions = new ArrayList<>();
|
||||
private final String initialRecipient;
|
||||
private final UiNotifications notifications = UiNotifications.instance();
|
||||
private TextFieldWidget amountField;
|
||||
private TextFieldWidget recipientField;
|
||||
private TextFieldWidget commentField;
|
||||
private ButtonWidget senderLeftButton;
|
||||
private ButtonWidget senderCardLabelButton;
|
||||
private ButtonWidget senderRightButton;
|
||||
private ButtonWidget recipientCardDropdownButton;
|
||||
private EditBox amountField;
|
||||
private EditBox recipientField;
|
||||
private EditBox commentField;
|
||||
private Button senderLeftButton;
|
||||
private Button senderCardLabelButton;
|
||||
private Button senderRightButton;
|
||||
private Button recipientCardDropdownButton;
|
||||
private boolean recipientDropdownExpanded;
|
||||
private String selectedRecipientCard = "";
|
||||
private String lastRecipientLookup = "";
|
||||
private String pendingRecipientLookup = "";
|
||||
private long pendingRecipientLookupAt;
|
||||
private Text senderCardText = Text.literal("Карта отправителя: не выбрана");
|
||||
private ButtonWidget transferButton;
|
||||
private ButtonWidget backButton;
|
||||
private Component senderCardText = Component.literal("Карта отправителя: не выбрана");
|
||||
private Button transferButton;
|
||||
private Button backButton;
|
||||
private boolean transferInProgress;
|
||||
|
||||
public PaymentScreen(Screen parent) {
|
||||
@@ -47,7 +46,7 @@ public class PaymentScreen extends Screen {
|
||||
}
|
||||
|
||||
public PaymentScreen(Screen parent, String initialRecipient) {
|
||||
super(Text.literal("Оплата"));
|
||||
super(Component.literal("Оплата"));
|
||||
this.parent = parent;
|
||||
this.initialRecipient = initialRecipient == null ? "" : initialRecipient.trim();
|
||||
}
|
||||
@@ -60,17 +59,17 @@ public class PaymentScreen extends Screen {
|
||||
int rightX = centerX + 14;
|
||||
int leftWidth = 196;
|
||||
int rightWidth = 196;
|
||||
TextRenderer tr = this.textRenderer;
|
||||
Font tr = this.font;
|
||||
|
||||
amountField = new TextFieldWidget(tr, leftX, startY, leftWidth, 20, Text.literal("Сумма"));
|
||||
amountField = new EditBox(tr, leftX, startY, leftWidth, 20, Component.literal("Сумма"));
|
||||
amountField.setMaxLength(16);
|
||||
amountField.setPlaceholder(Text.literal("Сумма перевода"));
|
||||
this.addDrawableChild(amountField);
|
||||
amountField.setHint(Component.literal("Сумма перевода"));
|
||||
this.addRenderableWidget(amountField);
|
||||
|
||||
recipientField = new TextFieldWidget(tr, leftX, startY + 28, leftWidth, 20, Text.literal("Получатель"));
|
||||
recipientField = new EditBox(tr, leftX, startY + 28, leftWidth, 20, Component.literal("Получатель"));
|
||||
recipientField.setMaxLength(32);
|
||||
recipientField.setPlaceholder(Text.literal("Ник или 5 цифр карты"));
|
||||
recipientField.setChangedListener(value -> {
|
||||
recipientField.setHint(Component.literal("Ник или 5 цифр карты"));
|
||||
recipientField.setResponder(value -> {
|
||||
String input = value.trim();
|
||||
if (isNickname(input)) {
|
||||
scheduleRecipientLookup(input);
|
||||
@@ -83,60 +82,60 @@ public class PaymentScreen extends Screen {
|
||||
updateRecipientDropdownVisibility();
|
||||
updateSenderCardText();
|
||||
});
|
||||
this.addDrawableChild(recipientField);
|
||||
this.addRenderableWidget(recipientField);
|
||||
|
||||
commentField = new TextFieldWidget(tr, leftX, startY + 56, leftWidth, 20, Text.literal("Комментарий"));
|
||||
commentField = new EditBox(tr, leftX, startY + 56, leftWidth, 20, Component.literal("Комментарий"));
|
||||
commentField.setMaxLength(64);
|
||||
commentField.setPlaceholder(Text.literal("Комментарий (необязательно)"));
|
||||
this.addDrawableChild(commentField);
|
||||
commentField.setHint(Component.literal("Комментарий (необязательно)"));
|
||||
this.addRenderableWidget(commentField);
|
||||
|
||||
recipientCardDropdownButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("Карта игрока: -"), button -> {
|
||||
recipientCardDropdownButton = this.addRenderableWidget(Button.builder(Component.literal("Карта игрока: -"), button -> {
|
||||
recipientDropdownExpanded = !recipientDropdownExpanded;
|
||||
updateRecipientDropdownVisibility();
|
||||
}).dimensions(leftX, startY + 84, leftWidth, 20).build());
|
||||
}).bounds(leftX, startY + 84, leftWidth, 20).build());
|
||||
|
||||
recipientCardOptionButtons.clear();
|
||||
for (int i = 0; i < MAX_RECIPIENT_OPTION_BUTTONS; i++) {
|
||||
final int index = i;
|
||||
ButtonWidget optionButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("-"), button -> {
|
||||
Button optionButton = this.addRenderableWidget(Button.builder(Component.literal("-"), button -> {
|
||||
if (index < recipientCardOptions.size()) {
|
||||
selectedRecipientCard = recipientCardOptions.get(index);
|
||||
recipientDropdownExpanded = false;
|
||||
updateRecipientDropdownVisibility();
|
||||
updateSenderCardText();
|
||||
}
|
||||
}).dimensions(leftX, startY + 108 + i * 22, leftWidth, 20).build());
|
||||
}).bounds(leftX, startY + 108 + i * 22, leftWidth, 20).build());
|
||||
recipientCardOptionButtons.add(optionButton);
|
||||
}
|
||||
|
||||
if (!initialRecipient.isEmpty()) {
|
||||
recipientField.setText(initialRecipient);
|
||||
recipientField.setValue(initialRecipient);
|
||||
}
|
||||
|
||||
int arrowWidth = 36;
|
||||
int middleWidth = rightWidth - arrowWidth * 2 - 8;
|
||||
senderLeftButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("<"), button -> {
|
||||
senderLeftButton = this.addRenderableWidget(Button.builder(Component.literal("<"), button -> {
|
||||
bankUiService.cycleSelectedCard(-1);
|
||||
updateSenderCardSelector();
|
||||
updateSenderCardText();
|
||||
}).dimensions(rightX, startY, arrowWidth, 20).build());
|
||||
}).bounds(rightX, startY, arrowWidth, 20).build());
|
||||
|
||||
senderCardLabelButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("00000: 0 АР"), button -> {
|
||||
}).dimensions(rightX + arrowWidth + 4, startY, middleWidth, 20).build());
|
||||
senderCardLabelButton = this.addRenderableWidget(Button.builder(Component.literal("00000: 0 АР"), button -> {
|
||||
}).bounds(rightX + arrowWidth + 4, startY, middleWidth, 20).build());
|
||||
senderCardLabelButton.active = false;
|
||||
|
||||
senderRightButton = this.addDrawableChild(ButtonWidget.builder(Text.literal(">"), button -> {
|
||||
senderRightButton = this.addRenderableWidget(Button.builder(Component.literal(">"), button -> {
|
||||
bankUiService.cycleSelectedCard(1);
|
||||
updateSenderCardSelector();
|
||||
updateSenderCardText();
|
||||
}).dimensions(rightX + arrowWidth + 4 + middleWidth + 4, startY, arrowWidth, 20).build());
|
||||
}).bounds(rightX + arrowWidth + 4 + middleWidth + 4, startY, arrowWidth, 20).build());
|
||||
|
||||
transferButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("Перевести"), button -> submit())
|
||||
.dimensions(rightX, startY + 28, rightWidth, 20)
|
||||
transferButton = this.addRenderableWidget(Button.builder(Component.literal("Перевести"), button -> submit())
|
||||
.bounds(rightX, startY + 28, rightWidth, 20)
|
||||
.build());
|
||||
|
||||
backButton = this.addDrawableChild(ButtonWidget.builder(Text.literal("Назад"), button -> this.close())
|
||||
.dimensions(rightX, startY + 56, rightWidth, 20)
|
||||
backButton = this.addRenderableWidget(Button.builder(Component.literal("Назад"), button -> this.onClose())
|
||||
.bounds(rightX, startY + 56, rightWidth, 20)
|
||||
.build());
|
||||
|
||||
updateSenderCardSelector();
|
||||
@@ -145,9 +144,9 @@ public class PaymentScreen extends Screen {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
public void onClose() {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,63 +168,71 @@ public class PaymentScreen extends Screen {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
/*? if mc_26 {*/
|
||||
public void extractRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
/*?} else {*/
|
||||
/*public void render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
*//*?}*/
|
||||
/*? if mc_26 {*/
|
||||
super.extractRenderState(context, mouseX, mouseY, delta);
|
||||
/*?} else {*/
|
||||
/*super.render(context, mouseX, mouseY, delta);
|
||||
*//*?}*/
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int startY = this.height / 2 - 82;
|
||||
int leftX = centerX - 210;
|
||||
int rightX = centerX + 14;
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 20, 0xFFFFFF);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Левая колонка: ввод"), leftX, startY - 18, 0xBFBFBF);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Правая колонка: действия"), rightX, startY - 18, 0xBFBFBF);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Сумма:"), leftX, startY - 10, 0xCCCCCC);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Получатель:"), leftX, startY + 18, 0xCCCCCC);
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Комментарий:"), leftX, startY + 46, 0xCCCCCC);
|
||||
context.centeredText(this.font, this.title, centerX, 20, 0xFFFFFF);
|
||||
context.text(this.font, Component.literal("Левая колонка: ввод"), leftX, startY - 18, 0xBFBFBF);
|
||||
context.text(this.font, Component.literal("Правая колонка: действия"), rightX, startY - 18, 0xBFBFBF);
|
||||
context.text(this.font, Component.literal("Сумма:"), leftX, startY - 10, 0xCCCCCC);
|
||||
context.text(this.font, Component.literal("Получатель:"), leftX, startY + 18, 0xCCCCCC);
|
||||
context.text(this.font, Component.literal("Комментарий:"), leftX, startY + 46, 0xCCCCCC);
|
||||
|
||||
if (recipientCardDropdownButton.visible) {
|
||||
context.drawTextWithShadow(this.textRenderer, Text.literal("Карта игрока"), leftX, startY + 74, 0xD7B2FF);
|
||||
context.text(this.font, Component.literal("Карта игрока"), leftX, startY + 74, 0xD7B2FF);
|
||||
}
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, senderCardText, centerX, this.height - 20, 0xA9E5A9);
|
||||
context.centeredText(this.font, senderCardText, centerX, this.height - 20, 0xA9E5A9);
|
||||
}
|
||||
|
||||
private void submit() {
|
||||
if (transferInProgress) {
|
||||
notifications.show(Text.literal("Перевод уже выполняется"));
|
||||
notifications.show(Component.literal("Перевод уже выполняется"));
|
||||
return;
|
||||
}
|
||||
|
||||
CardViewModel selectedCard = bankUiService.getSelectedCard();
|
||||
if (selectedCard == null) {
|
||||
notifications.show(Text.literal("Нет выбранной карты отправителя"));
|
||||
notifications.show(Component.literal("Нет выбранной карты отправителя"));
|
||||
return;
|
||||
}
|
||||
|
||||
long amount;
|
||||
try {
|
||||
amount = Long.parseLong(amountField.getText().trim());
|
||||
amount = Long.parseLong(amountField.getValue().trim());
|
||||
if (amount <= 0) {
|
||||
notifications.show(Text.literal("Сумма должна быть больше 0"));
|
||||
notifications.show(Component.literal("Сумма должна быть больше 0"));
|
||||
return;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
System.out.println(exception.getMessage());
|
||||
notifications.show(Text.literal("Некорректная сумма"));
|
||||
notifications.show(Component.literal("Некорректная сумма"));
|
||||
return;
|
||||
}
|
||||
|
||||
String recipientInput = recipientField.getText().trim();
|
||||
String recipientInput = recipientField.getValue().trim();
|
||||
if (!isValidRecipient(recipientInput)) {
|
||||
notifications.show(Text.literal("Укажи ник или 5 цифр карты"));
|
||||
notifications.show(Component.literal("Укажи ник или 5 цифр карты"));
|
||||
return;
|
||||
}
|
||||
|
||||
String receiver = recipientInput;
|
||||
if (isNickname(recipientInput)) {
|
||||
if (selectedRecipientCard.isEmpty()) {
|
||||
notifications.show(Text.literal("Выбери карту получателя из списка"));
|
||||
notifications.show(Component.literal("Выбери карту получателя из списка"));
|
||||
return;
|
||||
}
|
||||
receiver = selectedRecipientCard;
|
||||
@@ -235,18 +242,18 @@ public class PaymentScreen extends Screen {
|
||||
selectedCard.id(),
|
||||
receiver,
|
||||
amount,
|
||||
commentField.getText().trim()
|
||||
commentField.getValue().trim()
|
||||
);
|
||||
|
||||
setTransferInProgress(true);
|
||||
notifications.show(Text.literal("Отправка перевода..."));
|
||||
notifications.show(Component.literal("Отправка перевода..."));
|
||||
|
||||
bankUiService.submitPaymentAsync(draft)
|
||||
.thenAccept(accepted -> {
|
||||
if (this.client == null) {
|
||||
if (this.minecraft == null) {
|
||||
return;
|
||||
}
|
||||
this.client.execute(() -> {
|
||||
this.minecraft.execute(() -> {
|
||||
setTransferInProgress(false);
|
||||
|
||||
String serviceMessage = bankUiService.getLastMessage();
|
||||
@@ -254,8 +261,8 @@ public class PaymentScreen extends Screen {
|
||||
notifications.showMessage(serviceMessage);
|
||||
} else {
|
||||
notifications.show(accepted
|
||||
? Text.literal("Перевод выполнен")
|
||||
: Text.literal("Перевод отклонен"));
|
||||
? Component.literal("Перевод выполнен")
|
||||
: Component.literal("Перевод отклонен"));
|
||||
}
|
||||
|
||||
updateSenderCardSelector();
|
||||
@@ -263,10 +270,10 @@ public class PaymentScreen extends Screen {
|
||||
});
|
||||
})
|
||||
.exceptionally(exception -> {
|
||||
if (this.client != null) {
|
||||
this.client.execute(() -> {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.execute(() -> {
|
||||
setTransferInProgress(false);
|
||||
notifications.show(Text.literal("Ошибка перевода: " + exception.getMessage()));
|
||||
notifications.show(Component.literal("Ошибка перевода: " + exception.getMessage()));
|
||||
});
|
||||
}
|
||||
return null;
|
||||
@@ -290,11 +297,11 @@ public class PaymentScreen extends Screen {
|
||||
private void updateSenderCardText() {
|
||||
CardViewModel selectedCard = bankUiService.getSelectedCard();
|
||||
if (selectedCard == null) {
|
||||
senderCardText = Text.literal("Карта отправителя: не выбрана");
|
||||
senderCardText = Component.literal("Карта отправителя: не выбрана");
|
||||
return;
|
||||
}
|
||||
|
||||
String recipient = recipientField == null ? "" : recipientField.getText().trim();
|
||||
String recipient = recipientField == null ? "" : recipientField.getValue().trim();
|
||||
String suffix;
|
||||
if (isNickname(recipient)) {
|
||||
String recipientCard = selectedRecipientCard.isEmpty() ? "карта не выбрана" : selectedRecipientCard;
|
||||
@@ -303,7 +310,7 @@ public class PaymentScreen extends Screen {
|
||||
suffix = " (режим: перевод по номеру карты)";
|
||||
}
|
||||
|
||||
senderCardText = Text.literal(
|
||||
senderCardText = Component.literal(
|
||||
"Карта отправителя: " + selectedCard.title() + " | Баланс: " + selectedCard.balance() + suffix
|
||||
);
|
||||
}
|
||||
@@ -311,7 +318,7 @@ public class PaymentScreen extends Screen {
|
||||
private void updateSenderCardSelector() {
|
||||
CardViewModel selectedCard = bankUiService.getSelectedCard();
|
||||
if (selectedCard == null) {
|
||||
senderCardLabelButton.setMessage(Text.literal("TEST 00000: 0 АР"));
|
||||
senderCardLabelButton.setMessage(Component.literal("TEST 00000: 0 АР"));
|
||||
senderLeftButton.active = false;
|
||||
senderRightButton.active = false;
|
||||
return;
|
||||
@@ -320,7 +327,7 @@ public class PaymentScreen extends Screen {
|
||||
senderLeftButton.active = true;
|
||||
senderRightButton.active = true;
|
||||
String balance = Long.toString(selectedCard.balance());
|
||||
senderCardLabelButton.setMessage(Text.literal(selectedCard.title() + ": " + balance + " АР"));
|
||||
senderCardLabelButton.setMessage(Component.literal(selectedCard.title() + ": " + balance + " АР"));
|
||||
senderCardLabelButton.active = false;
|
||||
}
|
||||
|
||||
@@ -329,7 +336,7 @@ public class PaymentScreen extends Screen {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean nicknameMode = isNickname(recipientField == null ? "" : recipientField.getText().trim());
|
||||
boolean nicknameMode = isNickname(recipientField == null ? "" : recipientField.getValue().trim());
|
||||
|
||||
boolean hasRecipientCards = !recipientCardOptions.isEmpty();
|
||||
recipientCardDropdownButton.visible = nicknameMode;
|
||||
@@ -350,15 +357,15 @@ public class PaymentScreen extends Screen {
|
||||
} else {
|
||||
dropdownText = "Карта игрока: " + selectedRecipientCard;
|
||||
}
|
||||
recipientCardDropdownButton.setMessage(Text.literal(dropdownText));
|
||||
recipientCardDropdownButton.setMessage(Component.literal(dropdownText));
|
||||
|
||||
for (int i = 0; i < recipientCardOptionButtons.size(); i++) {
|
||||
ButtonWidget optionButton = recipientCardOptionButtons.get(i);
|
||||
Button optionButton = recipientCardOptionButtons.get(i);
|
||||
boolean showOption = nicknameMode && recipientDropdownExpanded && i < recipientCardOptions.size();
|
||||
optionButton.visible = showOption;
|
||||
optionButton.active = showOption;
|
||||
if (i < recipientCardOptions.size()) {
|
||||
optionButton.setMessage(Text.literal(recipientCardOptions.get(i)));
|
||||
optionButton.setMessage(Component.literal(recipientCardOptions.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,11 +390,11 @@ public class PaymentScreen extends Screen {
|
||||
|
||||
bankUiService.loadRecipientCardsAsync(username)
|
||||
.thenAccept(cards -> {
|
||||
if (this.client == null) {
|
||||
if (this.minecraft == null) {
|
||||
return;
|
||||
}
|
||||
this.client.execute(() -> {
|
||||
if (recipientField == null || !username.equals(recipientField.getText().trim())) {
|
||||
this.minecraft.execute(() -> {
|
||||
if (recipientField == null || !username.equals(recipientField.getValue().trim())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package git.yawaflua.tech.spmega.client.ui;
|
||||
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
/*? if mc_1_21_11 {*/
|
||||
import net.minecraft.util.Util;
|
||||
/*?} else {*/
|
||||
// import net.minecraft.Util;
|
||||
/*?}*/
|
||||
|
||||
import java.awt.*;
|
||||
import java.net.URI;
|
||||
@@ -14,7 +18,7 @@ public class QRcodeAcceptScreen extends Screen {
|
||||
private final Screen parent;
|
||||
|
||||
public QRcodeAcceptScreen(String url, Screen parent) {
|
||||
super(Text.translatable("screen.spmega.qr.confirm_title"));
|
||||
super(Component.translatable("screen.spmega.qr.confirm_title"));
|
||||
this.url = url;
|
||||
this.parent = parent;
|
||||
}
|
||||
@@ -23,44 +27,55 @@ public class QRcodeAcceptScreen extends Screen {
|
||||
protected void init() {
|
||||
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.translatable("button.spmega.qr.cancel"), button -> {
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
this.addRenderableWidget(Button.builder(Component.translatable("button.spmega.qr.cancel"), button -> {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}).dimensions(this.width / 2 - 155, this.height / 2 + 30, 150, 20).build());
|
||||
}).bounds(this.width / 2 - 155, this.height / 2 + 30, 150, 20).build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.translatable("button.spmega.qr.open_link"), button -> {
|
||||
this.addRenderableWidget(Button.builder(Component.translatable("button.spmega.qr.open_link"), button -> {
|
||||
try {
|
||||
Util.getOperatingSystem().open(new URI(url));
|
||||
Util.getPlatform().openUri(new URI(url));
|
||||
} catch (Exception exception) {
|
||||
if (this.client != null && this.client.player != null) {
|
||||
this.client.player.sendMessage(Text.translatable("message.spmega.qr.failed_open"), false);
|
||||
if (this.minecraft != null && this.minecraft.player != null) {
|
||||
/*? if mc_26 {*/
|
||||
this.minecraft.player.sendSystemMessage(Component.translatable("message.spmega.qr.failed_open"));
|
||||
/*?} else {*/
|
||||
/*this.minecraft.player.displayClientMessage(Component.translatable("message.spmega.qr.failed_open"), false);
|
||||
*//*?}*/
|
||||
}
|
||||
}
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}).dimensions(this.width / 2 + 5, this.height / 2 + 30, 150, 20).build());
|
||||
}).bounds(this.width / 2 + 5, this.height / 2 + 30, 150, 20).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
public void onClose() {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
/*? if mc_26 {*/
|
||||
public void extractRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
/*?} else {*/
|
||||
/*public void render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
*//*?}*/
|
||||
/*? if mc_26 {*/
|
||||
super.extractRenderState(context, mouseX, mouseY, delta);
|
||||
/*?} else {*/
|
||||
/*super.render(context, mouseX, mouseY, delta);
|
||||
*//*?}*/
|
||||
|
||||
context.drawCenteredTextWithShadow(
|
||||
this.textRenderer,
|
||||
Text.literal("Найдена ссылка: " + url),
|
||||
context.centeredText(
|
||||
this.font,
|
||||
Component.literal("Найдена ссылка: " + url),
|
||||
this.width / 2,
|
||||
this.height / 2,
|
||||
Color.WHITE.getRGB()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,14 @@ package git.yawaflua.tech.spmega.client.ui;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BackendAuthenticator;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankDatabase.LocalTransaction;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class TransactionHistoryScreen extends Screen {
|
||||
private final Screen parent;
|
||||
@@ -24,7 +23,7 @@ public class TransactionHistoryScreen extends Screen {
|
||||
private int currentPage = 1;
|
||||
|
||||
public TransactionHistoryScreen(Screen parent, String cardId, String cardTitle) {
|
||||
super(Text.literal("История транзакций"));
|
||||
super(Component.literal("История транзакций"));
|
||||
this.parent = parent;
|
||||
this.cardId = cardId;
|
||||
this.cardTitle = cardTitle;
|
||||
@@ -35,34 +34,34 @@ public class TransactionHistoryScreen extends Screen {
|
||||
int centerX = this.width / 2;
|
||||
int startY = this.height / 2 + 50;
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("Назад"), button -> this.close())
|
||||
.dimensions(centerX - 60, startY + 10, 120, 20)
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Назад"), button -> this.onClose())
|
||||
.bounds(centerX - 60, startY + 10, 120, 20)
|
||||
.build());
|
||||
|
||||
// Scroll Up/Down buttons
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("▲"), button -> scrollUp())
|
||||
.dimensions(centerX + 140, 65, 20, 20)
|
||||
this.addRenderableWidget(Button.builder(Component.literal("▲"), button -> scrollUp())
|
||||
.bounds(centerX + 140, 65, 20, 20)
|
||||
.build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("▼"), button -> scrollDown())
|
||||
.dimensions(centerX + 140, 155, 20, 20)
|
||||
this.addRenderableWidget(Button.builder(Component.literal("▼"), button -> scrollDown())
|
||||
.bounds(centerX + 140, 155, 20, 20)
|
||||
.build());
|
||||
|
||||
// Prev Page button
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("<"), button -> {
|
||||
this.addRenderableWidget(Button.builder(Component.literal("<"), button -> {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
scrollOffset = 0;
|
||||
loadTransactions();
|
||||
}
|
||||
}).dimensions(centerX - 100, startY - 15, 30, 20).build());
|
||||
}).bounds(centerX - 100, startY - 15, 30, 20).build());
|
||||
|
||||
// Next Page button
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal(">"), button -> {
|
||||
this.addRenderableWidget(Button.builder(Component.literal(">"), button -> {
|
||||
currentPage++;
|
||||
scrollOffset = 0;
|
||||
loadTransactions();
|
||||
}).dimensions(centerX + 70, startY - 15, 30, 20).build());
|
||||
}).bounds(centerX + 70, startY - 15, 30, 20).build());
|
||||
|
||||
loadTransactions();
|
||||
}
|
||||
@@ -105,7 +104,7 @@ public class TransactionHistoryScreen extends Screen {
|
||||
errorMessage = "";
|
||||
transactions.clear();
|
||||
|
||||
UiNotifications.instance().show(Text.literal("Загрузка..."));
|
||||
UiNotifications.instance().show(Component.literal("Загрузка..."));
|
||||
|
||||
int page = currentPage;
|
||||
System.out.println("[SPMEGA] Transaction history loading started for card: " + cardId + ", page: " + page);
|
||||
@@ -132,7 +131,7 @@ public class TransactionHistoryScreen extends Screen {
|
||||
return local;
|
||||
})
|
||||
.whenComplete((list, exception) -> {
|
||||
MinecraftClient minecraftClient = MinecraftClient.getInstance();
|
||||
Minecraft minecraftClient = Minecraft.getInstance();
|
||||
if (minecraftClient == null) {
|
||||
System.out.println("[SPMEGA] MinecraftClient.getInstance() is null in loadTransactions callback!");
|
||||
return;
|
||||
@@ -151,30 +150,38 @@ public class TransactionHistoryScreen extends Screen {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.client != null) {
|
||||
this.client.setScreen(parent);
|
||||
public void onClose() {
|
||||
if (this.minecraft != null) {
|
||||
this.minecraft.gui.setScreen(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
/*? if mc_26 {*/
|
||||
public void extractRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
/*?} else {*/
|
||||
/*public void render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
|
||||
*//*?}*/
|
||||
/*? if mc_26 {*/
|
||||
super.extractRenderState(context, mouseX, mouseY, delta);
|
||||
/*?} else {*/
|
||||
/*super.render(context, mouseX, mouseY, delta);
|
||||
*//*?}*/
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int startY = 50;
|
||||
int bottomStartY = this.height / 2 + 50;
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 20, 0xFFFFFFFF);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Карта: " + cardTitle), centerX, 35, 0xFFBFBFBF);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Стр. " + currentPage), centerX, bottomStartY - 9, 0xFFFFFFFF);
|
||||
context.centeredText(this.font, this.title, centerX, 20, 0xFFFFFFFF);
|
||||
context.centeredText(this.font, Component.literal("Карта: " + cardTitle), centerX, 35, 0xFFBFBFBF);
|
||||
context.centeredText(this.font, Component.literal("Стр. " + currentPage), centerX, bottomStartY - 9, 0xFFFFFFFF);
|
||||
|
||||
if (loading) {
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Загрузка транзакций..."), centerX, this.height / 2 - 10, 0xFFCCCCCC);
|
||||
context.centeredText(this.font, Component.literal("Загрузка транзакций..."), centerX, this.height / 2 - 10, 0xFFCCCCCC);
|
||||
} else if (!errorMessage.isEmpty()) {
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Ошибка: " + errorMessage), centerX, this.height / 2 - 10, 0xFFFF5555);
|
||||
context.centeredText(this.font, Component.literal("Ошибка: " + errorMessage), centerX, this.height / 2 - 10, 0xFFFF5555);
|
||||
} else if (transactions.isEmpty()) {
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Транзакций не найдено"), centerX, this.height / 2 - 10, 0xFFCCCCCC);
|
||||
context.centeredText(this.font, Component.literal("Транзакций не найдено"), centerX, this.height / 2 - 10, 0xFFCCCCCC);
|
||||
} else {
|
||||
int y = startY + 15;
|
||||
int limit = Math.min(6, transactions.size() - scrollOffset);
|
||||
@@ -196,7 +203,7 @@ public class TransactionHistoryScreen extends Screen {
|
||||
String status = tx.status();
|
||||
int color = (status != null && status.equalsIgnoreCase("SUCCESS")) ? 0xFF55FF55 : 0xFFFF5555;
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal(line), centerX, y, color);
|
||||
context.centeredText(this.font, Component.literal(line), centerX, y, color);
|
||||
y += 18;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,22 @@ package git.yawaflua.tech.spmega.client.ui;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.Strictness;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import git.yawaflua.tech.spmega.GpsHudPosition;
|
||||
import git.yawaflua.tech.spmega.SPMega;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public final class UiNotifications {
|
||||
private static final int DEFAULT_DURATION_TICKS = 70;
|
||||
private static final UiNotifications INSTANCE = new UiNotifications();
|
||||
|
||||
private Text currentText = Text.empty();
|
||||
private final Queue<Text> queuedTexts = new ArrayDeque<>();
|
||||
private Component currentText = Component.empty();
|
||||
private final Queue<Component> queuedTexts = new ArrayDeque<>();
|
||||
private int remainingTicks;
|
||||
|
||||
private UiNotifications() {
|
||||
@@ -37,7 +35,7 @@ public final class UiNotifications {
|
||||
|
||||
try {
|
||||
var reader = new JsonReader(new java.io.StringReader(raw));
|
||||
reader.setStrictness(Strictness.LENIENT);
|
||||
reader.setLenient(true);
|
||||
|
||||
JsonElement parsed = JsonParser.parseReader(reader);
|
||||
JsonObject object = parsed.isJsonArray() ? parsed.getAsJsonArray().get(0).getAsJsonObject() : parsed.getAsJsonObject();
|
||||
@@ -52,7 +50,7 @@ public final class UiNotifications {
|
||||
return raw;
|
||||
}
|
||||
|
||||
public synchronized void show(Text text) {
|
||||
public synchronized void show(Component text) {
|
||||
if (text == null || text.getString().isBlank()) {
|
||||
return;
|
||||
}
|
||||
@@ -64,10 +62,10 @@ public final class UiNotifications {
|
||||
if (message == null || message.isBlank()) {
|
||||
return;
|
||||
}
|
||||
show(Text.literal(extractMessage(message)));
|
||||
show(Component.literal(extractMessage(message)));
|
||||
}
|
||||
|
||||
public synchronized void showQueued(Text text) {
|
||||
public synchronized void showQueued(Component text) {
|
||||
if (text == null || text.getString().isBlank()) {
|
||||
return;
|
||||
}
|
||||
@@ -78,16 +76,16 @@ public final class UiNotifications {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void render(DrawContext context, TextRenderer textRenderer, int width, int height) {
|
||||
public synchronized void render(GuiGraphicsExtractor context, Font textRenderer, int width, int height) {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String message = currentText.getString();
|
||||
int padding = 6;
|
||||
int textWidth = textRenderer.getWidth(message);
|
||||
int textWidth = textRenderer.width(message);
|
||||
int boxWidth = textWidth + padding * 2;
|
||||
int boxHeight = textRenderer.fontHeight + padding * 2;
|
||||
int boxHeight = textRenderer.lineHeight + padding * 2;
|
||||
GpsHudPosition position = SPMega.getConfig() == null
|
||||
? GpsHudPosition.BOTTOM_RIGHT
|
||||
: SPMega.getConfig().notificationPosition();
|
||||
@@ -102,7 +100,7 @@ public final class UiNotifications {
|
||||
};
|
||||
|
||||
context.fill(x, y, x + boxWidth, y + boxHeight, Color.DARK_GRAY.getRGB());
|
||||
context.drawTextWithShadow(textRenderer, currentText, x + padding, y + padding, Color.WHITE.getRGB());
|
||||
context.text(textRenderer, currentText, x + padding, y + padding, Color.WHITE.getRGB());
|
||||
}
|
||||
|
||||
public synchronized void tick() {
|
||||
@@ -111,7 +109,7 @@ public final class UiNotifications {
|
||||
}
|
||||
remainingTicks--;
|
||||
if (remainingTicks <= 0) {
|
||||
currentText = queuedTexts.isEmpty() ? Text.empty() : queuedTexts.remove();
|
||||
currentText = queuedTexts.isEmpty() ? Component.empty() : queuedTexts.remove();
|
||||
remainingTicks = currentText.getString().isBlank() ? 0 : DEFAULT_DURATION_TICKS;
|
||||
}
|
||||
}
|
||||
@@ -120,4 +118,3 @@ public final class UiNotifications {
|
||||
return currentText != null && !currentText.getString().isBlank() && remainingTicks > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
package git.yawaflua.tech.spmega.client.ui;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
|
||||
public final class UiOpeners {
|
||||
private UiOpeners() {
|
||||
}
|
||||
|
||||
public static void openMainMenu(MinecraftClient client) {
|
||||
public static void openMainMenu(Minecraft client) {
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Screen current = client.currentScreen;
|
||||
client.setScreen(new MainBankScreen(current));
|
||||
Screen current = client.gui.screen();
|
||||
client.gui.setScreen(new MainBankScreen(current));
|
||||
}
|
||||
|
||||
public static void openPaymentMenu(MinecraftClient client, String recipient) {
|
||||
public static void openPaymentMenu(Minecraft client, String recipient) {
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Screen current = client.currentScreen;
|
||||
client.setScreen(new PaymentScreen(current, recipient));
|
||||
Screen current = client.gui.screen();
|
||||
client.gui.setScreen(new PaymentScreen(current, recipient));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+122
-15
@@ -8,9 +8,7 @@ import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||
import git.yawaflua.tech.spmega.ModConfig;
|
||||
import git.yawaflua.tech.spmega.SPMega;
|
||||
import git.yawaflua.tech.spmega.client.telemetry.InstrumentedHttpClient;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.session.Session;
|
||||
|
||||
import git.yawaflua.tech.spmega.client.ui.UiNotifications;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
@@ -20,32 +18,134 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.User;
|
||||
|
||||
public final class BackendAuthenticator {
|
||||
private static final long TOKEN_CHECK_INTERVAL_MILLIS = 60L * 60L * 1000L;
|
||||
private static final AtomicBoolean tokenMaintenanceRunning = new AtomicBoolean();
|
||||
private static volatile long nextTokenCheckMillis;
|
||||
private static CompletableFuture<Boolean> activeReauthentication;
|
||||
|
||||
private BackendAuthenticator() {
|
||||
}
|
||||
|
||||
public static CompletableFuture<Boolean> authenticateAsync(MinecraftClient client) {
|
||||
Session session = client.getSession();
|
||||
if (session == null || session.getUuidOrNull() == null) {
|
||||
public static void tickTokenRefresh(Minecraft client) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now < nextTokenCheckMillis || !tokenMaintenanceRunning.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextTokenCheckMillis = now + TOKEN_CHECK_INTERVAL_MILLIS;
|
||||
maintainTokenAsync(client).whenComplete((ignored, throwable) -> {
|
||||
tokenMaintenanceRunning.set(false);
|
||||
if (throwable != null) {
|
||||
Throwable cause = throwable.getCause() == null ? throwable : throwable.getCause();
|
||||
System.err.println("[SPMEGA] Token maintenance failed: " + cause.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CompletableFuture<Boolean> maintainTokenAsync(Minecraft client) {
|
||||
ModConfig config = SPMega.getConfig();
|
||||
if (config == null || !config.allowBackend()) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
if (config.apiToken() == null || config.apiToken().isBlank()
|
||||
|| ModConfig.DEFAULT_API_TOKEN.equals(config.apiToken())) {
|
||||
return reauthenticateAsync(client);
|
||||
}
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(backendUrl(config, "/api/v1/auth/refresh")))
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Bearer " + config.apiToken())
|
||||
.POST(HttpRequest.BodyPublishers.noBody())
|
||||
.build();
|
||||
|
||||
return new InstrumentedHttpClient().sendAsync(request).thenCompose(response -> {
|
||||
if (response.statusCode() == 401) {
|
||||
return reauthenticateAsync(client);
|
||||
}
|
||||
if (response.statusCode() != 200) {
|
||||
throw new CompletionException(new IOException(
|
||||
"Refresh endpoint returned status " + response.statusCode() + ": " + response.body()));
|
||||
}
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
if (json.has("refreshed") && json.get("refreshed").getAsBoolean()) {
|
||||
if (!json.has("token") || json.get("token").isJsonNull()) {
|
||||
throw new CompletionException(new IOException("Refresh endpoint did not return a token."));
|
||||
}
|
||||
saveToken(json.get("token").getAsString());
|
||||
System.out.println("[SPMEGA] Backend access token refreshed.");
|
||||
}
|
||||
return CompletableFuture.completedFuture(true);
|
||||
});
|
||||
}
|
||||
|
||||
public static void onReauthenticationRequired() {
|
||||
ModConfig config = SPMega.getConfig();
|
||||
if (config == null || !config.allowBackend()) {
|
||||
return;
|
||||
}
|
||||
reauthenticateAsync(Minecraft.getInstance()).exceptionally(throwable -> {
|
||||
Throwable cause = throwable.getCause() == null ? throwable : throwable.getCause();
|
||||
System.err.println("[SPMEGA] Reauthentication failed: " + cause.getMessage());
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private static synchronized CompletableFuture<Boolean> reauthenticateAsync(Minecraft client) {
|
||||
if (activeReauthentication != null && !activeReauthentication.isDone()) {
|
||||
return activeReauthentication;
|
||||
}
|
||||
|
||||
UiNotifications.instance().showMessage("Сессия SPMega устарела. Выполняется повторная авторизация…");
|
||||
CompletableFuture<Boolean> reauthentication = authenticateAsync(client).thenApply(success -> {
|
||||
if (success) {
|
||||
UiNotifications.instance().showMessage("Авторизация SPMega обновлена.");
|
||||
} else {
|
||||
UiNotifications.instance().showMessage("Не удалось обновить авторизацию SPMega.");
|
||||
}
|
||||
return success;
|
||||
});
|
||||
activeReauthentication = reauthentication;
|
||||
reauthentication.whenComplete((ignored, throwable) -> clearActiveReauthentication(reauthentication));
|
||||
return reauthentication;
|
||||
}
|
||||
|
||||
private static synchronized void clearActiveReauthentication(CompletableFuture<Boolean> completed) {
|
||||
if (activeReauthentication == completed) {
|
||||
activeReauthentication = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletableFuture<Boolean> authenticateAsync(Minecraft client) {
|
||||
User session = client.getUser();
|
||||
if (session == null || session.getProfileId() == null) {
|
||||
System.err.println("Cannot authenticate: Client has no valid session.");
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
MinecraftSessionService sessionService = client.getApiServices().sessionService();
|
||||
return sendStartSessionRequestToBackendAsync(session.getUsername(), session.getUuidOrNull())
|
||||
/*? if mc_1_21_11 {*/
|
||||
MinecraftSessionService sessionService = client.services().sessionService();
|
||||
/*?} else {*/
|
||||
// MinecraftSessionService sessionService = client.getMinecraftSessionService();
|
||||
/*?}*/
|
||||
return sendStartSessionRequestToBackendAsync(session.getName(), session.getProfileId())
|
||||
.thenCompose(serverId -> CompletableFuture.runAsync(() -> {
|
||||
System.out.println("[SPMEGA] Trying to auth in mojang with serverId: " + serverId);
|
||||
try {
|
||||
sessionService.joinServer(session.getUuidOrNull(), session.getAccessToken(), serverId);
|
||||
sessionService.joinServer(session.getProfileId(), session.getAccessToken(), serverId);
|
||||
} catch (AuthenticationException exception) {
|
||||
throw new CompletionException(exception);
|
||||
}
|
||||
})
|
||||
.thenCompose(ignored -> {
|
||||
System.out.println("[SPMEGA] Sending session submitter to backend");
|
||||
return sendAuthRequestToBackendAsync(session.getUuidOrNull(), serverId);
|
||||
return sendAuthRequestToBackendAsync(session.getProfileId(), serverId);
|
||||
}))
|
||||
.exceptionally(throwable -> {
|
||||
Throwable cause = throwable.getCause() == null ? throwable : throwable.getCause();
|
||||
@@ -138,16 +238,23 @@ public final class BackendAuthenticator {
|
||||
throw new CompletionException(new IOException("Config is null, cannot save token."));
|
||||
}
|
||||
|
||||
String token = json.get("token").getAsString();
|
||||
SPMega.setConfig(new ModConfig(
|
||||
config.apiDomain(), token, config.allowBackend(), config.signQuickPayEnabled(),
|
||||
config.gpsEnabled(), config.gpsPosition(), config.notificationPosition(),
|
||||
config.telemetryIntervalSeconds(), config.telemetryCollectSystemInfo()));
|
||||
saveToken(json.get("token").getAsString());
|
||||
System.out.println("[SPMEGA] Backend auth successful, saved token.");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static void saveToken(String token) {
|
||||
ModConfig current = SPMega.getConfig();
|
||||
if (current == null) {
|
||||
throw new CompletionException(new IOException("Config is null, cannot save token."));
|
||||
}
|
||||
SPMega.setConfig(new ModConfig(
|
||||
current.apiDomain(), token, current.allowBackend(), current.signQuickPayEnabled(),
|
||||
current.gpsEnabled(), current.gpsPosition(), current.notificationPosition(),
|
||||
current.telemetryIntervalSeconds(), current.telemetryCollectSystemInfo()));
|
||||
}
|
||||
|
||||
public static void sendCardToBackend(String cardId, String cardToken) {
|
||||
ModConfig config = SPMega.getConfig();
|
||||
if (config == null || !config.allowBackend()) {
|
||||
|
||||
@@ -6,9 +6,7 @@ import git.yawaflua.tech.spmega.client.api.SPWorldsApiClient;
|
||||
import git.yawaflua.tech.spmega.client.telemetry.TelemetryCollector;
|
||||
import git.yawaflua.tech.spmega.client.telemetry.TelemetryEvent;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -59,8 +57,8 @@ public final class BankUiService {
|
||||
}
|
||||
|
||||
private void runOnMainThread(Runnable runnable) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client == null || client.isOnThread()) {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client == null || client.isSameThread()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
client.execute(runnable);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package git.yawaflua.tech.spmega.mixin.client;
|
||||
|
||||
import git.yawaflua.tech.spmega.client.ui.UiOpeners;
|
||||
import net.minecraft.client.gui.screen.GameMenuScreen;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.PauseScreen;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(GameMenuScreen.class)
|
||||
@Mixin(PauseScreen.class)
|
||||
public abstract class GameMenuScreenMixin extends Screen {
|
||||
protected GameMenuScreenMixin(Text title) {
|
||||
protected GameMenuScreenMixin(Component title) {
|
||||
super(title);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ public abstract class GameMenuScreenMixin extends Screen {
|
||||
int buttonWidth = 96;
|
||||
int x = this.width / 2 + 106;
|
||||
int y = this.height / 4 + 24 - 16;
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("SPMega"), button -> {
|
||||
UiOpeners.openMainMenu(this.client);
|
||||
}).dimensions(x, y, buttonWidth, 20).build());
|
||||
this.addRenderableWidget(Button.builder(Component.literal("SPMega"), button -> {
|
||||
UiOpeners.openMainMenu(this.minecraft);
|
||||
}).bounds(x, y, buttonWidth, 20).build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package git.yawaflua.tech.spmega;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
|
||||
public class SPMega implements ModInitializer {
|
||||
private static ModConfig config;
|
||||
private static volatile ModConfig config;
|
||||
|
||||
public static ModConfig getConfig() {
|
||||
return config;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://yawaflua.tech",
|
||||
"issues": "https://git.yawaflua.tech/spmega",
|
||||
"sources": "https://git.yawaflua.tech/spmega"
|
||||
"issues": "https://github.com/yawaflua/SPMega",
|
||||
"sources": "https://github.com/yawaflua/SPMega"
|
||||
},
|
||||
"license": "CC BY-NC-ND 4.0",
|
||||
"icon": "icon.png",
|
||||
@@ -45,7 +45,8 @@
|
||||
"depends": {
|
||||
"fabricloader": ">=${loader_version}",
|
||||
"fabric-api": "*",
|
||||
"minecraft": "${minecraft_version}"
|
||||
"minecraft": "${minecraft_version}",
|
||||
"java": ">=${java_version}"
|
||||
},
|
||||
"suggests": {
|
||||
"cloth-config2": ">=${cloth_config_version}"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
id("dev.kikugie.stonecutter")
|
||||
id("com.modrinth.minotaur") version "2.9.0" apply false
|
||||
}
|
||||
|
||||
stonecutter active "26.2.x"
|
||||
|
||||
stonecutter parameters {
|
||||
constants["mc_26"] = node.metadata.version >= "26.1"
|
||||
constants["mc_26_2"] = node.metadata.version >= "26.2"
|
||||
constants["mc_1_21_11"] = node.metadata.version >= "1.21.11"
|
||||
|
||||
replacements {
|
||||
string(node.metadata.version >= "26.1") {
|
||||
replace("GuiGraphics", "GuiGraphicsExtractor")
|
||||
replace(".drawString(", ".text(")
|
||||
replace(".drawCenteredString(", ".centeredText(")
|
||||
replace("ScreenEvents.afterRender", "ScreenEvents.afterExtract")
|
||||
replace(".getDayTime()", ".getOverworldClockTime()")
|
||||
replace("client.keybinding.v1.KeyBindingHelper", "client.keymapping.v1.KeyMappingHelper")
|
||||
replace("KeyBindingHelper.registerKeyBinding", "KeyMappingHelper.registerKeyMapping")
|
||||
}
|
||||
string(node.metadata.version >= "26.2") {
|
||||
replace(".setScreen(", ".gui.setScreen(")
|
||||
replace("client.screen ==", "client.gui.screen() ==")
|
||||
replace("client.screen !=", "client.gui.screen() !=")
|
||||
replace("client.screen)", "client.gui.screen())")
|
||||
replace("client.screen;", "client.gui.screen();")
|
||||
replace("client.getMainRenderTarget()", "client.gameRenderer.mainRenderTarget()")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
[constants]
|
||||
mod_id = "spmega"
|
||||
|
||||
[dependencies]
|
||||
fabric_version = "0.141.5+1.21.11"
|
||||
cloth_config_version = "21.11.153"
|
||||
modmenu_version = "v6Xx3fbU"
|
||||
|
||||
[metadata]
|
||||
minecraft_compat = "1.21.11"
|
||||
loader_compat = "0.18.1"
|
||||
|
||||
["1.21.1"]
|
||||
dependencies.fabric_version = "0.116.10+1.21.1"
|
||||
dependencies.cloth_config_version = "15.0.140"
|
||||
dependencies.modmenu_version = "v6Xx3fbU"
|
||||
metadata.minecraft_compat = "1.21.1"
|
||||
|
||||
["26.1.x"]
|
||||
dependencies.fabric_version = "0.155.2+26.1.2"
|
||||
dependencies.cloth_config_version = "26.1.154"
|
||||
dependencies.modmenu_version = "3rsoV1bk"
|
||||
metadata.minecraft_compat = "~26.1"
|
||||
metadata.loader_compat = "0.19.0"
|
||||
|
||||
["26.2.x"]
|
||||
dependencies.fabric_version = "0.155.2+26.2"
|
||||
dependencies.cloth_config_version = "26.2.155"
|
||||
dependencies.modmenu_version = "njXb639R"
|
||||
metadata.minecraft_compat = "~26.2"
|
||||
metadata.loader_compat = "0.19.0"
|
||||
Reference in New Issue
Block a user