├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── assets │ │ └── factions │ │ │ └── icon.png │ ├── factions.mixins.json │ └── fabric.mod.json │ └── java │ └── io │ └── icker │ └── factions │ ├── config │ ├── SafeConfig.java │ ├── HomeConfig.java │ ├── PowerConfig.java │ └── Config.java │ ├── mixin │ ├── BucketItemAccessor.java │ ├── DamageTrackerAccessor.java │ ├── ItemInvoker.java │ ├── MinecraftServerMixin.java │ ├── ServerPlayerInteractionManagerMixin.java │ ├── EnderChestBlockMixin.java │ ├── LockableContainerBlockEntityMixin.java │ ├── PlayerManagerMixin.java │ ├── ExplosionBehaviorMixin.java │ ├── ServerPlayNetworkHandlerMixin.java │ └── ServerPlayerEntityMixin.java │ ├── database │ ├── Name.java │ ├── Field.java │ └── Database.java │ ├── util │ ├── StyledChatCompatibility.java │ ├── GuiInteract.java │ ├── WorldUtils.java │ ├── Message.java │ ├── PlaceholdersWrapper.java │ ├── Icons.java │ └── SquareMapWrapper.java │ ├── api │ ├── persistents │ │ ├── Home.java │ │ ├── Relationship.java │ │ ├── Claim.java │ │ └── User.java │ └── events │ │ ├── ClaimEvents.java │ │ ├── MiscEvents.java │ │ ├── RelationshipEvents.java │ │ └── FactionEvents.java │ ├── command │ ├── SafeCommand.java │ ├── ListCommand.java │ ├── LeaveCommand.java │ ├── JoinCommand.java │ ├── CreateCommand.java │ ├── DisbandCommand.java │ ├── MapCommand.java │ ├── KickCommand.java │ ├── HomeCommand.java │ ├── InviteCommand.java │ ├── SettingsCommand.java │ ├── DeclareCommand.java │ └── InfoCommand.java │ ├── core │ ├── InteractionsUtil.java │ ├── ServerManager.java │ ├── SoundManager.java │ ├── ChatManager.java │ ├── WorldManager.java │ └── FactionsManager.java │ ├── ui │ ├── InputGui.java │ ├── ListGui.java │ └── PagedGui.java │ └── FactionsMod.java ├── settings.gradle ├── log4j-dev.xml ├── .gitignore ├── .vscode └── settings.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ ├── javadoc.yml │ ├── build.yml │ └── publish.yml ├── gradle.properties ├── LICENSE ├── README.md ├── gradlew.bat └── geysermc-example-custom-skulls.yml /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ickerio/factions/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/assets/factions/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ickerio/factions/HEAD/src/main/resources/assets/factions/icon.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /log4j-dev.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/config/SafeConfig.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.config; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class SafeConfig { 6 | @SerializedName("enderChest") 7 | public boolean ENDER_CHEST = true; 8 | 9 | @SerializedName("double") 10 | public boolean DOUBLE = true; 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/launch.json 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # macos 28 | 29 | *.DS_Store 30 | 31 | # fabric 32 | 33 | run/ 34 | remappedSrc/ 35 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/BucketItemAccessor.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import net.minecraft.fluid.Fluid; 4 | import net.minecraft.item.BucketItem; 5 | 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | @Mixin(BucketItem.class) 10 | public interface BucketItemAccessor { 11 | @Accessor 12 | Fluid getFluid(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/database/Name.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.database; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface Name { 11 | public String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/database/Field.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.database; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Field { 11 | public String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/config/HomeConfig.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.config; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class HomeConfig { 6 | @SerializedName("claimOnly") 7 | public boolean CLAIM_ONLY = true; 8 | 9 | @SerializedName("damageTickCooldown") 10 | public int DAMAGE_COOLDOWN = 100; 11 | 12 | @SerializedName("homeWarpCooldownSecond") 13 | public int HOME_WARP_COOLDOWN_SECOND = 15; 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.format.settings.profile": "AOSPStyle", 3 | "java.format.settings.url": "https://raw.github.com/android/platform_development/master/ide/eclipse/android-formatting.xml", 4 | "editor.formatOnSave": true, 5 | "editor.tabSize": 4, 6 | "editor.insertSpaces": true, 7 | "editor.detectIndentation": false, // For whatever reason AOSP style messes with VSCode indentation auto-detection 8 | "java.format.settings.google.extra": "--aosp" 9 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Wiki 4 | url: https://github.com/ickerio/factions/wiki 5 | about: Please find helpful guides related to Factions Mod here 6 | - name: Join Discord 7 | url: https://discord.gg/tHPFegeAY8 8 | about: Please get involved with Faction Mod's Development 9 | - name: About Fabric 10 | url: https://fabricmc.net/ 11 | about: Please find information regarding the Fabric mod loader here -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/DamageTrackerAccessor.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import net.minecraft.entity.LivingEntity; 4 | import net.minecraft.entity.damage.DamageTracker; 5 | 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | @Mixin(DamageTracker.class) 10 | public interface DamageTrackerAccessor { 11 | @Accessor 12 | int getAgeOnLastDamage(); 13 | 14 | @Accessor 15 | LivingEntity getEntity(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/util/StyledChatCompatibility.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.util; 2 | 3 | import eu.pb4.styledchat.other.StyledChatSentMessage; 4 | 5 | import net.minecraft.network.message.SentMessage; 6 | 7 | import java.util.UUID; 8 | 9 | public class StyledChatCompatibility { 10 | public static UUID getSender(SentMessage message) { 11 | return ((StyledChatSentMessage.Chat) message).message().link().sender(); 12 | } 13 | 14 | public static boolean isNotPlayer(SentMessage message) { 15 | return !(message instanceof StyledChatSentMessage.Chat); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2G 2 | 3 | # Fabric Properties 4 | minecraft_version=1.21.11 5 | yarn_mappings=1.21.11+build.1 6 | loader_version=0.18.1 7 | 8 | # Mod Properties 9 | mod_version = 2.9.3 10 | maven_group = io.icker 11 | archives_base_name = factions 12 | 13 | # Dependencies 14 | fabric_version=0.139.4+1.21.11 15 | lucko_permissions_version=0.6.1 16 | dynmap_api_version=3.7-SNAPSHOT 17 | papi_version=2.8.1+1.21.10 18 | styled_chat_version=nW0Cfq7D 19 | bluemap_api_version=2.7.1 20 | squaremap_api_version=1.2.3 21 | sgui_version=1.11.0+1.21.9 22 | stapi_version=2.5.2+1.21.9-pre3 23 | flow_version=1.0.3 24 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/ItemInvoker.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import net.minecraft.entity.player.PlayerEntity; 4 | import net.minecraft.item.Item; 5 | import net.minecraft.util.hit.BlockHitResult; 6 | import net.minecraft.world.RaycastContext; 7 | import net.minecraft.world.World; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.gen.Invoker; 11 | 12 | @Mixin(Item.class) 13 | public interface ItemInvoker { 14 | @Invoker("raycast") 15 | static BlockHitResult raycast( 16 | World world, PlayerEntity player, RaycastContext.FluidHandling fluidHandling) { 17 | throw new AssertionError(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/factions.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.icker.factions.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "BucketItemAccessor", 8 | "DamageTrackerAccessor", 9 | "EnderChestBlockMixin", 10 | "ItemInvoker", 11 | "LockableContainerBlockEntityMixin", 12 | "MinecraftServerMixin", 13 | "PlayerManagerMixin", 14 | "ServerPlayerEntityMixin", 15 | "ServerPlayerInteractionManagerMixin", 16 | "ServerPlayNetworkHandlerMixin", 17 | "ExplosionBehaviorMixin" 18 | ], 19 | "injectors": { 20 | "defaultRequire": 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/javadoc.yml: -------------------------------------------------------------------------------- 1 | name: javadoc 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: checkout repository 11 | uses: actions/checkout@v4 12 | - name: validate gradle wrapper 13 | uses: gradle/actions/wrapper-validation@v4 14 | - name: setup jdk 21 15 | uses: actions/setup-java@v4 16 | with: 17 | distribution: 'zulu' 18 | java-version: 21 19 | - name: make gradle wrapper executable 20 | run: chmod +x ./gradlew 21 | - name: javadoc 22 | run: ./gradlew javadoc 23 | - name: deploy 24 | uses: crazy-max/ghaction-github-pages@v3 25 | with: 26 | target_branch: gh-pages 27 | build_dir: build/docs/javadoc 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/config/PowerConfig.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.config; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class PowerConfig { 6 | @SerializedName("base") 7 | public int BASE = 20; 8 | 9 | @SerializedName("member") 10 | public int MEMBER = 20; 11 | 12 | @SerializedName("claimWeight") 13 | public int CLAIM_WEIGHT = 5; 14 | 15 | @SerializedName("deathPenalty") 16 | public int DEATH_PENALTY = 10; 17 | 18 | @SerializedName("powerPerAlly") 19 | public int POWER_PER_ALLY = 0; 20 | 21 | @SerializedName("powerTicks") 22 | public PowerTicks POWER_TICKS = new PowerTicks(); 23 | 24 | public static class PowerTicks { 25 | @SerializedName("ticks") 26 | public int TICKS = 12000; 27 | 28 | @SerializedName("reward") 29 | public int REWARD = 1; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/MinecraftServerMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.api.events.MiscEvents; 4 | 5 | import net.minecraft.server.MinecraftServer; 6 | 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | 12 | @Mixin(MinecraftServer.class) 13 | public class MinecraftServerMixin { 14 | @Inject(at = @At("HEAD"), method = "Lnet/minecraft/server/MinecraftServer;save(ZZZ)Z") 15 | public void save( 16 | boolean suppressLogs, 17 | boolean flush, 18 | boolean force, 19 | CallbackInfoReturnable ci) { 20 | MiscEvents.ON_SAVE.invoker().onSave((MinecraftServer) (Object) this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [ push, pull_request ] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - name: checkout repository 12 | uses: actions/checkout@v4 13 | - name: validate gradle wrapper 14 | uses: gradle/actions/wrapper-validation@v4 15 | - name: setup jdk 21 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: 'zulu' 19 | java-version: 21 20 | - name: make gradle wrapper executable 21 | if: ${{ runner.os != 'Windows' }} 22 | run: chmod +x ./gradlew 23 | - name: build 24 | run: ./gradlew build 25 | - name: capture build artifacts 26 | if: ${{ runner.os == 'Linux' }} 27 | uses: actions/upload-artifact@v4 28 | with: 29 | path: build/libs/ 30 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/persistents/Home.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.persistents; 2 | 3 | import io.icker.factions.database.Field; 4 | 5 | import java.util.UUID; 6 | 7 | public class Home { 8 | @Field("X") 9 | public double x; 10 | 11 | @Field("Y") 12 | public double y; 13 | 14 | @Field("Z") 15 | public double z; 16 | 17 | @Field("Yaw") 18 | public float yaw; 19 | 20 | @Field("Pitch") 21 | public float pitch; 22 | 23 | @Field("Level") 24 | public String level; 25 | 26 | private UUID factionID; 27 | 28 | public Home( 29 | UUID factionID, double x, double y, double z, float yaw, float pitch, String level) { 30 | this.factionID = factionID; 31 | this.x = x; 32 | this.y = y; 33 | this.z = z; 34 | this.yaw = yaw; 35 | this.pitch = pitch; 36 | this.level = level; 37 | } 38 | 39 | public Home() {} 40 | 41 | public Faction getFaction() { 42 | return Faction.get(factionID); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/persistents/Relationship.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.persistents; 2 | 3 | import io.icker.factions.FactionsMod; 4 | import io.icker.factions.database.Field; 5 | 6 | import java.util.ArrayList; 7 | import java.util.UUID; 8 | 9 | public class Relationship { 10 | public enum Status { 11 | ALLY, 12 | NEUTRAL, 13 | ENEMY, 14 | } 15 | 16 | public enum Permissions { 17 | USE_BLOCKS, 18 | PLACE_BLOCKS, 19 | BREAK_BLOCKS, 20 | USE_ENTITIES, 21 | ATTACK_ENTITIES, 22 | USE_INVENTORIES 23 | } 24 | 25 | @Field("Target") 26 | public UUID target; 27 | 28 | @Field("Status") 29 | public Status status; 30 | 31 | @Field("Permissions") 32 | public ArrayList permissions = 33 | new ArrayList<>(FactionsMod.CONFIG.RELATIONSHIPS.DEFAULT_GUEST_PERMISSIONS); 34 | 35 | public Relationship(UUID target, Status status) { 36 | this.target = target; 37 | this.status = status; 38 | } 39 | 40 | public Relationship() {} 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "factions", 4 | "version": "${version}", 5 | 6 | "name": "Factions Mod", 7 | "description": "Highly customizable, lightweight and elegant Factions mod for the Fabric loader", 8 | "authors": [ 9 | "ickerio", 10 | "BlueZeeKing", 11 | "PyPylia" 12 | ], 13 | "contact": { 14 | "homepage": "https://icker.io/factions/", 15 | "sources": "https://github.com/ickerio/factions" 16 | }, 17 | 18 | "license": "MIT", 19 | "icon": "assets/factions/icon.png", 20 | 21 | "environment": "*", 22 | "entrypoints": { 23 | "main": [ 24 | "io.icker.factions.FactionsMod" 25 | ] 26 | }, 27 | "mixins": [ 28 | "factions.mixins.json" 29 | ], 30 | 31 | "depends": { 32 | "fabricloader": ">=0.13.3", 33 | "minecraft": ">=1.21.11", 34 | "java": ">=17", 35 | "fabric-api": "*" 36 | }, 37 | "suggests": { 38 | "fabric-permissions-api-v0": "*", 39 | "dynmap": ">=3.0", 40 | "squaremap": ">=1.0", 41 | "bluemap": ">=2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ickerio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/util/GuiInteract.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.util; 2 | 3 | import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; 4 | import net.minecraft.registry.Registries; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import net.minecraft.sound.SoundCategory; 7 | import net.minecraft.sound.SoundEvent; 8 | import net.minecraft.util.Identifier; 9 | 10 | public class GuiInteract { 11 | public static void playClickSound(ServerPlayerEntity player) { 12 | playSound(player, SoundEvent.of(Identifier.of("minecraft:ui.button.click")), 1, 1); 13 | } 14 | 15 | public static void playSound( 16 | ServerPlayerEntity player, SoundEvent sound, float volume, float pitch) { 17 | player.networkHandler.sendPacket( 18 | new PlaySoundS2CPacket( 19 | Registries.SOUND_EVENT.getEntry(sound), 20 | SoundCategory.UI, 21 | player.getX(), 22 | player.getY(), 23 | player.getZ(), 24 | volume, 25 | pitch, 26 | player.getRandom().nextLong())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: checkout repository 13 | uses: actions/checkout@v4 14 | - name: validate gradle wrapper 15 | uses: gradle/actions/wrapper-validation@v4 16 | - name: setup jdk 21 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: 'zulu' 20 | java-version: 21 21 | - name: make gradle wrapper executable 22 | run: chmod +x ./gradlew 23 | - name: build and generate javadoc 24 | run: | 25 | ./gradlew build 26 | ./gradlew javadoc 27 | - name: Create hash 28 | uses: BlueZeeKing/fabric-mod-hash@v1.1 29 | - name: Release 30 | uses: softprops/action-gh-release@v2 31 | with: 32 | files: | 33 | ./build/libs/factions-mc!(-sources).jar 34 | ./*.sha512 35 | draft: true 36 | - name: Deploy javadoc 37 | uses: crazy-max/ghaction-github-pages@v3 38 | with: 39 | target_branch: gh-pages 40 | build_dir: build/docs/javadoc 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/ServerPlayerInteractionManagerMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.api.events.PlayerEvents; 4 | 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.item.ItemUsageContext; 7 | import net.minecraft.server.network.ServerPlayerInteractionManager; 8 | import net.minecraft.util.ActionResult; 9 | 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Redirect; 13 | 14 | @Mixin(ServerPlayerInteractionManager.class) 15 | public class ServerPlayerInteractionManagerMixin { 16 | @Redirect( 17 | method = "interactBlock", 18 | at = 19 | @At( 20 | value = "INVOKE", 21 | target = 22 | "Lnet/minecraft/item/ItemStack;useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;")) 23 | public ActionResult place(ItemStack instance, ItemUsageContext context) { 24 | if (PlayerEvents.PLACE_BLOCK.invoker().onPlaceBlock(context) == ActionResult.FAIL) { 25 | return ActionResult.FAIL; 26 | } 27 | return instance.useOnBlock(context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/SafeCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.tree.LiteralCommandNode; 6 | 7 | import io.icker.factions.FactionsMod; 8 | import io.icker.factions.api.events.PlayerEvents; 9 | import io.icker.factions.util.Command; 10 | 11 | import net.minecraft.server.command.CommandManager; 12 | import net.minecraft.server.command.ServerCommandSource; 13 | import net.minecraft.server.network.ServerPlayerEntity; 14 | 15 | public class SafeCommand implements Command { 16 | 17 | private int run(CommandContext context) throws CommandSyntaxException { 18 | ServerPlayerEntity player = context.getSource().getPlayerOrThrow(); 19 | PlayerEvents.OPEN_SAFE.invoker().onOpenSafe(player, Command.getUser(player).getFaction()); 20 | return 1; 21 | } 22 | 23 | @Override 24 | public LiteralCommandNode getNode() { 25 | return CommandManager.literal("safe") 26 | .requires( 27 | Requires.multiple( 28 | Requires.hasPerms("faction.safe", 0), 29 | Requires.isMember(), 30 | s -> FactionsMod.CONFIG.SAFE != null)) 31 | .executes(this::run) 32 | .build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for filling out a feature request! Please be as detailed as possible so that we may consider and review the request easier. 8 | We ask that you search all the issues to avoid a duplicate feature request. If one exists, please reply if you have anything to add. 9 | Before requesting a new feature, please make sure you are using the latest version and that the feature you are requesting is not already in the mod. 10 | 11 | - type: textarea 12 | attributes: 13 | label: Is your feature request related to a problem? 14 | description: Please give some context for this request. Why do you want it added? 15 | validations: 16 | required: true 17 | 18 | - type: textarea 19 | attributes: 20 | label: Describe the solution you'd like. 21 | description: A clear and concise description of what you want. 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | attributes: 27 | label: Describe alternatives you've considered. 28 | description: List any alternatives you might have tried to get the feature you want. 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | attributes: 34 | label: Other 35 | description: Add any other context or screenshots about the feature request below. 36 | validations: 37 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for filling out a bug report! We ask that you search all the issues to avoid a duplicate bug report. If one exists, please reply with as much information as possible. 8 | Before reporting a bug here, please make sure you are on the latest supported version. 9 | 10 | - type: textarea 11 | attributes: 12 | label: Expected behavior 13 | description: What you expected to see. 14 | validations: 15 | required: true 16 | 17 | - type: textarea 18 | attributes: 19 | label: Observed/actual behavior 20 | description: What you actually saw. 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | attributes: 26 | label: Steps/models to reproduce 27 | description: This may include a screenshot, a video, or detailed instructions to help reconstruct the issue. 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | attributes: 33 | label: Version 34 | description: Please provide the version of the mod you're running. This can be found in the file name of the mod jar. 35 | validations: 36 | required: true 37 | 38 | - type: textarea 39 | attributes: 40 | label: Other 41 | description: | 42 | Please include other information that may be helpful below. 43 | The more information we receive, the quicker and more effective we can be at finding the solution to the issue. 44 | validations: 45 | required: false -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/EnderChestBlockMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.FactionsMod; 4 | import io.icker.factions.api.events.PlayerEvents; 5 | import io.icker.factions.api.persistents.User; 6 | 7 | import net.minecraft.block.BlockState; 8 | import net.minecraft.block.EnderChestBlock; 9 | import net.minecraft.entity.player.PlayerEntity; 10 | import net.minecraft.util.ActionResult; 11 | import net.minecraft.util.hit.BlockHitResult; 12 | import net.minecraft.util.math.BlockPos; 13 | import net.minecraft.world.World; 14 | 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 19 | 20 | @Mixin(EnderChestBlock.class) 21 | public class EnderChestBlockMixin { 22 | @Inject(method = "onUse", at = @At("HEAD"), cancellable = true) 23 | public void onUse( 24 | BlockState state, 25 | World world, 26 | BlockPos pos, 27 | PlayerEntity player, 28 | BlockHitResult hit, 29 | CallbackInfoReturnable info) { 30 | if (FactionsMod.CONFIG.SAFE == null || !FactionsMod.CONFIG.SAFE.ENDER_CHEST) return; 31 | 32 | ActionResult result = 33 | PlayerEvents.OPEN_SAFE 34 | .invoker() 35 | .onOpenSafe(player, User.get(player.getUuid()).getFaction()); 36 | if (result != ActionResult.PASS) { 37 | info.setReturnValue(result); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/LockableContainerBlockEntityMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.api.events.PlayerEvents; 4 | 5 | import net.minecraft.block.entity.LockableContainerBlockEntity; 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.util.ActionResult; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(LockableContainerBlockEntity.class) 15 | public class LockableContainerBlockEntityMixin { 16 | @Inject( 17 | method = "checkUnlocked(Lnet/minecraft/entity/player/PlayerEntity;)Z", 18 | at = @At("RETURN"), 19 | cancellable = true) 20 | private void checkUnlocked(PlayerEntity player, CallbackInfoReturnable cir) { 21 | cir.setReturnValue( 22 | cir.getReturnValue() 23 | && PlayerEvents.USE_INVENTORY 24 | .invoker() 25 | .onUseInventory( 26 | player, 27 | ((LockableContainerBlockEntity) (Object) this) 28 | .getPos(), 29 | ((LockableContainerBlockEntity) (Object) this) 30 | .getWorld()) 31 | != ActionResult.FAIL); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/events/ClaimEvents.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.events; 2 | 3 | import io.icker.factions.api.persistents.Claim; 4 | import io.icker.factions.api.persistents.Faction; 5 | 6 | import net.fabricmc.fabric.api.event.Event; 7 | import net.fabricmc.fabric.api.event.EventFactory; 8 | 9 | /** Events related to {@link Claim} */ 10 | public final class ClaimEvents { 11 | /** Called when a chunk claim is added by a faction (See {@link Claim}) */ 12 | public static final Event ADD = 13 | EventFactory.createArrayBacked( 14 | Add.class, 15 | callbacks -> 16 | (claim) -> { 17 | for (Add callback : callbacks) { 18 | callback.onAdd(claim); 19 | } 20 | }); 21 | 22 | /** Called when a faction removes a claim (See {@link Claim}) */ 23 | public static final Event REMOVE = 24 | EventFactory.createArrayBacked( 25 | Remove.class, 26 | callbacks -> 27 | (x, z, level, faction) -> { 28 | for (Remove callback : callbacks) { 29 | callback.onRemove(x, z, level, faction); 30 | } 31 | }); 32 | 33 | @FunctionalInterface 34 | public interface Add { 35 | void onAdd(Claim claim); 36 | } 37 | 38 | @FunctionalInterface 39 | public interface Remove { 40 | void onRemove(int x, int z, String level, Faction faction); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/events/MiscEvents.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.events; 2 | 3 | import net.fabricmc.fabric.api.event.Event; 4 | import net.fabricmc.fabric.api.event.EventFactory; 5 | import net.minecraft.server.MinecraftServer; 6 | 7 | /** Events related to miscellaneous actions */ 8 | public final class MiscEvents { 9 | /** 10 | * Called when the Factions database is saved (which is also when the server saves world and 11 | * player files) 12 | */ 13 | public static final Event ON_SAVE = 14 | EventFactory.createArrayBacked( 15 | Save.class, 16 | callbacks -> 17 | (server) -> { 18 | for (Save callback : callbacks) { 19 | callback.onSave(server); 20 | } 21 | }); 22 | 23 | /** Called when the game attempts to spawn in mobs (UNIMPLEMENTED) */ 24 | public static final Event ON_MOB_SPAWN_ATTEMPT = 25 | EventFactory.createArrayBacked( 26 | MobSpawnAttempt.class, 27 | callbacks -> 28 | () -> { 29 | for (MobSpawnAttempt callback : callbacks) { 30 | callback.onMobSpawnAttempt(); 31 | } 32 | }); 33 | 34 | @FunctionalInterface 35 | public interface Save { 36 | void onSave(MinecraftServer server); 37 | } 38 | 39 | @FunctionalInterface 40 | public interface MobSpawnAttempt { 41 | void onMobSpawnAttempt(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/core/InteractionsUtil.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.core; 2 | 3 | import io.icker.factions.api.persistents.User; 4 | import io.icker.factions.util.Message; 5 | 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.server.network.ServerPlayerEntity; 8 | import net.minecraft.text.Text; 9 | import net.minecraft.util.Hand; 10 | 11 | public class InteractionsUtil { 12 | public static void sync(ServerPlayerEntity player, ItemStack itemStack, Hand hand) { 13 | player.setStackInHand(hand, itemStack); 14 | itemStack.setCount(itemStack.getCount()); 15 | if (itemStack.isDamageable()) { 16 | itemStack.setDamage(itemStack.getDamage()); 17 | } 18 | 19 | if (!player.isUsingItem()) { 20 | player.playerScreenHandler.syncState(); 21 | } 22 | } 23 | 24 | public static void warn(ServerPlayerEntity player, InteractionsUtilActions action) { 25 | SoundManager.warningSound(player); 26 | User user = User.get(player.getUuid()); 27 | new Message( 28 | Text.translatable( 29 | "factions.interactions.cannot_do", 30 | Text.translatable( 31 | "factions.interactions.name." 32 | + action.toString().toLowerCase()))) 33 | .fail() 34 | .send(player, !user.radar); 35 | } 36 | 37 | public enum InteractionsUtilActions { 38 | BREAK_BLOCKS, 39 | USE_BLOCKS, 40 | PLACE_BLOCKS, 41 | PLACE_OR_PICKUP_LIQUIDS, 42 | ATTACK_ENTITIES, 43 | USE_ENTITIES, 44 | USE_INVENTORY 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/core/ServerManager.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.core; 2 | 3 | import io.icker.factions.api.events.MiscEvents; 4 | import io.icker.factions.api.persistents.Claim; 5 | import io.icker.factions.api.persistents.Faction; 6 | import io.icker.factions.api.persistents.User; 7 | import io.icker.factions.util.Message; 8 | 9 | import net.fabricmc.fabric.api.networking.v1.PacketSender; 10 | import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; 11 | import net.minecraft.server.MinecraftServer; 12 | import net.minecraft.server.network.ServerPlayNetworkHandler; 13 | import net.minecraft.server.network.ServerPlayerEntity; 14 | import net.minecraft.text.Text; 15 | 16 | public class ServerManager { 17 | public static void register() { 18 | ServerPlayConnectionEvents.JOIN.register(ServerManager::playerJoin); 19 | MiscEvents.ON_SAVE.register(ServerManager::save); 20 | } 21 | 22 | private static void save(MinecraftServer server) { 23 | Claim.save(); 24 | Faction.save(); 25 | User.save(); 26 | } 27 | 28 | private static void playerJoin( 29 | ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server) { 30 | ServerPlayerEntity player = handler.getPlayer(); 31 | User user = User.get(player.getUuid()); 32 | 33 | if (user.isInFaction()) { 34 | Faction faction = user.getFaction(); 35 | new Message( 36 | Text.translatable( 37 | "factions.events.member_returns", player.getName().getString())) 38 | .send(player, false); 39 | new Message(faction.getMOTD()).prependFaction(faction).send(player, false); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/ListCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.tree.LiteralCommandNode; 6 | 7 | import io.icker.factions.FactionsMod; 8 | import io.icker.factions.api.persistents.Faction; 9 | import io.icker.factions.api.persistents.User; 10 | import io.icker.factions.ui.ListGui; 11 | import io.icker.factions.util.Command; 12 | import io.icker.factions.util.Message; 13 | 14 | import net.minecraft.server.command.CommandManager; 15 | import net.minecraft.server.command.ServerCommandSource; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.text.Text; 18 | 19 | import java.util.Collection; 20 | 21 | public class ListCommand implements Command { 22 | private int run(CommandContext context) throws CommandSyntaxException { 23 | ServerCommandSource source = context.getSource(); 24 | ServerPlayerEntity player = source.getPlayerOrThrow(); 25 | 26 | User user = User.get(player.getUuid()); 27 | 28 | if (FactionsMod.CONFIG.GUI) { 29 | new ListGui(player, user, null); 30 | return 1; 31 | } 32 | 33 | Collection factions = Faction.all(); 34 | int size = factions.size(); 35 | 36 | new Message(Text.translatable("factions.gui.list.title")).send(player, false); 37 | 38 | if (size == 0) return 1; 39 | 40 | Message list = new Message(); 41 | for (Faction faction : factions) { 42 | String name = faction.getName(); 43 | list.add(new Message(name).click("/factions info " + name).format(faction.getColor())) 44 | .add(", "); 45 | } 46 | 47 | list.send(player, false); 48 | 49 | return 1; 50 | } 51 | 52 | public LiteralCommandNode getNode() { 53 | return CommandManager.literal("list") 54 | .requires(Requires.hasPerms("factions.list", 0)) 55 | .executes(this::run) 56 | .build(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/LeaveCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.tree.LiteralCommandNode; 6 | 7 | import io.icker.factions.FactionsMod; 8 | import io.icker.factions.api.persistents.Faction; 9 | import io.icker.factions.api.persistents.User; 10 | import io.icker.factions.util.Command; 11 | import io.icker.factions.util.Message; 12 | 13 | import net.minecraft.server.command.CommandManager; 14 | import net.minecraft.server.command.ServerCommandSource; 15 | import net.minecraft.server.network.ServerPlayerEntity; 16 | import net.minecraft.text.Text; 17 | 18 | public class LeaveCommand implements Command { 19 | private int run(CommandContext context) throws CommandSyntaxException { 20 | ServerCommandSource source = context.getSource(); 21 | ServerPlayerEntity player = source.getPlayerOrThrow(); 22 | 23 | User user = Command.getUser(player); 24 | Faction faction = user.getFaction(); 25 | 26 | user.leaveFaction(); 27 | new Message(Text.translatable("factions.command.leave.success.actor")) 28 | .prependFaction(faction) 29 | .send(player, false); 30 | new Message( 31 | Text.translatable( 32 | "factions.command.leave.success.subject", 33 | player.getName().getString())) 34 | .send(faction); 35 | 36 | context.getSource().getServer().getPlayerManager().sendCommandTree(player); 37 | 38 | if (faction.getUsers().size() == 0) { 39 | faction.remove(); 40 | } else { 41 | faction.adjustPower(-FactionsMod.CONFIG.POWER.MEMBER); 42 | } 43 | 44 | return 1; 45 | } 46 | 47 | public LiteralCommandNode getNode() { 48 | return CommandManager.literal("leave") 49 | .requires( 50 | Requires.multiple( 51 | Requires.require(m -> m.isInFaction() && m.rank != User.Rank.OWNER), 52 | Requires.hasPerms("factions.leave", 0))) 53 | .executes(this::run) 54 | .build(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/util/WorldUtils.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.util; 2 | 3 | import net.fabricmc.fabric.api.event.Event; 4 | import net.fabricmc.fabric.api.event.EventFactory; 5 | import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 6 | import net.minecraft.registry.RegistryKey; 7 | import net.minecraft.server.MinecraftServer; 8 | import net.minecraft.server.world.ServerWorld; 9 | import net.minecraft.util.Identifier; 10 | import net.minecraft.world.World; 11 | 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.Objects; 15 | import java.util.Optional; 16 | 17 | public class WorldUtils { 18 | public static MinecraftServer server; 19 | 20 | public static final Event ON_READY = 21 | EventFactory.createArrayBacked( 22 | Ready.class, 23 | callbacks -> 24 | () -> { 25 | for (Ready callback : callbacks) { 26 | callback.onReady(); 27 | } 28 | }); 29 | 30 | public static void register() { 31 | ServerLifecycleEvents.SERVER_STARTING.register( 32 | (server1) -> { 33 | WorldUtils.server = server1; 34 | ON_READY.invoker().onReady(); 35 | }); 36 | } 37 | 38 | public static boolean isReady() { 39 | return server != null; 40 | } 41 | 42 | public static boolean hasWorlds() { 43 | return !WorldUtils.server.getWorldRegistryKeys().isEmpty(); 44 | } 45 | 46 | public static boolean isValid(String level) { 47 | return WorldUtils.server.getWorldRegistryKeys().stream() 48 | .anyMatch(key -> Objects.equals(key.getValue(), Identifier.of(level))); 49 | } 50 | 51 | @Nullable 52 | public static ServerWorld getWorld(String level) { 53 | Optional> key = 54 | WorldUtils.server.getWorldRegistryKeys().stream() 55 | .filter(testKey -> Objects.equals(testKey.getValue(), Identifier.of(level))) 56 | .findAny(); 57 | 58 | if (key.isEmpty()) { 59 | return null; 60 | } else { 61 | return server.getWorld(key.get()); 62 | } 63 | } 64 | 65 | @FunctionalInterface 66 | public interface Ready { 67 | void onReady(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/core/SoundManager.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.core; 2 | 3 | import io.icker.factions.api.events.ClaimEvents; 4 | import io.icker.factions.api.events.FactionEvents; 5 | import io.icker.factions.api.persistents.Faction; 6 | import io.icker.factions.api.persistents.User; 7 | import io.icker.factions.util.GuiInteract; 8 | 9 | import net.minecraft.registry.entry.RegistryEntry; 10 | import net.minecraft.server.PlayerManager; 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.minecraft.sound.SoundEvent; 13 | import net.minecraft.sound.SoundEvents; 14 | 15 | public class SoundManager { 16 | public static PlayerManager playerManager; 17 | 18 | public static void register() { 19 | ClaimEvents.ADD.register( 20 | claim -> playFaction(claim.getFaction(), SoundEvents.BLOCK_NOTE_BLOCK_PLING, 2.0F)); 21 | ClaimEvents.REMOVE.register( 22 | (x, z, level, faction) -> 23 | playFaction(faction, SoundEvents.BLOCK_NOTE_BLOCK_PLING, 0.5F)); 24 | FactionEvents.POWER_CHANGE.register( 25 | (faction, oldPower) -> 26 | playFaction(faction, SoundEvents.BLOCK_NOTE_BLOCK_CHIME, 1F)); 27 | FactionEvents.MEMBER_JOIN.register( 28 | (faction, user) -> playFaction(faction, SoundEvents.BLOCK_NOTE_BLOCK_BIT, 2.0F)); 29 | FactionEvents.MEMBER_LEAVE.register( 30 | (faction, user) -> playFaction(faction, SoundEvents.BLOCK_NOTE_BLOCK_BIT, 0.5F)); 31 | } 32 | 33 | private static void playFaction( 34 | Faction faction, RegistryEntry.Reference soundEvent, float pitch) { 35 | for (User user : faction.getUsers()) { 36 | ServerPlayerEntity player = FactionsManager.playerManager.getPlayer(user.getID()); 37 | if (player != null 38 | && (user.sounds == User.SoundMode.ALL 39 | || user.sounds == User.SoundMode.FACTION)) { 40 | GuiInteract.playSound(player, soundEvent.value(), 0.2F, pitch); 41 | } 42 | } 43 | } 44 | 45 | public static void warningSound(ServerPlayerEntity player) { 46 | User user = User.get(player.getUuid()); 47 | if (user.sounds == User.SoundMode.ALL || user.sounds == User.SoundMode.WARNINGS) { 48 | GuiInteract.playSound(player, SoundEvents.BLOCK_NOTE_BLOCK_BASS.value(), 0.5F, 1.0F); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/persistents/Claim.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.persistents; 2 | 3 | import io.icker.factions.api.events.ClaimEvents; 4 | import io.icker.factions.api.persistents.User.Rank; 5 | import io.icker.factions.database.Database; 6 | import io.icker.factions.database.Field; 7 | import io.icker.factions.database.Name; 8 | import io.icker.factions.util.WorldUtils; 9 | 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | @Name("Claim") 15 | public class Claim { 16 | private static final HashMap STORE = Database.load(Claim.class, c -> c.getKey()); 17 | 18 | @Field("X") 19 | public int x; 20 | 21 | @Field("Z") 22 | public int z; 23 | 24 | /** The dimension of the claim */ 25 | @Field("Level") 26 | public String level; 27 | 28 | @Field("FactionID") 29 | public UUID factionID; 30 | 31 | @Field("AccessLevel") 32 | public Rank accessLevel; 33 | 34 | public Claim(int x, int z, String level, UUID factionID) { 35 | this.x = x; 36 | this.z = z; 37 | this.level = level; 38 | this.factionID = factionID; 39 | this.accessLevel = Rank.MEMBER; 40 | } 41 | 42 | public Claim() {} 43 | 44 | public String getKey() { 45 | return String.format("%s-%d-%d", level, x, z); 46 | } 47 | 48 | public static Claim get(int x, int z, String level) { 49 | return STORE.get(String.format("%s-%d-%d", level, x, z)); 50 | } 51 | 52 | public static List getByFaction(UUID factionID) { 53 | return STORE.values().stream().filter(c -> c.factionID.equals(factionID)).toList(); 54 | } 55 | 56 | public static void audit() { 57 | STORE.values() 58 | .removeIf( 59 | (claim) -> 60 | Faction.get(claim.factionID) == null 61 | || !WorldUtils.isValid(claim.level)); 62 | } 63 | 64 | public static void add(Claim claim) { 65 | STORE.put(claim.getKey(), claim); 66 | ClaimEvents.ADD.invoker().onAdd(claim); 67 | } 68 | 69 | public Faction getFaction() { 70 | return Faction.get(factionID); 71 | } 72 | 73 | public void remove() { 74 | STORE.remove(getKey()); 75 | ClaimEvents.REMOVE.invoker().onRemove(x, z, level, Faction.get(factionID)); 76 | } 77 | 78 | public static void save() { 79 | Database.save(Claim.class, STORE.values().stream().toList()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/events/RelationshipEvents.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.events; 2 | 3 | import io.icker.factions.api.persistents.Relationship; 4 | 5 | import net.fabricmc.fabric.api.event.Event; 6 | import net.fabricmc.fabric.api.event.EventFactory; 7 | 8 | /** All events related to relationships */ 9 | public final class RelationshipEvents { 10 | /** When a faction is declared as a different status */ 11 | public static final Event NEW_DECLARATION = 12 | EventFactory.createArrayBacked( 13 | NewDecleration.class, 14 | callbacks -> 15 | (relationship) -> { 16 | for (NewDecleration callback : callbacks) { 17 | callback.onNewDecleration(relationship); 18 | } 19 | }); 20 | 21 | /** 22 | * When two factions are declared to have the same status 23 | * 24 | *

For example, mutual allies 25 | */ 26 | public static final Event NEW_MUTUAL = 27 | EventFactory.createArrayBacked( 28 | NewMutual.class, 29 | callbacks -> 30 | (relationship) -> { 31 | for (NewMutual callback : callbacks) { 32 | callback.onNewMutual(relationship); 33 | } 34 | }); 35 | 36 | /** When a mutual relationship is ended by either of the two factions */ 37 | public static final Event END_MUTUAL = 38 | EventFactory.createArrayBacked( 39 | EndMutual.class, 40 | callbacks -> 41 | (relationship, oldStatus) -> { 42 | for (EndMutual callback : callbacks) { 43 | callback.onEndMutual(relationship, oldStatus); 44 | } 45 | }); 46 | 47 | @FunctionalInterface 48 | public interface NewDecleration { 49 | void onNewDecleration(Relationship relationship); 50 | } 51 | 52 | @FunctionalInterface 53 | public interface NewMutual { 54 | void onNewMutual(Relationship relationship); 55 | } 56 | 57 | @FunctionalInterface 58 | public interface EndMutual { 59 | void onEndMutual(Relationship relationship, Relationship.Status oldStatus); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/PlayerManagerMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.api.persistents.User; 4 | import io.icker.factions.util.StyledChatCompatibility; 5 | 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import net.minecraft.network.message.MessageType; 8 | import net.minecraft.network.message.SentMessage; 9 | import net.minecraft.server.PlayerManager; 10 | import net.minecraft.server.network.ServerPlayerEntity; 11 | 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Redirect; 15 | 16 | @Mixin(PlayerManager.class) 17 | public class PlayerManagerMixin { 18 | @Redirect( 19 | method = 20 | "broadcast(Lnet/minecraft/network/message/SignedMessage;Ljava/util/function/Predicate;Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/network/message/MessageType$Parameters;)V", 21 | at = 22 | @At( 23 | value = "INVOKE", 24 | target = 25 | "Lnet/minecraft/server/network/ServerPlayerEntity;sendChatMessage(Lnet/minecraft/network/message/SentMessage;ZLnet/minecraft/network/message/MessageType$Parameters;)V")) 26 | public void sendChatMessage( 27 | ServerPlayerEntity player, 28 | SentMessage message, 29 | boolean bl, 30 | MessageType.Parameters parameters) { 31 | if (message instanceof SentMessage.Profileless 32 | || (FabricLoader.getInstance().isModLoaded("styledchat") 33 | && StyledChatCompatibility.isNotPlayer(message))) { 34 | player.sendChatMessage(message, bl, parameters); 35 | return; 36 | } 37 | 38 | User sender; 39 | 40 | if (FabricLoader.getInstance().isModLoaded("styledchat")) { 41 | sender = User.get(StyledChatCompatibility.getSender(message)); 42 | } else { 43 | sender = User.get(((SentMessage.Chat) message).message().link().sender()); 44 | } 45 | 46 | User target = User.get(player.getUuid()); 47 | 48 | if (sender.chat == User.ChatMode.GLOBAL && target.chat != User.ChatMode.FOCUS) { 49 | player.sendChatMessage(message, bl, parameters); 50 | } 51 | 52 | if ((sender.chat == User.ChatMode.FACTION || sender.chat == User.ChatMode.FOCUS) 53 | && sender.getFaction().equals(target.getFaction())) { 54 | player.sendChatMessage(message, bl, parameters); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/ExplosionBehaviorMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.api.events.PlayerEvents; 4 | 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.util.ActionResult; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.world.BlockView; 10 | import net.minecraft.world.explosion.Explosion; 11 | import net.minecraft.world.explosion.ExplosionBehavior; 12 | import net.minecraft.world.explosion.ExplosionImpl; 13 | 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Redirect; 17 | 18 | @Mixin(ExplosionImpl.class) 19 | public class ExplosionBehaviorMixin { 20 | @Redirect( 21 | method = "getBlocksToDestroy", 22 | at = 23 | @At( 24 | value = "INVOKE", 25 | target = 26 | "Lnet/minecraft/world/explosion/ExplosionBehavior;canDestroyBlock(Lnet/minecraft/world/explosion/Explosion;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;F)Z")) 27 | public boolean canDestroyBlock( 28 | ExplosionBehavior behavior, 29 | Explosion explosion, 30 | BlockView world, 31 | BlockPos pos, 32 | BlockState state, 33 | float power) { 34 | ActionResult result = 35 | PlayerEvents.EXPLODE_BLOCK.invoker().onExplodeBlock(explosion, world, pos, state); 36 | if (result.isAccepted()) { 37 | return true; 38 | } else if (result == ActionResult.FAIL) { 39 | return false; 40 | } 41 | 42 | return behavior.canDestroyBlock(explosion, world, pos, state, power); 43 | } 44 | 45 | @Redirect( 46 | method = "damageEntities", 47 | at = 48 | @At( 49 | value = "INVOKE", 50 | target = 51 | "Lnet/minecraft/entity/Entity;isImmuneToExplosion(Lnet/minecraft/world/explosion/Explosion;)Z")) 52 | public boolean shouldDamage(Entity entity, Explosion explosion) { 53 | ActionResult result = 54 | PlayerEvents.EXPLODE_DAMAGE.invoker().onExplodeDamage(explosion, entity); 55 | if (result.isAccepted()) { 56 | return false; 57 | } else if (result == ActionResult.FAIL) { 58 | return true; 59 | } 60 | 61 | return entity.isImmuneToExplosion(explosion); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/ui/InputGui.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.ui; 2 | 3 | import eu.pb4.sgui.api.elements.GuiElementBuilder; 4 | import eu.pb4.sgui.api.gui.AnvilInputGui; 5 | 6 | import io.icker.factions.util.GuiInteract; 7 | 8 | import net.minecraft.component.DataComponentTypes; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.item.Items; 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.minecraft.sound.SoundEvent; 13 | import net.minecraft.text.MutableText; 14 | import net.minecraft.text.Style; 15 | import net.minecraft.text.Text; 16 | import net.minecraft.util.Formatting; 17 | import net.minecraft.util.Identifier; 18 | 19 | import java.util.Objects; 20 | import java.util.Timer; 21 | import java.util.TimerTask; 22 | 23 | public class InputGui extends AnvilInputGui { 24 | public GuiElementBuilder returnBtn; 25 | public GuiElementBuilder confirmBtn; 26 | private final Timer timer = new Timer(); 27 | 28 | public InputGui(ServerPlayerEntity player) { 29 | super(player, false); 30 | 31 | this.returnBtn = 32 | new GuiElementBuilder(Items.BARRIER) 33 | .setName( 34 | Text.translatable("factions.gui.generic.back") 35 | .formatted(Formatting.RED)); 36 | this.confirmBtn = 37 | new GuiElementBuilder(Items.SLIME_BALL) 38 | .setName( 39 | Text.translatable("factions.gui.generic.confirm") 40 | .formatted(Formatting.GREEN)); 41 | } 42 | 43 | public void showErrorMessage(String text, int slotIndex) { 44 | showErrorMessage(Text.literal(text), slotIndex); 45 | } 46 | 47 | public void showErrorMessage(MutableText text, int slotIndex) { 48 | ItemStack item = Objects.requireNonNull(this.getSlot(slotIndex)).getItemStack(); 49 | item.set( 50 | DataComponentTypes.CUSTOM_NAME, 51 | text.setStyle(Style.EMPTY.withItalic(false).withColor(Formatting.RED))); 52 | GuiInteract.playSound( 53 | player, SoundEvent.of(Identifier.of("minecraft:item.shield.break")), 1f, 1f); 54 | timer.schedule( 55 | new TimerTask() { 56 | @Override 57 | public void run() { 58 | item.remove(DataComponentTypes.CUSTOM_NAME); 59 | } 60 | }, 61 | 1500); 62 | } 63 | 64 | @Override 65 | public boolean open() { 66 | this.setSlot(1, returnBtn); 67 | this.setSlot(2, confirmBtn); 68 | return super.open(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/core/ChatManager.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.core; 2 | 3 | import io.icker.factions.FactionsMod; 4 | import io.icker.factions.api.persistents.Faction; 5 | import io.icker.factions.api.persistents.User; 6 | import io.icker.factions.util.Message; 7 | 8 | import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent; 9 | import net.minecraft.server.network.ServerPlayerEntity; 10 | import net.minecraft.text.Text; 11 | import net.minecraft.util.Formatting; 12 | 13 | import java.util.UUID; 14 | 15 | public class ChatManager { 16 | public static void register() { 17 | ServerMessageDecoratorEvent.EVENT.register( 18 | ServerMessageDecoratorEvent.CONTENT_PHASE, 19 | (sender, message) -> { 20 | if (sender != null && FactionsMod.CONFIG.DISPLAY.MODIFY_CHAT) { 21 | return ChatManager.handleMessage(sender, message.getString()); 22 | } 23 | return message; 24 | }); 25 | } 26 | 27 | public static Text handleMessage(ServerPlayerEntity sender, String message) { 28 | UUID id = sender.getUuid(); 29 | User member = User.get(id); 30 | 31 | if (member.chat == User.ChatMode.GLOBAL) { 32 | if (member.isInFaction()) { 33 | return ChatManager.inFactionGlobal(sender, member.getFaction(), message); 34 | } else { 35 | return ChatManager.global(sender, message); 36 | } 37 | } else { 38 | if (member.isInFaction()) { 39 | return ChatManager.faction(sender, member.getFaction(), message); 40 | } else { 41 | return ChatManager.global(sender, message); 42 | } 43 | } 44 | } 45 | 46 | private static Text global(ServerPlayerEntity sender, String message) { 47 | return new Message(message).format(Formatting.GRAY).raw(); 48 | } 49 | 50 | private static Text inFactionGlobal( 51 | ServerPlayerEntity sender, Faction faction, String message) { 52 | return new Message() 53 | .add(new Message(faction.getName()).format(Formatting.BOLD, faction.getColor())) 54 | .filler("»") 55 | .add(new Message(message).format(Formatting.GRAY)) 56 | .raw(); 57 | } 58 | 59 | private static Text faction(ServerPlayerEntity sender, Faction faction, String message) { 60 | return new Message() 61 | .add( 62 | new Message(Text.translatable("factions.chat.in_faction_symbol")) 63 | .format(Formatting.BOLD, faction.getColor())) 64 | .filler("»") 65 | .add(new Message(message).format(Formatting.GRAY)) 66 | .raw(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Factions Mod Icon 4 | 5 | # Factions Mod 6 | 7 | Highly customizable, lightweight and elegant factions mod for the [Fabric loader][fabric] in Minecraft 1.19 8 | 9 | [![Release](https://img.shields.io/github/v/release/ickerio/factions?style=for-the-badge&include_prereleases&sort=semver)][github:releases] 10 | [![Available For](https://img.shields.io/badge/dynamic/json?label=Available%20For&style=for-the-badge&color=e64626&query=version&url=https%3A%2F%2Fapi.blueish.dev%2Fapi%2Fminecraft%2Fversion%3Fid%3Dfactions)][modrinth] 11 | [![Downloads](https://img.shields.io/badge/dynamic/json?label=Downloads&style=for-the-badge&color=e64626&query=downloads&url=https%3A%2F%2Fapi.blueish.dev%2Fapi%2Fminecraft%2Fdownloads%3Fcurseid%3D497362%26modrinthid%3Dfactions)][modrinth:releases] 12 | 13 |
14 | 15 | ### **ABOUT** 16 | 17 | Factions Mod is an ultra lightweight, fast, and elegant solution to factions in modern minecraft. The **server-side** mod expands upon all the classic factions features whilst also focusing on customization and performance. Grow your faction, expand your claims, and storm your enemies for their chunks and loot. 18 | 19 | A faction's power cap increases as new members join, expanding their ability to claim more land. For each claim they make, it requires that faction to sustain more power. Dying to other players will temporarily lose faction power and if it drops below the required threshold, all their claims will be vulnerable to being overtaken. 20 | 21 |   22 | 23 | ### **FEATURES** 24 | 25 | - 🎯 Fully featured factions mod with over 30 [commands][wiki:commands] 26 | - ✨ Faction ranks, colors, MOTD and descriptions 27 | - 🎉 In faction private chat, global chat and a stylized player list 28 | - ⚡ Extreme performance and reliability 29 | - ⚙️ Advanced [configuration][wiki:config] and customization options 30 | - 🔥 Dynmap and Lucko Perms support out the box 31 | - 🚀 Event driven API for further extensibility 32 | - 💬 Strong [community][discord] and active developer support 33 | 34 |   35 | 36 | ### **GET STARTED** 37 | 38 | Factions Mod is very intuitive and works immediately after installation, requiring no additional configuration. However, you can read further about the mod on the [Wiki][wiki]. Our wiki goes in depth about the factions mechanics, its configuration, commands and integrations. 39 | 40 | A list of all **commands** is available on our [wiki][wiki:commands] 41 | 42 | Have an issue or a suggestion? Join [our discord][discord] 43 | 44 | ### **License** 45 | [MIT](LICENSE) 46 | 47 | [fabric]: https://fabricmc.net/ 48 | [modrinth]: https://modrinth.com/mod/factions 49 | [modrinth:releases]: https://modrinth.com/mod/factions/versions 50 | [github:releases]: https://github.com/ickerio/factions/releases 51 | [wiki]: https://github.com/ickerio/factions/wiki 52 | [wiki:config]: https://github.com/ickerio/factions/wiki/Config 53 | [wiki:commands]: https://github.com/ickerio/factions/wiki/Commands 54 | [discord]: https://discord.gg/tHPFegeAY8 55 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/core/WorldManager.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.core; 2 | 3 | import io.icker.factions.FactionsMod; 4 | import io.icker.factions.api.events.MiscEvents; 5 | import io.icker.factions.api.events.PlayerEvents; 6 | import io.icker.factions.api.persistents.Claim; 7 | import io.icker.factions.api.persistents.Faction; 8 | import io.icker.factions.api.persistents.User; 9 | import io.icker.factions.util.Message; 10 | 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.minecraft.server.world.ServerWorld; 13 | import net.minecraft.text.Text; 14 | import net.minecraft.util.Formatting; 15 | import net.minecraft.util.math.ChunkPos; 16 | 17 | public class WorldManager { 18 | public static void register() { 19 | PlayerEvents.ON_MOVE.register(WorldManager::onMove); 20 | MiscEvents.ON_MOB_SPAWN_ATTEMPT.register(WorldManager::onMobSpawnAttempt); 21 | } 22 | 23 | private static void onMobSpawnAttempt() { 24 | // TODO Implement this 25 | } 26 | 27 | private static void onMove(ServerPlayerEntity player) { 28 | User user = User.get(player.getUuid()); 29 | ServerWorld world = (ServerWorld) player.getEntityWorld(); 30 | String dimension = world.getRegistryKey().getValue().toString(); 31 | 32 | ChunkPos chunkPos = world.getChunk(player.getBlockPos()).getPos(); 33 | 34 | Claim claim = Claim.get(chunkPos.x, chunkPos.z, dimension); 35 | if (user.autoclaim && claim == null) { 36 | Faction faction = user.getFaction(); 37 | int requiredPower = 38 | (faction.getClaims().size() + 1) * FactionsMod.CONFIG.POWER.CLAIM_WEIGHT; 39 | int maxPower = 40 | faction.getUsers().size() * FactionsMod.CONFIG.POWER.MEMBER 41 | + FactionsMod.CONFIG.POWER.BASE 42 | + faction.getAdminPower(); 43 | 44 | if (maxPower < requiredPower) { 45 | new Message(Text.translatable("factions.events.autoclaim.fail")) 46 | .fail() 47 | .send(player, false); 48 | user.autoclaim = false; 49 | } else { 50 | faction.addClaim(chunkPos.x, chunkPos.z, dimension); 51 | claim = Claim.get(chunkPos.x, chunkPos.z, dimension); 52 | new Message( 53 | Text.translatable( 54 | "factions.events.autoclaim.success", 55 | chunkPos.x, 56 | chunkPos.z, 57 | player.getName().getString())) 58 | .send(faction); 59 | } 60 | } 61 | if (user.radar) { 62 | if (claim != null) { 63 | new Message(claim.getFaction().getName()) 64 | .format(claim.getFaction().getColor()) 65 | .send(player, true); 66 | } else { 67 | new Message(Text.translatable("factions.radar.wilderness")) 68 | .format(Formatting.GREEN) 69 | .send(player, true); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/JoinCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.arguments.StringArgumentType; 4 | import com.mojang.brigadier.context.CommandContext; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import com.mojang.brigadier.tree.LiteralCommandNode; 7 | 8 | import io.icker.factions.FactionsMod; 9 | import io.icker.factions.api.persistents.Faction; 10 | import io.icker.factions.api.persistents.User; 11 | import io.icker.factions.util.Command; 12 | import io.icker.factions.util.Message; 13 | 14 | import net.minecraft.server.command.CommandManager; 15 | import net.minecraft.server.command.ServerCommandSource; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.text.Text; 18 | 19 | public class JoinCommand implements Command { 20 | private int run(CommandContext context) throws CommandSyntaxException { 21 | String name = StringArgumentType.getString(context, "name"); 22 | ServerCommandSource source = context.getSource(); 23 | ServerPlayerEntity player = source.getPlayerOrThrow(); 24 | 25 | Faction faction = Faction.getByName(name); 26 | 27 | if (faction == null) { 28 | new Message(Text.translatable("factions.command.join.fail.nonexistent_faction")) 29 | .fail() 30 | .send(player, false); 31 | return 0; 32 | } 33 | 34 | boolean invited = faction.isInvited(player.getUuid()); 35 | 36 | if (!faction.isOpen() && !invited) { 37 | new Message(Text.translatable("factions.command.join.fail.private_no_invite")) 38 | .fail() 39 | .send(player, false); 40 | return 0; 41 | } 42 | 43 | if (FactionsMod.CONFIG.MAX_FACTION_SIZE != -1 44 | && faction.getUsers().size() >= FactionsMod.CONFIG.MAX_FACTION_SIZE) { 45 | new Message(Text.translatable("factions.command.join.fail.faction_full")) 46 | .fail() 47 | .send(player, false); 48 | return 0; 49 | } 50 | 51 | if (invited) faction.invites.remove(player.getUuid()); 52 | Command.getUser(player).joinFaction(faction.getID(), User.Rank.MEMBER); 53 | source.getServer().getPlayerManager().sendCommandTree(player); 54 | 55 | new Message( 56 | Text.translatable( 57 | "factions.command.join.success", player.getName().getString())) 58 | .send(faction); 59 | faction.adjustPower(FactionsMod.CONFIG.POWER.MEMBER); 60 | return 1; 61 | } 62 | 63 | public LiteralCommandNode getNode() { 64 | return CommandManager.literal("join") 65 | .requires( 66 | Requires.multiple( 67 | Requires.isFactionless(), Requires.hasPerms("factions.join", 0))) 68 | .then( 69 | CommandManager.argument("name", StringArgumentType.greedyString()) 70 | .suggests(Suggests.openInvitedFactions()) 71 | .executes(this::run)) 72 | .build(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/CreateCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.arguments.StringArgumentType; 4 | import com.mojang.brigadier.context.CommandContext; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import com.mojang.brigadier.tree.LiteralCommandNode; 7 | 8 | import io.icker.factions.FactionsMod; 9 | import io.icker.factions.api.persistents.Faction; 10 | import io.icker.factions.api.persistents.User; 11 | import io.icker.factions.util.Command; 12 | import io.icker.factions.util.Message; 13 | 14 | import net.minecraft.server.command.CommandManager; 15 | import net.minecraft.server.command.ServerCommandSource; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.text.Text; 18 | import net.minecraft.util.Formatting; 19 | 20 | import xyz.nucleoid.server.translations.api.Localization; 21 | 22 | import java.util.Locale; 23 | 24 | public class CreateCommand implements Command { 25 | private int run(CommandContext context) throws CommandSyntaxException { 26 | String name = StringArgumentType.getString(context, "name"); 27 | 28 | ServerCommandSource source = context.getSource(); 29 | ServerPlayerEntity player = source.getPlayerOrThrow(); 30 | 31 | if (FactionsMod.CONFIG.DISPLAY.NAME_BLACKLIST.contains(name.toLowerCase(Locale.ROOT))) { 32 | new Message(Text.translatable("factions.command.create.fail.blacklisted_name")) 33 | .fail() 34 | .send(player, false); 35 | return 0; 36 | } 37 | 38 | if (FactionsMod.CONFIG.DISPLAY.NAME_MAX_LENGTH > 0 39 | && FactionsMod.CONFIG.DISPLAY.NAME_MAX_LENGTH < name.length()) { 40 | new Message(Text.translatable("factions.command.create.fail.name_too_long")) 41 | .fail() 42 | .send(player, false); 43 | return 0; 44 | } 45 | 46 | if (Faction.getByName(name) != null) { 47 | new Message(Text.translatable("factions.command.create.fail.name_taken")) 48 | .fail() 49 | .send(player, false); 50 | return 0; 51 | } 52 | 53 | Faction faction = 54 | new Faction( 55 | name, 56 | Localization.raw("factions.default_description", player), 57 | Localization.raw("factions.default_motd", player), 58 | Formatting.WHITE, 59 | false, 60 | FactionsMod.CONFIG.POWER.BASE + FactionsMod.CONFIG.POWER.MEMBER); 61 | Faction.add(faction); 62 | Command.getUser(player).joinFaction(faction.getID(), User.Rank.OWNER); 63 | 64 | source.getServer().getPlayerManager().sendCommandTree(player); 65 | new Message(Text.translatable("factions.command.create.success")).send(player, false); 66 | return 1; 67 | } 68 | 69 | public LiteralCommandNode getNode() { 70 | return CommandManager.literal("create") 71 | .requires( 72 | Requires.multiple( 73 | Requires.isFactionless(), Requires.hasPerms("factions.create", 0))) 74 | .then( 75 | CommandManager.argument("name", StringArgumentType.greedyString()) 76 | .executes(this::run)) 77 | .build(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/DisbandCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.tree.LiteralCommandNode; 6 | 7 | import io.icker.factions.api.persistents.Faction; 8 | import io.icker.factions.api.persistents.User; 9 | import io.icker.factions.util.Command; 10 | import io.icker.factions.util.Message; 11 | 12 | import net.minecraft.item.ItemStack; 13 | import net.minecraft.server.PlayerManager; 14 | import net.minecraft.server.command.CommandManager; 15 | import net.minecraft.server.command.ServerCommandSource; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.text.Text; 18 | import net.minecraft.util.Formatting; 19 | import net.minecraft.util.ItemScatterer; 20 | import net.minecraft.util.collection.DefaultedList; 21 | 22 | public class DisbandCommand implements Command { 23 | private int run(CommandContext context, boolean confirm) 24 | throws CommandSyntaxException { 25 | ServerCommandSource source = context.getSource(); 26 | ServerPlayerEntity player = source.getPlayerOrThrow(); 27 | 28 | if (player == null) { 29 | return 0; 30 | } 31 | 32 | User user = Command.getUser(player); 33 | Faction faction = user.getFaction(); 34 | if (faction == null) return 0; 35 | 36 | if (!faction.getSafe().isEmpty() && !confirm) { 37 | new Message(Text.translatable("factions.command.disband.fail.safe_not_empty")) 38 | .add( 39 | new Message( 40 | Text.translatable( 41 | "factions.command.disband.fail.safe_not_empty.prompt")) 42 | .hover( 43 | Text.translatable( 44 | "factions.command.disband.fail.safe_not_empty.prompt.hover")) 45 | .click("/f disband confirm") 46 | .format(Formatting.GREEN)) 47 | .send(player, false); 48 | return 0; 49 | } 50 | 51 | DefaultedList safe = faction.clearSafe(); 52 | 53 | ItemScatterer.spawn(player.getEntityWorld(), player.getBlockPos(), safe); 54 | 55 | new Message( 56 | Text.translatable( 57 | "factions.command.disband.success", player.getName().getString())) 58 | .send(faction); 59 | faction.remove(); 60 | 61 | PlayerManager manager = source.getServer().getPlayerManager(); 62 | for (ServerPlayerEntity p : manager.getPlayerList()) { 63 | manager.sendCommandTree(p); 64 | } 65 | return 1; 66 | } 67 | 68 | @Override 69 | public LiteralCommandNode getNode() { 70 | return CommandManager.literal("disband") 71 | .requires( 72 | Requires.multiple( 73 | Requires.isOwner(), Requires.hasPerms("factions.disband", 0))) 74 | .executes(context -> this.run(context, false)) 75 | .then( 76 | CommandManager.literal("confirm") 77 | .executes(context -> this.run(context, true))) 78 | .build(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/ServerPlayNetworkHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.api.events.PlayerEvents; 4 | import io.icker.factions.api.persistents.User; 5 | import io.icker.factions.util.Message; 6 | import io.icker.factions.util.WorldUtils; 7 | 8 | import net.minecraft.entity.Entity; 9 | import net.minecraft.network.message.SignedMessage; 10 | import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket; 11 | import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; 12 | import net.minecraft.server.network.ServerPlayNetworkHandler; 13 | import net.minecraft.server.network.ServerPlayerEntity; 14 | import net.minecraft.server.world.ServerWorld; 15 | import net.minecraft.text.Text; 16 | import net.minecraft.util.ActionResult; 17 | import net.minecraft.util.Hand; 18 | import net.minecraft.util.math.Vec3d; 19 | import net.minecraft.world.World; 20 | 21 | import org.spongepowered.asm.mixin.Mixin; 22 | import org.spongepowered.asm.mixin.Shadow; 23 | import org.spongepowered.asm.mixin.injection.At; 24 | import org.spongepowered.asm.mixin.injection.Inject; 25 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 26 | 27 | @Mixin(ServerPlayNetworkHandler.class) 28 | public class ServerPlayNetworkHandlerMixin { 29 | @Shadow public ServerPlayerEntity player; 30 | 31 | @Inject(method = "onPlayerMove", at = @At("HEAD")) 32 | public void onPlayerMove(PlayerMoveC2SPacket packet, CallbackInfo ci) { 33 | PlayerEvents.ON_MOVE.invoker().onMove(player); 34 | } 35 | 36 | @Inject(method = "handleDecoratedMessage", at = @At("HEAD"), cancellable = true) 37 | public void handleDecoratedMessage(SignedMessage signedMessage, CallbackInfo ci) { 38 | User member = User.get(signedMessage.link().sender()); 39 | 40 | boolean factionChat = 41 | member.chat == User.ChatMode.FACTION || member.chat == User.ChatMode.FOCUS; 42 | 43 | if (factionChat && !member.isInFaction()) { 44 | new Message(Text.translatable("factions.chat.faction_chat_when_not_in_faction")) 45 | .fail() 46 | .hover( 47 | Text.translatable( 48 | "factions.chat.faction_chat_when_not_in_faction.hover")) 49 | .click("/factions settings chat global") 50 | .send( 51 | WorldUtils.server 52 | .getPlayerManager() 53 | .getPlayer(signedMessage.link().sender()), 54 | false); 55 | 56 | ci.cancel(); 57 | } 58 | } 59 | 60 | @Inject(method = "onPlayerInteractEntity", at = @At("HEAD"), cancellable = true) 61 | public void onPlayerInteractEntity(PlayerInteractEntityC2SPacket packet, CallbackInfo ci) { 62 | World world = player.getEntityWorld(); 63 | Entity entity = packet.getEntity((ServerWorld) world); 64 | if (entity == null) return; 65 | 66 | packet.handle( 67 | new PlayerInteractEntityC2SPacket.Handler() { 68 | @Override 69 | public void interact(Hand hand) { 70 | if (PlayerEvents.USE_ENTITY.invoker().onUseEntity(player, entity, world) 71 | == ActionResult.FAIL) { 72 | ci.cancel(); 73 | } 74 | } 75 | 76 | @Override 77 | public void interactAt(Hand hand, Vec3d pos) { 78 | if (PlayerEvents.USE_ENTITY.invoker().onUseEntity(player, entity, world) 79 | == ActionResult.FAIL) { 80 | ci.cancel(); 81 | } 82 | } 83 | 84 | @Override 85 | public void attack() {} 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/MapCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.tree.LiteralCommandNode; 6 | 7 | import io.icker.factions.api.persistents.Claim; 8 | import io.icker.factions.api.persistents.Faction; 9 | import io.icker.factions.util.Command; 10 | import io.icker.factions.util.Message; 11 | 12 | import net.minecraft.server.command.CommandManager; 13 | import net.minecraft.server.command.ServerCommandSource; 14 | import net.minecraft.server.network.ServerPlayerEntity; 15 | import net.minecraft.server.world.ServerWorld; 16 | import net.minecraft.text.Text; 17 | import net.minecraft.util.Formatting; 18 | import net.minecraft.util.math.ChunkPos; 19 | 20 | public class MapCommand implements Command { 21 | private int run(CommandContext context) throws CommandSyntaxException { 22 | ServerCommandSource source = context.getSource(); 23 | 24 | ServerPlayerEntity player = source.getPlayerOrThrow(); 25 | ServerWorld world = (ServerWorld) player.getEntityWorld(); 26 | 27 | ChunkPos chunkPos = world.getChunk(player.getBlockPos()).getPos(); 28 | String dimension = world.getRegistryKey().getValue().toString(); 29 | 30 | // Print the header of the faction map. 31 | new Message( 32 | Text.literal("──┤ ") 33 | .formatted(Formatting.DARK_GRAY) 34 | .append( 35 | Text.translatable("factions.command.map.title") 36 | .formatted(Formatting.GREEN)) 37 | .append(Text.literal("├──").formatted(Formatting.DARK_GRAY))) 38 | .send(player, false); 39 | 40 | for (int z = -4; z <= 4; z++) { // Rows (9) 41 | Message row = new Message(); 42 | for (int x = -5; x <= 5; x++) { // Columns (11) 43 | Claim claim = Claim.get(chunkPos.x + x, chunkPos.z + z, dimension); 44 | if (x == 0 && z == 0) { // Check if middle (your chunk) 45 | if (claim == null) { 46 | row.add( 47 | new Message("⏺") 48 | .format(Formatting.DARK_GRAY) 49 | .hover( 50 | Text.translatable( 51 | "factions.command.map.you_wilderness"))); 52 | } else { 53 | Faction owner = claim.getFaction(); 54 | row.add( 55 | new Message("⏺") 56 | .format(owner.getColor()) 57 | .hover( 58 | Text.translatable( 59 | "factions.command.map.you_owner", 60 | owner.getName()))); 61 | } 62 | } else { 63 | if (claim == null) { 64 | row.add("□").format(Formatting.DARK_GRAY); 65 | } else { 66 | Faction owner = claim.getFaction(); 67 | row.add(new Message("■").format(owner.getColor()).hover(owner.getName())); 68 | } 69 | } 70 | row.add(" "); 71 | } 72 | row.send(player, false); 73 | } 74 | 75 | return 1; 76 | } 77 | 78 | @Override 79 | public LiteralCommandNode getNode() { 80 | return CommandManager.literal("map") 81 | .requires(Requires.hasPerms("factions.map", 0)) 82 | .executes(this::run) 83 | .build(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/util/Message.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.util; 2 | 3 | import io.icker.factions.api.persistents.Faction; 4 | import io.icker.factions.api.persistents.User; 5 | 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.server.PlayerManager; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.text.ClickEvent.RunCommand; 10 | import net.minecraft.text.HoverEvent.ShowText; 11 | import net.minecraft.text.MutableText; 12 | import net.minecraft.text.Text; 13 | import net.minecraft.util.Formatting; 14 | 15 | public class Message { 16 | public static PlayerManager manager; 17 | private MutableText text; 18 | 19 | public Message() { 20 | text = Text.literal(""); 21 | } 22 | 23 | public Message(String message) { 24 | text = (MutableText) Text.of(message); 25 | } 26 | 27 | public Message(String message, Object... args) { 28 | text = (MutableText) Text.of(String.format(message, args)); 29 | } 30 | 31 | public Message(MutableText text) { 32 | this.text = text; 33 | } 34 | 35 | public Message add(String message) { 36 | text.append(message); 37 | return this; 38 | } 39 | 40 | public Message add(String message, Object... args) { 41 | text.append(String.format(message, args)); 42 | return this; 43 | } 44 | 45 | public Message add(Message message) { 46 | text.append(message.raw()); 47 | return this; 48 | } 49 | 50 | public Message format(Formatting... format) { 51 | text.formatted(format); 52 | return this; 53 | } 54 | 55 | public Message fail() { 56 | text.formatted(Formatting.RED); 57 | return this; 58 | } 59 | 60 | public Message hover(String message) { 61 | return this.hover(Text.of(message)); 62 | } 63 | 64 | public Message hover(Text message) { 65 | text.styled(s -> s.withHoverEvent(new ShowText(message))); 66 | return this; 67 | } 68 | 69 | public Message click(String message) { 70 | text.styled(s -> s.withClickEvent(new RunCommand(message))); 71 | return this; 72 | } 73 | 74 | public Message send(PlayerEntity player, boolean actionBar) { 75 | player.sendMessage(text, actionBar); 76 | return this; 77 | } 78 | 79 | public Message send(Faction faction) { 80 | Message message = this.prependFaction(faction); 81 | for (User member : faction.getUsers()) { 82 | ServerPlayerEntity player = manager.getPlayer(member.getID()); 83 | if (player != null) message.send(player, false); 84 | } 85 | return this; 86 | } 87 | 88 | public void sendToGlobalChat() { 89 | for (ServerPlayerEntity player : manager.getPlayerList()) { 90 | User.ChatMode option = User.get(player.getUuid()).chat; 91 | if (option != User.ChatMode.FOCUS) player.sendMessage(text, false); 92 | } 93 | } 94 | 95 | public void sendToFactionChat(Faction faction) { 96 | for (User member : faction.getUsers()) { 97 | ServerPlayerEntity player = manager.getPlayer(member.getID()); 98 | player.sendMessage(text, false); 99 | } 100 | } 101 | 102 | public Message prependFaction(Faction faction) { 103 | text = 104 | new Message() 105 | .add( 106 | new Message( 107 | faction.getColor().toString() 108 | + Formatting.BOLD 109 | + faction.getName()) 110 | .hover(faction.getDescription())) 111 | .filler("»") 112 | .raw() 113 | .append(text); 114 | return this; 115 | } 116 | 117 | public Message filler(String symbol) { 118 | text.append( 119 | Text.of( 120 | " " 121 | + Formatting.RESET 122 | + Formatting.DARK_GRAY 123 | + symbol 124 | + Formatting.RESET 125 | + " ")); 126 | return this; 127 | } 128 | 129 | public MutableText raw() { 130 | return text; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/mixin/ServerPlayerEntityMixin.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.mixin; 2 | 3 | import io.icker.factions.FactionsMod; 4 | import io.icker.factions.api.events.PlayerEvents; 5 | import io.icker.factions.api.persistents.Faction; 6 | import io.icker.factions.api.persistents.User; 7 | import io.icker.factions.util.Message; 8 | 9 | import net.minecraft.entity.Entity; 10 | import net.minecraft.entity.EntityType; 11 | import net.minecraft.entity.LivingEntity; 12 | import net.minecraft.entity.damage.DamageSource; 13 | import net.minecraft.server.network.ServerPlayerEntity; 14 | import net.minecraft.server.world.ServerWorld; 15 | import net.minecraft.text.Text; 16 | import net.minecraft.util.ActionResult; 17 | import net.minecraft.util.Formatting; 18 | import net.minecraft.world.World; 19 | 20 | import org.spongepowered.asm.mixin.Mixin; 21 | import org.spongepowered.asm.mixin.injection.At; 22 | import org.spongepowered.asm.mixin.injection.Inject; 23 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 24 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 25 | 26 | @Mixin(ServerPlayerEntity.class) 27 | public abstract class ServerPlayerEntityMixin extends LivingEntity { 28 | 29 | protected ServerPlayerEntityMixin(EntityType entityType, World world) { 30 | super(entityType, world); 31 | } 32 | 33 | @Inject(at = @At("HEAD"), method = "onDeath") 34 | public void onDeath(DamageSource source, CallbackInfo info) { 35 | Entity entity = source.getSource(); 36 | if (entity == null || !entity.isPlayer()) return; 37 | PlayerEvents.ON_KILLED_BY_PLAYER 38 | .invoker() 39 | .onKilledByPlayer((ServerPlayerEntity) (Object) this, source); 40 | } 41 | 42 | @Inject(at = @At("HEAD"), method = "tick") 43 | public void tick(CallbackInfo info) { 44 | if (age % FactionsMod.CONFIG.POWER.POWER_TICKS.TICKS != 0 || age == 0) return; 45 | PlayerEvents.ON_POWER_TICK.invoker().onPowerTick((ServerPlayerEntity) (Object) this); 46 | } 47 | 48 | @Inject(method = "isInvulnerableTo", at = @At("RETURN"), cancellable = true) 49 | public void isInvulnerableTo( 50 | ServerWorld world, DamageSource damageSource, CallbackInfoReturnable info) { 51 | Entity source = damageSource.getAttacker(); 52 | if (source == null) return; 53 | 54 | ActionResult result = 55 | PlayerEvents.IS_INVULNERABLE 56 | .invoker() 57 | .isInvulnerable( 58 | damageSource.getAttacker(), (ServerPlayerEntity) (Object) this); 59 | 60 | if (result != ActionResult.PASS) info.setReturnValue(result == ActionResult.SUCCESS); 61 | } 62 | 63 | @Inject(method = "getPlayerListName", at = @At("HEAD"), cancellable = true) 64 | public void getPlayerListName(CallbackInfoReturnable cir) { 65 | if (FactionsMod.CONFIG.DISPLAY.TAB_MENU) { 66 | User member = User.get(((ServerPlayerEntity) (Object) this).getUuid()); 67 | if (member.isInFaction()) { 68 | Faction faction = member.getFaction(); 69 | cir.setReturnValue( 70 | new Message(String.format("[%s] ", faction.getName())) 71 | .format(faction.getColor()) 72 | .add( 73 | new Message( 74 | ((ServerPlayerEntity) (Object) this) 75 | .getName() 76 | .getString()) 77 | .format(Formatting.WHITE)) 78 | .raw()); 79 | } else { 80 | cir.setReturnValue( 81 | new Message(Text.translatable("factions.factionless")) 82 | .format(Formatting.GRAY) 83 | .add( 84 | new Message( 85 | ((ServerPlayerEntity) (Object) this) 86 | .getName() 87 | .getString()) 88 | .format(Formatting.WHITE)) 89 | .raw()); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/ui/ListGui.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.ui; 2 | 3 | import eu.pb4.sgui.api.ClickType; 4 | import eu.pb4.sgui.api.elements.GuiElementBuilder; 5 | 6 | import io.icker.factions.api.persistents.Faction; 7 | import io.icker.factions.api.persistents.Home; 8 | import io.icker.factions.api.persistents.User; 9 | import io.icker.factions.command.HomeCommand; 10 | import io.icker.factions.util.GuiInteract; 11 | import io.icker.factions.util.Icons; 12 | 13 | import net.minecraft.item.Items; 14 | import net.minecraft.server.network.ServerPlayerEntity; 15 | import net.minecraft.text.Style; 16 | import net.minecraft.text.Text; 17 | import net.minecraft.util.Formatting; 18 | 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | public class ListGui extends PagedGui { 25 | List factions; 26 | int size; 27 | User user; 28 | 29 | public ListGui(ServerPlayerEntity player, User user, @Nullable Runnable closeCallback) { 30 | super(player, closeCallback); 31 | this.user = user; 32 | 33 | this.factions = new ArrayList<>(Faction.all().stream().toList()); 34 | Faction userFaction; 35 | if ((userFaction = user.getFaction()) != null) { 36 | this.factions.remove(userFaction); 37 | this.factions.addFirst(userFaction); 38 | } 39 | this.size = factions.size(); 40 | 41 | this.setTitle(Text.translatable("factions.gui.list.title")); 42 | this.updateDisplay(); 43 | this.open(); 44 | } 45 | 46 | @Override 47 | protected int getPageAmount() { 48 | return this.size / PAGE_SIZE; 49 | } 50 | 51 | @Override 52 | protected DisplayElement getElement(int id) { 53 | if (this.size > id) { 54 | var faction = this.factions.get(id); 55 | 56 | boolean isInFaction = faction.equals(this.user.getFaction()); 57 | Home home = faction.getHome(); 58 | 59 | var icon = new GuiElementBuilder(Items.PLAYER_HEAD); 60 | icon.setProfileSkinTexture( 61 | isInFaction ? Icons.GUI_CASTLE_NORMAL : Icons.GUI_CASTLE_OPEN); 62 | icon.setName(Text.literal(faction.getColor() + faction.getName())); 63 | 64 | List lore = 65 | new ArrayList<>( 66 | List.of( 67 | Text.literal(faction.getDescription()) 68 | .setStyle( 69 | Style.EMPTY 70 | .withItalic(false) 71 | .withColor(Formatting.GRAY)))); 72 | if (isInFaction && home != null) { 73 | lore.add( 74 | Text.translatable("factions.gui.list.entry.view_info") 75 | .setStyle( 76 | Style.EMPTY.withItalic(false).withColor(Formatting.GRAY))); 77 | lore.add( 78 | Text.translatable("factions.gui.list.entry.teleport") 79 | .setStyle( 80 | Style.EMPTY 81 | .withItalic(false) 82 | .withColor(Formatting.DARK_AQUA))); 83 | icon.setCallback( 84 | (index, clickType, actionType) -> { 85 | GuiInteract.playClickSound(player); 86 | if (clickType == ClickType.MOUSE_RIGHT) { 87 | new HomeCommand().execGo(player, user, faction); 88 | this.close(); 89 | return; 90 | } 91 | new InfoGui(player, faction, this::open); 92 | }); 93 | } else { 94 | lore.add( 95 | Text.translatable("factions.gui.list.entry.view_info") 96 | .setStyle( 97 | Style.EMPTY.withItalic(false).withColor(Formatting.GRAY))); 98 | icon.setCallback( 99 | (index, clickType, actionType) -> { 100 | GuiInteract.playClickSound(player); 101 | new InfoGui(player, faction, this::open); 102 | }); 103 | } 104 | icon.setLore(lore); 105 | 106 | return DisplayElement.of(icon); 107 | } 108 | 109 | return DisplayElement.empty(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/KickCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import com.mojang.brigadier.arguments.StringArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.tree.LiteralCommandNode; 8 | 9 | import io.icker.factions.api.persistents.User; 10 | import io.icker.factions.util.Command; 11 | import io.icker.factions.util.Message; 12 | 13 | import net.minecraft.server.command.CommandManager; 14 | import net.minecraft.server.command.ServerCommandSource; 15 | import net.minecraft.server.network.ServerPlayerEntity; 16 | import net.minecraft.text.Text; 17 | import net.minecraft.util.Formatting; 18 | 19 | import xyz.nucleoid.server.translations.api.Localization; 20 | 21 | import java.util.Optional; 22 | import java.util.UUID; 23 | 24 | public class KickCommand implements Command { 25 | private int run(CommandContext context) throws CommandSyntaxException { 26 | ServerCommandSource source = context.getSource(); 27 | ServerPlayerEntity player = source.getPlayerOrThrow(); 28 | 29 | String name = StringArgumentType.getString(context, "player"); 30 | 31 | User target; 32 | 33 | Optional profile; 34 | if ((profile = source.getServer().getApiServices().profileResolver().getProfileByName(name)) 35 | .isPresent()) { 36 | target = User.get(profile.get().id()); 37 | } else { 38 | try { 39 | target = User.get(UUID.fromString(name)); 40 | } catch (Exception e) { 41 | new Message(Text.translatable("factions.gui.spoof.fail.no_player", name)) 42 | .format(Formatting.RED) 43 | .send(player, false); 44 | return 0; 45 | } 46 | } 47 | 48 | if (target.getID().equals(player.getUuid())) { 49 | new Message(Text.translatable("factions.command.kick.fail.self")) 50 | .fail() 51 | .send(player, false); 52 | return 0; 53 | } 54 | 55 | User selfUser = Command.getUser(player); 56 | 57 | if (target.getFaction() == null || !target.getFaction().equals(selfUser.getFaction())) { 58 | new Message(Text.translatable("factions.command.kick.fail.other_faction")) 59 | .fail() 60 | .send(player, false); 61 | return 0; 62 | } 63 | 64 | if (selfUser.rank == User.Rank.LEADER 65 | && (target.rank == User.Rank.LEADER || target.rank == User.Rank.OWNER)) { 66 | new Message(Text.translatable("factions.command.kick.fail.high_rank")) 67 | .fail() 68 | .send(player, false); 69 | return 0; 70 | } 71 | 72 | ServerPlayerEntity targetPlayer = 73 | player.getEntityWorld().getServer().getPlayerManager().getPlayer(target.getID()); 74 | 75 | target.leaveFaction(); 76 | 77 | if (targetPlayer != null) { 78 | context.getSource().getServer().getPlayerManager().sendCommandTree(targetPlayer); 79 | 80 | new Message( 81 | Text.translatable( 82 | "factions.command.kick.success.subject", 83 | player.getName().getString())) 84 | .send(targetPlayer, false); 85 | } 86 | 87 | new Message( 88 | Text.translatable( 89 | "factions.command.kick.success.actor", 90 | profile.map((found_profile) -> found_profile.name()) 91 | .orElse( 92 | Localization.raw( 93 | "factions.gui.members.entry.unknown_player", 94 | player)))) 95 | .send(player, false); 96 | 97 | return 1; 98 | } 99 | 100 | public LiteralCommandNode getNode() { 101 | return CommandManager.literal("kick") 102 | .requires( 103 | Requires.multiple( 104 | Requires.isLeader(), Requires.hasPerms("factions.kick", 0))) 105 | .then( 106 | CommandManager.argument("player", StringArgumentType.string()) 107 | .suggests(Suggests.allPlayersInYourFactionButYou()) 108 | .executes(this::run)) 109 | .build(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/persistents/User.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.persistents; 2 | 3 | import io.icker.factions.api.events.FactionEvents; 4 | import io.icker.factions.database.Database; 5 | import io.icker.factions.database.Field; 6 | import io.icker.factions.database.Name; 7 | import io.icker.factions.util.WorldUtils; 8 | 9 | import net.minecraft.server.network.ServerPlayerEntity; 10 | 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.Collection; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.UUID; 18 | 19 | @Name("User") 20 | public class User { 21 | private static final HashMap STORE = Database.load(User.class, User::getID); 22 | 23 | public enum ChatMode { 24 | FOCUS, 25 | FACTION, 26 | GLOBAL 27 | } 28 | 29 | public enum Rank { 30 | OWNER, 31 | LEADER, 32 | COMMANDER, 33 | MEMBER, 34 | GUEST 35 | } 36 | 37 | public enum SoundMode { 38 | NONE, 39 | WARNINGS, 40 | FACTION, 41 | ALL 42 | } 43 | 44 | @Field("ID") 45 | private UUID id; 46 | 47 | @Field("FactionID") 48 | private UUID factionID; 49 | 50 | @Field("Rank") 51 | public Rank rank; 52 | 53 | @Field("Radar") 54 | public boolean radar = false; 55 | 56 | @Field("Chat") 57 | public ChatMode chat = ChatMode.GLOBAL; 58 | 59 | @Field("Sounds") 60 | public SoundMode sounds = SoundMode.ALL; 61 | 62 | @Field("HomeCooldown") 63 | public long homeCooldown = -1; 64 | 65 | public boolean autoclaim = false; 66 | public boolean bypass = false; 67 | 68 | private User spoof; 69 | 70 | public User(UUID id) { 71 | this.id = id; 72 | } 73 | 74 | public User() {} 75 | 76 | public String getKey() { 77 | return id.toString(); 78 | } 79 | 80 | @NotNull 81 | public static User get(UUID id) { 82 | if (!STORE.containsKey(id)) { 83 | User.add(new User(id)); 84 | } 85 | return STORE.get(id); 86 | } 87 | 88 | public static List getByFaction(UUID factionID) { 89 | return STORE.values().stream() 90 | .filter(m -> m.isInFaction() && m.factionID.equals(factionID)) 91 | .toList(); 92 | } 93 | 94 | public static void add(User user) { 95 | STORE.put(user.id, user); 96 | } 97 | 98 | public UUID getID() { 99 | return id; 100 | } 101 | 102 | public boolean isInFaction() { 103 | return factionID != null; 104 | } 105 | 106 | private String getEnumName(Enum value) { 107 | return value.name().toLowerCase(); 108 | } 109 | 110 | public String getRankName() { 111 | return getEnumName(rank); 112 | } 113 | 114 | public String getChatName() { 115 | return getEnumName(chat); 116 | } 117 | 118 | public String getSoundName() { 119 | return getEnumName(sounds); 120 | } 121 | 122 | @Nullable 123 | public Faction getFaction() { 124 | return Faction.get(factionID); 125 | } 126 | 127 | public User getSpoof() { 128 | return spoof; 129 | } 130 | 131 | public void setSpoof(User user) { 132 | this.spoof = user; 133 | } 134 | 135 | public void joinFaction(UUID factionID, Rank rank) { 136 | this.factionID = factionID; 137 | this.rank = rank; 138 | FactionEvents.MEMBER_JOIN.invoker().onMemberJoin(Faction.get(factionID), this); 139 | } 140 | 141 | public void leaveFaction() { 142 | UUID oldFactionID = factionID; 143 | factionID = null; 144 | rank = null; 145 | FactionEvents.MEMBER_LEAVE.invoker().onMemberLeave(Faction.get(oldFactionID), this); 146 | } 147 | 148 | public static Collection all() { 149 | return STORE.values(); 150 | } 151 | 152 | public static void audit() { 153 | STORE.values() 154 | .forEach( 155 | (user) -> { 156 | if (Faction.get(user.factionID) == null) { 157 | user.factionID = null; 158 | } 159 | 160 | if (!user.isInFaction()) { 161 | user.rank = null; 162 | } 163 | }); 164 | } 165 | 166 | @Nullable 167 | public String getLanguage() { 168 | ServerPlayerEntity player = WorldUtils.server.getPlayerManager().getPlayer(this.id); 169 | 170 | if (player == null) { 171 | return null; 172 | } 173 | 174 | return player.getClientOptions().language(); 175 | } 176 | 177 | public static void save() { 178 | Database.save(User.class, STORE.values().stream().toList()); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/FactionsMod.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import com.mojang.brigadier.tree.LiteralCommandNode; 5 | 6 | import io.icker.factions.command.AdminCommand; 7 | import io.icker.factions.command.ClaimCommand; 8 | import io.icker.factions.command.CreateCommand; 9 | import io.icker.factions.command.DeclareCommand; 10 | import io.icker.factions.command.DisbandCommand; 11 | import io.icker.factions.command.HomeCommand; 12 | import io.icker.factions.command.InfoCommand; 13 | import io.icker.factions.command.InviteCommand; 14 | import io.icker.factions.command.JoinCommand; 15 | import io.icker.factions.command.KickCommand; 16 | import io.icker.factions.command.LeaveCommand; 17 | import io.icker.factions.command.ListCommand; 18 | import io.icker.factions.command.MapCommand; 19 | import io.icker.factions.command.MemberCommand; 20 | import io.icker.factions.command.ModifyCommand; 21 | import io.icker.factions.command.PermissionCommand; 22 | import io.icker.factions.command.RankCommand; 23 | import io.icker.factions.command.SafeCommand; 24 | import io.icker.factions.command.SettingsCommand; 25 | import io.icker.factions.config.Config; 26 | import io.icker.factions.core.ChatManager; 27 | import io.icker.factions.core.FactionsManager; 28 | import io.icker.factions.core.InteractionManager; 29 | import io.icker.factions.core.ServerManager; 30 | import io.icker.factions.core.SoundManager; 31 | import io.icker.factions.core.WorldManager; 32 | import io.icker.factions.util.BlueMapWrapper; 33 | import io.icker.factions.util.Command; 34 | import io.icker.factions.util.DynmapWrapper; 35 | import io.icker.factions.util.PlaceholdersWrapper; 36 | import io.icker.factions.util.SquareMapWrapper; 37 | import io.icker.factions.util.WorldUtils; 38 | 39 | import net.fabricmc.api.ModInitializer; 40 | import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; 41 | import net.fabricmc.loader.api.FabricLoader; 42 | import net.minecraft.command.CommandRegistryAccess; 43 | import net.minecraft.server.command.CommandManager; 44 | import net.minecraft.server.command.ServerCommandSource; 45 | 46 | import org.slf4j.Logger; 47 | import org.slf4j.LoggerFactory; 48 | 49 | public class FactionsMod implements ModInitializer { 50 | public static Logger LOGGER = LoggerFactory.getLogger("Factions"); 51 | public static final String MODID = "factions"; 52 | 53 | public static Config CONFIG = Config.load(); 54 | public static DynmapWrapper dynmap; 55 | public static BlueMapWrapper bluemap; 56 | public static SquareMapWrapper squaremap; 57 | 58 | @Override 59 | public void onInitialize() { 60 | LOGGER.info("Initialized Factions Mod"); 61 | 62 | WorldUtils.register(); 63 | 64 | dynmap = FabricLoader.getInstance().isModLoaded("dynmap") ? new DynmapWrapper() : null; 65 | bluemap = FabricLoader.getInstance().isModLoaded("bluemap") ? new BlueMapWrapper() : null; 66 | squaremap = 67 | FabricLoader.getInstance().isModLoaded("squaremap") ? new SquareMapWrapper() : null; 68 | 69 | if (FabricLoader.getInstance().isModLoaded("placeholder-api")) PlaceholdersWrapper.init(); 70 | 71 | ChatManager.register(); 72 | FactionsManager.register(); 73 | InteractionManager.register(); 74 | ServerManager.register(); 75 | SoundManager.register(); 76 | WorldManager.register(); 77 | 78 | CommandRegistrationCallback.EVENT.register(FactionsMod::registerCommands); 79 | } 80 | 81 | private static void registerCommands( 82 | CommandDispatcher dispatcher, 83 | CommandRegistryAccess registryAccess, 84 | CommandManager.RegistrationEnvironment environment) { 85 | LiteralCommandNode factions = 86 | CommandManager.literal("factions").build(); 87 | 88 | LiteralCommandNode alias = CommandManager.literal("f").build(); 89 | 90 | dispatcher.getRoot().addChild(factions); 91 | dispatcher.getRoot().addChild(alias); 92 | 93 | Command[] commands = 94 | new Command[] { 95 | new AdminCommand(), 96 | new SettingsCommand(), 97 | new ClaimCommand(), 98 | new CreateCommand(), 99 | new DeclareCommand(), 100 | new DisbandCommand(), 101 | new HomeCommand(), 102 | new InfoCommand(), 103 | new InviteCommand(), 104 | new JoinCommand(), 105 | new KickCommand(), 106 | new LeaveCommand(), 107 | new ListCommand(), 108 | new MapCommand(), 109 | new MemberCommand(), 110 | new ModifyCommand(), 111 | new RankCommand(), 112 | new SafeCommand(), 113 | new PermissionCommand() 114 | }; 115 | 116 | for (Command command : commands) { 117 | factions.addChild(command.getNode()); 118 | alias.addChild(command.getNode()); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/config/Config.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.config; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.JsonDeserializationContext; 6 | import com.google.gson.JsonDeserializer; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonParseException; 9 | import com.google.gson.annotations.SerializedName; 10 | 11 | import io.icker.factions.FactionsMod; 12 | import io.icker.factions.api.persistents.Relationship; 13 | 14 | import net.fabricmc.loader.api.FabricLoader; 15 | 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.io.File; 19 | import java.io.FileReader; 20 | import java.io.FileWriter; 21 | import java.lang.reflect.Type; 22 | import java.util.List; 23 | 24 | public class Config { 25 | private static final int REQUIRED_VERSION = 3; 26 | private static final File file = 27 | FabricLoader.getInstance() 28 | .getGameDir() 29 | .resolve("config") 30 | .resolve("factions.json") 31 | .toFile(); 32 | 33 | public static Config load() { 34 | Gson gson = 35 | new GsonBuilder() 36 | .setPrettyPrinting() 37 | .disableHtmlEscaping() 38 | .serializeNulls() 39 | .registerTypeAdapter(HomeConfig.class, new Deserializer<>(HomeConfig.class)) 40 | .registerTypeAdapter( 41 | PowerConfig.class, new Deserializer<>(PowerConfig.class)) 42 | .registerTypeAdapter(SafeConfig.class, new Deserializer<>(SafeConfig.class)) 43 | .create(); 44 | 45 | try { 46 | if (!file.exists()) { 47 | file.getParentFile().mkdir(); 48 | 49 | Config defaults = new Config(); 50 | 51 | FileWriter writer = new FileWriter(file); 52 | gson.toJson(defaults, writer); 53 | writer.close(); 54 | 55 | return defaults; 56 | } 57 | 58 | Config config = gson.fromJson(new FileReader(file), Config.class); 59 | 60 | if (config.VERSION != REQUIRED_VERSION) { 61 | FactionsMod.LOGGER.error( 62 | String.format( 63 | "Config file incompatible (requires version %d)", 64 | REQUIRED_VERSION)); 65 | } 66 | 67 | return config; 68 | } catch (Exception e) { 69 | FactionsMod.LOGGER.error("An error occurred reading the factions config file", e); 70 | return new Config(); 71 | } 72 | } 73 | 74 | @SerializedName("version") 75 | public int VERSION = REQUIRED_VERSION; 76 | 77 | @SerializedName("gui") 78 | public boolean GUI = true; 79 | 80 | @SerializedName("blockTNT") 81 | public boolean BLOCK_TNT = false; 82 | 83 | @SerializedName("power") 84 | public PowerConfig POWER = new PowerConfig(); 85 | 86 | @SerializedName("safe") 87 | @Nullable 88 | public SafeConfig SAFE = new SafeConfig(); 89 | 90 | @SerializedName("home") 91 | @Nullable 92 | public HomeConfig HOME = new HomeConfig(); 93 | 94 | @SerializedName("display") 95 | public DisplayConfig DISPLAY = new DisplayConfig(); 96 | 97 | @SerializedName("relationships") 98 | public RelationshipConfig RELATIONSHIPS = new RelationshipConfig(); 99 | 100 | @SerializedName("maxFactionSize") 101 | public int MAX_FACTION_SIZE = -1; 102 | 103 | @SerializedName("friendlyFire") 104 | public boolean FRIENDLY_FIRE = false; 105 | 106 | @SerializedName("requiredBypassLevel") 107 | public int REQUIRED_BYPASS_LEVEL = 2; 108 | 109 | @SerializedName("claimProtections") 110 | public boolean CLAIM_PROTECTION = true; 111 | 112 | @SerializedName("language") 113 | public String LANGUAGE = "en_us"; 114 | 115 | public static class DisplayConfig { 116 | @SerializedName("factionNameMaxLength") 117 | public int NAME_MAX_LENGTH = -1; 118 | 119 | @SerializedName("changeChat") 120 | public boolean MODIFY_CHAT = true; 121 | 122 | @SerializedName("tabMenu") 123 | public boolean TAB_MENU = true; 124 | 125 | @SerializedName("nameBlackList") 126 | public List NAME_BLACKLIST = 127 | List.of("wilderness", "factionless", "без фракции"); // means no faction in english 128 | 129 | @SerializedName("powerMessage") 130 | public boolean POWER_MESSAGE = true; 131 | } 132 | 133 | public static class RelationshipConfig { 134 | @SerializedName("allyOverridesPermissions") 135 | public boolean ALLY_OVERRIDES_PERMISSIONS = true; 136 | 137 | @SerializedName("defaultGuestPermissions") 138 | public List DEFAULT_GUEST_PERMISSIONS = 139 | List.of(Relationship.Permissions.USE_BLOCKS, Relationship.Permissions.USE_ENTITIES); 140 | } 141 | 142 | public static class Deserializer implements JsonDeserializer { 143 | final Class clazz; 144 | 145 | public Deserializer(Class clazz) { 146 | this.clazz = clazz; 147 | } 148 | 149 | @Override 150 | public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 151 | throws JsonParseException { 152 | if (!json.isJsonObject() && !json.getAsBoolean()) { 153 | return null; 154 | } 155 | 156 | return new Gson().fromJson(json, clazz); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/api/events/FactionEvents.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.api.events; 2 | 3 | import io.icker.factions.api.persistents.Faction; 4 | import io.icker.factions.api.persistents.Home; 5 | import io.icker.factions.api.persistents.User; 6 | 7 | import net.fabricmc.fabric.api.event.Event; 8 | import net.fabricmc.fabric.api.event.EventFactory; 9 | 10 | /** Events related to {@link Faction} */ 11 | public final class FactionEvents { 12 | /** Called when a {@link Faction} is created */ 13 | public static final Event CREATE = 14 | EventFactory.createArrayBacked( 15 | Create.class, 16 | callbacks -> 17 | (faction, user) -> { 18 | for (Create callback : callbacks) { 19 | callback.onCreate(faction, user); 20 | } 21 | }); 22 | 23 | /** Called when a {@link Faction} is disbanded */ 24 | public static final Event DISBAND = 25 | EventFactory.createArrayBacked( 26 | Disband.class, 27 | callbacks -> 28 | (faction) -> { 29 | for (Disband callback : callbacks) { 30 | callback.onDisband(faction); 31 | } 32 | }); 33 | 34 | /** Called when a {@link User} joins a {@link Faction} */ 35 | public static final Event MEMBER_JOIN = 36 | EventFactory.createArrayBacked( 37 | MemberJoin.class, 38 | callbacks -> 39 | (faction, user) -> { 40 | for (MemberJoin callback : callbacks) { 41 | callback.onMemberJoin(faction, user); 42 | } 43 | }); 44 | 45 | /** Called when a {@link User} leaves a {@link Faction} */ 46 | public static final Event MEMBER_LEAVE = 47 | EventFactory.createArrayBacked( 48 | MemberLeave.class, 49 | callbacks -> 50 | (faction, user) -> { 51 | for (MemberLeave callback : callbacks) { 52 | callback.onMemberLeave(faction, user); 53 | } 54 | }); 55 | 56 | /** Called when a factions name, description, MOTD, color or open status is modified */ 57 | public static final Event MODIFY = 58 | EventFactory.createArrayBacked( 59 | Modify.class, 60 | callbacks -> 61 | (faction) -> { 62 | for (Modify callback : callbacks) { 63 | callback.onModify(faction); 64 | } 65 | }); 66 | 67 | /** Called when a factions power changes */ 68 | public static final Event POWER_CHANGE = 69 | EventFactory.createArrayBacked( 70 | PowerChange.class, 71 | callbacks -> 72 | (faction, oldPower) -> { 73 | for (PowerChange callback : callbacks) { 74 | callback.onPowerChange(faction, oldPower); 75 | } 76 | }); 77 | 78 | /** Called when a faction sets its {@link Home} */ 79 | public static final Event SET_HOME = 80 | EventFactory.createArrayBacked( 81 | SetHome.class, 82 | callbacks -> 83 | (faction, home) -> { 84 | for (SetHome callback : callbacks) { 85 | callback.onSetHome(faction, home); 86 | } 87 | }); 88 | 89 | /** 90 | * Called when a faction removes all its claims. (Note that each claim will also run a {@link 91 | * ClaimEvents} REMOVE event) 92 | */ 93 | public static final Event REMOVE_ALL_CLAIMS = 94 | EventFactory.createArrayBacked( 95 | RemoveAllClaims.class, 96 | callbacks -> 97 | (faction) -> { 98 | for (RemoveAllClaims callback : callbacks) { 99 | callback.onRemoveAllClaims(faction); 100 | } 101 | }); 102 | 103 | @FunctionalInterface 104 | public interface Create { 105 | void onCreate(Faction faction, User owner); 106 | } 107 | 108 | @FunctionalInterface 109 | public interface Disband { 110 | void onDisband(Faction faction); 111 | } 112 | 113 | @FunctionalInterface 114 | public interface MemberJoin { 115 | void onMemberJoin(Faction faction, User user); 116 | } 117 | 118 | // TODO add Reason: LEAVE, KICK, DISBAND 119 | @FunctionalInterface 120 | public interface MemberLeave { 121 | void onMemberLeave(Faction faction, User user); 122 | } 123 | 124 | @FunctionalInterface 125 | public interface Modify { 126 | void onModify(Faction faction); 127 | } 128 | 129 | @FunctionalInterface 130 | public interface PowerChange { 131 | void onPowerChange(Faction faction, int oldPower); 132 | } 133 | 134 | @FunctionalInterface 135 | public interface SetHome { 136 | void onSetHome(Faction faction, Home home); 137 | } 138 | 139 | @FunctionalInterface 140 | public interface RemoveAllClaims { 141 | void onRemoveAllClaims(Faction faction); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /geysermc-example-custom-skulls.yml: -------------------------------------------------------------------------------- 1 | player-profiles: 2 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzcwNjkxNTU1MCwKICAicHJvZmlsZUlkIiA6ICJiZDgwZjkzZDBiODk0MjIwODVhMzZkNDFmMzE4ZmM5MiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZXl3b29kXzA2IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzVlYzUwM2I3NzQyZTMzM2FmMDc5MjRlMzA1M2QxMWM1MDllYjkxZDAxOWExMTFiNjVhM2UyNjJhMzc0Yzk3MDciLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ== 3 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzgzMTA2MDY2MSwKICAicHJvZmlsZUlkIiA6ICI0OWIzODUyNDdhMWY0NTM3YjBmN2MwZTFmMTVjMTc2NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJiY2QyMDMzYzYzZWM0YmY4IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzQyMzU0YTYwMTQ3YjdjY2Y1OWJhOTQ4OGZlNDFmNjM2YjYzNDFmOTJkZmY5YjM0MjIxNjA0MzNhN2VjNzI1N2QiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ== 4 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzcwNzAxMzY2MywKICAicHJvZmlsZUlkIiA6ICIzZGE2ZDgxOTI5MTY0MTNlODhlNzg2MjQ3NzA4YjkzZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJGZXJTdGlsZSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MjVkNDUzMGQwYjA5ODc2MzE0ZTE1NWYzMTViYTAyOWVmNDc4YTFhMzE2YzUyMWY1NzQzMWY1MmVlZTMxZmJiIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0= 5 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTM3ZTMxNjY0NzhhY2Q3MDZhYjhiYTJkYjc4YzAzMzg2MTA4YTMwZWNjNjc4ZWMyNjRhMjdkZWZhZjAzNDA1OSJ9fX0= 6 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjNjNzk1Y2U5ZWViNmRhNDMzMTU3MTZkNmNjYjg1Yjc4YWFmZTY2ZWRhMWJiY2Y5NjliMjAyODRiZTE5YTY1In19fQ== 7 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjgyMDllZWEwYjYyYjBkMWY1NmQ4YWZkNGY5Y2UyOGYzNDMzNDkwY2I2NmMzNWYxYTA5ZWI3MWNhNzY2ZmZkYiJ9fX0= 8 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2ZhNTE0OGU4MjFjMWZiZTIzZDYzZDA4Nzc2YjA1NWIwNjU0MDI4ZTJlMzk2NWVjMGRjNDhiNGU2MmNlNmQwNSJ9fX0= 9 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzc0NDc3OTc5MywKICAicHJvZmlsZUlkIiA6ICJjNmViMzdjNmE4YjM0MDI3OGJjN2FmZGE3ZjMxOWJmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJFbFJleUNhbGFiYXphbCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMTc0MGFkYWJmYzFiOWVlZGI3N2MyNDQ0NWRlNDE1ZmRmZmMzNDFhMTEwODM5NGIxZDlkZmQ5YjMzMjRiNzY1IiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0= 10 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWFlYTNjYWM2MDk5YjY5ZDJjZjdjNWE2MWU1YTk1NDI5MTdmNDU4MzFmN2Y2OWRkZmFmMzJlNjc2ZGJiODhkYyJ9fX0= 11 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDE2Y2VlMTdhYTg1ZTAwNTgwMzIxNzU5YzE2ZGU4Y2Q0Nzc5MGY0Njk2MWFmY2ZiOGZiNWYzZWYyZjY2N2Y1NSJ9fX0= 12 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzdhOGJmZmYwMjY5YzI1YTVhYzQ1NDI4YzQ2ZWZjZTkyODYwODI0YzE0ZDNjNTRjMjg0MmU1ZGUxODJjIn19fQ== 13 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWM4MGY4NjdkOWM4MGY2OWUxYjBhMzZhYmQ1ZGM2ZDYzNmJmYWQyYjYyZGYzMmUwYTA4MWJmZTkxMWQ3NTVkMiJ9fX0= 14 | - ewogICJ0aW1lc3RhbXAiIDogMTczNDIzMjI0OTU3OSwKICAicHJvZmlsZUlkIiA6ICI1ZjU5NmViY2JlOTQ0NmQxYmI0M2JlNGYzZjRiOGJlNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJUZWlsMHNzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2FlNzgzZmMwNmRlOTRiYTU4YzYyMTc5MGNmMjMxYmZjNThhNGZhMGM1YjIwODdjN2IwOTY1NGI1YWM5YTc5YTIiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ== 15 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzEwODI5OGZmMmIyNjk1MWQ2ODNlNWFkZTQ2YTQyZTkwYzJmN2M3ZGQ0MWJhYTkwOGJjNTg1MmY4YzMyZTU4MyJ9fX0 16 | - ewogICJ0aW1lc3RhbXAiIDogMTY0MDYxNjE5MjE0MiwKICAicHJvZmlsZUlkIiA6ICJmMjc0YzRkNjI1MDQ0ZTQxOGVmYmYwNmM3NWIyMDIxMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJIeXBpZ3NlbCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MDgyMGY3NmUzZTA0MWM3NWY3NmQwZjMwMTIzMmJkZjQ4MzIxYjUzNGZlNmE4NTljY2I4NzNkMjk4MWE5NjIzIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0= 17 | - eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzg2MTg1YjFkNTE5YWRlNTg1ZjE4NGMzNGYzZjNlMjBiYjY0MWRlYjg3OWU4MTM3OGU0ZWFmMjA5Mjg3In19fQ 18 | - ewogICJ0aW1lc3RhbXAiIDogMTY0MDYxNjExMDQ4OCwKICAicHJvZmlsZUlkIiA6ICIxZjEyNTNhYTVkYTQ0ZjU5YWU1YWI1NmFhZjRlNTYxNyIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb3RNaUt5IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdlNTc3MjBhNDg3OGM4YmNhYjBlOWM5YzQ3ZDllNTUxMjhjY2Q3N2JhMzQ0NWE1NGE5MWUzZTFlMWEyNzM1NmUiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ== 19 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzc5NDEzMzQ5OSwKICAicHJvZmlsZUlkIiA6ICI1ZjU5NmViY2JlOTQ0NmQxYmI0M2JlNGYzZjRiOGJlNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJUZWlsMHNzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzhkNmQ1ZWM3NzlhM2JkMThlZTllODM3MTJkZDU0MjE5YjQ5NjM3ZjkwZWVlMmJkMThiMjNkYTZkOTlmYjcyMWIiCiAgICB9CiAgfQp9 20 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzc5NDEzNTI5OSwKICAicHJvZmlsZUlkIiA6ICJlOThhZTBlMTI5MDg0ZDA5OTk0MTg4N2Q2YTk0ZTI2NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJUYXVuYWhpWmVhbG90Qm90IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzMxNjMyZDIwZTQ1YjNlNmE2YWFlZmQzMjgwZmRlMmIzNjdiYmU4NTk3NGUwMzk1YTViZmY1YWNkZWU0MWU2NzYiCiAgICB9CiAgfQp9 21 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzczNDc2MjMzMCwKICAicHJvZmlsZUlkIiA6ICIwMzBlMDA1OWQwY2M0YTZhODY3N2RkZWU3MjEzMjg1MyIsCiAgInByb2ZpbGVOYW1lIiA6ICJTbXVnRm9vZGllIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2U4NTJhZjkyMjIxZDZlMDI5NDcxYjA5MWMxNTI5YmZkOWJkZTI3MWU4MTk3NWQxY2ZiNmNjNDkzMDg3MDQzNTYiCiAgICB9CiAgfQp9 22 | - ewogICJ0aW1lc3RhbXAiIDogMTczMzczNDc2MDU1MiwKICAicHJvZmlsZUlkIiA6ICI5N2VmNDYyMzdhNGY0ZTQxYWY2ZTljYjg2MTdmNzc2OSIsCiAgInByb2ZpbGVOYW1lIiA6ICJZdWthcmlLYXplIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzczYThlY2QyYTRhMTMyNWVkNzM0MGJhNjgzMDdmZWZjOGI5MjQ0OWMzYTBiMzQxMzgyOTI4ZDIxODgzYjE4ODYiCiAgICB9CiAgfQp9 23 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/core/FactionsManager.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.core; 2 | 3 | import io.icker.factions.FactionsMod; 4 | import io.icker.factions.api.events.ClaimEvents; 5 | import io.icker.factions.api.events.FactionEvents; 6 | import io.icker.factions.api.events.PlayerEvents; 7 | import io.icker.factions.api.persistents.Faction; 8 | import io.icker.factions.api.persistents.Home; 9 | import io.icker.factions.api.persistents.User; 10 | import io.icker.factions.util.Message; 11 | import io.icker.factions.util.WorldUtils; 12 | 13 | import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 14 | import net.minecraft.entity.damage.DamageSource; 15 | import net.minecraft.entity.player.PlayerEntity; 16 | import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; 17 | import net.minecraft.screen.GenericContainerScreenHandler; 18 | import net.minecraft.screen.SimpleNamedScreenHandlerFactory; 19 | import net.minecraft.server.MinecraftServer; 20 | import net.minecraft.server.PlayerManager; 21 | import net.minecraft.server.network.ServerPlayerEntity; 22 | import net.minecraft.server.world.ServerWorld; 23 | import net.minecraft.text.Text; 24 | import net.minecraft.util.ActionResult; 25 | import net.minecraft.util.math.BlockPos; 26 | import net.minecraft.util.math.ChunkPos; 27 | 28 | import java.util.EnumSet; 29 | import java.util.List; 30 | import java.util.Objects; 31 | 32 | public class FactionsManager { 33 | public static PlayerManager playerManager; 34 | 35 | public static void register() { 36 | ServerLifecycleEvents.SERVER_STARTED.register(FactionsManager::serverStarted); 37 | FactionEvents.MODIFY.register(FactionsManager::factionModified); 38 | FactionEvents.MEMBER_JOIN.register(FactionsManager::memberChange); 39 | FactionEvents.MEMBER_LEAVE.register(FactionsManager::memberChange); 40 | PlayerEvents.ON_KILLED_BY_PLAYER.register(FactionsManager::playerDeath); 41 | PlayerEvents.ON_POWER_TICK.register(FactionsManager::powerTick); 42 | PlayerEvents.OPEN_SAFE.register(FactionsManager::openSafe); 43 | 44 | if (FactionsMod.CONFIG.HOME != null && FactionsMod.CONFIG.HOME.CLAIM_ONLY) { 45 | ClaimEvents.REMOVE.register( 46 | (x, z, level, faction) -> { 47 | Home home = faction.getHome(); 48 | 49 | if (home == null || !Objects.equals(home.level, level)) { 50 | return; 51 | } 52 | 53 | BlockPos homePos = BlockPos.ofFloored(home.x, home.y, home.z); 54 | 55 | ServerWorld world = WorldUtils.getWorld(home.level); 56 | 57 | ChunkPos homeChunkPos = world.getChunk(homePos).getPos(); 58 | 59 | if (homeChunkPos.x == x && homeChunkPos.z == z) { 60 | faction.setHome(null); 61 | } 62 | }); 63 | } 64 | } 65 | 66 | private static void serverStarted(MinecraftServer server) { 67 | playerManager = server.getPlayerManager(); 68 | Message.manager = server.getPlayerManager(); 69 | } 70 | 71 | private static void factionModified(Faction faction) { 72 | ServerPlayerEntity[] players = 73 | faction.getUsers().stream() 74 | .map(user -> playerManager.getPlayer(user.getID())) 75 | .filter(player -> player != null) 76 | .toArray(ServerPlayerEntity[]::new); 77 | updatePlayerList(players); 78 | } 79 | 80 | private static void memberChange(Faction faction, User user) { 81 | ServerPlayerEntity player = playerManager.getPlayer(user.getID()); 82 | if (player != null) { 83 | updatePlayerList(player); 84 | } 85 | } 86 | 87 | private static void playerDeath(ServerPlayerEntity player, DamageSource source) { 88 | User member = User.get(player.getUuid()); 89 | if (!member.isInFaction()) return; 90 | 91 | Faction faction = member.getFaction(); 92 | 93 | int adjusted = faction.adjustPower(-FactionsMod.CONFIG.POWER.DEATH_PENALTY); 94 | new Message( 95 | Text.translatable( 96 | "factions.events.lose_power_by_death", 97 | player.getName().getString(), 98 | adjusted)) 99 | .send(faction); 100 | } 101 | 102 | private static void powerTick(ServerPlayerEntity player) { 103 | User member = User.get(player.getUuid()); 104 | if (!member.isInFaction()) return; 105 | 106 | Faction faction = member.getFaction(); 107 | 108 | int adjusted = faction.adjustPower(FactionsMod.CONFIG.POWER.POWER_TICKS.REWARD); 109 | if (adjusted != 0 && FactionsMod.CONFIG.DISPLAY.POWER_MESSAGE) 110 | new Message( 111 | Text.translatable( 112 | "factions.events.get_power_by_tick", 113 | player.getName().getString(), 114 | adjusted)) 115 | .send(faction); 116 | } 117 | 118 | private static void updatePlayerList(ServerPlayerEntity... players) { 119 | playerManager.sendToAll( 120 | new PlayerListS2CPacket( 121 | EnumSet.of(PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME), 122 | List.of(players))); 123 | } 124 | 125 | private static ActionResult openSafe(PlayerEntity player, Faction faction) { 126 | User user = User.get(player.getUuid()); 127 | 128 | if (!user.isInFaction()) { 129 | if (FactionsMod.CONFIG.SAFE != null && FactionsMod.CONFIG.SAFE.ENDER_CHEST) { 130 | new Message(Text.translatable("factions.events.no_enderchests_without_faction")) 131 | .fail() 132 | .send(player, false); 133 | return ActionResult.FAIL; 134 | } 135 | return ActionResult.PASS; 136 | } 137 | 138 | player.openHandledScreen( 139 | new SimpleNamedScreenHandlerFactory( 140 | (syncId, inventory, p) -> { 141 | if (FactionsMod.CONFIG.SAFE.DOUBLE) { 142 | return GenericContainerScreenHandler.createGeneric9x6( 143 | syncId, inventory, faction.getSafe()); 144 | } else { 145 | return GenericContainerScreenHandler.createGeneric9x3( 146 | syncId, inventory, faction.getSafe()); 147 | } 148 | }, 149 | Text.translatable("factions.gui.safe.title", faction.getName()))); 150 | 151 | return ActionResult.SUCCESS; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/util/PlaceholdersWrapper.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.util; 2 | 3 | import eu.pb4.placeholders.api.PlaceholderResult; 4 | import eu.pb4.placeholders.api.Placeholders; 5 | 6 | import io.icker.factions.FactionsMod; 7 | import io.icker.factions.api.persistents.Faction; 8 | import io.icker.factions.api.persistents.User; 9 | 10 | import net.minecraft.text.Style; 11 | import net.minecraft.text.Text; 12 | import net.minecraft.util.Formatting; 13 | import net.minecraft.util.Identifier; 14 | 15 | import xyz.nucleoid.server.translations.api.Localization; 16 | import xyz.nucleoid.server.translations.api.language.ServerLanguage; 17 | 18 | import java.util.function.Function; 19 | 20 | public class PlaceholdersWrapper { 21 | private static final Text UNFORMATTED_NULL = Text.translatable("factions.papi.factionless"); 22 | private static final Text FORMATTED_NULL = 23 | UNFORMATTED_NULL.copy().formatted(Formatting.DARK_GRAY); 24 | 25 | private static void register(String identifier, Function handler) { 26 | Placeholders.register( 27 | Identifier.of(FactionsMod.MODID, identifier), 28 | (ctx, argument) -> { 29 | if (!ctx.hasPlayer()) 30 | return PlaceholderResult.invalid( 31 | Localization.raw( 32 | "argument.entity.notfound.player", 33 | ServerLanguage.getLanguage(FactionsMod.CONFIG.LANGUAGE))); 34 | 35 | User member = User.get(ctx.player().getUuid()); 36 | return PlaceholderResult.value(handler.apply(member)); 37 | }); 38 | } 39 | 40 | public static void init() { 41 | register( 42 | "name", 43 | (member) -> { 44 | Faction faction = member.getFaction(); 45 | if (faction == null) return FORMATTED_NULL; 46 | 47 | return Text.literal(faction.getName()) 48 | .formatted(member.getFaction().getColor()); 49 | }); 50 | 51 | register( 52 | "colorless_name", 53 | (member) -> { 54 | Faction faction = member.getFaction(); 55 | if (faction == null) return FORMATTED_NULL; 56 | 57 | return Text.of(faction.getName()); 58 | }); 59 | 60 | register( 61 | "chat", 62 | (member) -> { 63 | if (member.chat == User.ChatMode.GLOBAL || !member.isInFaction()) 64 | return Text.translatable("factions.papi.chat.global"); 65 | 66 | return Text.translatable("factions.papi.chat.faction"); 67 | }); 68 | 69 | register( 70 | "rank", 71 | (member) -> { 72 | if (!member.isInFaction()) return FORMATTED_NULL; 73 | 74 | return Text.of(member.getRankName()); 75 | }); 76 | 77 | register( 78 | "color", 79 | (member) -> { 80 | if (!member.isInFaction()) return Text.of("reset"); 81 | 82 | return Text.of(member.getFaction().getColor().getName()); 83 | }); 84 | 85 | register( 86 | "description", 87 | (member) -> { 88 | Faction faction = member.getFaction(); 89 | if (faction == null) return FORMATTED_NULL; 90 | 91 | return Text.of(faction.getDescription()); 92 | }); 93 | 94 | register( 95 | "state", 96 | (member) -> { 97 | Faction faction = member.getFaction(); 98 | if (faction == null) return UNFORMATTED_NULL; 99 | 100 | return Text.of(String.valueOf(faction.isOpen())); 101 | }); 102 | 103 | register( 104 | "power", 105 | (member) -> { 106 | Faction faction = member.getFaction(); 107 | if (faction == null) return UNFORMATTED_NULL; 108 | 109 | return Text.of(String.valueOf(faction.getPower())); 110 | }); 111 | 112 | register( 113 | "power_formatted", 114 | (member) -> { 115 | Faction faction = member.getFaction(); 116 | if (faction == null) return FORMATTED_NULL; 117 | 118 | int red = 119 | mapBoundRange( 120 | faction.calculateMaxPower(), 0, 170, 255, faction.getPower()); 121 | int green = 122 | mapBoundRange( 123 | 0, faction.calculateMaxPower(), 170, 255, faction.getPower()); 124 | return Text.literal(String.valueOf(faction.getPower())) 125 | .setStyle(Style.EMPTY.withColor(rgbToInt(red, green, 170))); 126 | }); 127 | 128 | register( 129 | "max_power", 130 | (member) -> { 131 | Faction faction = member.getFaction(); 132 | if (faction == null) return UNFORMATTED_NULL; 133 | 134 | return Text.of(String.valueOf(faction.calculateMaxPower())); 135 | }); 136 | 137 | register( 138 | "player_power", 139 | (member) -> { 140 | return Text.of(String.valueOf(FactionsMod.CONFIG.POWER.MEMBER)); 141 | }); 142 | 143 | register( 144 | "required_power", 145 | (member) -> { 146 | Faction faction = member.getFaction(); 147 | if (faction == null) return UNFORMATTED_NULL; 148 | 149 | return Text.of( 150 | String.valueOf( 151 | faction.getClaims().size() 152 | * FactionsMod.CONFIG.POWER.CLAIM_WEIGHT)); 153 | }); 154 | 155 | register( 156 | "required_power_formatted", 157 | (member) -> { 158 | Faction faction = member.getFaction(); 159 | if (faction == null) return FORMATTED_NULL; 160 | 161 | int reqPower = 162 | faction.getClaims().size() * FactionsMod.CONFIG.POWER.CLAIM_WEIGHT; 163 | int red = mapBoundRange(0, faction.getPower(), 85, 255, reqPower); 164 | return Text.literal(String.valueOf(reqPower)) 165 | .setStyle(Style.EMPTY.withColor(rgbToInt(red, 85, 85))); 166 | }); 167 | } 168 | 169 | private static int rgbToInt(int red, int green, int blue) { 170 | return (red & 255 << 16) | (green & 255 << 8) | (blue & 255); 171 | } 172 | 173 | private static int mapBoundRange( 174 | int from_min, int from_max, int to_min, int to_max, int value) { 175 | return Math.min( 176 | to_max, 177 | Math.max( 178 | to_min, 179 | to_min + ((value - from_min) * (to_max - to_min)) / (from_max - from_min))); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/HomeCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.tree.LiteralCommandNode; 6 | 7 | import io.icker.factions.FactionsMod; 8 | import io.icker.factions.api.persistents.Claim; 9 | import io.icker.factions.api.persistents.Faction; 10 | import io.icker.factions.api.persistents.Home; 11 | import io.icker.factions.api.persistents.User; 12 | import io.icker.factions.mixin.DamageTrackerAccessor; 13 | import io.icker.factions.util.Command; 14 | import io.icker.factions.util.Message; 15 | import io.icker.factions.util.WorldUtils; 16 | 17 | import net.minecraft.server.command.CommandManager; 18 | import net.minecraft.server.command.ServerCommandSource; 19 | import net.minecraft.server.network.ServerPlayerEntity; 20 | import net.minecraft.server.world.ServerWorld; 21 | import net.minecraft.text.Text; 22 | import net.minecraft.util.math.BlockPos; 23 | import net.minecraft.util.math.ChunkPos; 24 | 25 | import java.time.Instant; 26 | import java.util.Date; 27 | import java.util.HashSet; 28 | 29 | public class HomeCommand implements Command { 30 | private int go(CommandContext context) throws CommandSyntaxException { 31 | ServerCommandSource source = context.getSource(); 32 | ServerPlayerEntity player = source.getPlayerOrThrow(); 33 | 34 | if (player == null) return 0; 35 | 36 | User user = Command.getUser(player); 37 | Faction faction = user.getFaction(); 38 | 39 | if (faction == null) return 0; 40 | 41 | return execGo(player, user, faction); 42 | } 43 | 44 | public int execGo(ServerPlayerEntity player, User user, Faction faction) { 45 | Home home = faction.getHome(); 46 | 47 | if (home == null) { 48 | new Message(Text.translatable("factions.command.home.warp.fail.no_home")) 49 | .fail() 50 | .send(player, false); 51 | return 0; 52 | } 53 | 54 | if (player.getEntityWorld().getServer() == null) return 0; 55 | 56 | ServerWorld world = WorldUtils.getWorld(home.level); 57 | 58 | if (world == null) { 59 | new Message(Text.translatable("factions.command.home.warp.fail.no_world")) 60 | .fail() 61 | .send(player, false); 62 | return 0; 63 | } 64 | 65 | if (checkLimitToClaim(faction, world, BlockPos.ofFloored(home.x, home.y, home.z))) { 66 | new Message(Text.translatable("factions.command.home.warp.fail.no_claim")) 67 | .fail() 68 | .send(player, false); 69 | return 0; 70 | } 71 | 72 | long elapsed_time = Date.from(Instant.now()).getTime() - user.homeCooldown; 73 | if (elapsed_time < FactionsMod.CONFIG.HOME.HOME_WARP_COOLDOWN_SECOND * 1000) { 74 | new Message( 75 | "Cannot warp home while on warp cooldown, please wait %.0f seconds", 76 | (double) 77 | (FactionsMod.CONFIG.HOME.HOME_WARP_COOLDOWN_SECOND 78 | * 1000 79 | - elapsed_time) 80 | / 1000.0) 81 | .fail() 82 | .send(player, false); 83 | return 0; 84 | } 85 | 86 | if (((DamageTrackerAccessor) player.getDamageTracker()).getAgeOnLastDamage() == 0 87 | || player.age 88 | - ((DamageTrackerAccessor) player.getDamageTracker()) 89 | .getAgeOnLastDamage() 90 | > FactionsMod.CONFIG.HOME.DAMAGE_COOLDOWN) { 91 | player.teleport( 92 | world, home.x, home.y, home.z, new HashSet<>(), home.yaw, home.pitch, false); 93 | user.homeCooldown = Date.from(Instant.now()).getTime(); 94 | 95 | new Message(Text.translatable("factions.command.home.warp.success")) 96 | .send(player, false); 97 | } else { 98 | new Message(Text.translatable("factions.command.home.warp.fail.combat")) 99 | .fail() 100 | .send(player, false); 101 | } 102 | return 1; 103 | } 104 | 105 | private int set(CommandContext context) throws CommandSyntaxException { 106 | ServerCommandSource source = context.getSource(); 107 | ServerPlayerEntity player = source.getPlayerOrThrow(); 108 | 109 | Faction faction = Command.getUser(player).getFaction(); 110 | 111 | if (checkLimitToClaim( 112 | faction, (ServerWorld) player.getEntityWorld(), player.getBlockPos())) { 113 | new Message(Text.translatable("factions.command.home.fail.no_claim")) 114 | .fail() 115 | .send(player, false); 116 | return 0; 117 | } 118 | 119 | Home home = 120 | new Home( 121 | faction.getID(), 122 | player.getX(), 123 | player.getY(), 124 | player.getZ(), 125 | player.getHeadYaw(), 126 | player.getPitch(), 127 | player.getEntityWorld().getRegistryKey().getValue().toString()); 128 | 129 | faction.setHome(home); 130 | new Message( 131 | Text.translatable( 132 | "factions.command.home.set.success", 133 | home.x, 134 | home.y, 135 | home.z, 136 | player.getName().getString())) 137 | .send(faction); 138 | return 1; 139 | } 140 | 141 | private static boolean checkLimitToClaim(Faction faction, ServerWorld world, BlockPos pos) { 142 | if (!FactionsMod.CONFIG.HOME.CLAIM_ONLY) return false; 143 | 144 | ChunkPos chunkPos = world.getChunk(pos).getPos(); 145 | String dimension = world.getRegistryKey().getValue().toString(); 146 | 147 | Claim possibleClaim = Claim.get(chunkPos.x, chunkPos.z, dimension); 148 | return possibleClaim == null || possibleClaim.getFaction().getID() != faction.getID(); 149 | } 150 | 151 | @Override 152 | public LiteralCommandNode getNode() { 153 | return CommandManager.literal("home") 154 | .requires( 155 | Requires.multiple( 156 | Requires.isMember(), 157 | s -> FactionsMod.CONFIG.HOME != null, 158 | Requires.hasPerms("factions.home", 0))) 159 | .executes(this::go) 160 | .then( 161 | CommandManager.literal("set") 162 | .requires( 163 | Requires.multiple( 164 | Requires.hasPerms("factions.home.set", 0), 165 | Requires.isLeader())) 166 | .executes(this::set)) 167 | .build(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/InviteCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import com.mojang.brigadier.context.CommandContext; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import com.mojang.brigadier.tree.LiteralCommandNode; 7 | 8 | import io.icker.factions.api.persistents.Faction; 9 | import io.icker.factions.api.persistents.User; 10 | import io.icker.factions.util.Command; 11 | import io.icker.factions.util.Message; 12 | 13 | import net.minecraft.command.argument.EntityArgumentType; 14 | import net.minecraft.server.GameProfileResolver; 15 | import net.minecraft.server.command.CommandManager; 16 | import net.minecraft.server.command.ServerCommandSource; 17 | import net.minecraft.server.network.ServerPlayerEntity; 18 | import net.minecraft.text.Text; 19 | import net.minecraft.util.Formatting; 20 | import net.minecraft.util.Util; 21 | 22 | import xyz.nucleoid.server.translations.api.Localization; 23 | 24 | import java.util.List; 25 | import java.util.UUID; 26 | import java.util.stream.Collectors; 27 | 28 | public class InviteCommand implements Command { 29 | private int list(CommandContext context) throws CommandSyntaxException { 30 | ServerCommandSource source = context.getSource(); 31 | 32 | ServerPlayerEntity player = source.getPlayerOrThrow(); 33 | List invites = Command.getUser(player).getFaction().invites; 34 | int count = invites.size(); 35 | 36 | new Message( 37 | Text.translatable( 38 | "factions.command.invite.list", 39 | Text.literal(String.valueOf(count)).formatted(Formatting.YELLOW))) 40 | .send(player, false); 41 | 42 | if (count == 0) return 1; 43 | 44 | GameProfileResolver resolver = source.getServer().getApiServices().profileResolver(); 45 | String players = 46 | invites.stream() 47 | .map( 48 | invite -> 49 | resolver.getProfileById(invite) 50 | .orElse( 51 | new GameProfile( 52 | Util.NIL_UUID, 53 | Localization.raw( 54 | "factions.gui.generic.unknown_player", 55 | player))) 56 | .name()) 57 | .collect(Collectors.joining(", ")); 58 | 59 | new Message(players).format(Formatting.ITALIC).send(player, false); 60 | return 1; 61 | } 62 | 63 | private int add(CommandContext context) throws CommandSyntaxException { 64 | ServerPlayerEntity target = EntityArgumentType.getPlayer(context, "player"); 65 | 66 | ServerCommandSource source = context.getSource(); 67 | ServerPlayerEntity player = source.getPlayerOrThrow(); 68 | 69 | Faction faction = Command.getUser(source.getPlayerOrThrow()).getFaction(); 70 | if (faction.isInvited(player.getUuid())) { 71 | new Message( 72 | Text.translatable( 73 | "factions.command.invite.add.fail.already_invited", 74 | target.getName().getString())) 75 | .fail() 76 | .send(player, false); 77 | return 0; 78 | } 79 | 80 | User targetUser = User.get(target.getUuid()); 81 | UUID targetFaction = targetUser.isInFaction() ? targetUser.getFaction().getID() : null; 82 | if (faction.getID().equals(targetFaction)) { 83 | new Message( 84 | Text.translatable( 85 | "factions.command.invite.add.fail.already_member", 86 | target.getName().getString())) 87 | .fail() 88 | .send(player, false); 89 | return 0; 90 | } 91 | 92 | faction.invites.add(target.getUuid()); 93 | 94 | new Message( 95 | Text.translatable( 96 | "factions.command.invite.add.success.actor", 97 | target.getName().getString())) 98 | .send(faction); 99 | new Message(Text.translatable("factions.command.invite.add.success.subject")) 100 | .format(Formatting.YELLOW) 101 | .hover(Text.translatable("factions.command.invite.add.success.subject.hover")) 102 | .click("/factions join " + faction.getName()) 103 | .prependFaction(faction) 104 | .send(target, false); 105 | return 1; 106 | } 107 | 108 | private int remove(CommandContext context) throws CommandSyntaxException { 109 | ServerPlayerEntity target = EntityArgumentType.getPlayer(context, "player"); 110 | 111 | ServerCommandSource source = context.getSource(); 112 | ServerPlayerEntity player = source.getPlayerOrThrow(); 113 | 114 | Faction faction = Command.getUser(player).getFaction(); 115 | if (faction.invites.remove(target.getUuid())) { 116 | new Message( 117 | Text.translatable( 118 | "factions.command.invite.remove.success", 119 | target.getName().getString())) 120 | .send(player, false); 121 | return 1; 122 | } else { 123 | new Message( 124 | Text.translatable( 125 | "factions.command.invite.remove.fail", 126 | target.getName().getString())) 127 | .fail() 128 | .send(player, false); 129 | return 0; 130 | } 131 | } 132 | 133 | public LiteralCommandNode getNode() { 134 | return CommandManager.literal("invite") 135 | .requires(Requires.isCommander()) 136 | .then( 137 | CommandManager.literal("list") 138 | .requires(Requires.hasPerms("factions.invite.list", 0)) 139 | .executes(this::list)) 140 | .then( 141 | CommandManager.literal("add") 142 | .requires(Requires.hasPerms("factions.invite.add", 0)) 143 | .then( 144 | CommandManager.argument( 145 | "player", EntityArgumentType.player()) 146 | .executes(this::add))) 147 | .then( 148 | CommandManager.literal("remove") 149 | .requires(Requires.hasPerms("factions.invite.remove", 0)) 150 | .then( 151 | CommandManager.argument( 152 | "player", EntityArgumentType.player()) 153 | .executes(this::remove))) 154 | .build(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/SettingsCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.tree.LiteralCommandNode; 6 | 7 | import io.icker.factions.api.persistents.User; 8 | import io.icker.factions.util.Command; 9 | import io.icker.factions.util.Message; 10 | 11 | import net.minecraft.server.command.CommandManager; 12 | import net.minecraft.server.command.ServerCommandSource; 13 | import net.minecraft.server.network.ServerPlayerEntity; 14 | import net.minecraft.text.Text; 15 | import net.minecraft.util.Formatting; 16 | 17 | public class SettingsCommand implements Command { 18 | private int setChat(CommandContext context, User.ChatMode option) 19 | throws CommandSyntaxException { 20 | ServerPlayerEntity player = context.getSource().getPlayerOrThrow(); 21 | User user = User.get(player.getUuid()); 22 | user.chat = option; 23 | 24 | new Message(Text.translatable("factions.command.settings.chat")) 25 | .filler("·") 26 | .add( 27 | new Message( 28 | Text.translatable( 29 | "factions.command.settings.chat." 30 | + user.getChatName())) 31 | .format(Formatting.BLUE)) 32 | .send(player, false); 33 | 34 | return 1; 35 | } 36 | 37 | private int setSounds(CommandContext context, User.SoundMode option) 38 | throws CommandSyntaxException { 39 | ServerPlayerEntity player = context.getSource().getPlayerOrThrow(); 40 | User user = User.get(player.getUuid()); 41 | user.sounds = option; 42 | 43 | new Message(Text.translatable("factions.command.settings.sound")) 44 | .filler("·") 45 | .add( 46 | new Message( 47 | Text.translatable( 48 | "factions.command.settings.sound." 49 | + user.getSoundName())) 50 | .format(Formatting.BLUE)) 51 | .send(player, false); 52 | 53 | return 1; 54 | } 55 | 56 | private int radar(CommandContext context) throws CommandSyntaxException { 57 | ServerCommandSource source = context.getSource(); 58 | ServerPlayerEntity player = source.getPlayerOrThrow(); 59 | 60 | User config = User.get(player.getUuid()); 61 | boolean radar = !config.radar; 62 | config.radar = radar; 63 | 64 | new Message(Text.translatable("factions.command.settings.radar")) 65 | .filler("·") 66 | .add( 67 | new Message(Text.translatable("options." + (radar ? "on" : "off"))) 68 | .format(radar ? Formatting.GREEN : Formatting.RED)) 69 | .send(player, false); 70 | 71 | return 1; 72 | } 73 | 74 | public LiteralCommandNode getNode() { 75 | return CommandManager.literal("settings") 76 | .requires(Requires.hasPerms("factions.settings", 0)) 77 | .then( 78 | CommandManager.literal("chat") 79 | .requires(Requires.hasPerms("factions.settings.chat", 0)) 80 | .then( 81 | CommandManager.literal("global") 82 | .executes( 83 | context -> 84 | setChat( 85 | context, 86 | User.ChatMode.GLOBAL))) 87 | .then( 88 | CommandManager.literal("faction") 89 | .executes( 90 | context -> 91 | setChat( 92 | context, 93 | User.ChatMode.FACTION))) 94 | .then( 95 | CommandManager.literal("focus") 96 | .executes( 97 | context -> 98 | setChat( 99 | context, 100 | User.ChatMode.FOCUS)))) 101 | .then( 102 | CommandManager.literal("radar") 103 | .requires(Requires.hasPerms("factions.settings.radar", 0)) 104 | .executes(this::radar)) 105 | .then( 106 | CommandManager.literal("sounds") 107 | .requires(Requires.hasPerms("factions.settings.sounds", 0)) 108 | .then( 109 | CommandManager.literal("none") 110 | .executes( 111 | context -> 112 | setSounds( 113 | context, 114 | User.SoundMode.NONE))) 115 | .then( 116 | CommandManager.literal("warnings") 117 | .executes( 118 | context -> 119 | setSounds( 120 | context, 121 | User.SoundMode.WARNINGS))) 122 | .then( 123 | CommandManager.literal("faction") 124 | .executes( 125 | context -> 126 | setSounds( 127 | context, 128 | User.SoundMode.FACTION))) 129 | .then( 130 | CommandManager.literal("all") 131 | .executes( 132 | context -> 133 | setSounds( 134 | context, 135 | User.SoundMode.ALL)))) 136 | .build(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/database/Database.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.database; 2 | 3 | import io.icker.factions.FactionsMod; 4 | 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import net.minecraft.nbt.NbtCompound; 7 | import net.minecraft.nbt.NbtElement; 8 | import net.minecraft.nbt.NbtIo; 9 | import net.minecraft.nbt.NbtList; 10 | import net.minecraft.nbt.NbtSizeTracker; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.ParameterizedType; 16 | import java.nio.file.Path; 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.function.Function; 22 | 23 | public class Database { 24 | private static final File BASE_PATH = 25 | FabricLoader.getInstance().getGameDir().resolve("factions").toFile(); 26 | private static final HashMap, HashMap> cache = 27 | new HashMap, HashMap>(); 28 | private static final String KEY = "CORE"; 29 | 30 | public static HashMap load(Class clazz, Function getStoreKey) { 31 | String name = clazz.getAnnotation(Name.class).value(); 32 | File file = new File(BASE_PATH, name.toLowerCase() + ".dat"); 33 | 34 | if (!cache.containsKey(clazz)) setup(clazz); 35 | 36 | HashMap store = new HashMap(); 37 | 38 | if (!file.exists()) { 39 | if (!BASE_PATH.exists()) BASE_PATH.mkdir(); 40 | try { 41 | file.createNewFile(); 42 | } catch (IOException e) { 43 | FactionsMod.LOGGER.error("Failed to create file ({})", file, e); 44 | } 45 | return store; 46 | } 47 | 48 | try { 49 | NbtList list = 50 | (NbtList) 51 | NbtIo.readCompressed( 52 | Path.of(file.getPath()), 53 | NbtSizeTracker.ofUnlimitedBytes()) 54 | .get(KEY); 55 | for (T item : deserializeList(clazz, list)) { 56 | store.put(getStoreKey.apply(item), item); 57 | } 58 | } catch (IOException | ReflectiveOperationException e) { 59 | FactionsMod.LOGGER.error("Failed to read NBT data ({})", file, e); 60 | } 61 | 62 | return store; 63 | } 64 | 65 | private static T deserialize(Class clazz, NbtElement value) 66 | throws IOException, ReflectiveOperationException { 67 | if (SerializerRegistry.contains(clazz)) { 68 | return SerializerRegistry.fromNbtElement(clazz, value); 69 | } 70 | 71 | NbtCompound compound = (NbtCompound) value; 72 | T item = (T) clazz.getDeclaredConstructor().newInstance(); 73 | 74 | HashMap fields = cache.get(clazz); 75 | for (Map.Entry entry : fields.entrySet()) { 76 | String key = entry.getKey(); 77 | Field field = entry.getValue(); 78 | 79 | if (!compound.contains(key)) continue; 80 | 81 | Class type = field.getType(); 82 | 83 | if (ArrayList.class.isAssignableFrom(type)) { 84 | Class genericType = 85 | (Class) 86 | ((ParameterizedType) field.getGenericType()) 87 | .getActualTypeArguments()[0]; 88 | field.set(item, deserializeList(genericType, (NbtList) compound.get(key))); 89 | } else { 90 | field.set(item, deserialize(type, compound.get(key))); 91 | } 92 | } 93 | 94 | return item; 95 | } 96 | 97 | private static ArrayList deserializeList(Class clazz, NbtList list) 98 | throws IOException, ReflectiveOperationException { 99 | ArrayList store = new ArrayList(); 100 | 101 | for (int i = 0; i < list.size(); i++) { 102 | store.add(deserialize(clazz, list.get(i))); 103 | } 104 | 105 | return store; 106 | } 107 | 108 | public static void save(Class clazz, List items) { 109 | String name = clazz.getAnnotation(Name.class).value(); 110 | File file = new File(BASE_PATH, name.toLowerCase() + ".dat"); 111 | 112 | if (!cache.containsKey(clazz)) setup(clazz); 113 | 114 | try { 115 | NbtCompound fileData = new NbtCompound(); 116 | fileData.put(KEY, serializeList(clazz, items)); 117 | NbtIo.writeCompressed(fileData, Path.of(file.getPath())); 118 | } catch (IOException | ReflectiveOperationException e) { 119 | FactionsMod.LOGGER.error("Failed to write NBT data ({})", file, e); 120 | } 121 | } 122 | 123 | private static NbtElement serialize(Class clazz, T item) 124 | throws IOException, ReflectiveOperationException { 125 | if (SerializerRegistry.contains(clazz)) { 126 | return SerializerRegistry.toNbtElement(clazz, item); 127 | } 128 | 129 | HashMap fields = cache.get(clazz); 130 | NbtCompound compound = new NbtCompound(); 131 | for (Map.Entry entry : fields.entrySet()) { 132 | String key = entry.getKey(); 133 | Field field = entry.getValue(); 134 | 135 | Class type = field.getType(); 136 | Object data = field.get(item); 137 | 138 | if (data == null) continue; 139 | 140 | if (ArrayList.class.isAssignableFrom(type)) { 141 | Class genericType = 142 | (Class) 143 | ((ParameterizedType) field.getGenericType()) 144 | .getActualTypeArguments()[0]; 145 | compound.put(key, serializeList(genericType, cast(data))); 146 | } else { 147 | compound.put(key, serialize(type, cast(data))); 148 | } 149 | } 150 | 151 | return compound; 152 | } 153 | 154 | private static NbtList serializeList(Class clazz, List items) 155 | throws IOException, ReflectiveOperationException { 156 | NbtList list = new NbtList(); 157 | 158 | for (T item : items) { 159 | list.add(list.size(), serialize(clazz, item)); 160 | } 161 | 162 | return list; 163 | } 164 | 165 | private static void setup(Class clazz) { 166 | HashMap fields = new HashMap(); 167 | 168 | for (Field field : clazz.getDeclaredFields()) { 169 | if (field.isAnnotationPresent(io.icker.factions.database.Field.class)) { 170 | field.setAccessible(true); 171 | fields.put( 172 | field.getAnnotation(io.icker.factions.database.Field.class).value(), field); 173 | 174 | Class type = field.getType(); 175 | if (!SerializerRegistry.contains(type)) { 176 | if (ArrayList.class.isAssignableFrom(type)) { 177 | ParameterizedType genericType = (ParameterizedType) field.getGenericType(); 178 | setup((Class) genericType.getActualTypeArguments()[0]); 179 | } else { 180 | setup(type); 181 | } 182 | } 183 | } 184 | } 185 | 186 | cache.put(clazz, fields); 187 | } 188 | 189 | @SuppressWarnings("unchecked") 190 | private static T cast(Object key) { 191 | return (T) key; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/util/Icons.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.util; 2 | 3 | public class Icons { 4 | public static final String GUI_TESSERACT_BLUE = 5 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzcwNjkxNTU1MCwKICAicHJvZmlsZUlkIiA6ICJiZDgwZjkzZDBiODk0MjIwODVhMzZkNDFmMzE4ZmM5MiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZXl3b29kXzA2IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzVlYzUwM2I3NzQyZTMzM2FmMDc5MjRlMzA1M2QxMWM1MDllYjkxZDAxOWExMTFiNjVhM2UyNjJhMzc0Yzk3MDciLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="; 6 | public static final String GUI_TESSERACT_RED = 7 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzgzMTA2MDY2MSwKICAicHJvZmlsZUlkIiA6ICI0OWIzODUyNDdhMWY0NTM3YjBmN2MwZTFmMTVjMTc2NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJiY2QyMDMzYzYzZWM0YmY4IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzQyMzU0YTYwMTQ3YjdjY2Y1OWJhOTQ4OGZlNDFmNjM2YjYzNDFmOTJkZmY5YjM0MjIxNjA0MzNhN2VjNzI1N2QiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="; 8 | public static final String GUI_TESSERACT_OFF = 9 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzcwNzAxMzY2MywKICAicHJvZmlsZUlkIiA6ICIzZGE2ZDgxOTI5MTY0MTNlODhlNzg2MjQ3NzA4YjkzZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJGZXJTdGlsZSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MjVkNDUzMGQwYjA5ODc2MzE0ZTE1NWYzMTViYTAyOWVmNDc4YTFhMzE2YzUyMWY1NzQzMWY1MmVlZTMxZmJiIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0="; 10 | public static final String GUI_FIST = 11 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTM3ZTMxNjY0NzhhY2Q3MDZhYjhiYTJkYjc4YzAzMzg2MTA4YTMwZWNjNjc4ZWMyNjRhMjdkZWZhZjAzNDA1OSJ9fX0="; 12 | public static final String GUI_XENOMORPH = 13 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjNjNzk1Y2U5ZWViNmRhNDMzMTU3MTZkNmNjYjg1Yjc4YWFmZTY2ZWRhMWJiY2Y5NjliMjAyODRiZTE5YTY1In19fQ=="; 14 | public static final String GUI_BOOK = 15 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjgyMDllZWEwYjYyYjBkMWY1NmQ4YWZkNGY5Y2UyOGYzNDMzNDkwY2I2NmMzNWYxYTA5ZWI3MWNhNzY2ZmZkYiJ9fX0="; 16 | public static final String GUI_PLAYER = 17 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2ZhNTE0OGU4MjFjMWZiZTIzZDYzZDA4Nzc2YjA1NWIwNjU0MDI4ZTJlMzk2NWVjMGRjNDhiNGU2MmNlNmQwNSJ9fX0="; 18 | public static final String GUI_EARTH_RELOAD = 19 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzc0NDc3OTc5MywKICAicHJvZmlsZUlkIiA6ICJjNmViMzdjNmE4YjM0MDI3OGJjN2FmZGE3ZjMxOWJmMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJFbFJleUNhbGFiYXphbCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zMTc0MGFkYWJmYzFiOWVlZGI3N2MyNDQ0NWRlNDE1ZmRmZmMzNDFhMTEwODM5NGIxZDlkZmQ5YjMzMjRiNzY1IiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0="; 20 | public static final String GUI_TV_TEXT = 21 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWFlYTNjYWM2MDk5YjY5ZDJjZjdjNWE2MWU1YTk1NDI5MTdmNDU4MzFmN2Y2OWRkZmFmMzJlNjc2ZGJiODhkYyJ9fX0="; 22 | public static final String GUI_RADIO = 23 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDE2Y2VlMTdhYTg1ZTAwNTgwMzIxNzU5YzE2ZGU4Y2Q0Nzc5MGY0Njk2MWFmY2ZiOGZiNWYzZWYyZjY2N2Y1NSJ9fX0="; 24 | public static final String GUI_PAINT_BUCKET = 25 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzdhOGJmZmYwMjY5YzI1YTVhYzQ1NDI4YzQ2ZWZjZTkyODYwODI0YzE0ZDNjNTRjMjg0MmU1ZGUxODJjIn19fQ=="; 26 | public static final String GUI_LECTERN = 27 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWM4MGY4NjdkOWM4MGY2OWUxYjBhMzZhYmQ1ZGM2ZDYzNmJmYWQyYjYyZGYzMmUwYTA4MWJmZTkxMWQ3NTVkMiJ9fX0="; 28 | public static final String GUI_UNKNOWN_PLAYER = 29 | "ewogICJ0aW1lc3RhbXAiIDogMTczNDIzMjI0OTU3OSwKICAicHJvZmlsZUlkIiA6ICI1ZjU5NmViY2JlOTQ0NmQxYmI0M2JlNGYzZjRiOGJlNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJUZWlsMHNzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2FlNzgzZmMwNmRlOTRiYTU4YzYyMTc5MGNmMjMxYmZjNThhNGZhMGM1YjIwODdjN2IwOTY1NGI1YWM5YTc5YTIiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="; 30 | 31 | public static final String GUI_PREVIOUS_PAGE = 32 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzEwODI5OGZmMmIyNjk1MWQ2ODNlNWFkZTQ2YTQyZTkwYzJmN2M3ZGQ0MWJhYTkwOGJjNTg1MmY4YzMyZTU4MyJ9fX0"; 33 | public static final String GUI_PREVIOUS_PAGE_BLOCKED = 34 | "ewogICJ0aW1lc3RhbXAiIDogMTY0MDYxNjE5MjE0MiwKICAicHJvZmlsZUlkIiA6ICJmMjc0YzRkNjI1MDQ0ZTQxOGVmYmYwNmM3NWIyMDIxMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJIeXBpZ3NlbCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS81MDgyMGY3NmUzZTA0MWM3NWY3NmQwZjMwMTIzMmJkZjQ4MzIxYjUzNGZlNmE4NTljY2I4NzNkMjk4MWE5NjIzIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0="; 35 | public static final String GUI_NEXT_PAGE = 36 | "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzg2MTg1YjFkNTE5YWRlNTg1ZjE4NGMzNGYzZjNlMjBiYjY0MWRlYjg3OWU4MTM3OGU0ZWFmMjA5Mjg3In19fQ"; 37 | public static final String GUI_NEXT_PAGE_BLOCKED = 38 | "ewogICJ0aW1lc3RhbXAiIDogMTY0MDYxNjExMDQ4OCwKICAicHJvZmlsZUlkIiA6ICIxZjEyNTNhYTVkYTQ0ZjU5YWU1YWI1NmFhZjRlNTYxNyIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb3RNaUt5IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdlNTc3MjBhNDg3OGM4YmNhYjBlOWM5YzQ3ZDllNTUxMjhjY2Q3N2JhMzQ0NWE1NGE5MWUzZTFlMWEyNzM1NmUiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="; 39 | 40 | public static final String GUI_CASTLE_OPEN = 41 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzc5NDEzMzQ5OSwKICAicHJvZmlsZUlkIiA6ICI1ZjU5NmViY2JlOTQ0NmQxYmI0M2JlNGYzZjRiOGJlNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJUZWlsMHNzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzhkNmQ1ZWM3NzlhM2JkMThlZTllODM3MTJkZDU0MjE5YjQ5NjM3ZjkwZWVlMmJkMThiMjNkYTZkOTlmYjcyMWIiCiAgICB9CiAgfQp9"; 42 | public static final String GUI_CASTLE_NORMAL = 43 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzc5NDEzNTI5OSwKICAicHJvZmlsZUlkIiA6ICJlOThhZTBlMTI5MDg0ZDA5OTk0MTg4N2Q2YTk0ZTI2NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJUYXVuYWhpWmVhbG90Qm90IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzMxNjMyZDIwZTQ1YjNlNmE2YWFlZmQzMjgwZmRlMmIzNjdiYmU4NTk3NGUwMzk1YTViZmY1YWNkZWU0MWU2NzYiCiAgICB9CiAgfQp9"; 44 | public static final String GUI_CASTLE_ALLY = 45 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzczNDc2MjMzMCwKICAicHJvZmlsZUlkIiA6ICIwMzBlMDA1OWQwY2M0YTZhODY3N2RkZWU3MjEzMjg1MyIsCiAgInByb2ZpbGVOYW1lIiA6ICJTbXVnRm9vZGllIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2U4NTJhZjkyMjIxZDZlMDI5NDcxYjA5MWMxNTI5YmZkOWJkZTI3MWU4MTk3NWQxY2ZiNmNjNDkzMDg3MDQzNTYiCiAgICB9CiAgfQp9"; 46 | public static final String GUI_CASTLE_ENEMY = 47 | "ewogICJ0aW1lc3RhbXAiIDogMTczMzczNDc2MDU1MiwKICAicHJvZmlsZUlkIiA6ICI5N2VmNDYyMzdhNGY0ZTQxYWY2ZTljYjg2MTdmNzc2OSIsCiAgInByb2ZpbGVOYW1lIiA6ICJZdWthcmlLYXplIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzczYThlY2QyYTRhMTMyNWVkNzM0MGJhNjgzMDdmZWZjOGI5MjQ0OWMzYTBiMzQxMzgyOTI4ZDIxODgzYjE4ODYiCiAgICB9CiAgfQp9"; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/DeclareCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.brigadier.arguments.StringArgumentType; 4 | import com.mojang.brigadier.context.CommandContext; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import com.mojang.brigadier.tree.LiteralCommandNode; 7 | 8 | import io.icker.factions.api.events.RelationshipEvents; 9 | import io.icker.factions.api.persistents.Faction; 10 | import io.icker.factions.api.persistents.Relationship; 11 | import io.icker.factions.util.Command; 12 | import io.icker.factions.util.Message; 13 | 14 | import net.minecraft.server.command.CommandManager; 15 | import net.minecraft.server.command.ServerCommandSource; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.text.MutableText; 18 | import net.minecraft.text.Text; 19 | import net.minecraft.util.Formatting; 20 | 21 | import java.util.Locale; 22 | 23 | public class DeclareCommand implements Command { 24 | private int ally(CommandContext context) throws CommandSyntaxException { 25 | return updateRelationship(context, Relationship.Status.ALLY); 26 | } 27 | 28 | private int neutral(CommandContext context) throws CommandSyntaxException { 29 | return updateRelationship(context, Relationship.Status.NEUTRAL); 30 | } 31 | 32 | private int enemy(CommandContext context) throws CommandSyntaxException { 33 | return updateRelationship(context, Relationship.Status.ENEMY); 34 | } 35 | 36 | private int updateRelationship( 37 | CommandContext context, Relationship.Status status) 38 | throws CommandSyntaxException { 39 | String name = StringArgumentType.getString(context, "faction"); 40 | ServerCommandSource source = context.getSource(); 41 | ServerPlayerEntity player = source.getPlayerOrThrow(); 42 | 43 | Faction targetFaction = Faction.getByName(name); 44 | 45 | if (targetFaction == null) { 46 | new Message(Text.translatable("factions.command.declare.fail.nonexistent_faction")) 47 | .fail() 48 | .send(player, false); 49 | return 0; 50 | } 51 | 52 | Faction sourceFaction = Command.getUser(player).getFaction(); 53 | 54 | if (sourceFaction.equals(targetFaction)) { 55 | new Message(Text.translatable("factions.command.declare.fail.own_faction")) 56 | .fail() 57 | .send(player, false); 58 | return 0; 59 | } 60 | 61 | if (sourceFaction.getRelationship(targetFaction.getID()).status == status) { 62 | new Message(Text.translatable("factions.command.declare.fail.no_change")) 63 | .fail() 64 | .send(player, false); 65 | return 0; 66 | } 67 | 68 | Relationship.Status mutual = null; 69 | 70 | if (sourceFaction.getRelationship(targetFaction.getID()).status 71 | == targetFaction.getRelationship(sourceFaction.getID()).status) { 72 | mutual = sourceFaction.getRelationship(targetFaction.getID()).status; 73 | } 74 | 75 | Relationship rel = new Relationship(targetFaction.getID(), status); 76 | Relationship rev = targetFaction.getRelationship(sourceFaction.getID()); 77 | sourceFaction.setRelationship(rel); 78 | 79 | RelationshipEvents.NEW_DECLARATION.invoker().onNewDecleration(rel); 80 | 81 | MutableText msgStatus = 82 | rel.status == Relationship.Status.ALLY 83 | ? Text.translatable("factions.command.declare.success.status.ally") 84 | .formatted(Formatting.GREEN) 85 | : rel.status == Relationship.Status.ENEMY 86 | ? Text.translatable("factions.command.declare.success.status.enemy") 87 | .formatted(Formatting.RED) 88 | : Text.translatable( 89 | "factions.command.declare.success.status.neutral"); 90 | 91 | if (rel.status == rev.status) { 92 | RelationshipEvents.NEW_MUTUAL.invoker().onNewMutual(rel); 93 | new Message( 94 | Text.translatable( 95 | "factions.command.declare.success.mutual", 96 | msgStatus, 97 | targetFaction.getName())) 98 | .send(sourceFaction); 99 | new Message( 100 | Text.translatable( 101 | "factions.command.declare.success.mutual", 102 | msgStatus, 103 | sourceFaction.getName())) 104 | .send(targetFaction); 105 | return 1; 106 | } else if (mutual != null) { 107 | RelationshipEvents.END_MUTUAL.invoker().onEndMutual(rel, mutual); 108 | } 109 | 110 | new Message( 111 | Text.translatable( 112 | "factions.command.declare.success.actor", 113 | targetFaction.getName(), 114 | msgStatus)) 115 | .send(sourceFaction); 116 | 117 | if (rel.status != Relationship.Status.NEUTRAL) 118 | new Message( 119 | Text.translatable( 120 | "factions.command.declare.success.subject", 121 | sourceFaction.getName(), 122 | msgStatus)) 123 | .hover(Text.translatable("factions.command.declare.success.subject.hover")) 124 | .click( 125 | String.format( 126 | "/factions declare %s %s", 127 | rel.status.toString().toLowerCase(Locale.ROOT), 128 | sourceFaction.getName())) 129 | .send(targetFaction); 130 | 131 | return 1; 132 | } 133 | 134 | @Override 135 | public LiteralCommandNode getNode() { 136 | return CommandManager.literal("declare") 137 | .requires(Requires.isLeader()) 138 | .then( 139 | CommandManager.literal("ally") 140 | .requires(Requires.hasPerms("factions.declare.ally", 0)) 141 | .then( 142 | CommandManager.argument( 143 | "faction", 144 | StringArgumentType.greedyString()) 145 | .suggests(Suggests.allFactions(false)) 146 | .executes(this::ally))) 147 | .then( 148 | CommandManager.literal("neutral") 149 | .requires(Requires.hasPerms("factions.declare.neutral", 0)) 150 | .then( 151 | CommandManager.argument( 152 | "faction", 153 | StringArgumentType.greedyString()) 154 | .suggests(Suggests.allFactions(false)) 155 | .executes(this::neutral))) 156 | .then( 157 | CommandManager.literal("enemy") 158 | .requires(Requires.hasPerms("factions.declare.enemy", 0)) 159 | .then( 160 | CommandManager.argument( 161 | "faction", 162 | StringArgumentType.greedyString()) 163 | .suggests(Suggests.allFactions(false)) 164 | .executes(this::enemy))) 165 | .build(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/util/SquareMapWrapper.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.util; 2 | 3 | import com.flowpowered.math.vector.Vector2i; 4 | 5 | import io.icker.factions.api.events.ClaimEvents; 6 | import io.icker.factions.api.events.FactionEvents; 7 | import io.icker.factions.api.persistents.Claim; 8 | import io.icker.factions.api.persistents.Faction; 9 | import io.icker.factions.api.persistents.Home; 10 | 11 | import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 12 | 13 | import xyz.jpenilla.squaremap.api.Key; 14 | import xyz.jpenilla.squaremap.api.MapWorld; 15 | import xyz.jpenilla.squaremap.api.Point; 16 | import xyz.jpenilla.squaremap.api.SimpleLayerProvider; 17 | import xyz.jpenilla.squaremap.api.Squaremap; 18 | import xyz.jpenilla.squaremap.api.SquaremapProvider; 19 | import xyz.jpenilla.squaremap.api.WorldIdentifier; 20 | import xyz.jpenilla.squaremap.api.marker.Icon; 21 | import xyz.jpenilla.squaremap.api.marker.Marker; 22 | import xyz.jpenilla.squaremap.api.marker.MarkerOptions; 23 | 24 | import java.awt.Color; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.Set; 29 | import java.util.UUID; 30 | import java.util.stream.Collectors; 31 | 32 | public class SquareMapWrapper { 33 | private HashMap layers = new HashMap<>(); 34 | private Squaremap api; 35 | 36 | public SquareMapWrapper() { 37 | ClaimEvents.ADD.register( 38 | (Claim claim) -> { 39 | generateMarkers(); 40 | }); 41 | ClaimEvents.REMOVE.register( 42 | (x, z, level, faction) -> { 43 | generateMarkers(); 44 | }); 45 | 46 | ServerLifecycleEvents.SERVER_STARTED.register( 47 | (server) -> { 48 | this.api = SquaremapProvider.get(); 49 | 50 | generateMarkers(); 51 | }); 52 | 53 | FactionEvents.SET_HOME.register(this::setHome); 54 | FactionEvents.MODIFY.register(faction -> generateMarkers()); 55 | FactionEvents.MEMBER_JOIN.register((faction, user) -> generateMarkers()); 56 | FactionEvents.MEMBER_LEAVE.register((faction, user) -> generateMarkers()); 57 | FactionEvents.POWER_CHANGE.register((faction, oldPower) -> generateMarkers()); 58 | FactionEvents.DISBAND.register((faction) -> generateMarkers()); 59 | } 60 | 61 | private void generateMarkers() { 62 | for (SimpleLayerProvider layer : layers.values()) { 63 | for (Key id : layer.registeredMarkers().keySet()) { 64 | layer.removeMarker(id); 65 | } 66 | } 67 | 68 | for (Faction faction : Faction.all()) { 69 | Home home = faction.getHome(); 70 | if (home != null) { 71 | setHome(faction, home); 72 | } 73 | 74 | String info = getInfo(faction); 75 | for (Map.Entry> entry : 76 | ClaimGrouper.separateClaimsByLevel(faction).entrySet()) { 77 | String level = entry.getKey(); 78 | for (Map group : 79 | ClaimGrouper.convertClaimsToLineSegmentGroups(entry.getValue())) { 80 | List> outlines = 81 | ClaimGrouper.convertLineSegmentsToOutlines(group); 82 | List> points = 83 | outlines.stream() 84 | .map( 85 | (hole) -> 86 | hole.stream() 87 | .map( 88 | (point) -> 89 | Point.of( 90 | point.getX(), 91 | point.getY())) 92 | .collect(Collectors.toList())) 93 | .collect(Collectors.toList()); 94 | 95 | SimpleLayerProvider layer = layers.get(level); 96 | if (layer == null) { 97 | layer = 98 | SimpleLayerProvider.builder("factions-" + level) 99 | .showControls(true) 100 | .build(); 101 | 102 | MapWorld world = 103 | api.getWorldIfEnabled(WorldIdentifier.parse(level)).orElse(null); 104 | if (world != null) { 105 | world.layerRegistry() 106 | .register(Key.of("factions-" + level.replace(':', '-')), layer); 107 | } 108 | 109 | layers.put(level, layer); 110 | } 111 | 112 | Marker marker = 113 | Marker.polygon(points.removeFirst(), points) 114 | .markerOptions( 115 | MarkerOptions.builder() 116 | .fillColor( 117 | new Color( 118 | faction.getColor() 119 | .getColorValue())) 120 | .strokeColor( 121 | new Color( 122 | faction.getColor() 123 | .getColorValue())) 124 | .hoverTooltip(faction.getName()) 125 | .clickTooltip(info)); 126 | 127 | layer.addMarker(Key.of(UUID.randomUUID().toString()), marker); 128 | } 129 | } 130 | } 131 | } 132 | 133 | private void setHome(Faction faction, Home home) { 134 | if (home == null) { 135 | for (Map.Entry entry : layers.entrySet()) { 136 | entry.getValue().removeMarker(Key.of(faction.getID().toString() + "-home")); 137 | } 138 | return; 139 | } 140 | 141 | SimpleLayerProvider layer = layers.get(home.level); 142 | 143 | if (layer == null) { 144 | layer = 145 | SimpleLayerProvider.builder("factions-" + home.level) 146 | .showControls(true) 147 | .build(); 148 | 149 | MapWorld world = api.getWorldIfEnabled(WorldIdentifier.parse(home.level)).orElse(null); 150 | if (world != null) { 151 | world.layerRegistry() 152 | .register(Key.of("factions-" + home.level.replace(':', '-')), layer); 153 | } else { 154 | } 155 | 156 | layers.put(home.level, layer); 157 | } 158 | 159 | for (Map.Entry entry : layers.entrySet()) { 160 | if (entry.getKey().equals(home.level)) { 161 | continue; 162 | } 163 | 164 | entry.getValue().removeMarker(Key.of(faction.getID().toString() + "-home")); 165 | } 166 | 167 | Marker marker = layer.registeredMarkers().get(Key.of(faction.getID().toString() + "-home")); 168 | 169 | if (marker == null) { 170 | Marker homeMarker = 171 | Marker.icon(Point.of(home.x, home.z), Key.of("squaremap-spawn_icon"), 16) 172 | .markerOptions( 173 | MarkerOptions.builder() 174 | .clickTooltip(getInfo(faction)) 175 | .hoverTooltip(faction.getName() + "'s Home")); 176 | layer.addMarker(Key.of(faction.getID().toString() + "-home"), homeMarker); 177 | } else { 178 | ((Icon) marker).point(Point.of(home.x, home.z)); 179 | } 180 | } 181 | 182 | private String getInfo(Faction faction) { 183 | return "Name: " 184 | + faction.getName() 185 | + "
" 186 | + "Description: " 187 | + faction.getDescription() 188 | + "
" 189 | + "Power: " 190 | + faction.getPower() 191 | + "
" 192 | + "Number of members: " 193 | + faction.getUsers().size(); // + "
" 194 | // + "Allies: " + Ally.getAllies(faction.getName).stream().map(ally -> 195 | // ally.target).collect(Collectors.joining(", ")); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/ui/PagedGui.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.ui; 2 | 3 | import eu.pb4.sgui.api.elements.GuiElement; 4 | import eu.pb4.sgui.api.elements.GuiElementBuilder; 5 | import eu.pb4.sgui.api.elements.GuiElementBuilderInterface; 6 | import eu.pb4.sgui.api.elements.GuiElementInterface; 7 | import eu.pb4.sgui.api.gui.SimpleGui; 8 | 9 | import io.icker.factions.util.GuiInteract; 10 | import io.icker.factions.util.Icons; 11 | 12 | import net.minecraft.item.ItemStack; 13 | import net.minecraft.item.Items; 14 | import net.minecraft.screen.ScreenHandlerType; 15 | import net.minecraft.screen.slot.Slot; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.sound.SoundEvents; 18 | import net.minecraft.text.Text; 19 | import net.minecraft.util.Formatting; 20 | 21 | import org.jetbrains.annotations.ApiStatus; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | // Shamelessly stolen from Get off My Lawn https://github.com/Patbox/get-off-my-lawn-reserved 25 | @ApiStatus.Internal 26 | public abstract class PagedGui extends SimpleGui { 27 | public static final int PAGE_SIZE = 9 * 4; 28 | protected final Runnable closeCallback; 29 | protected int page = 0; 30 | public boolean ignoreCloseCallback; 31 | 32 | public PagedGui(ServerPlayerEntity player, @Nullable Runnable closeCallback) { 33 | super(ScreenHandlerType.GENERIC_9X5, player, false); 34 | this.closeCallback = closeCallback; 35 | } 36 | 37 | public void refreshOpen() { 38 | this.updateDisplay(); 39 | this.open(); 40 | } 41 | 42 | @Override 43 | public void onClose() { 44 | if (this.closeCallback != null && !ignoreCloseCallback) { 45 | this.closeCallback.run(); 46 | } 47 | } 48 | 49 | protected void nextPage() { 50 | this.page = Math.min(this.getPageAmount() - 1, this.page + 1); 51 | this.updateDisplay(); 52 | } 53 | 54 | protected boolean canNextPage() { 55 | return this.getPageAmount() > this.page + 1; 56 | } 57 | 58 | protected void previousPage() { 59 | this.page = Math.max(0, this.page - 1); 60 | this.updateDisplay(); 61 | } 62 | 63 | protected boolean canPreviousPage() { 64 | return this.page - 1 >= 0; 65 | } 66 | 67 | protected void updateDisplay() { 68 | var offset = this.page * PAGE_SIZE; 69 | 70 | for (int i = 0; i < PAGE_SIZE; i++) { 71 | var element = this.getElement(offset + i); 72 | 73 | if (element == null) { 74 | element = DisplayElement.empty(); 75 | } 76 | 77 | if (element.element() != null) { 78 | this.setSlot(i, element.element()); 79 | } else if (element.slot() != null) { 80 | this.setSlotRedirect(i, element.slot()); 81 | } 82 | } 83 | 84 | for (int i = 0; i < 9; i++) { 85 | var navElement = this.getNavElement(i); 86 | 87 | if (navElement == null) { 88 | navElement = DisplayElement.EMPTY; 89 | } 90 | 91 | if (navElement.element != null) { 92 | this.setSlot(i + PAGE_SIZE, navElement.element); 93 | } else if (navElement.slot != null) { 94 | this.setSlotRedirect(i + PAGE_SIZE, navElement.slot); 95 | } 96 | } 97 | } 98 | 99 | protected int getPage() { 100 | return this.page; 101 | } 102 | 103 | protected abstract int getPageAmount(); 104 | 105 | protected abstract DisplayElement getElement(int id); 106 | 107 | protected DisplayElement getNavElement(int id) { 108 | return switch (id) { 109 | case 1 -> DisplayElement.previousPage(this); 110 | case 3 -> DisplayElement.nextPage(this); 111 | case 7 -> 112 | DisplayElement.of( 113 | new GuiElementBuilder(Items.STRUCTURE_VOID) 114 | .setName( 115 | Text.translatable( 116 | this.closeCallback != null 117 | ? "factions.gui.generic.back" 118 | : "factions.gui.generic.close") 119 | .formatted(Formatting.RED)) 120 | .hideDefaultTooltip() 121 | .setCallback( 122 | (x, y, z) -> { 123 | playClickSound(this.player); 124 | this.close(this.closeCallback != null); 125 | })); 126 | default -> DisplayElement.filler(); 127 | }; 128 | } 129 | 130 | public record DisplayElement(@Nullable GuiElementInterface element, @Nullable Slot slot) { 131 | private static final DisplayElement EMPTY = 132 | DisplayElement.of( 133 | new GuiElement(ItemStack.EMPTY, GuiElementInterface.EMPTY_CALLBACK)); 134 | private static final DisplayElement FILLER = 135 | DisplayElement.of( 136 | new GuiElementBuilder(Items.WHITE_STAINED_GLASS_PANE) 137 | .setName(Text.empty()) 138 | .hideTooltip()); 139 | 140 | public static DisplayElement of(GuiElementInterface element) { 141 | return new DisplayElement(element, null); 142 | } 143 | 144 | public static DisplayElement of(GuiElementBuilderInterface element) { 145 | return new DisplayElement(element.build(), null); 146 | } 147 | 148 | public static DisplayElement of(Slot slot) { 149 | return new DisplayElement(null, slot); 150 | } 151 | 152 | public static DisplayElement nextPage(PagedGui gui) { 153 | if (gui.canNextPage()) { 154 | return DisplayElement.of( 155 | new GuiElementBuilder(Items.PLAYER_HEAD) 156 | .setName( 157 | Text.translatable("factions.gui.generic.next_page") 158 | .formatted(Formatting.WHITE)) 159 | .hideDefaultTooltip() 160 | .setProfileSkinTexture(Icons.GUI_NEXT_PAGE) 161 | .setCallback( 162 | (x, y, z) -> { 163 | playClickSound(gui.player); 164 | gui.nextPage(); 165 | })); 166 | } else { 167 | return DisplayElement.of( 168 | new GuiElementBuilder(Items.PLAYER_HEAD) 169 | .setName( 170 | Text.translatable("factions.gui.generic.next_page") 171 | .formatted(Formatting.DARK_GRAY)) 172 | .hideDefaultTooltip() 173 | .setProfileSkinTexture(Icons.GUI_NEXT_PAGE_BLOCKED)); 174 | } 175 | } 176 | 177 | public static DisplayElement previousPage(PagedGui gui) { 178 | if (gui.canPreviousPage()) { 179 | return DisplayElement.of( 180 | new GuiElementBuilder(Items.PLAYER_HEAD) 181 | .setName( 182 | Text.translatable("factions.gui.generic.previous_page") 183 | .formatted(Formatting.WHITE)) 184 | .hideDefaultTooltip() 185 | .setProfileSkinTexture(Icons.GUI_PREVIOUS_PAGE) 186 | .setCallback( 187 | (x, y, z) -> { 188 | playClickSound(gui.player); 189 | gui.previousPage(); 190 | })); 191 | } else { 192 | return DisplayElement.of( 193 | new GuiElementBuilder(Items.PLAYER_HEAD) 194 | .setName( 195 | Text.translatable("factions.gui.generic.previous_page") 196 | .formatted(Formatting.DARK_GRAY)) 197 | .hideDefaultTooltip() 198 | .setProfileSkinTexture(Icons.GUI_PREVIOUS_PAGE_BLOCKED)); 199 | } 200 | } 201 | 202 | public static DisplayElement filler() { 203 | return FILLER; 204 | } 205 | 206 | public static DisplayElement empty() { 207 | return EMPTY; 208 | } 209 | } 210 | 211 | public static final void playClickSound(ServerPlayerEntity player) { 212 | GuiInteract.playSound(player, SoundEvents.UI_BUTTON_CLICK.value(), 1f, 1f); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/main/java/io/icker/factions/command/InfoCommand.java: -------------------------------------------------------------------------------- 1 | package io.icker.factions.command; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import com.mojang.brigadier.arguments.StringArgumentType; 5 | import com.mojang.brigadier.context.CommandContext; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.tree.LiteralCommandNode; 8 | 9 | import io.icker.factions.FactionsMod; 10 | import io.icker.factions.api.persistents.Faction; 11 | import io.icker.factions.api.persistents.User; 12 | import io.icker.factions.ui.InfoGui; 13 | import io.icker.factions.util.Command; 14 | import io.icker.factions.util.Message; 15 | 16 | import net.minecraft.server.GameProfileResolver; 17 | import net.minecraft.server.command.CommandManager; 18 | import net.minecraft.server.command.ServerCommandSource; 19 | import net.minecraft.server.network.ServerPlayerEntity; 20 | import net.minecraft.text.Text; 21 | import net.minecraft.util.Formatting; 22 | import net.minecraft.util.Util; 23 | 24 | import java.util.List; 25 | import java.util.stream.Collectors; 26 | 27 | public class InfoCommand implements Command { 28 | private int self(CommandContext context) throws CommandSyntaxException { 29 | ServerCommandSource source = context.getSource(); 30 | ServerPlayerEntity player = source.getPlayerOrThrow(); 31 | 32 | User user = Command.getUser(player); 33 | if (!user.isInFaction()) { 34 | new Message(Text.translatable("factions.command.info.fail.no_faction")) 35 | .fail() 36 | .send(player, false); 37 | return 0; 38 | } 39 | 40 | return info(player, user.getFaction()); 41 | } 42 | 43 | private int any(CommandContext context) throws CommandSyntaxException { 44 | String factionName = StringArgumentType.getString(context, "faction"); 45 | 46 | ServerCommandSource source = context.getSource(); 47 | ServerPlayerEntity player = source.getPlayerOrThrow(); 48 | 49 | Faction faction = Faction.getByName(factionName); 50 | if (faction == null) { 51 | new Message(Text.translatable("factions.command.info.fail.nonexistent_faction")) 52 | .fail() 53 | .send(player, false); 54 | return 0; 55 | } 56 | 57 | return info(player, faction); 58 | } 59 | 60 | public static int info(ServerPlayerEntity player, Faction faction) { 61 | if (FactionsMod.CONFIG.GUI) { 62 | new InfoGui(player, faction, null); 63 | return 1; 64 | } 65 | List users = faction.getUsers(); 66 | 67 | GameProfileResolver resolver = 68 | player.getEntityWorld().getServer().getApiServices().profileResolver(); 69 | String owner = 70 | Formatting.WHITE 71 | + users.stream() 72 | .filter(u -> u.rank == User.Rank.OWNER) 73 | .map( 74 | user -> 75 | resolver.getProfileById(user.getID()) 76 | .orElse( 77 | new GameProfile( 78 | Util.NIL_UUID, 79 | "{Uncached Player}")) 80 | .name()) 81 | .collect(Collectors.joining(", ")); 82 | 83 | String usersList = 84 | users.stream() 85 | .map( 86 | user -> 87 | resolver.getProfileById(user.getID()) 88 | .orElse( 89 | new GameProfile( 90 | Util.NIL_UUID, "{Uncached Player}")) 91 | .name()) 92 | .collect(Collectors.joining(", ")); 93 | 94 | String mutualAllies = 95 | faction.getMutualAllies().stream() 96 | .map(rel -> Faction.get(rel.target)) 97 | .map(fac -> fac.getColor() + fac.getName()) 98 | .collect(Collectors.joining(Formatting.GRAY + ", ")); 99 | 100 | String enemiesWith = 101 | Formatting.GRAY 102 | + faction.getEnemiesWith().stream() 103 | .map(rel -> Faction.get(rel.target)) 104 | .map(fac -> fac.getColor() + fac.getName()) 105 | .collect(Collectors.joining(Formatting.GRAY + ", ")); 106 | 107 | int requiredPower = faction.getClaims().size() * FactionsMod.CONFIG.POWER.CLAIM_WEIGHT; 108 | int maxPower = 109 | users.size() * FactionsMod.CONFIG.POWER.MEMBER + FactionsMod.CONFIG.POWER.BASE; 110 | 111 | // generate the --- 112 | int numDashes = 32 - faction.getName().length(); 113 | String dashes = 114 | new StringBuilder("--------------------------------").substring(0, numDashes / 2); 115 | 116 | new Message( 117 | Formatting.BLACK 118 | + dashes 119 | + "[ " 120 | + faction.getColor() 121 | + faction.getName() 122 | + Formatting.BLACK 123 | + " ]" 124 | + dashes) 125 | .send(player, false); 126 | new Message(Text.translatable("factions.gui.info.description").formatted(Formatting.GOLD)) 127 | .add(Formatting.WHITE + faction.getDescription()) 128 | .send(player, false); 129 | new Message(Text.translatable("factions.gui.info.owner").formatted(Formatting.GOLD)) 130 | .add(Formatting.WHITE + owner) 131 | .send(player, false); 132 | new Message( 133 | Text.translatable( 134 | "factions.gui.info.members", 135 | Text.literal(Integer.toString(users.size())) 136 | .formatted(Formatting.WHITE)) 137 | .formatted(Formatting.GOLD)) 138 | .add(usersList) 139 | .send(player, false); 140 | new Message(Text.translatable("factions.gui.info.power").formatted(Formatting.GOLD)) 141 | .add( 142 | Formatting.GREEN.toString() 143 | + faction.getPower() 144 | + slash() 145 | + requiredPower 146 | + slash() 147 | + maxPower) 148 | .hover(Text.translatable("factions.gui.info.power.description")) 149 | .send(player, false); 150 | new Message( 151 | Text.translatable( 152 | "factions.gui.info.allies.some", 153 | Text.literal( 154 | Integer.toString( 155 | faction.getMutualAllies().size())) 156 | .formatted(Formatting.WHITE)) 157 | .formatted(Formatting.GREEN)) 158 | .add(mutualAllies) 159 | .send(player, false); 160 | new Message( 161 | Text.translatable( 162 | "factions.gui.info.enemies.some", 163 | Text.literal( 164 | Integer.toString( 165 | faction.getEnemiesWith().size())) 166 | .formatted(Formatting.WHITE)) 167 | .formatted(Formatting.RED)) 168 | .add(enemiesWith) 169 | .send(player, false); 170 | 171 | return 1; 172 | } 173 | 174 | private static String slash() { 175 | return Formatting.GRAY + " / " + Formatting.GREEN; 176 | } 177 | 178 | public LiteralCommandNode getNode() { 179 | return CommandManager.literal("info") 180 | .requires(Requires.hasPerms("factions.info", 0)) 181 | .executes(this::self) 182 | .then( 183 | CommandManager.argument("faction", StringArgumentType.greedyString()) 184 | .requires(Requires.hasPerms("factions.info.other", 0)) 185 | .suggests(Suggests.allFactions()) 186 | .executes(this::any)) 187 | .build(); 188 | } 189 | } 190 | --------------------------------------------------------------------------------