初始化上传
Some checks failed
build / build (push) Has been cancelled

This commit is contained in:
2026-07-24 11:36:23 +08:00
commit 2bd90bdb50
42 changed files with 6154 additions and 0 deletions

11
.gitattributes vendored Normal file
View File

@@ -0,0 +1,11 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
/build/
/.gradle/

30
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
# Automatically build the project and run any configured tests for every push
# and submitted pull request. This can help catch issues that only occur on
# certain platforms or Java versions, and provides a first line of defence
# against bad commits.
name: build
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: validate gradle wrapper
uses: gradle/actions/wrapper-validation@v6
- name: setup jdk
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'microsoft'
- name: make gradle wrapper executable
run: chmod +x ./gradlew
- name: build
run: ./gradlew build
- name: capture build artifacts
uses: actions/upload-artifact@v7
with:
name: Artifacts
path: build/libs/

40
.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# gradle
.gradle/
build/
out/
classes/
# eclipse
*.launch
# idea
.idea/
*.iml
*.ipr
*.iws
# vscode
.settings/
.vscode/
bin/
.classpath
.project
# macos
*.DS_Store
# fabric
run/
# java
hs_err_*.log
replay_*.log
*.hprof
*.jfr

121
LICENSE Normal file
View File

@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# Cardinal Cooperative Capitalism: Market Economy
## Setup
For setup instructions, please see the [Fabric Documentation page](https://docs.fabricmc.net/develop/getting-started/creating-a-project#setting-up) related to the IDE that you are using.
## License
This template is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects.

1
TODO Normal file
View File

@@ -0,0 +1 @@
礼物,信,神器,点歌

88
build.gradle Normal file
View File

@@ -0,0 +1,88 @@
plugins {
id 'net.fabricmc.fabric-loom-remap' version "${loom_version}"
id 'maven-publish'
}
version = project.mod_version
group = project.maven_group
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
}
loom {
mods {
"cardinal-cooperative-capitalism-market-economy" {
sourceSet sourceSets.main
}
}
}
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_api_version}"
implementation("org.xerial:sqlite-jdbc:3.45.1.0")
implementation("com.mysql:mysql-connector-j:8.3.0")
implementation("com.zaxxer:HikariCP:5.1.0")
include("org.xerial:sqlite-jdbc:3.45.1.0")
include("com.mysql:mysql-connector-j:8.3.0")
include("com.zaxxer:HikariCP:5.1.0")
}
processResources {
def version = project.version
inputs.property "version", version
filesMatching("fabric.mod.json") {
expand "version": version
}
}
tasks.withType(JavaCompile).configureEach {
it.options.release = 21
}
java {
// 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()
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
jar {
def projectName = project.name
inputs.property "projectName", projectName
from("LICENSE") {
rename { "${it}_$projectName"}
}
}
// configure the maven publication
publishing {
publications {
create("mavenJava", MavenPublication) {
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.
}
}

20
gradle.properties Normal file
View File

@@ -0,0 +1,20 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.parallel=true
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
org.gradle.configuration-cache=false
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.21.10
yarn_mappings=1.21.10+build.3
loader_version=0.19.2
loom_version=1.16-SNAPSHOT
# Mod Properties
mod_version=1.0.0
maven_group=com.gvsds.tcme
# Dependencies
fabric_api_version=0.138.4+1.21.10

40
gradle/init.gradle Normal file
View File

@@ -0,0 +1,40 @@
allprojects {
buildscript {
repositories {
maven {
name 'Aliyun'
url 'https://maven.aliyun.com/repository/central'
}
maven {
name 'AliyunPublic'
url 'https://maven.aliyun.com/repository/public'
}
maven {
name 'Tencent'
url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/'
}
mavenCentral()
gradlePluginPortal()
}
}
repositories {
maven {
name 'Aliyun'
url 'https://maven.aliyun.com/repository/central'
}
maven {
name 'AliyunPublic'
url 'https://maven.aliyun.com/repository/public'
}
maven {
name 'Tencent'
url 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/'
}
maven {
name 'Modrinth'
url 'https://api.modrinth.com/maven'
}
mavenCentral()
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=file:///D:/%E8%BF%85%E9%9B%B7%E4%B8%8B%E8%BD%BD/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

248
gradlew vendored Normal file
View File

@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

93
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

58
settings.gradle Normal file
View File

@@ -0,0 +1,58 @@
pluginManagement {
repositories {
// 阿里云镜像
maven {
name = 'Aliyun'
url = 'https://maven.aliyun.com/repository/central'
}
maven {
name = 'AliyunPublic'
url = 'https://maven.aliyun.com/repository/public'
}
// 腾讯云镜像
maven {
name = 'Tencent'
url = 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/'
}
// Fabric 官方源
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
// 阿里云镜像 - 用于通用依赖
maven {
name = 'Aliyun'
url = 'https://maven.aliyun.com/repository/central'
}
maven {
name = 'AliyunPublic'
url = 'https://maven.aliyun.com/repository/public'
}
// 腾讯云镜像
maven {
name = 'Tencent'
url = 'https://mirrors.cloud.tencent.com/nexus/repository/maven-public/'
}
// Modrinth 镜像 - 用于 Minecraft 模组相关依赖
maven {
name = 'Modrinth'
url = 'https://api.modrinth.com/maven'
}
// Fabric 官方源 - 用于 Fabric 相关依赖
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
}
}
// Should match your modid
rootProject.name = 'cardinal-cooperative-capitalism-market-economy'

View File

@@ -0,0 +1,124 @@
package com.gvsds.tcme;
import com.gvsds.tcme.command.CMECommand;
import com.gvsds.tcme.config.Config;
import com.gvsds.tcme.config.ConfigManager;
import com.gvsds.tcme.database.DatabaseManager;
import com.gvsds.tcme.gui.InputScreenHandler;
import com.gvsds.tcme.manager.BalanceManager;
import com.gvsds.tcme.manager.CurrencyManager;
import com.gvsds.tcme.manager.ExchangeRateManager;
import com.gvsds.tcme.manager.PaymentRequestManager;
import com.gvsds.tcme.manager.PlayerSettingsManager;
import com.gvsds.tcme.manager.TransactionManager;
import com.gvsds.tcme.i18n.LanguageManager;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CardinalCooperativeCapitalismMarketEconomy implements ModInitializer {
public static final String MOD_ID = "cardinal-cooperative-capitalism-market-economy";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
private static ConfigManager configManager;
private static DatabaseManager databaseManager;
private static CurrencyManager currencyManager;
private static BalanceManager balanceManager;
private static TransactionManager transactionManager;
private static PaymentRequestManager paymentRequestManager;
private static PlayerSettingsManager playerSettingsManager;
private static LanguageManager languageManager;
private static ExchangeRateManager exchangeRateManager;
@Override
public void onInitialize() {
LOGGER.info("Initializing Cardinal Cooperative Capitalism: Market Economy");
configManager = new ConfigManager();
LOGGER.info("Configuration loaded successfully");
CMECommand command = new CMECommand();
command.register();
LOGGER.info("CME command tree registered via CommandRegistrationCallback");
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
LOGGER.info("Server starting, initializing database and managers...");
Config.Database dbConfig = configManager.getConfig().getDatabase();
databaseManager = new DatabaseManager(dbConfig);
databaseManager.connect();
if (databaseManager.isConnected()) {
boolean isMySQL = "mysql".equalsIgnoreCase(dbConfig.getType());
Config.Tables tables = dbConfig.getTables();
currencyManager = new CurrencyManager(databaseManager, tables);
balanceManager = new BalanceManager(databaseManager, tables, isMySQL);
transactionManager = new TransactionManager(databaseManager, tables, balanceManager, isMySQL);
paymentRequestManager = new PaymentRequestManager(databaseManager, tables, balanceManager, transactionManager, isMySQL);
playerSettingsManager = new PlayerSettingsManager(databaseManager, tables, isMySQL);
exchangeRateManager = new ExchangeRateManager(databaseManager, tables, isMySQL);
languageManager = new LanguageManager(databaseManager, isMySQL);
CMECommand.setManagers(currencyManager, balanceManager, transactionManager, paymentRequestManager, playerSettingsManager, languageManager, exchangeRateManager);
LOGGER.info("Managers initialized and set to CMECommand");
}
});
ServerMessageEvents.CHAT_MESSAGE.register((message, sender, params) -> {
String text = message.getContent().getString();
if (InputScreenHandler.hasSession(sender.getUuid())) {
InputScreenHandler.submitInput(sender, text);
}
});
ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
LOGGER.info("Server stopping, closing database connection...");
if (databaseManager != null) {
databaseManager.disconnect();
}
});
LOGGER.info("Initialization complete!");
}
public static ConfigManager getConfigManager() {
return configManager;
}
public static DatabaseManager getDatabaseManager() {
return databaseManager;
}
public static CurrencyManager getCurrencyManager() {
return currencyManager;
}
public static BalanceManager getBalanceManager() {
return balanceManager;
}
public static TransactionManager getTransactionManager() {
return transactionManager;
}
public static PaymentRequestManager getPaymentRequestManager() {
return paymentRequestManager;
}
public static PlayerSettingsManager getPlayerSettingsManager() {
return playerSettingsManager;
}
public static LanguageManager getLanguageManager() {
return languageManager;
}
public static ExchangeRateManager getExchangeRateManager() {
return exchangeRateManager;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
package com.gvsds.tcme.config;
import java.util.List;
public class Config {
public static class Tables {
private String currencies = "cme_currencies";
private String balances = "cme_balances";
private String transactions = "cme_transactions";
private String paymentRequests = "cme_payment_requests";
public String getCurrencies() { return currencies; }
public void setCurrencies(String currencies) { this.currencies = currencies; }
public String getBalances() { return balances; }
public void setBalances(String balances) { this.balances = balances; }
public String getTransactions() { return transactions; }
public void setTransactions(String transactions) { this.transactions = transactions; }
public String getPaymentRequests() { return paymentRequests; }
public void setPaymentRequests(String paymentRequests) { this.paymentRequests = paymentRequests; }
}
public static class Database {
private String type = "sqlite";
private String sqlitePath = "./config/cardinal-economy/database";
private String mysqlHost = "localhost";
private int mysqlPort = 3306;
private String mysqlDatabase = "cardinal_economy";
private String mysqlUsername = "root";
private String mysqlPassword = "";
private boolean useSSL = false;
private Tables tables = new Tables();
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getSqlitePath() { return sqlitePath; }
public void setSqlitePath(String sqlitePath) { this.sqlitePath = sqlitePath; }
public String getMysqlHost() { return mysqlHost; }
public void setMysqlHost(String mysqlHost) { this.mysqlHost = mysqlHost; }
public int getMysqlPort() { return mysqlPort; }
public void setMysqlPort(int mysqlPort) { this.mysqlPort = mysqlPort; }
public String getMysqlDatabase() { return mysqlDatabase; }
public void setMysqlDatabase(String mysqlDatabase) { this.mysqlDatabase = mysqlDatabase; }
public String getMysqlUsername() { return mysqlUsername; }
public void setMysqlUsername(String mysqlUsername) { this.mysqlUsername = mysqlUsername; }
public String getMysqlPassword() { return mysqlPassword; }
public void setMysqlPassword(String mysqlPassword) { this.mysqlPassword = mysqlPassword; }
public boolean isUseSSL() { return useSSL; }
public void setUseSSL(boolean useSSL) { this.useSSL = useSSL; }
public Tables getTables() { return tables; }
public void setTables(Tables tables) { this.tables = tables; }
}
private Database database = new Database();
public Database getDatabase() { return database; }
public void setDatabase(Database database) { this.database = database; }
}

View File

@@ -0,0 +1,55 @@
package com.gvsds.tcme.config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ConfigManager {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Path CONFIG_DIR = Path.of("config", "cardinal-economy");
private static final Path CONFIG_FILE = CONFIG_DIR.resolve("config.json");
private Config config;
public ConfigManager() {
loadConfig();
}
private void loadConfig() {
try {
if (!Files.exists(CONFIG_DIR)) {
Files.createDirectories(CONFIG_DIR);
}
if (Files.exists(CONFIG_FILE)) {
String json = Files.readString(CONFIG_FILE);
config = GSON.fromJson(json, Config.class);
} else {
config = new Config();
saveConfig();
}
} catch (IOException e) {
throw new RuntimeException("Failed to load config", e);
}
}
public void saveConfig() {
try {
if (!Files.exists(CONFIG_DIR)) {
Files.createDirectories(CONFIG_DIR);
}
String json = GSON.toJson(config);
Files.writeString(CONFIG_FILE, json);
} catch (IOException e) {
throw new RuntimeException("Failed to save config", e);
}
}
public Config getConfig() {
return config;
}
}

View File

@@ -0,0 +1,187 @@
package com.gvsds.tcme.database;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.config.Config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DatabaseManager {
private Config.Database dbConfig;
private boolean initialized = false;
private HikariDataSource dataSource;
public DatabaseManager(Config.Database dbConfig) {
this.dbConfig = dbConfig;
}
public void connect() {
try {
HikariConfig hikariConfig = new HikariConfig();
if ("sqlite".equalsIgnoreCase(dbConfig.getType())) {
String dbPath = dbConfig.getSqlitePath();
java.nio.file.Path path = java.nio.file.Path.of(dbPath);
try {
java.nio.file.Files.createDirectories(path);
} catch (java.io.IOException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to create SQLite directory", e);
}
String jdbcUrl = "jdbc:sqlite:" + dbPath + "/cardinal_economy.db";
hikariConfig.setJdbcUrl(jdbcUrl);
hikariConfig.setDriverClassName("org.sqlite.JDBC");
hikariConfig.setMaximumPoolSize(1);
hikariConfig.setConnectionTimeout(10000);
hikariConfig.setAutoCommit(false);
} else if ("mysql".equalsIgnoreCase(dbConfig.getType())) {
String jdbcUrl = String.format("jdbc:mysql://%s:%d/%s?useSSL=%s&allowPublicKeyRetrieval=true&serverTimezone=UTC",
dbConfig.getMysqlHost(),
dbConfig.getMysqlPort(),
dbConfig.getMysqlDatabase(),
dbConfig.isUseSSL());
hikariConfig.setJdbcUrl(jdbcUrl);
hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariConfig.setUsername(dbConfig.getMysqlUsername());
hikariConfig.setPassword(dbConfig.getMysqlPassword());
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setMinimumIdle(2);
hikariConfig.setConnectionTimeout(30000);
hikariConfig.setIdleTimeout(600000);
hikariConfig.setMaxLifetime(1800000);
hikariConfig.setAutoCommit(false);
hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
} else {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Unknown database type: {}", dbConfig.getType());
return;
}
hikariConfig.setPoolName("3CME-Pool");
this.dataSource = new HikariDataSource(hikariConfig);
try (Connection testConn = dataSource.getConnection()) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Successfully connected to {} database (HikariCP pool initialized)", dbConfig.getType());
}
initialized = true;
initializeTables();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to connect to database", e);
}
}
private String getJdbcUrl(Config.Database dbConfig) {
if ("sqlite".equalsIgnoreCase(dbConfig.getType())) {
String dbPath = dbConfig.getSqlitePath();
return "jdbc:sqlite:" + dbPath + "/cardinal_economy.db";
} else if ("mysql".equalsIgnoreCase(dbConfig.getType())) {
return String.format("jdbc:mysql://%s:%d/%s?useSSL=%s&allowPublicKeyRetrieval=true&serverTimezone=UTC",
dbConfig.getMysqlHost(),
dbConfig.getMysqlPort(),
dbConfig.getMysqlDatabase(),
dbConfig.isUseSSL());
}
return "";
}
private void initializeTables() {
try (Connection conn = getConnection()) {
try (var statement = conn.createStatement()) {
String dbType = dbConfig.getType();
boolean isMySQL = "mysql".equalsIgnoreCase(dbType);
String uuidType = isMySQL ? "BINARY(16)" : "BLOB";
String textType = isMySQL ? "VARCHAR(255)" : "TEXT";
String autoIncrement = isMySQL ? "AUTO_INCREMENT" : "AUTOINCREMENT";
String currentTimestamp = isMySQL ? "CURRENT_TIMESTAMP" : "CURRENT_TIMESTAMP";
String engineClause = isMySQL ? "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" : "";
String currenciesTable = String.format("""
CREATE TABLE IF NOT EXISTS %s (
id INTEGER PRIMARY KEY %s,
name %s NOT NULL UNIQUE,
symbol %s NOT NULL,
tradable INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT %s
) %s
""", dbConfig.getTables().getCurrencies(), autoIncrement, textType, textType, currentTimestamp, engineClause);
String balancesTable = String.format("""
CREATE TABLE IF NOT EXISTS %s (
id INTEGER PRIMARY KEY %s,
user_uuid %s NOT NULL,
currency_id INTEGER NOT NULL,
balance REAL DEFAULT 0.0,
created_at TIMESTAMP DEFAULT %s,
updated_at TIMESTAMP DEFAULT %s,
FOREIGN KEY (currency_id) REFERENCES %s(id),
UNIQUE(user_uuid, currency_id)
) %s
""", dbConfig.getTables().getBalances(), autoIncrement, uuidType, currentTimestamp, currentTimestamp,
dbConfig.getTables().getCurrencies(), engineClause);
String transactionsTable = String.format("""
CREATE TABLE IF NOT EXISTS %s (
id INTEGER PRIMARY KEY %s,
from_uuid %s,
to_uuid %s,
currency_id INTEGER NOT NULL,
amount REAL NOT NULL,
type %s NOT NULL,
description %s,
created_at TIMESTAMP DEFAULT %s,
FOREIGN KEY (currency_id) REFERENCES %s(id)
) %s
""", dbConfig.getTables().getTransactions(), autoIncrement, uuidType, uuidType,
textType, textType, currentTimestamp, dbConfig.getTables().getCurrencies(), engineClause);
String paymentRequestsTable = String.format("""
CREATE TABLE IF NOT EXISTS %s (
id INTEGER PRIMARY KEY %s,
from_uuid %s NOT NULL,
to_uuid %s NOT NULL,
currency_id INTEGER NOT NULL,
amount REAL NOT NULL,
status %s DEFAULT 'pending',
created_at TIMESTAMP DEFAULT %s,
FOREIGN KEY (currency_id) REFERENCES %s(id)
) %s
""", dbConfig.getTables().getPaymentRequests(), autoIncrement, uuidType, uuidType,
textType, currentTimestamp, dbConfig.getTables().getCurrencies(), engineClause);
statement.execute(currenciesTable);
statement.execute(balancesTable);
statement.execute(transactionsTable);
statement.execute(paymentRequestsTable);
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Database tables initialized");
}
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to initialize database tables", e);
}
}
public Connection getConnection() throws SQLException {
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
return conn;
}
public void disconnect() {
initialized = false;
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("HikariCP datasource closed");
}
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Database manager disconnected");
}
public boolean isConnected() {
return initialized && dataSource != null && !dataSource.isClosed();
}
}

