Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ dependencies {
listOf(
"fabric-api-base",
"fabric-api-lookup-api-v1",
"fabric-command-api-v2",
"fabric-data-attachment-api-v1",
"fabric-gametest-api-v1",
"fabric-item-api-v1",
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/dev/galacticraft/machinelib/api/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ static Config loadFrom(File file) {
*/
void setFluidUnits(FluidUnits units);

/**
* {@return the id of the team system used by MachineLib security}
*/
String teamSystem();

/**
* Sets the team system used by MachineLib security.
*
* @param teamSystem the registered team system id
*/
void setTeamSystem(String teamSystem);

/**
* Copies the state of the provided config into this config.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

package dev.galacticraft.machinelib.api.machine.configuration;

import dev.galacticraft.machinelib.api.machine.security.TeamSystems;
import dev.galacticraft.machinelib.api.misc.DeltaPacketSerializable;
import dev.galacticraft.machinelib.api.misc.PacketSerializable;
import dev.galacticraft.machinelib.api.misc.Serializable;
Expand Down Expand Up @@ -134,7 +135,9 @@ public boolean hasAccess(
return switch (accessLevel) {
case PUBLIC -> true;

case TEAM -> this.isOwner(player); // todo: teams
case TEAM -> this.isOwner(player)
|| this.owner != null
&& TeamSystems.areTeammates(this.owner, player);

case PRIVATE -> this.isOwner(player);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2021-2026 Team Galacticraft
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.galacticraft.machinelib.api.machine.security;

import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.NotNull;

import java.util.UUID;

/**
* Provides team membership information for MachineLib security checks.
*
* <p>Implementations are registered through {@link TeamSystems}. MachineLib
* selects one registered implementation using its {@code teamSystem} config
* option.</p>
*/
@FunctionalInterface
public interface TeamSystem {
/**
* Determines whether the supplied player is considered a teammate of the
* machine owner.
*
* <p>The owner check itself is performed by MachineLib before this method is
* called. Implementations therefore only need to determine team membership.</p>
*
* @param owner the UUID of the machine owner
* @param player the player attempting to access the machine
* @return whether the player is a teammate of the owner
*/
boolean areTeammates(@NotNull UUID owner, @NotNull Player player);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2021-2026 Team Galacticraft
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.galacticraft.machinelib.api.machine.security;

import dev.galacticraft.machinelib.impl.MachineLib;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;

/**
* Registry for team systems exposed to MachineLib.
*
* <p>A mod may register its team implementation during initialization and a
* modpack may then select it using MachineLib's {@code teamSystem} config
* option.</p>
*/
public final class TeamSystems {
/**
* The identifier of MachineLib's built-in vanilla team implementation.
*/
public static final String MINECRAFT = "minecraft";

private static final Pattern VALID_ID = Pattern.compile("[a-z0-9_.-]+");
private static final Map<String, TeamSystem> SYSTEMS = new LinkedHashMap<>();

private TeamSystems() {}

/**
* Registers a team system.
*
* <p>It is recommended that mods use their mod id as the team system id.</p>
*
* @param id the unique team system id
* @param system the team system implementation
* @throws IllegalArgumentException if the id is invalid or already registered
*/
public static void register(@NotNull String id, @NotNull TeamSystem system) {
if (!VALID_ID.matcher(id).matches()) {
throw new IllegalArgumentException("Invalid team system id: " + id);
}

if (SYSTEMS.putIfAbsent(id, system) != null) {
throw new IllegalArgumentException("Team system already registered: " + id);
}
}

/**
* {@return whether a team system with the supplied id is registered}
*
* @param id the team system id
*/
@Contract(pure = true)
public static boolean isRegistered(@NotNull String id) {
return SYSTEMS.containsKey(id);
}

/**
* Returns a registered team system.
*
* @param id the team system id
* @return the registered implementation, or {@code null} if none exists
*/
@Contract(pure = true)
public static @Nullable TeamSystem get(@NotNull String id) {
return SYSTEMS.get(id);
}

/**
* {@return an immutable ordered set containing all registered team system ids}
*/
@Contract(pure = true)
public static @NotNull Set<String> getRegisteredIds() {
return Collections.unmodifiableSet(SYSTEMS.keySet());
}

/**
* {@return an immutable ordered view of all registered team systems}
*/
@Contract(pure = true)
public static @NotNull Map<String, TeamSystem> getRegistered() {
return Collections.unmodifiableMap(SYSTEMS);
}

/**
* Determines whether the supplied player is a teammate of the owner using
* the currently configured team system.
*
* <p>If the configured implementation is unavailable, the built-in
* {@value #MINECRAFT} implementation is used. If that implementation is also
* unavailable, access is denied.</p>
*
* @param owner the machine owner
* @param player the player attempting access
* @return whether the configured team system considers them teammates
*/
public static boolean areTeammates(
@NotNull UUID owner,
@NotNull Player player
) {
TeamSystem system = getConfigured();

return system != null && system.areTeammates(owner, player);
}

/**
* Returns the currently configured team system, falling back to vanilla.
*
* @return the active team system, or {@code null} if no usable implementation
* is registered
*/
public static @Nullable TeamSystem getConfigured() {
String configured = MachineLib.CONFIG.teamSystem();
TeamSystem system = SYSTEMS.get(configured);

if (system != null) {
return system;
}

return SYSTEMS.get(MINECRAFT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@
package dev.galacticraft.machinelib.client.impl.compat;

import dev.galacticraft.machinelib.api.config.Config;
import dev.galacticraft.machinelib.api.machine.security.TeamSystems;
import dev.galacticraft.machinelib.impl.MachineLib;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import me.shedaniel.clothconfig2.impl.builders.DropdownMenuBuilder;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;

import java.util.ArrayList;

public class ClothConfigScreen {
public static Screen factory(Screen screen) {
final ConfigBuilder builder = ConfigBuilder.create();
Expand All @@ -50,6 +54,16 @@ public static Screen factory(Screen screen) {
.setEnumNameProvider(v -> ((Config.FluidUnits) v).getName())
.build()
);
String configuredTeamSystem = MachineLib.CONFIG.teamSystem();
String displayedTeamSystem = TeamSystems.isRegistered(configuredTeamSystem) ? configuredTeamSystem : TeamSystems.MINECRAFT;
general.addEntry(entryBuilder.startDropdownMenu(Component.translatable("ui.machinelib.config.team_system"), DropdownMenuBuilder.TopCellElementBuilder.of(displayedTeamSystem, value -> value), DropdownMenuBuilder.CellCreatorBuilder.of(value -> Component.literal(value)))
.setSelections(new ArrayList<>(TeamSystems.getRegisteredIds()))
.setDefaultValue(Config.DEFAULT.teamSystem())
.setSuggestionMode(false)
.setSaveConsumer(MachineLib.CONFIG::setTeamSystem)
.setTooltip(Component.translatable("ui.machinelib.config.team_system.tooltip"))
.build()
);

// ConfigCategory debug = builder.getOrCreateCategory(Component.translatable("ui.machinelib.config.category.debug"));
return builder.build();
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/dev/galacticraft/machinelib/impl/MachineLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

import dev.galacticraft.machinelib.api.component.MLDataComponents;
import dev.galacticraft.machinelib.api.config.Config;
import dev.galacticraft.machinelib.api.machine.security.TeamSystems;
import dev.galacticraft.machinelib.impl.command.MachineLibCommands;
import dev.galacticraft.machinelib.impl.machine.security.VanillaTeamSystem;
import dev.galacticraft.machinelib.impl.network.MachineLibPackets;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributes;
Expand All @@ -39,6 +42,13 @@ public final class MachineLib implements ModInitializer {

@Override
public void onInitialize() {
TeamSystems.register(
TeamSystems.MINECRAFT,
VanillaTeamSystem.INSTANCE
);

MachineLibCommands.register();

MachineLibPackets.registerChannels();
MachineLibPackets.registerServer();
MLDataComponents.init();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2021-2026 Team Galacticraft
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.galacticraft.machinelib.impl.command;

import dev.galacticraft.machinelib.api.machine.security.TeamSystems;
import dev.galacticraft.machinelib.impl.MachineLib;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public final class MachineLibCommands {
private MachineLibCommands() {}

public static void register() {
CommandRegistrationCallback.EVENT.register(
(dispatcher, registryAccess, environment) -> dispatcher.register(
Commands.literal("machinelib").then(
Commands.literal("exposedTeamSystems").executes(context -> {
String configured = MachineLib.CONFIG.teamSystem();
boolean configuredRegistered = TeamSystems.isRegistered(configured);

context.getSource().sendSuccess(
() -> Component.literal("Registered MachineLib team systems:"),
false
);

for (String id : TeamSystems.getRegisteredIds()) {
boolean active = id.equals(configured)
|| !configuredRegistered && id.equals(TeamSystems.MINECRAFT);

Component message = Component.literal(" - " + id);

if (active) {
message = message.copy().append(
Component.literal(" [ACTIVE]")
.withStyle(ChatFormatting.GREEN)
);
}

Component finalMessage = message;

context.getSource().sendSuccess(() -> finalMessage, false);
}

if (!configuredRegistered) {
context.getSource().sendSuccess(
() -> Component.literal(
String.format(
"Configured team system '%s' is not registered; using '%s'.",
configured,
TeamSystems.MINECRAFT
)
).withStyle(ChatFormatting.YELLOW),
false
);
}

return TeamSystems.getRegisteredIds().size();
})
)
)
);
}
}
Loading
Loading