From 6238d332a6e3e18b476f0dc93ac9a9dd39b522ad Mon Sep 17 00:00:00 2001 From: Thom van den Akker Date: Sun, 3 Nov 2024 16:50:42 +0100 Subject: [PATCH] Fix colony teams assignment behaviour (#10239) Consistently ensure citizens are registered to the correct team Use the colony team name instead of a per citizen team Ensure that upon deletion of a colony it's respective team is removed Fix the colony deletion event not triggering for command based deletion --- .../entity/citizen/AbstractEntityCitizen.java | 14 ++++ .../entity/mobs/AbstractEntityRaiderMob.java | 47 +++--------- .../other/AbstractFastMinecoloniesEntity.java | 71 +++++++++++++++++++ .../minecolonies/api/events/ColonyEvents.java | 38 ++++++++++ .../com/minecolonies/core/colony/Colony.java | 28 +++----- .../core/colony/ColonyManager.java | 2 + .../core/colony/managers/CitizenManager.java | 17 +---- .../core/entity/citizen/EntityCitizen.java | 38 +--------- .../core/entity/mobs/EntityMercenary.java | 47 ++++++------ .../server/colony/ColonyDeleteOwnMessage.java | 22 ++---- .../com/minecolonies/core/util/TeamUtils.java | 48 +++++++++++++ 11 files changed, 228 insertions(+), 144 deletions(-) create mode 100644 src/main/java/com/minecolonies/api/events/ColonyEvents.java create mode 100644 src/main/java/com/minecolonies/core/util/TeamUtils.java diff --git a/src/main/java/com/minecolonies/api/entity/citizen/AbstractEntityCitizen.java b/src/main/java/com/minecolonies/api/entity/citizen/AbstractEntityCitizen.java index 5f9b3a51179..6d631267eb1 100755 --- a/src/main/java/com/minecolonies/api/entity/citizen/AbstractEntityCitizen.java +++ b/src/main/java/com/minecolonies/api/entity/citizen/AbstractEntityCitizen.java @@ -46,8 +46,10 @@ import net.minecraft.world.item.ShieldItem; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.PlayerTeam; import net.minecraftforge.items.IItemHandler; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -233,6 +235,18 @@ public boolean isNoAi() return false; } + @Override + @Nullable + protected PlayerTeam getAssignedTeam() + { + final ICitizenColonyHandler citizenColonyHandler = getCitizenColonyHandler(); + if (citizenColonyHandler == null || citizenColonyHandler.getColony() == null) + { + return null; + } + return citizenColonyHandler.getColony().getTeam(); + } + /** * Sets the textures of all citizens and distinguishes between male and female. */ diff --git a/src/main/java/com/minecolonies/api/entity/mobs/AbstractEntityRaiderMob.java b/src/main/java/com/minecolonies/api/entity/mobs/AbstractEntityRaiderMob.java index 1512667c435..6cce739ffdc 100644 --- a/src/main/java/com/minecolonies/api/entity/mobs/AbstractEntityRaiderMob.java +++ b/src/main/java/com/minecolonies/api/entity/mobs/AbstractEntityRaiderMob.java @@ -52,17 +52,13 @@ import static com.minecolonies.api.util.constant.ColonyManagerConstants.NO_COLONY_ID; import static com.minecolonies.api.util.constant.NbtTagConstants.*; import static com.minecolonies.api.util.constant.RaiderConstants.*; +import static com.minecolonies.core.util.TeamUtils.checkOrCreateTeam; /** * Abstract for all raider entities. */ public abstract class AbstractEntityRaiderMob extends AbstractFastMinecoloniesEntity implements IThreatTableEntity, Enemy { - /** - * Difficulty at which raiders team up - */ - private static final double TEAM_DIFFICULTY = 2.0d; - /** * The percent of life taken per damage modifier */ @@ -521,23 +517,23 @@ private void onEnterChunk(final ChunkPos newChunkPos) } } - @org.jetbrains.annotations.Nullable + @Nullable @Override public SpawnGroupData finalizeSpawn( final ServerLevelAccessor worldIn, final DifficultyInstance difficultyIn, final MobSpawnType reason, - @org.jetbrains.annotations.Nullable final SpawnGroupData spawnDataIn, - @org.jetbrains.annotations.Nullable final CompoundTag dataTag) + @Nullable final SpawnGroupData spawnDataIn, + @Nullable final CompoundTag dataTag) { RaiderMobUtils.setEquipment(this); return super.finalizeSpawn(worldIn, difficultyIn, reason, spawnDataIn, dataTag); } @Override - public void remove(RemovalReason reason) + public void remove(@NotNull final RemovalReason reason) { - if (!level().isClientSide && colony != null && eventID > 0) + if (!level.isClientSide && colony != null && eventID > 0) { colony.getEventManager().unregisterEntity(this, eventID); } @@ -732,38 +728,15 @@ public void initStatsFor(final double baseHealth, final double difficulty, final this.setEnvDamageImmunity(true); } - if (difficulty >= TEAM_DIFFICULTY) - { - level().getScoreboard().addPlayerToTeam(getScoreboardName(), checkOrCreateTeam()); - } - this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(baseHealth); this.setHealth(this.getMaxHealth()); } - /** - * Creates or gets the scoreboard team - * - * @return Scoreboard team - */ - private PlayerTeam checkOrCreateTeam() - { - if (this.level().getScoreboard().getPlayerTeam(getTeamName()) == null) - { - this.level().getScoreboard().addPlayerTeam(getTeamName()); - this.level().getScoreboard().getPlayerTeam(getTeamName()).setAllowFriendlyFire(false); - } - return this.level().getScoreboard().getPlayerTeam(getTeamName()); - } - - /** - * Gets the scoreboard team name - * - * @return - */ - protected String getTeamName() + @Override + @Nullable + protected PlayerTeam getAssignedTeam() { - return RAID_TEAM; + return checkOrCreateTeam(level, RAID_TEAM); } /** diff --git a/src/main/java/com/minecolonies/api/entity/other/AbstractFastMinecoloniesEntity.java b/src/main/java/com/minecolonies/api/entity/other/AbstractFastMinecoloniesEntity.java index 2dfd4cd7d32..1b871a315f7 100644 --- a/src/main/java/com/minecolonies/api/entity/other/AbstractFastMinecoloniesEntity.java +++ b/src/main/java/com/minecolonies/api/entity/other/AbstractFastMinecoloniesEntity.java @@ -14,12 +14,15 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Team; import net.minecraftforge.common.util.ITeleporter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Special abstract minecolonies mob that overrides laggy vanilla behaviour. @@ -316,6 +319,74 @@ public void updateSwimAmount() } + /** + * Get the team this entity is assigned to. + * + * @return the team instance. + */ + @Nullable + protected abstract PlayerTeam getAssignedTeam(); + + @Override + @Nullable + public final Team getTeam() + { + final PlayerTeam assignedTeam = getAssignedTeam(); + registerToTeamInternal(assignedTeam); + return assignedTeam; + } + + /** + * Register this entity to its own assigned team. + */ + public void registerToTeam() + { + registerToTeamInternal(getAssignedTeam()); + } + + /** + * Internal method for team registration. + * + * @param team the team to register to. + */ + private void registerToTeamInternal(@Nullable final PlayerTeam team) + { + if (team != null && !isInTeam(team)) + { + level.getScoreboard().addPlayerToTeam(getScoreboardName(), team); + } + } + + /** + * Remove the entity from its own assigned team. + */ + public void removeFromTeam() + { + final PlayerTeam team = getAssignedTeam(); + if (team != null && isInTeam(team)) + { + level.getScoreboard().removePlayerFromTeam(getScoreboardName(), team); + } + } + + /** + * Check if the current entity is assigned to the provided team. + * + * @param team the input team. + * @return true if so. + */ + private boolean isInTeam(@NotNull final PlayerTeam team) + { + return Objects.equals(level.getScoreboard().getPlayersTeam(getScoreboardName()), team); + } + + @Override + public void remove(@NotNull final RemovalReason reason) + { + super.remove(reason); + removeFromTeam(); + } + /** * Static Byte values to avoid frequent autoboxing */ diff --git a/src/main/java/com/minecolonies/api/events/ColonyEvents.java b/src/main/java/com/minecolonies/api/events/ColonyEvents.java new file mode 100644 index 00000000000..aef3f6d7b4c --- /dev/null +++ b/src/main/java/com/minecolonies/api/events/ColonyEvents.java @@ -0,0 +1,38 @@ +package com.minecolonies.api.events; + +import com.minecolonies.api.colony.IColony; +import com.minecolonies.api.colony.event.ColonyDeletedEvent; +import com.minecolonies.api.util.Log; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.Event; + +/** + * Event manager for all forge events. + */ +public class ColonyEvents +{ + /** + * Event triggered when a colony is being deleted. + * + * @param colony the colony in question. + */ + public static void deleteColony(final IColony colony) + { + sendEventSafe(new ColonyDeletedEvent(colony)); + } + + /** + * Underlying logic for transmitting an event. + */ + private static void sendEventSafe(final Event event) + { + try + { + MinecraftForge.EVENT_BUS.post(event); + } + catch (final Exception e) + { + Log.getLogger().atError().withThrowable(e).log("Exception occurred during {} event", event.getClass().getName()); + } + } +} diff --git a/src/main/java/com/minecolonies/core/colony/Colony.java b/src/main/java/com/minecolonies/core/colony/Colony.java index 981d6c2ce5f..885c6ee1ba1 100644 --- a/src/main/java/com/minecolonies/core/colony/Colony.java +++ b/src/main/java/com/minecolonies/core/colony/Colony.java @@ -80,6 +80,7 @@ import static com.minecolonies.api.util.constant.NbtTagConstants.*; import static com.minecolonies.api.util.constant.TranslationConstants.*; import static com.minecolonies.core.MineColonies.getConfig; +import static com.minecolonies.core.util.TeamUtils.checkOrCreateTeam; /** * This class describes a colony and contains all the data and methods for manipulating a Colony. @@ -362,7 +363,7 @@ protected Colony(final int id, @Nullable final Level world) { this.dimensionId = world.dimension(); onWorldLoad(world); - checkOrCreateTeam(); + checkOrCreateTeam(world, getTeamName(), false); } this.permissions = new Permissions(this); researchManager = new ResearchManager(this); @@ -627,23 +628,11 @@ public void updateAttackingPlayers() } @Override + @Nullable public PlayerTeam getTeam() { // This getter will create the team if it doesn't exist. Could do something different though in the future. - return checkOrCreateTeam(); - } - - /** - * Check or create the team. - */ - private PlayerTeam checkOrCreateTeam() - { - if (this.world.getScoreboard().getPlayerTeam(getTeamName()) == null) - { - this.world.getScoreboard().addPlayerTeam(getTeamName()); - this.world.getScoreboard().getPlayerTeam(getTeamName()).setAllowFriendlyFire(false); - } - return this.world.getScoreboard().getPlayerTeam(getTeamName()); + return checkOrCreateTeam(world, getTeamName(), false); } /** @@ -655,10 +644,13 @@ public void setColonyColor(final ChatFormatting colonyColor) { if (this.world != null) { - checkOrCreateTeam(); this.colonyTeamColor = colonyColor; - this.world.getScoreboard().getPlayerTeam(getTeamName()).setColor(colonyColor); - this.world.getScoreboard().getPlayerTeam(getTeamName()).setPlayerPrefix(Component.literal(colonyColor.toString())); + final PlayerTeam team = getTeam(); + if (team != null) + { + team.setColor(colonyColor); + team.setPlayerPrefix(Component.literal(colonyColor.toString())); + } } this.markDirty(); } diff --git a/src/main/java/com/minecolonies/core/colony/ColonyManager.java b/src/main/java/com/minecolonies/core/colony/ColonyManager.java index 073006e0c75..b11c9cbed41 100755 --- a/src/main/java/com/minecolonies/core/colony/ColonyManager.java +++ b/src/main/java/com/minecolonies/core/colony/ColonyManager.java @@ -11,6 +11,7 @@ import com.minecolonies.api.compatibility.CompatibilityManager; import com.minecolonies.api.compatibility.ICompatibilityManager; import com.minecolonies.api.crafting.IRecipeManager; +import com.minecolonies.api.events.ColonyEvents; import com.minecolonies.api.sounds.SoundManager; import com.minecolonies.api.util.BlockPosUtil; import com.minecolonies.api.util.ColonyUtils; @@ -201,6 +202,7 @@ private void deleteColony(@Nullable final IColony iColony, final boolean canDest return; } + ColonyEvents.deleteColony(colony); cap.deleteColony(id); BackUpHelper.markColonyDeleted(colony.getID(), colony.getDimension()); colony.getImportantMessageEntityPlayers() diff --git a/src/main/java/com/minecolonies/core/colony/managers/CitizenManager.java b/src/main/java/com/minecolonies/core/colony/managers/CitizenManager.java index db640e6eb2a..d3696441380 100755 --- a/src/main/java/com/minecolonies/core/colony/managers/CitizenManager.java +++ b/src/main/java/com/minecolonies/core/colony/managers/CitizenManager.java @@ -139,10 +139,10 @@ public void registerCivilian(final AbstractCivilianEntity entity) final Optional existingCitizen = data.getEntity(); - if (!existingCitizen.isPresent()) + if (existingCitizen.isEmpty()) { data.setEntity(entity); - entity.level.getScoreboard().addPlayerToTeam(entity.getScoreboardName(), colony.getTeam()); + entity.registerToTeam(); return; } @@ -159,18 +159,7 @@ public void unregisterCivilian(final AbstractCivilianEntity entity) final ICitizenData data = citizens.get(entity.getCivilianID()); if (data != null && data.getEntity().isPresent() && data.getEntity().get() == entity) { - try - { - if (colony.getWorld().getScoreboard().getPlayersTeam(entity.getScoreboardName()) == colony.getTeam()) - { - colony.getWorld().getScoreboard().removePlayerFromTeam(entity.getScoreboardName(), colony.getTeam()); - } - } - catch (Exception ignored) - { - // For some weird reason we can get an exception here, though the exception is thrown for team != colony team which we check == on before - } - + entity.removeFromTeam(); citizens.get(entity.getCivilianID()).setEntity(null); } } diff --git a/src/main/java/com/minecolonies/core/entity/citizen/EntityCitizen.java b/src/main/java/com/minecolonies/core/entity/citizen/EntityCitizen.java index 06ae4bec90e..f366e6daaa2 100755 --- a/src/main/java/com/minecolonies/core/entity/citizen/EntityCitizen.java +++ b/src/main/java/com/minecolonies/core/entity/citizen/EntityCitizen.java @@ -99,7 +99,6 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraft.world.scores.Team; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ForgeCapabilities; @@ -227,11 +226,6 @@ public class EntityCitizen extends AbstractEntityCitizen implements IThreatTable */ private ILocation location = null; - /** - * Cached team name the entity belongs to. - */ - private String cachedTeamName; - /** * The current chunkpos. */ @@ -243,11 +237,6 @@ public class EntityCitizen extends AbstractEntityCitizen implements IThreatTable private final ThreatTable threatTable = new ThreatTable<>(this); private int interactionCooldown = 0; - /** - * Cache the entire team object. - */ - private Team cachedTeam; - /** * The citizen AI */ @@ -337,7 +326,6 @@ private EntityState initialize() final IColonyView colonyView = IColonyManager.getInstance().getColonyView(citizenColonyHandler.getColonyId(), level.dimension()); if (colonyView != null) { - this.cachedTeamName = colonyView.getTeamName(); this.citizenDataView = colonyView.getCitizen(citizenId); if (citizenDataView != null) { @@ -633,6 +621,7 @@ private void eatFoodInteraction(final ItemStack usedStack, final Player player, } @Override + @NotNull public String getScoreboardName() { return getName().getString() + " (" + getCivilianID() + ")"; @@ -1810,31 +1799,6 @@ public void setRemoved(final RemovalReason reason) super.setRemoved(reason); } - @Override - public Team getTeam() - { - if (level == null || (level.isClientSide && cachedTeamName == null)) - { - return null; - } - - if (cachedTeam != null) - { - return cachedTeam; - } - - if (level.isClientSide) - { - cachedTeam = level.getScoreboard().getPlayerTeam(this.cachedTeamName); - } - else - { - cachedTeam = level.getScoreboard().getPlayerTeam(getScoreboardName()); - } - - return cachedTeam; - } - @Override public void setCustomName(@Nullable final Component name) { diff --git a/src/main/java/com/minecolonies/core/entity/mobs/EntityMercenary.java b/src/main/java/com/minecolonies/core/entity/mobs/EntityMercenary.java index 36ec2876868..2e47c72d2a9 100755 --- a/src/main/java/com/minecolonies/core/entity/mobs/EntityMercenary.java +++ b/src/main/java/com/minecolonies/core/entity/mobs/EntityMercenary.java @@ -10,7 +10,6 @@ import com.minecolonies.api.entity.ai.statemachine.tickratestatemachine.TickRateStateMachine; import com.minecolonies.api.entity.ai.statemachine.tickratestatemachine.TickingTransition; import com.minecolonies.api.entity.other.AbstractFastMinecoloniesEntity; -import com.minecolonies.core.entity.pathfinding.navigation.AbstractAdvancedPathNavigate; import com.minecolonies.api.sounds.MercenarySounds; import com.minecolonies.api.util.DamageSourceKeys; import com.minecolonies.api.util.ItemStackUtils; @@ -18,28 +17,37 @@ import com.minecolonies.api.util.MessageUtils; import com.minecolonies.core.entity.ai.minimal.EntityAIInteractToggleAble; import com.minecolonies.core.entity.citizen.EntityCitizen; -import com.minecolonies.core.entity.pathfinding.proxy.GeneralEntityWalkToProxy; +import com.minecolonies.core.entity.pathfinding.navigation.AbstractAdvancedPathNavigate; import com.minecolonies.core.entity.pathfinding.navigation.MinecoloniesAdvancedPathNavigate; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.item.enchantment.Enchantments; +import com.minecolonies.core.entity.pathfinding.proxy.GeneralEntityWalkToProxy; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.util.Tuple; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; +import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; +import net.minecraft.world.entity.animal.horse.Llama; import net.minecraft.world.entity.monster.Enemy; import net.minecraft.world.entity.monster.Monster; -import net.minecraft.world.entity.animal.horse.Llama; +import net.minecraft.world.entity.npc.Npc; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.util.*; -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.Component; -import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.scores.PlayerTeam; import net.minecraftforge.items.IItemHandler; import org.jetbrains.annotations.NotNull; @@ -56,14 +64,6 @@ import static com.minecolonies.api.util.constant.TranslationConstants.MESSAGE_INFO_COLONY_MERCENARY_STEAL_CITIZEN; import static com.minecolonies.core.entity.ai.minimal.EntityAIInteractToggleAble.*; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.npc.Npc; - /** * Class for Mercenary entities, which can be spawned to protect the colony @@ -563,4 +563,11 @@ private static boolean isValidSpawnForMercenaries(final LevelAccessor world, fin } return true; } + + @Override + @Nullable + protected PlayerTeam getAssignedTeam() + { + return colony.getTeam(); + } } diff --git a/src/main/java/com/minecolonies/core/network/messages/server/colony/ColonyDeleteOwnMessage.java b/src/main/java/com/minecolonies/core/network/messages/server/colony/ColonyDeleteOwnMessage.java index 5adf8b4d2de..9e32176ff75 100755 --- a/src/main/java/com/minecolonies/core/network/messages/server/colony/ColonyDeleteOwnMessage.java +++ b/src/main/java/com/minecolonies/core/network/messages/server/colony/ColonyDeleteOwnMessage.java @@ -2,18 +2,16 @@ import com.minecolonies.api.colony.IColony; import com.minecolonies.api.colony.IColonyManager; -import com.minecolonies.api.colony.event.ColonyDeletedEvent; import com.minecolonies.api.network.IMessage; -import com.minecolonies.api.util.Log; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.common.MinecraftForge; -import net.minecraft.network.FriendlyByteBuf; import com.minecolonies.api.util.MessageUtils; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.network.NetworkEvent; import org.jetbrains.annotations.Nullable; -import static com.minecolonies.api.util.constant.TranslationConstants.*; +import static com.minecolonies.api.util.constant.TranslationConstants.MESSAGE_INFO_COLONY_DESTROY_SUCCESS; +import static com.minecolonies.api.util.constant.TranslationConstants.MESSAGE_INFO_COLONY_NOT_FOUND; /** * Message for deleting an owned colony @@ -53,18 +51,6 @@ public void onExecute(final NetworkEvent.Context ctxIn, final boolean isLogicalS { IColonyManager.getInstance().deleteColonyByDimension(colony.getID(), false, colony.getDimension()); MessageUtils.format(MESSAGE_INFO_COLONY_DESTROY_SUCCESS).sendTo(player); - - if (isLogicalServer) - { - try - { - MinecraftForge.EVENT_BUS.post(new ColonyDeletedEvent(colony)); - } - catch (final Exception e) - { - Log.getLogger().error("Error during ColonyDeletedEvent", e); - } - } } else { diff --git a/src/main/java/com/minecolonies/core/util/TeamUtils.java b/src/main/java/com/minecolonies/core/util/TeamUtils.java new file mode 100644 index 00000000000..88c7a9b3509 --- /dev/null +++ b/src/main/java/com/minecolonies/core/util/TeamUtils.java @@ -0,0 +1,48 @@ +package com.minecolonies.core.util; + +import net.minecraft.world.level.Level; +import net.minecraft.world.scores.PlayerTeam; +import org.jetbrains.annotations.Nullable; + +/** + * Utility class for working with {@link net.minecraft.world.scores.Team instances}. + */ +public class TeamUtils +{ + + /** + * Check or create a team. + * + * @param level the level to create the team in. + * @param name the team name. + */ + @Nullable + public static PlayerTeam checkOrCreateTeam(@Nullable final Level level, final String name) + { + return checkOrCreateTeam(level, name, true); + } + + /** + * Check or create a team. + * + * @param level the level to create the team in. + * @param name the team name. + * @param allowFriendlyFire whether this team allows friendly fire or not. + */ + @Nullable + public static PlayerTeam checkOrCreateTeam(@Nullable final Level level, final String name, boolean allowFriendlyFire) + { + if (level == null) + { + return null; + } + + PlayerTeam team = level.getScoreboard().getPlayerTeam(name); + if (team == null) + { + team = level.getScoreboard().addPlayerTeam(name); + } + team.setAllowFriendlyFire(allowFriendlyFire); + return team; + } +}