View File

@@ -0,0 +1,106 @@
package com.gvsds.tcme.gui;
import com.gvsds.tcme.util.TextUtil;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.packet.s2c.play.CloseScreenS2CPacket;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.util.function.Consumer;
public class AlertScreenHandler extends ScreenHandler {
private final GuiInventory inventory;
private final Consumer<Boolean> callback;
private boolean closed = false;
public AlertScreenHandler(int syncId, PlayerInventory playerInventory, String message, Consumer<Boolean> onClose) {
super(ScreenHandlerType.GENERIC_9X3, syncId);
this.callback = onClose;
this.inventory = new GuiInventory(27);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
this.addSlot(new Slot(this.inventory, col + row * 9, 8 + col * 18, 18 + row * 18) {
@Override
public boolean canInsert(ItemStack stack) {
return false;
}
@Override
public boolean canTakeItems(PlayerEntity playerEntity) {
return false;
}
});
}
}
ItemStack infoItem = new ItemStack(Items.PAPER);
infoItem.set(DataComponentTypes.CUSTOM_NAME, TextUtil.parseFormatting(message));
inventory.setSlot(13, infoItem);
ItemStack closeItem = new ItemStack(Items.RED_WOOL);
closeItem.set(DataComponentTypes.CUSTOM_NAME, Text.literal("[ Close ]").formatted(net.minecraft.util.Formatting.RED));
inventory.setSlot(22, closeItem);
for (int i = 0; i < 9; i++) {
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
}
}
@Override
public ItemStack quickMove(PlayerEntity player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean canUse(PlayerEntity player) {
return true;
}
private void closeGui(PlayerEntity player) {
if (closed) return;
closed = true;
if (player instanceof ServerPlayerEntity serverPlayer) {
serverPlayer.networkHandler.sendPacket(new CloseScreenS2CPacket(this.syncId));
}
}
@Override
public void onSlotClick(int slotIndex, int button, SlotActionType actionType, PlayerEntity player) {
if (closed) return;
if (slotIndex == 22 && actionType == SlotActionType.PICKUP) {
closeGui(player);
if (callback != null) callback.accept(true);
return;
}
if (slotIndex >= 0 && slotIndex < 27 && actionType == SlotActionType.PICKUP) {
closeGui(player);
if (callback != null) callback.accept(true);
return;
}
if (player instanceof ServerPlayerEntity sp) {
sp.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(this.syncId, 0, slotIndex, this.getSlot(slotIndex).getStack()));
}
}
@Override
public void onClosed(PlayerEntity player) {
if (!closed) {
closed = true;
if (callback != null) callback.accept(true);
}
super.onClosed(player);
}
}

View File

@@ -0,0 +1,117 @@
package com.gvsds.tcme.gui;
import com.gvsds.tcme.util.TextUtil;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.packet.s2c.play.CloseScreenS2CPacket;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.util.function.Consumer;
public class ConfirmScreenHandler extends ScreenHandler {
private final GuiInventory inventory;
private final Consumer<Boolean> callback;
private boolean responded = false;
public ConfirmScreenHandler(int syncId, PlayerInventory playerInventory, String message, Consumer<Boolean> onConfirm) {
super(ScreenHandlerType.GENERIC_9X3, syncId);
this.callback = onConfirm;
this.inventory = new GuiInventory(27);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
this.addSlot(new Slot(this.inventory, col + row * 9, 8 + col * 18, 18 + row * 18) {
@Override
public boolean canInsert(ItemStack stack) {
return false;
}
@Override
public boolean canTakeItems(PlayerEntity playerEntity) {
return false;
}
});
}
}
ItemStack infoItem = new ItemStack(Items.BOOK);
infoItem.set(DataComponentTypes.CUSTOM_NAME, TextUtil.parseFormatting(message));
inventory.setSlot(13, infoItem);
ItemStack confirmItem = new ItemStack(Items.LIME_WOOL);
confirmItem.set(DataComponentTypes.CUSTOM_NAME, Text.literal("[ Confirm ]").formatted(Formatting.GREEN));
inventory.setSlot(20, confirmItem);
ItemStack cancelItem = new ItemStack(Items.RED_WOOL);
cancelItem.set(DataComponentTypes.CUSTOM_NAME, Text.literal("[ Cancel ]").formatted(Formatting.RED));
inventory.setSlot(24, cancelItem);
for (int i = 0; i < 9; i++) {
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
}
}
@Override
public ItemStack quickMove(PlayerEntity player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean canUse(PlayerEntity player) {
return true;
}
private void closeGui(PlayerEntity player) {
if (player instanceof ServerPlayerEntity serverPlayer) {
serverPlayer.networkHandler.sendPacket(new CloseScreenS2CPacket(this.syncId));
}
}
@Override
public void onSlotClick(int slotIndex, int button, SlotActionType actionType, PlayerEntity player) {
if (responded) {
closeGui(player);
return;
}
if (actionType != SlotActionType.PICKUP) {
if (player instanceof ServerPlayerEntity sp) {
sp.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(this.syncId, 0, slotIndex, this.getSlot(slotIndex).getStack()));
}
return;
}
if (slotIndex == 20) {
responded = true;
closeGui(player);
if (callback != null) callback.accept(true);
} else if (slotIndex == 24) {
responded = true;
closeGui(player);
if (callback != null) callback.accept(false);
} else {
if (player instanceof ServerPlayerEntity sp) {
sp.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(this.syncId, 0, slotIndex, this.getSlot(slotIndex).getStack()));
}
}
}
@Override
public void onClosed(PlayerEntity player) {
if (!responded) {
responded = true;
if (callback != null) callback.accept(false);
}
super.onClosed(player);
}
}

View File

@@ -0,0 +1,87 @@
package com.gvsds.tcme.gui;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
public class GuiInventory implements Inventory {
private final ItemStack[] items;
public GuiInventory(int size) {
this.items = new ItemStack[size];
for (int i = 0; i < size; i++) {
items[i] = ItemStack.EMPTY;
}
}
public void setSlot(int index, ItemStack stack) {
if (index >= 0 && index < items.length) {
items[index] = stack;
}
}
public void setSlot(int index, net.minecraft.item.Item item) {
setSlot(index, new ItemStack(item));
}
public void setSlot(int index, net.minecraft.item.Item item, int count) {
setSlot(index, new ItemStack(item, count));
}
@Override
public int size() {
return items.length;
}
@Override
public boolean isEmpty() {
for (ItemStack stack : items) {
if (!stack.isEmpty()) return false;
}
return true;
}
@Override
public ItemStack getStack(int slot) {
if (slot >= 0 && slot < items.length) {
return items[slot];
}
return ItemStack.EMPTY;
}
@Override
public ItemStack removeStack(int slot, int amount) {
return ItemStack.EMPTY;
}
@Override
public ItemStack removeStack(int slot) {
return ItemStack.EMPTY;
}
@Override
public void setStack(int slot, ItemStack stack) {
if (slot >= 0 && slot < items.length) {
items[slot] = stack.copy();
}
}
@Override
public void markDirty() {}
@Override
public boolean canPlayerUse(PlayerEntity player) {
return true;
}
@Override
public void clear() {
for (int i = 0; i < items.length; i++) {
items[i] = ItemStack.EMPTY;
}
}
}

View File

@@ -0,0 +1,151 @@
package com.gvsds.tcme.gui;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.packet.s2c.play.CloseScreenS2CPacket;
import net.minecraft.screen.AnvilScreenHandler;
import net.minecraft.screen.ScreenHandlerContext;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.server.network.ServerPlayerEntity;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
public class InputScreenHandler extends AnvilScreenHandler {
private static final Map<UUID, InputSession> sessions = new HashMap<>();
private final UUID playerId;
private boolean closed = false;
public InputScreenHandler(int syncId, PlayerInventory playerInventory, String hint, Consumer<String> callback, ServerPlayerEntity sp) {
super(syncId, playerInventory, ScreenHandlerContext.create(sp.getEntityWorld(), sp.getBlockPos()));
this.playerId = sp.getUuid();
InputSession session = new InputSession(syncId, callback);
sessions.put(this.playerId, session);
ItemStack hintItem = new ItemStack(Items.PAPER);
hintItem.setCount(1);
this.getSlot(0).setStack(hintItem);
}
public static boolean hasSession(UUID playerId) {
InputSession session = sessions.get(playerId);
return session != null && !session.responded;
}
public static void submitInput(ServerPlayerEntity player, String text) {
InputSession session = sessions.get(player.getUuid());
if (session != null && !session.responded) {
session.responded = true;
player.networkHandler.sendPacket(new CloseScreenS2CPacket(session.syncId));
if (session.callback != null) {
session.callback.accept(text);
}
sessions.remove(player.getUuid());
}
}
@Override
public void onSlotClick(int slotIndex, int button, SlotActionType actionType, PlayerEntity player) {
if (slotIndex == 2) {
if (player instanceof ServerPlayerEntity sp && !closed) {
ItemStack result = this.getSlot(2).getStack();
if (!result.isEmpty()) {
closed = true;
InputSession session = sessions.get(sp.getUuid());
if (session != null && !session.responded) {
session.responded = true;
String text = result.getName().getString();
if (session.callback != null) {
session.callback.accept(text);
}
sp.networkHandler.sendPacket(new CloseScreenS2CPacket(session.syncId));
sessions.remove(sp.getUuid());
}
}
}
return;
}
if (slotIndex == 0 || slotIndex == 1) {
return;
}
super.onSlotClick(slotIndex, button, actionType, player);
}
@Override
public void onClosed(PlayerEntity player) {
if (player instanceof ServerPlayerEntity sp) {
InputSession session = sessions.get(sp.getUuid());
if (session != null && !session.responded) {
session.responded = true;
if (session.callback != null) {
session.callback.accept(null);
}
}
this.getSlot(0).setStack(ItemStack.EMPTY);
this.getSlot(1).setStack(ItemStack.EMPTY);
this.getSlot(2).setStack(ItemStack.EMPTY);
sessions.remove(sp.getUuid());
}
super.onClosed(player);
}
@Override
public ItemStack quickMove(PlayerEntity player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean canUse(PlayerEntity player) {
return true;
}
@Override
public boolean canTakeOutput(PlayerEntity player, boolean present) {
return false;
}
@Override
public boolean onButtonClick(PlayerEntity player, int id) {
if (id == 0 && !closed) {
ItemStack result = this.getSlot(2).getStack();
if (!result.isEmpty()) {
if (player instanceof ServerPlayerEntity sp) {
closed = true;
InputSession session = sessions.get(sp.getUuid());
if (session != null && !session.responded) {
session.responded = true;
String text = result.getName().getString();
if (session.callback != null) {
session.callback.accept(text);
}
sp.networkHandler.sendPacket(new CloseScreenS2CPacket(session.syncId));
sessions.remove(sp.getUuid());
return true;
}
}
}
}
return false;
}
@Override
public void onTakeOutput(PlayerEntity player, ItemStack stack) {
}
public static class InputSession {
public final int syncId;
public boolean responded;
public final Consumer<String> callback;
public InputSession(int syncId, Consumer<String> callback) {
this.syncId = syncId;
this.callback = callback;
}
}
}

View File

@@ -0,0 +1,66 @@
package com.gvsds.tcme.gui;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.server.network.ServerPlayerEntity;
public class LargeChestScreenHandler extends ScreenHandler {
private final GuiInventory inventory;
public LargeChestScreenHandler(int syncId, PlayerInventory playerInventory, GuiInventory guiInventory) {
super(ScreenHandlerType.GENERIC_9X6, syncId);
this.inventory = guiInventory;
for (int row = 0; row < 6; ++row) {
for (int col = 0; col < 9; ++col) {
this.addSlot(new Slot(this.inventory, col + row * 9, 8 + col * 18, 18 + row * 18) {
@Override
public boolean canInsert(ItemStack stack) {
return false;
}
@Override
public boolean canTakeItems(PlayerEntity playerEntity) {
return false;
}
});
}
}
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
this.addSlot(new Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, 140 + row * 18));
}
}
for (int i = 0; i < 9; i++) {
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 198));
}
}
@Override
public ItemStack quickMove(PlayerEntity player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean canUse(PlayerEntity player) {
return true;
}
@Override
public void onSlotClick(int slotIndex, int button, SlotActionType actionType, PlayerEntity player) {
if (player instanceof ServerPlayerEntity sp) {
if (slotIndex >= 0 && slotIndex < 54) {
sp.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(this.syncId, 0, slotIndex, this.getSlot(slotIndex).getStack()));
}
sp.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(this.syncId, -1, -1, ItemStack.EMPTY));
}
}
}

View File

@@ -0,0 +1,134 @@
package com.gvsds.tcme.gui;
import com.gvsds.tcme.util.TextUtil;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.AnvilScreenHandler;
import net.minecraft.screen.ScreenHandlerContext;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
public class ServerGuiAPI {
public static void showAlert(ServerPlayerEntity player, String title, String message) {
showAlert(player, title, message, null);
}
public static void showAlert(ServerPlayerEntity player, String title, String message, Consumer<Boolean> onClose) {
NamedScreenHandlerFactory factory = new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return TextUtil.parseFormatting(title);
}
@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInv, net.minecraft.entity.player.PlayerEntity player) {
return new AlertScreenHandler(syncId, playerInv, message, onClose);
}
};
player.openHandledScreen(factory);
}
public static void showConfirm(ServerPlayerEntity player, String title, String message, Consumer<Boolean> onConfirm) {
NamedScreenHandlerFactory factory = new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return TextUtil.parseFormatting(title);
}
@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInv, net.minecraft.entity.player.PlayerEntity player) {
return new ConfirmScreenHandler(syncId, playerInv, message, onConfirm);
}
};
player.openHandledScreen(factory);
}
public static void showInputBox(ServerPlayerEntity player, String title, String hint, Consumer<String> onSubmit) {
NamedScreenHandlerFactory factory = new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return TextUtil.parseFormatting(title);
}
@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInv, net.minecraft.entity.player.PlayerEntity player) {
return new InputScreenHandler(syncId, playerInv, hint, onSubmit, player instanceof ServerPlayerEntity ? (ServerPlayerEntity) player : null);
}
};
player.openHandledScreen(factory);
}
public static void showSmallChest(ServerPlayerEntity player, String title, Consumer<Integer> onSlotClick) {
GuiInventory inventory = new GuiInventory(9);
player.openHandledScreen(new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return TextUtil.parseFormatting(title);
}
@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInv, net.minecraft.entity.player.PlayerEntity player) {
return new SmallChestScreenHandler(syncId, playerInv, inventory);
}
});
}
public static void showMediumChest(ServerPlayerEntity player, String title, Consumer<Integer> onSlotClick) {
GuiInventory inventory = new GuiInventory(27);
player.openHandledScreen(new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return TextUtil.parseFormatting(title);
}
@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInv, net.minecraft.entity.player.PlayerEntity player) {
return GenericContainerScreenHandler.createGeneric9x3(syncId, playerInv, inventory);
}
});
}
public static void showLargeChest(ServerPlayerEntity player, String title, Consumer<Integer> onSlotClick) {
GuiInventory inventory = new GuiInventory(54);
player.openHandledScreen(new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return TextUtil.parseFormatting(title);
}
@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInv, net.minecraft.entity.player.PlayerEntity player) {
return new LargeChestScreenHandler(syncId, playerInv, inventory);
}
});
}
public static void showAnvil(ServerPlayerEntity player, String title) {
player.openHandledScreen(new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return TextUtil.parseFormatting(title);
}
@Nullable
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory playerInv, net.minecraft.entity.player.PlayerEntity player) {
ServerPlayerEntity sp = (ServerPlayerEntity) player;
return new AnvilScreenHandler(syncId, playerInv,
ScreenHandlerContext.create(sp.getEntityWorld(), sp.getBlockPos()));
}
});
}
}

View File

@@ -0,0 +1,60 @@
package com.gvsds.tcme.gui;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.server.network.ServerPlayerEntity;
public class SmallChestScreenHandler extends ScreenHandler {
private final GuiInventory inventory;
public SmallChestScreenHandler(int syncId, PlayerInventory playerInventory, GuiInventory guiInventory) {
super(ScreenHandlerType.GENERIC_9X1, syncId);
this.inventory = guiInventory;
for (int col = 0; col < 9; ++col) {
this.addSlot(new Slot(this.inventory, col, 8 + col * 18, 18) {
@Override
public boolean canInsert(ItemStack stack) {
return false;
}
@Override
public boolean canTakeItems(PlayerEntity playerEntity) {
return false;
}
});
}
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 9; ++col) {
this.addSlot(new Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, 84 + row * 18));
}
}
}
@Override
public ItemStack quickMove(PlayerEntity player, int index) {
return ItemStack.EMPTY;
}
@Override
public boolean canUse(PlayerEntity player) {
return true;
}
@Override
public void onSlotClick(int slotIndex, int button, SlotActionType actionType, PlayerEntity player) {
if (player instanceof ServerPlayerEntity sp) {
if (slotIndex >= 0 && slotIndex < 9) {
sp.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(this.syncId, 0, slotIndex, this.getSlot(slotIndex).getStack()));
}
sp.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(this.syncId, -1, -1, ItemStack.EMPTY));
}
}
}

View File

@@ -0,0 +1,225 @@
package com.gvsds.tcme.i18n;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.database.DatabaseManager;
import java.nio.ByteBuffer;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class LanguageManager {
private static final Map<String, Map<String, String>> LANGUAGES = new HashMap<>();
private final Map<UUID, String> playerLanguages = new HashMap<>();
private DatabaseManager dbManager;
private boolean isMySQL;
public LanguageManager(DatabaseManager dbManager, boolean isMySQL) {
this.dbManager = dbManager;
this.isMySQL = isMySQL;
loadLanguages();
loadAllPlayerLanguages();
}
private void loadLanguages() {
Map<String, String> enUS = new HashMap<>();
enUS.put("cme.wallet.title", "=== Your Wallet ===");
enUS.put("cme.wallet.balance", " %s: %.2f %s");
enUS.put("cme.wallet.empty", "Your wallet is empty.");
enUS.put("cme.lang.changed", "Language changed to %s");
enUS.put("cme.lang.available", "Available languages: %s");
enUS.put("cme.lang.notfound", "Language not found: %s");
enUS.put("cme.currency.created", "Created currency: %s (%s) - Tradable: %s");
enUS.put("cme.currency.create_failed", "Failed to create currency: %s");
enUS.put("cme.currency.deleted", "Deleted currency: %s");
enUS.put("cme.currency.delete_failed", "Failed to delete currency: %s");
enUS.put("cme.currency.tradable", "Currency %s tradable set to: %s");
enUS.put("cme.currency.tradable_failed", "Failed to set tradable for currency: %s");
enUS.put("cme.currency.list", "Currencies:");
enUS.put("cme.currency.not_found", "Currency not found: %s");
enUS.put("cme.currency.no_currencies", "No currencies found");
enUS.put("cme.currency.no_available", "No currencies available");
enUS.put("cme.currency.not_tradable", "Currency %s does not allow free trading");
enUS.put("cme.balance.check", "%s's balance: %.2f %s");
enUS.put("cme.balance.added", "Added %.2f %s to %s");
enUS.put("cme.balance.removed", "Removed %.2f %s from %s");
enUS.put("cme.balance.failed", "Failed to %s balance");
enUS.put("cme.pay.success", "Successfully paid %.2f %s to %s");
enUS.put("cme.pay.received", "%s paid you %.2f %s");
enUS.put("cme.pay.failed", "Payment failed. Insufficient balance or other error");
enUS.put("cme.pay.self", "Cannot pay yourself");
enUS.put("cme.apay.sent", "Payment request sent to %s: %.2f %s [ID: %s]");
enUS.put("cme.apay.received", "%s requested %.2f %s from you [ID: %s]");
enUS.put("cme.apay.self", "Cannot request payment from yourself");
enUS.put("cme.apay.failed", "Failed to create payment request");
enUS.put("cme.requests.list", "Pending payment requests:");
enUS.put("cme.requests.empty", "No pending payment requests");
enUS.put("cme.requests.item", " [ID: %s] Requested: %.2f (Currency: %s)");
enUS.put("cme.requests.accepted", "Payment request #%s accepted");
enUS.put("cme.requests.accept_failed", "Failed to accept payment request #%s");
enUS.put("cme.requests.cancelled", "Payment request #%s cancelled");
enUS.put("cme.requests.cancel_failed", "Failed to cancel payment request #%s");
enUS.put("cme.currency.renamed", "Renamed currency: %s -> %s");
enUS.put("cme.currency.rename_failed", "Failed to rename currency: %s");
enUS.put("cme.currency.updated", "Updated currency %s: symbol=%s, tradable=%s");
enUS.put("cme.currency.update_failed", "Failed to update currency: %s");
enUS.put("cme.currency.list_item", " %s (%s) - Tradable: %s - Rate: %.2f");
enUS.put("cme.lang.usage", "Use /cme lang <language> to change");
enUS.put("cme.exchange_rate.set", "Exchange rate for %s set to: %.2f");
enUS.put("cme.exchange_rate.list", "Exchange Rates (relative to base):");
enUS.put("cme.exchange_rate.item", " %s: %.2f");
enUS.put("cme.convert.invalid_amount", "Amount must be greater than 0");
enUS.put("cme.convert.same_currency", "Cannot convert to the same currency");
enUS.put("cme.convert.insufficient", "Insufficient balance. You have %s %s, but need %s");
enUS.put("cme.convert.title", "=== Currency Conversion Preview ===");
enUS.put("cme.convert.from", " From: %s %s (%s)");
enUS.put("cme.convert.to", " To: %s %s (%s)");
enUS.put("cme.convert.rate", " Rate: %s : %s = %.2f : %.2f");
enUS.put("cme.convert.balance_from", " Current %s balance: %s");
enUS.put("cme.convert.balance_to", " Total %s balance after conversion: %s");
enUS.put("cme.convert.prompt", "Use /cme convert %s %s %s confirm to proceed");
enUS.put("cme.convert.success", "Successfully converted %s %s to %s %s");
enUS.put("cme.convert.failed", "Conversion failed. Please try again");
enUS.put("cme.convert.refunded", "Conversion failed. Your balance has been refunded");
enUS.put("cme.requests.rejected", "Rejected payment request #%s");
enUS.put("cme.requests.reject_failed", "Failed to reject payment request #%s");
enUS.put("cme.requests.rejected_by", "%s rejected your payment request #%s: %.2f %s");
enUS.put("cme.requests.revoked", "Revoked payment request #%s");
enUS.put("cme.requests.revoke_failed", "Failed to revoke payment request #%s");
enUS.put("cme.requests.revoked_by", "%s revoked payment request #%s: %.2f %s");
enUS.put("cme.requests.accepted_by", "%s accepted your payment request #%s: %.2f %s");
enUS.put("cme.requests.not_yours", "This is not your request");
enUS.put("cme.requests.not_pending", "This request is no longer pending");
LANGUAGES.put("en_us", enUS);
Map<String, String> zhCN = new HashMap<>();
zhCN.put("cme.wallet.title", "=== 你的钱包 ===");
zhCN.put("cme.wallet.balance", " %s: %.2f %s");
zhCN.put("cme.wallet.empty", "你的钱包是空的。");
zhCN.put("cme.lang.changed", "语言已更改为 %s");
zhCN.put("cme.lang.available", "可用语言: %s");
zhCN.put("cme.lang.notfound", "未找到语言: %s");
zhCN.put("cme.currency.created", "创建币种: %s (%s) - 可自由交易: %s");
zhCN.put("cme.currency.create_failed", "创建币种失败: %s");
zhCN.put("cme.currency.deleted", "已删除ID为 %s 的币种");
zhCN.put("cme.currency.delete_failed", "删除币种失败: %s");
zhCN.put("cme.currency.tradable", "币种 %s 的自由交易设置为: %s");
zhCN.put("cme.currency.tradable_failed", "设置币种自由交易失败: %s");
zhCN.put("cme.currency.list", "币种列表:");
zhCN.put("cme.currency.not_found", "未找到币种: %s");
zhCN.put("cme.currency.no_currencies", "未找到任何币种");
zhCN.put("cme.currency.no_available", "没有可用的币种");
zhCN.put("cme.currency.not_tradable", "币种 %s 不允许自由交易");
zhCN.put("cme.balance.check", "%s 的余额: %.2f %s");
zhCN.put("cme.balance.added", "已添加 %.2f %s 到 %s");
zhCN.put("cme.balance.removed", "已移除 %.2f %s (来自 %s)");
zhCN.put("cme.balance.failed", "余额操作失败");
zhCN.put("cme.pay.success", "成功支付 %.2f %s 给 %s");
zhCN.put("cme.pay.received", "%s 向你支付了 %.2f %s");
zhCN.put("cme.pay.failed", "支付失败,余额不足或其他错误");
zhCN.put("cme.pay.self", "不能支付给自己");
zhCN.put("cme.apay.sent", "已向 %s 发送支付请求: %.2f %s [ID: %s]");
zhCN.put("cme.apay.received", "%s 请求你支付 %.2f %s [ID: %s]");
zhCN.put("cme.apay.self", "不能向自己请求支付");
zhCN.put("cme.apay.failed", "创建支付请求失败");
zhCN.put("cme.requests.list", "待处理的支付请求:");
zhCN.put("cme.requests.empty", "没有待处理的支付请求");
zhCN.put("cme.requests.item", " [ID: %s] 请求金额: %.2f (币种: %s)");
zhCN.put("cme.requests.accepted", "已接受支付请求 #%s");
zhCN.put("cme.requests.accept_failed", "接受支付请求 #%s 失败");
zhCN.put("cme.requests.cancelled", "已取消支付请求 #%s");
zhCN.put("cme.requests.cancel_failed", "取消支付请求 #%s 失败");
zhCN.put("cme.currency.renamed", "已将货币从 %s 重命名为 %s");
zhCN.put("cme.currency.rename_failed", "重命名货币 %s 失败");
zhCN.put("cme.currency.updated", "已更新货币 %s: 符号=%s, 可交易=%s");
zhCN.put("cme.currency.update_failed", "更新货币 %s 失败");
zhCN.put("cme.currency.list_item", " %s (%s) - 可交易: %s - 汇率: %.2f");
zhCN.put("cme.lang.usage", "使用 /cme lang <语言> 来切换语言");
zhCN.put("cme.exchange_rate.set", "%s 的汇率已设置为: %.2f");
zhCN.put("cme.exchange_rate.list", "汇率列表(相对基准汇率):");
zhCN.put("cme.exchange_rate.item", " %s: %.2f");
zhCN.put("cme.convert.invalid_amount", "数量必须大于 0");
zhCN.put("cme.convert.same_currency", "不能转换为相同的货币");
zhCN.put("cme.convert.insufficient", "余额不足。你有 %s %s但需要 %s");
zhCN.put("cme.convert.title", "=== 货币转换预览 ===");
zhCN.put("cme.convert.from", " 来源: %s %s (%s)");
zhCN.put("cme.convert.to", " 目标: %s %s (%s)");
zhCN.put("cme.convert.rate", " 汇率: %s : %s = %.2f : %.2f");
zhCN.put("cme.convert.balance_from", " 当前 %s 余额: %s");
zhCN.put("cme.convert.balance_to", " 转换后 %s 总余额: %s");
zhCN.put("cme.convert.prompt", "使用 /cme convert %s %s %s confirm 确认执行");
zhCN.put("cme.convert.success", "成功将 %s %s 转换为 %s %s");
zhCN.put("cme.convert.failed", "转换失败,请重试");
zhCN.put("cme.convert.refunded", "转换失败,已退还扣除的货币");
zhCN.put("cme.requests.rejected", "已拒绝付款请求 #%s");
zhCN.put("cme.requests.reject_failed", "拒绝付款请求 #%s 失败");
zhCN.put("cme.requests.rejected_by", "%s 拒绝了你的付款请求 #%s: %.2f %s");
zhCN.put("cme.requests.revoked", "已撤回付款请求 #%s");
zhCN.put("cme.requests.revoke_failed", "撤回付款请求 #%s 失败");
zhCN.put("cme.requests.revoked_by", "%s 撤回了付款请求 #%s: %.2f %s");
zhCN.put("cme.requests.accepted_by", "%s 已接受你的付款请求 #%s: %.2f %s");
zhCN.put("cme.requests.not_yours", "这不是你的请求");
zhCN.put("cme.requests.not_pending", "该请求已不在待处理状态");
LANGUAGES.put("zh_cn", zhCN);
}
private void loadAllPlayerLanguages() {
if (dbManager == null || !dbManager.isConnected()) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.warn("Database not connected, cannot load player languages");
return;
}
String sql = "SELECT user_uuid, language FROM cme_player_settings";
try (Connection conn = dbManager.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
byte[] uuidBytes = rs.getBytes("user_uuid");
if (uuidBytes != null && uuidBytes.length == 16) {
UUID uuid = bytesToUUID(uuidBytes);
String lang = rs.getString("language");
playerLanguages.put(uuid, lang.toLowerCase());
}
}
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Loaded {} player language settings", playerLanguages.size());
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to load player languages", e);
}
}
public void setPlayerLanguage(UUID playerUUID, String language) {
playerLanguages.put(playerUUID, language.toLowerCase());
}
public String getPlayerLanguage(UUID playerUUID) {
if (playerUUID == null) {
return "en_us";
}
return playerLanguages.getOrDefault(playerUUID, "en_us");
}
public String getMessageRaw(UUID playerUUID, String key) {
String lang = getPlayerLanguage(playerUUID);
Map<String, String> langMap = LANGUAGES.getOrDefault(lang, LANGUAGES.get("en_us"));
return langMap.getOrDefault(key, key);
}
public boolean hasLanguage(String lang) {
return LANGUAGES.containsKey(lang.toLowerCase());
}
public String getAvailableLanguages() {
return String.join(", ", LANGUAGES.keySet());
}
private UUID bytesToUUID(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
long mostSigBits = buffer.getLong();
long leastSigBits = buffer.getLong();
return new UUID(mostSigBits, leastSigBits);
}
}

View File

@@ -0,0 +1,157 @@
package com.gvsds.tcme.manager;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.config.Config;
import com.gvsds.tcme.database.DatabaseManager;
import com.gvsds.tcme.model.PlayerBalance;
import java.nio.ByteBuffer;
import java.sql.*;
import java.util.Optional;
import java.util.UUID;
public class BalanceManager {
private final DatabaseManager dbManager;
private final Config.Tables tables;
private final boolean isMySQL;
public BalanceManager(DatabaseManager dbManager, Config.Tables tables, boolean isMySQL) {
this.dbManager = dbManager;
this.tables = tables;
this.isMySQL = isMySQL;
}
public double getBalance(UUID playerUUID, int currencyId) {
String sql = String.format("SELECT balance FROM %s WHERE user_uuid = ? AND currency_id = ?", tables.getBalances());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
setUUIDParameter(stmt, 1, playerUUID);
stmt.setInt(2, currencyId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getDouble("balance");
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get balance for player: {}", playerUUID, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return 0.0;
}
public boolean setBalance(UUID playerUUID, int currencyId, double amount) {
Connection conn = null;
try {
conn = dbManager.getConnection();
boolean result = setBalance(conn, playerUUID, currencyId, amount);
if (result) {
conn.commit();
} else {
conn.rollback();
}
return result;
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to set balance for player: {}", playerUUID, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public boolean addBalance(UUID playerUUID, int currencyId, double amount) {
double currentBalance = getBalance(playerUUID, currencyId);
return setBalance(playerUUID, currencyId, currentBalance + amount);
}
public double getBalance(Connection conn, UUID playerUUID, int currencyId) throws SQLException {
String sql = String.format("SELECT balance FROM %s WHERE user_uuid = ? AND currency_id = ?", tables.getBalances());
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
setUUIDParameter(stmt, 1, playerUUID);
stmt.setInt(2, currencyId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getDouble("balance");
}
}
}
return 0.0;
}
public boolean setBalance(Connection conn, UUID playerUUID, int currencyId, double amount) throws SQLException {
String sql;
if (isMySQL) {
sql = String.format(
"INSERT INTO %s (user_uuid, currency_id, balance, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON DUPLICATE KEY UPDATE balance = VALUES(balance), updated_at = CURRENT_TIMESTAMP",
tables.getBalances()
);
} else {
sql = String.format(
"INSERT INTO %s (user_uuid, currency_id, balance, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON CONFLICT(user_uuid, currency_id) DO UPDATE SET balance = excluded.balance, updated_at = CURRENT_TIMESTAMP",
tables.getBalances()
);
}
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
setUUIDParameter(stmt, 1, playerUUID);
stmt.setInt(2, currencyId);
stmt.setDouble(3, amount);
stmt.executeUpdate();
return true;
}
}
public boolean addBalance(Connection conn, UUID playerUUID, int currencyId, double amount) throws SQLException {
double currentBalance = getBalance(conn, playerUUID, currencyId);
return setBalance(conn, playerUUID, currencyId, currentBalance + amount);
}
public boolean subtractBalance(UUID playerUUID, int currencyId, double amount) {
double currentBalance = getBalance(playerUUID, currencyId);
if (currentBalance < amount) {
return false;
}
return setBalance(playerUUID, currencyId, currentBalance - amount);
}
public boolean hasSufficientBalance(UUID playerUUID, int currencyId, double amount) {
return getBalance(playerUUID, currencyId) >= amount;
}
private void setUUIDParameter(PreparedStatement stmt, int parameterIndex, UUID uuid) throws SQLException {
ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
buffer.putLong(uuid.getMostSignificantBits());
buffer.putLong(uuid.getLeastSignificantBits());
stmt.setBytes(parameterIndex, buffer.array());
}
public UUID getUUIDFromResultSet(ResultSet rs, String columnName) throws SQLException {
byte[] bytes = rs.getBytes(columnName);
if (bytes == null) {
return null;
}
return bytesToUUID(bytes);
}
private byte[] uuidToBytes(UUID uuid) {
ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
buffer.putLong(uuid.getMostSignificantBits());
buffer.putLong(uuid.getLeastSignificantBits());
return buffer.array();
}
private UUID bytesToUUID(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
long mostSigBits = buffer.getLong();
long leastSigBits = buffer.getLong();
return new UUID(mostSigBits, leastSigBits);
}
}

View File

@@ -0,0 +1,286 @@
package com.gvsds.tcme.manager;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.config.Config;
import com.gvsds.tcme.database.DatabaseManager;
import com.gvsds.tcme.model.Currency;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class CurrencyManager {
private final DatabaseManager dbManager;
private final Config.Tables tables;
public CurrencyManager(DatabaseManager dbManager, Config.Tables tables) {
this.dbManager = dbManager;
this.tables = tables;
}
public Optional<Currency> createCurrency(String name, String symbol, boolean tradable) {
String sql = String.format("INSERT INTO %s (name, symbol, tradable) VALUES (?, ?, ?)", tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, name);
stmt.setString(2, symbol);
stmt.setInt(3, tradable ? 1 : 0);
stmt.executeUpdate();
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
Currency currency = new Currency(name, symbol, tradable);
currency.setId(rs.getInt(1));
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Created currency: {} ({})", name, symbol);
conn.commit();
return Optional.of(currency);
}
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to create currency: {}", name, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return Optional.empty();
}
public boolean deleteCurrency(String name) {
String sql = String.format("DELETE FROM %s WHERE name = ?", tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, name);
int rows = stmt.executeUpdate();
if (rows > 0) {
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Deleted currency: {}", name);
return true;
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to delete currency: {}", name, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public boolean deleteCurrency(int currencyId) {
String sql = String.format("DELETE FROM %s WHERE id = ?", tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, currencyId);
int rows = stmt.executeUpdate();
if (rows > 0) {
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Deleted currency with ID: {}", currencyId);
return true;
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to delete currency: {}", currencyId, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public boolean renameCurrency(String oldName, String newName) {
String sql = String.format("UPDATE %s SET name = ? WHERE name = ?", tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, newName);
stmt.setString(2, oldName);
int rows = stmt.executeUpdate();
if (rows == 0) {
conn.rollback();
return false;
}
}
try (PreparedStatement stmt = conn.prepareStatement("UPDATE cme_exchange_rates SET currency_name = ? WHERE currency_name = ?")) {
stmt.setString(1, newName);
stmt.setString(2, oldName);
stmt.executeUpdate();
}
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Renamed currency: {} -> {}", oldName, newName);
return true;
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to rename currency: {}", oldName, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public boolean updateCurrency(String name, String newSymbol, Boolean newTradable) {
StringBuilder sqlBuilder = new StringBuilder(String.format("UPDATE %s SET ", tables.getCurrencies()));
boolean first = true;
if (newSymbol != null) {
sqlBuilder.append("symbol = ?");
first = false;
}
if (newTradable != null) {
if (!first) sqlBuilder.append(", ");
sqlBuilder.append("tradable = ?");
}
sqlBuilder.append(" WHERE name = ?");
String sql = sqlBuilder.toString();
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
int paramIndex = 1;
if (newSymbol != null) {
stmt.setString(paramIndex++, newSymbol);
}
if (newTradable != null) {
stmt.setInt(paramIndex++, newTradable ? 1 : 0);
}
stmt.setString(paramIndex, name);
int rows = stmt.executeUpdate();
if (rows > 0) {
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Updated currency: {}", name);
return true;
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to update currency: {}", name, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public boolean setTradable(int currencyId, boolean tradable) {
String sql = String.format("UPDATE %s SET tradable = ? WHERE id = ?", tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, tradable ? 1 : 0);
stmt.setInt(2, currencyId);
int rows = stmt.executeUpdate();
if (rows > 0) {
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Currency {} tradable set to {}", currencyId, tradable);
return true;
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to set tradable for currency: {}", currencyId, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public Optional<Currency> getCurrency(int currencyId) {
String sql = String.format("SELECT * FROM %s WHERE id = ?", tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, currencyId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
conn.commit();
return Optional.of(mapCurrency(rs));
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get currency: {}", currencyId, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return Optional.empty();
}
public Optional<Currency> getCurrencyByName(String name) {
String sql = String.format("SELECT * FROM %s WHERE name = ?", tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, name);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
conn.commit();
return Optional.of(mapCurrency(rs));
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get currency by name: {}", name, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return Optional.empty();
}
public List<Currency> getAllCurrencies() {
String sql = String.format("SELECT * FROM %s", tables.getCurrencies());
List<Currency> currencies = new ArrayList<>();
Connection conn = null;
try {
conn = dbManager.getConnection();
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
currencies.add(mapCurrency(rs));
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get all currencies", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return currencies;
}
private Currency mapCurrency(ResultSet rs) throws SQLException {
Currency currency = new Currency();
currency.setId(rs.getInt("id"));
currency.setName(rs.getString("name"));
currency.setSymbol(rs.getString("symbol"));
currency.setTradable(rs.getInt("tradable") == 1);
return currency;
}
}

View File

@@ -0,0 +1,134 @@
package com.gvsds.tcme.manager;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.config.Config;
import com.gvsds.tcme.database.DatabaseManager;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class ExchangeRateManager {
private final DatabaseManager dbManager;
private final Config.Tables tables;
private final String exchangeRatesTable = "cme_exchange_rates";
private final boolean isMySQL;
public ExchangeRateManager(DatabaseManager dbManager, Config.Tables tables, boolean isMySQL) {
this.dbManager = dbManager;
this.tables = tables;
this.isMySQL = isMySQL;
initializeTable();
}
private void initializeTable() {
String textType = isMySQL ? "VARCHAR(255)" : "TEXT";
String autoIncrement = isMySQL ? "AUTO_INCREMENT" : "AUTOINCREMENT";
String engineClause = isMySQL ? "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" : "";
String sql = String.format("""
CREATE TABLE IF NOT EXISTS %s (
id INTEGER PRIMARY KEY %s,
currency_name %s NOT NULL UNIQUE,
rate REAL NOT NULL DEFAULT 1.0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) %s
""", exchangeRatesTable, autoIncrement, textType, engineClause);
Connection conn = null;
try {
conn = dbManager.getConnection();
try (Statement stmt = conn.createStatement()) {
stmt.execute(sql);
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to create exchange rates table", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
}
public void setRate(int currencyId, double rate) {
String sql;
if (isMySQL) {
sql = String.format(
"INSERT INTO %s (currency_name, rate, created_at, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON DUPLICATE KEY UPDATE rate = VALUES(rate), updated_at = CURRENT_TIMESTAMP",
exchangeRatesTable
);
} else {
sql = String.format(
"INSERT INTO %s (currency_name, rate, created_at, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON CONFLICT(currency_name) DO UPDATE SET rate = ?, updated_at = CURRENT_TIMESTAMP",
exchangeRatesTable
);
}
Connection conn = null;
try {
conn = dbManager.getConnection();
String nameSql = String.format("SELECT name FROM %s WHERE id = ?", tables.getCurrencies());
try (java.sql.PreparedStatement nameStmt = conn.prepareStatement(nameSql)) {
nameStmt.setInt(1, currencyId);
try (ResultSet rs = nameStmt.executeQuery()) {
if (!rs.next()) {
conn.rollback();
return;
}
String currencyName = rs.getString("name");
try (java.sql.PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, currencyName);
stmt.setDouble(2, rate);
if (!isMySQL) {
stmt.setDouble(3, rate);
}
stmt.executeUpdate();
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to set exchange rate for currency: {}", currencyId, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
}
public double getRate(int currencyId) {
String sql = String.format("SELECT rate FROM %s WHERE currency_name = (SELECT name FROM %s WHERE id = ?)", exchangeRatesTable, tables.getCurrencies());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (java.sql.PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, currencyId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
double rate = rs.getDouble("rate");
conn.commit();
return rate;
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get exchange rate for currency: {}", currencyId, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return 1.0;
}
public double convert(double amount, int fromCurrencyId, int toCurrencyId) {
double fromRate = getRate(fromCurrencyId);
double toRate = getRate(toCurrencyId);
return amount * (fromRate / toRate);
}
}

View File

@@ -0,0 +1,254 @@
package com.gvsds.tcme.manager;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.config.Config;
import com.gvsds.tcme.database.DatabaseManager;
import com.gvsds.tcme.model.PaymentRequest;
import java.nio.ByteBuffer;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class PaymentRequestManager {
private final DatabaseManager dbManager;
private final Config.Tables tables;
private final boolean isMySQL;
public PaymentRequestManager(DatabaseManager dbManager, Config.Tables tables, BalanceManager balanceManager, TransactionManager transactionManager, boolean isMySQL) {
this.dbManager = dbManager;
this.tables = tables;
this.isMySQL = isMySQL;
}
public Optional<PaymentRequest> createRequest(UUID fromUUID, UUID toUUID, int currencyId, double amount) {
if (amount <= 0) {
return Optional.empty();
}
String sql = String.format(
"INSERT INTO %s (from_uuid, to_uuid, currency_id, amount, status, created_at) VALUES (?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP)",
tables.getPaymentRequests()
);
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
setUUID(stmt, 1, fromUUID);
setUUID(stmt, 2, toUUID);
stmt.setInt(3, currencyId);
stmt.setDouble(4, amount);
stmt.executeUpdate();
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
PaymentRequest request = new PaymentRequest(fromUUID, toUUID, currencyId, amount);
request.setId(rs.getInt(1));
conn.commit();
return Optional.of(request);
}
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to create payment request", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return Optional.empty();
}
public boolean acceptRequest(int requestId) {
String selectSql = String.format("SELECT * FROM %s WHERE id = ? AND status = 'pending'", tables.getPaymentRequests());
String updateSql = String.format("UPDATE %s SET status = 'completed' WHERE id = ?", tables.getPaymentRequests());
String insertTransactionSql = String.format(
"INSERT INTO %s (from_uuid, to_uuid, currency_id, amount, type, description, created_at) VALUES (?, ?, ?, ?, 'apay_accept', ?, CURRENT_TIMESTAMP)",
tables.getTransactions()
);
Connection conn = null;
try {
conn = dbManager.getConnection();
UUID fromUUID, toUUID;
int currencyId;
double amount;
try (PreparedStatement selectStmt = conn.prepareStatement(selectSql)) {
selectStmt.setInt(1, requestId);
try (ResultSet rs = selectStmt.executeQuery()) {
if (!rs.next()) {
conn.rollback();
return false;
}
fromUUID = getUUIDFromBytes(rs.getBytes("from_uuid"));
toUUID = getUUIDFromBytes(rs.getBytes("to_uuid"));
currencyId = rs.getInt("currency_id");
amount = rs.getDouble("amount");
}
}
BalanceManager bm = CardinalCooperativeCapitalismMarketEconomy.getBalanceManager();
double toBalance = bm.getBalance(conn, toUUID, currencyId);
if (toBalance < amount) {
conn.rollback();
return false;
}
bm.addBalance(conn, toUUID, currencyId, -amount);
bm.addBalance(conn, fromUUID, currencyId, amount);
try (PreparedStatement stmt = conn.prepareStatement(insertTransactionSql)) {
setUUID(stmt, 1, toUUID);
setUUID(stmt, 2, fromUUID);
stmt.setInt(3, currencyId);
stmt.setDouble(4, amount);
stmt.setString(5, "Accepted payment request #" + requestId);
stmt.executeUpdate();
}
try (PreparedStatement updateStmt = conn.prepareStatement(updateSql)) {
updateStmt.setInt(1, requestId);
updateStmt.executeUpdate();
}
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Payment request #{} accepted", requestId);
return true;
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to accept payment request", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public boolean cancelRequest(int requestId) {
String sql = String.format("UPDATE %s SET status = 'cancelled' WHERE id = ? AND status = 'pending'", tables.getPaymentRequests());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, requestId);
int rows = stmt.executeUpdate();
if (rows > 0) {
conn.commit();
return true;
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to cancel payment request", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public boolean rejectRequest(int requestId) {
String sql = String.format("UPDATE %s SET status = 'rejected' WHERE id = ? AND status = 'pending'", tables.getPaymentRequests());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, requestId);
int rows = stmt.executeUpdate();
if (rows > 0) {
conn.commit();
return true;
}
}
conn.rollback();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to reject payment request", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return false;
}
public Optional<PaymentRequest> getRequestById(int requestId) {
String sql = String.format("SELECT * FROM %s WHERE id = ?", tables.getPaymentRequests());
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, requestId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return Optional.of(mapPaymentRequest(rs));
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get payment request by id", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return Optional.empty();
}
public List<PaymentRequest> getPendingRequests(UUID playerUUID) {
String sql = String.format("SELECT * FROM %s WHERE to_uuid = ? AND status = 'pending'", tables.getPaymentRequests());
List<PaymentRequest> requests = new ArrayList<>();
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
setUUID(stmt, 1, playerUUID);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
requests.add(mapPaymentRequest(rs));
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get pending requests", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return requests;
}
private PaymentRequest mapPaymentRequest(ResultSet rs) throws SQLException {
PaymentRequest request = new PaymentRequest();
request.setId(rs.getInt("id"));
request.setFromUUID(getUUIDFromBytes(rs.getBytes("from_uuid")));
request.setToUUID(getUUIDFromBytes(rs.getBytes("to_uuid")));
request.setCurrencyId(rs.getInt("currency_id"));
request.setAmount(rs.getDouble("amount"));
request.setStatus(rs.getString("status"));
request.setCreatedAt(rs.getTimestamp("created_at"));
return request;
}
private void setUUID(PreparedStatement stmt, int index, UUID uuid) throws SQLException {
ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
buffer.putLong(uuid.getMostSignificantBits());
buffer.putLong(uuid.getLeastSignificantBits());
stmt.setBytes(index, buffer.array());
}
private UUID getUUIDFromBytes(byte[] bytes) {
if (bytes == null || bytes.length != 16) {
return null;
}
ByteBuffer buffer = ByteBuffer.wrap(bytes);
long mostSigBits = buffer.getLong();
long leastSigBits = buffer.getLong();
return new UUID(mostSigBits, leastSigBits);
}
}

View File

@@ -0,0 +1,125 @@
package com.gvsds.tcme.manager;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.config.Config;
import com.gvsds.tcme.database.DatabaseManager;
import java.nio.ByteBuffer;
import java.sql.*;
import java.util.Optional;
import java.util.UUID;
public class PlayerSettingsManager {
private final DatabaseManager dbManager;
private final Config.Tables tables;
private final boolean isMySQL;
private final String settingsTable;
public PlayerSettingsManager(DatabaseManager dbManager, Config.Tables tables, boolean isMySQL) {
this.dbManager = dbManager;
this.tables = tables;
this.isMySQL = isMySQL;
this.settingsTable = "cme_player_settings";
initializeTable();
}
private void initializeTable() {
String uuidType = isMySQL ? "BINARY(16)" : "BLOB";
String textType = isMySQL ? "VARCHAR(50)" : "TEXT";
String autoIncrement = isMySQL ? "AUTO_INCREMENT" : "AUTOINCREMENT";
String engineClause = isMySQL ? "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" : "";
String sql = String.format("""
CREATE TABLE IF NOT EXISTS %s (
id INTEGER PRIMARY KEY %s,
user_uuid %s NOT NULL UNIQUE,
language %s DEFAULT 'en_us',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) %s
""", settingsTable, autoIncrement, uuidType, textType, engineClause);
Connection conn = null;
try {
conn = dbManager.getConnection();
try (Statement stmt = conn.createStatement()) {
stmt.execute(sql);
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to create player settings table", e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
}
public void setLanguage(UUID playerUUID, String language) {
String sql;
if (isMySQL) {
sql = String.format(
"INSERT INTO %s (user_uuid, language, created_at, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON DUPLICATE KEY UPDATE language = VALUES(language), updated_at = CURRENT_TIMESTAMP",
settingsTable
);
} else {
sql = String.format(
"INSERT INTO %s (user_uuid, language, created_at, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON CONFLICT(user_uuid) DO UPDATE SET language = ?, updated_at = CURRENT_TIMESTAMP",
settingsTable
);
}
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
setUUID(stmt, 1, playerUUID);
stmt.setString(2, language);
if (!isMySQL) {
stmt.setString(3, language);
}
stmt.executeUpdate();
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to set language for player: {}", playerUUID, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
}
public Optional<String> getLanguage(UUID playerUUID) {
String sql = String.format("SELECT language FROM %s WHERE user_uuid = ?", settingsTable);
Connection conn = null;
try {
conn = dbManager.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
setUUID(stmt, 1, playerUUID);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
String lang = rs.getString("language");
conn.commit();
return Optional.of(lang);
}
}
}
conn.commit();
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to get language for player: {}", playerUUID, e);
if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} }
} finally {
if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} }
}
return Optional.empty();
}
private void setUUID(PreparedStatement stmt, int index, UUID uuid) throws SQLException {
ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
buffer.putLong(uuid.getMostSignificantBits());
buffer.putLong(uuid.getLeastSignificantBits());
stmt.setBytes(index, buffer.array());
}
}

View File

@@ -0,0 +1,141 @@
package com.gvsds.tcme.manager;
import com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy;
import com.gvsds.tcme.config.Config;
import com.gvsds.tcme.database.DatabaseManager;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
public class TransactionManager {
private final DatabaseManager dbManager;
private final Config.Tables tables;
private final boolean isMySQL;
public TransactionManager(DatabaseManager dbManager, Config.Tables tables, BalanceManager balanceManager, boolean isMySQL) {
this.dbManager = dbManager;
this.tables = tables;
this.isMySQL = isMySQL;
}
public boolean transfer(UUID fromUUID, UUID toUUID, int currencyId, double amount, String type, String description) {
if (amount <= 0) {
return false;
}
String sql = String.format(
"INSERT INTO %s (from_uuid, to_uuid, currency_id, amount, type, description, created_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)",
tables.getTransactions()
);
String checkBalanceSql = String.format("SELECT balance FROM %s WHERE user_uuid = ? AND currency_id = ?", tables.getBalances());
String updateBalanceSql;
if (isMySQL) {
updateBalanceSql = String.format(
"INSERT INTO %s (user_uuid, currency_id, balance, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON DUPLICATE KEY UPDATE balance = VALUES(balance), updated_at = CURRENT_TIMESTAMP",
tables.getBalances()
);
} else {
updateBalanceSql = String.format(
"INSERT INTO %s (user_uuid, currency_id, balance, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) " +
"ON CONFLICT(user_uuid, currency_id) DO UPDATE SET balance = excluded.balance, updated_at = CURRENT_TIMESTAMP",
tables.getBalances()
);
}
Connection conn = null;
try {
conn = dbManager.getConnection();
double fromBalance;
try (PreparedStatement stmt = conn.prepareStatement(checkBalanceSql)) {
setUUID(stmt, 1, fromUUID);
stmt.setInt(2, currencyId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
fromBalance = rs.getDouble("balance");
} else {
fromBalance = 0.0;
}
}
}
if (fromBalance < amount) {
conn.rollback();
return false;
}
try (PreparedStatement stmt = conn.prepareStatement(updateBalanceSql)) {
setUUID(stmt, 1, fromUUID);
stmt.setInt(2, currencyId);
stmt.setDouble(3, fromBalance - amount);
stmt.executeUpdate();
}
double toBalance;
try (PreparedStatement stmt = conn.prepareStatement(checkBalanceSql)) {
setUUID(stmt, 1, toUUID);
stmt.setInt(2, currencyId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
toBalance = rs.getDouble("balance");
} else {
toBalance = 0.0;
}
}
}
try (PreparedStatement stmt = conn.prepareStatement(updateBalanceSql)) {
setUUID(stmt, 1, toUUID);
stmt.setInt(2, currencyId);
stmt.setDouble(3, toBalance + amount);
stmt.executeUpdate();
}
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
setUUID(stmt, 1, fromUUID);
setUUID(stmt, 2, toUUID);
stmt.setInt(3, currencyId);
stmt.setDouble(4, amount);
stmt.setString(5, type);
stmt.setString(6, description);
stmt.executeUpdate();
}
conn.commit();
CardinalCooperativeCapitalismMarketEconomy.LOGGER.info("Transfer: {} -> {} amount: {} currency: {}", fromUUID, toUUID, amount, currencyId);
return true;
} catch (SQLException e) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to process transfer", e);
if (conn != null) {
try {
conn.rollback();
} catch (SQLException re) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to rollback transfer", re);
}
}
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException ce) {
CardinalCooperativeCapitalismMarketEconomy.LOGGER.error("Failed to close connection", ce);
}
}
}
return false;
}
private void setUUID(PreparedStatement stmt, int index, UUID uuid) throws SQLException {
ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
buffer.putLong(uuid.getMostSignificantBits());
buffer.putLong(uuid.getLeastSignificantBits());
stmt.setBytes(index, buffer.array());
}
}

View File

@@ -0,0 +1,30 @@
package com.gvsds.tcme.model;
import java.util.UUID;
public class Currency {
private int id;
private String name;
private String symbol;
private boolean tradable;
public Currency() {}
public Currency(String name, String symbol, boolean tradable) {
this.name = name;
this.symbol = symbol;
this.tradable = tradable;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSymbol() { return symbol; }
public void setSymbol(String symbol) { this.symbol = symbol; }
public boolean isTradable() { return tradable; }
public void setTradable(boolean tradable) { this.tradable = tradable; }
}

View File

@@ -0,0 +1,45 @@
package com.gvsds.tcme.model;
import java.sql.Timestamp;
import java.util.UUID;
public class PaymentRequest {
private int id;
private UUID fromUUID;
private UUID toUUID;
private int currencyId;
private double amount;
private String status;
private Timestamp createdAt;
public PaymentRequest() {}
public PaymentRequest(UUID fromUUID, UUID toUUID, int currencyId, double amount) {
this.fromUUID = fromUUID;
this.toUUID = toUUID;
this.currencyId = currencyId;
this.amount = amount;
this.status = "pending";
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public UUID getFromUUID() { return fromUUID; }
public void setFromUUID(UUID fromUUID) { this.fromUUID = fromUUID; }
public UUID getToUUID() { return toUUID; }
public void setToUUID(UUID toUUID) { this.toUUID = toUUID; }
public int getCurrencyId() { return currencyId; }
public void setCurrencyId(int currencyId) { this.currencyId = currencyId; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}

View File

@@ -0,0 +1,30 @@
package com.gvsds.tcme.model;
import java.util.UUID;
public class PlayerBalance {
private int id;
private UUID userUUID;
private int currencyId;
private double balance;
public PlayerBalance() {}
public PlayerBalance(UUID userUUID, int currencyId, double balance) {
this.userUUID = userUUID;
this.currencyId = currencyId;
this.balance = balance;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public UUID getUserUUID() { return userUUID; }
public void setUserUUID(UUID userUUID) { this.userUUID = userUUID; }
public int getCurrencyId() { return currencyId; }
public void setCurrencyId(int currencyId) { this.currencyId = currencyId; }
public double getBalance() { return balance; }
public void setBalance(double balance) { this.balance = balance; }
}

View File

@@ -0,0 +1,50 @@
package com.gvsds.tcme.model;
import java.sql.Timestamp;
import java.util.UUID;
public class Transaction {
private int id;
private UUID fromUUID;
private UUID toUUID;
private int currencyId;
private double amount;
private String type;
private String description;
private Timestamp createdAt;
public Transaction() {}
public Transaction(UUID fromUUID, UUID toUUID, int currencyId, double amount, String type, String description) {
this.fromUUID = fromUUID;
this.toUUID = toUUID;
this.currencyId = currencyId;
this.amount = amount;
this.type = type;
this.description = description;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public UUID getFromUUID() { return fromUUID; }
public void setFromUUID(UUID fromUUID) { this.fromUUID = fromUUID; }
public UUID getToUUID() { return toUUID; }
public void setToUUID(UUID toUUID) { this.toUUID = toUUID; }
public int getCurrencyId() { return currencyId; }
public void setCurrencyId(int currencyId) { this.currencyId = currencyId; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}

View File

@@ -0,0 +1,80 @@
package com.gvsds.tcme.util;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.util.*;
public class TextUtil {
private static final Map<Character, Formatting> CODE_MAP = new HashMap<>();
static {
CODE_MAP.put('0', Formatting.BLACK);
CODE_MAP.put('1', Formatting.DARK_BLUE);
CODE_MAP.put('2', Formatting.DARK_GREEN);
CODE_MAP.put('3', Formatting.DARK_AQUA);
CODE_MAP.put('4', Formatting.DARK_RED);
CODE_MAP.put('5', Formatting.DARK_PURPLE);
CODE_MAP.put('6', Formatting.GOLD);
CODE_MAP.put('7', Formatting.GRAY);
CODE_MAP.put('8', Formatting.DARK_GRAY);
CODE_MAP.put('9', Formatting.BLUE);
CODE_MAP.put('a', Formatting.GREEN);
CODE_MAP.put('b', Formatting.AQUA);
CODE_MAP.put('c', Formatting.RED);
CODE_MAP.put('d', Formatting.LIGHT_PURPLE);
CODE_MAP.put('e', Formatting.YELLOW);
CODE_MAP.put('f', Formatting.WHITE);
CODE_MAP.put('k', Formatting.OBFUSCATED);
CODE_MAP.put('l', Formatting.BOLD);
CODE_MAP.put('m', Formatting.STRIKETHROUGH);
CODE_MAP.put('n', Formatting.UNDERLINE);
CODE_MAP.put('o', Formatting.ITALIC);
}
public static Text parseFormatting(String text) {
if (text == null || text.isEmpty()) return Text.empty();
MutableText result = null;
StringBuilder currentText = new StringBuilder();
Set<Formatting> currentFormats = new LinkedHashSet<>();
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == '\u00A7' && i + 1 < text.length()) {
if (currentText.length() > 0) {
MutableText part = Text.literal(currentText.toString());
if (!currentFormats.isEmpty()) {
part.formatted(currentFormats.toArray(new Formatting[0]));
}
result = result == null ? part : result.append(part);
currentText = new StringBuilder();
}
char code = Character.toLowerCase(text.charAt(i + 1));
if (code == 'r') {
currentFormats.clear();
} else {
Formatting fmt = CODE_MAP.get(code);
if (fmt != null) {
currentFormats.add(fmt);
}
}
i++;
} else {
currentText.append(text.charAt(i));
}
}
if (currentText.length() > 0) {
MutableText part = Text.literal(currentText.toString());
if (!currentFormats.isEmpty()) {
part.formatted(currentFormats.toArray(new Formatting[0]));
}
result = result == null ? part : result.append(part);
}
return result != null ? result : Text.empty();
}
}

View File

@@ -0,0 +1,12 @@
{
"database": {
"type": "sqlite",
"sqlitePath": "./config/cardinal-economy/database",
"mysqlHost": "localhost",
"mysqlPort": 3306,
"mysqlDatabase": "cardinal_economy",
"mysqlUsername": "root",
"mysqlPassword": "",
"useSSL": false
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -0,0 +1,12 @@
{
"required": true,
"package": "com.gvsds.tcme.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}

View File

@@ -0,0 +1,31 @@
{
"schemaVersion": 1,
"id": "cardinal-cooperative-capitalism-market-economy",
"version": "${version}",
"name": "Cardinal Cooperative Capitalism: Market Economy",
"description": "A server-side economy mod with SQLite/MySQL database support",
"authors": [
"Me!"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
},
"license": "CC0-1.0",
"icon": "assets/cardinal-cooperative-capitalism-market-economy/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.gvsds.tcme.CardinalCooperativeCapitalismMarketEconomy"
]
},
"mixins": [
"cardinal-cooperative-capitalism-market-economy.mixins.json"
],
"depends": {
"fabricloader": ">=0.19.2",
"minecraft": "~1.21.10",
"java": ">=21",
"fabric-api": "*"
}
}