Skip to content

Commit 490dba4

Browse files
committed
lifetime filter supports setting multiple filters with or-relationship
1 parent 4590d4a commit 490dba4

7 files changed

Lines changed: 165 additions & 32 deletions

File tree

src/main/java/carpettisaddition/commands/lifetime/LifeTimeCommand.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,19 @@
2828
import carpettisaddition.commands.lifetime.utils.LifeTimeTrackerUtil;
2929
import carpettisaddition.commands.lifetime.utils.SpecificDetailMode;
3030
import carpettisaddition.utils.CarpetModUtil;
31+
import carpettisaddition.utils.CommandUtils;
32+
import com.google.common.collect.Lists;
3133
import com.mojang.brigadier.builder.ArgumentBuilder;
3234
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
3335
import com.mojang.brigadier.context.CommandContext;
34-
import net.minecraft.commands.arguments.selector.EntitySelector;
36+
import net.minecraft.commands.CommandSourceStack;
3537
import net.minecraft.commands.arguments.EntityArgument;
38+
import net.minecraft.commands.arguments.selector.EntitySelector;
3639
import net.minecraft.world.entity.EntityType;
37-
import net.minecraft.commands.CommandSourceStack;
3840
import org.jetbrains.annotations.Nullable;
3941

42+
import java.util.List;
43+
import java.util.Objects;
4044
import java.util.Optional;
4145
import java.util.function.BiFunction;
4246
import java.util.function.Consumer;
@@ -89,10 +93,10 @@ private int checkEntityTypeThen(CommandSourceStack source, @Nullable String enti
8993
return 1;
9094
}
9195

92-
private int setEntityFilter(CommandSourceStack source, @Nullable String entityTypeName, EntitySelector selector)
96+
private int setEntityFilter(CommandSourceStack source, @Nullable String entityTypeName, List<EntitySelector> selectors)
9397
{
9498
return checkEntityTypeThen(source, entityTypeName, entityType ->
95-
EntityFilterManager.getInstance().setEntityFilter(source, entityType, selector)
99+
EntityFilterManager.getInstance().setEntityFilter(source, entityType, selectors)
96100
);
97101
}
98102

@@ -109,14 +113,38 @@ private int printEntityFilter(CommandSourceStack source, @Nullable String entity
109113
* ------------------
110114
*/
111115

116+
private static final int MAX_FILTER_NUM = 16;
117+
118+
private static String makeFilterNodeName(int idx)
119+
{
120+
return idx > 1 ? ("filter" + idx) : "filter";
121+
}
122+
112123
private ArgumentBuilder<CommandSourceStack, ?> createFilterNode(ArgumentBuilder<CommandSourceStack, ?> node, Function<CommandContext<CommandSourceStack>, @Nullable String> entityTypeNameSupplier)
113124
{
125+
ArgumentBuilder<CommandSourceStack, ?> prevFilter = null;
126+
for (int i = MAX_FILTER_NUM; i >= 1; i--) // 16 filters should be enough
127+
{
128+
ArgumentBuilder<CommandSourceStack, ?> currentFilter = argument(makeFilterNodeName(i), EntityArgument.entities()).
129+
executes(c -> {
130+
List<EntitySelector> selectors = Lists.newArrayList();
131+
for (int j = 1; j <= MAX_FILTER_NUM; j++)
132+
{
133+
final int finalJ = j;
134+
CommandUtils.getOptArg(() -> c.getArgument(makeFilterNodeName(finalJ), EntitySelector.class)).ifPresent(selectors::add);
135+
}
136+
return setEntityFilter(c.getSource(), entityTypeNameSupplier.apply(c), selectors);
137+
});
138+
if (prevFilter != null)
139+
{
140+
currentFilter.then(prevFilter);
141+
}
142+
prevFilter = currentFilter;
143+
}
144+
114145
return node.
115146
executes(c -> printEntityFilter(c.getSource(), entityTypeNameSupplier.apply(c))).
116-
then(literal("set").then(
117-
argument("filter", EntityArgument.entities()).
118-
executes(c -> setEntityFilter(c.getSource(), entityTypeNameSupplier.apply(c), c.getArgument("filter", EntitySelector.class)))
119-
)).
147+
then(literal("set").then(Objects.requireNonNull(prevFilter))).
120148
then(literal("clear").executes(c -> setEntityFilter(c.getSource(), entityTypeNameSupplier.apply(c), null)));
121149
}
122150

src/main/java/carpettisaddition/commands/lifetime/filter/EntityFilterManager.java

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@
2626
import carpettisaddition.translations.TranslationContext;
2727
import carpettisaddition.utils.Messenger;
2828
import carpettisaddition.utils.entityfilter.EntityFilter;
29+
import com.google.common.collect.Lists;
2930
import com.google.common.collect.Maps;
31+
import net.minecraft.ChatFormatting;
3032
import net.minecraft.commands.arguments.selector.EntitySelector;
3133
import net.minecraft.world.entity.Entity;
3234
import net.minecraft.world.entity.EntityType;
3335
import net.minecraft.commands.CommandSourceStack;
3436
import net.minecraft.network.chat.BaseComponent;
35-
import net.minecraft.network.chat.ClickEvent;
37+
import org.jetbrains.annotations.NotNull;
3638
import org.jetbrains.annotations.Nullable;
3739

40+
import java.util.List;
3841
import java.util.Map;
3942
import java.util.function.Predicate;
4043

@@ -44,7 +47,7 @@ public class EntityFilterManager extends TranslationContext
4447
private static final Predicate<Entity> DEFAULT_FILTER = entity -> true;
4548

4649
// key null is for global filter
47-
private final Map<EntityType<?>, Predicate<Entity>> entityFilter = Maps.newLinkedHashMap();
50+
private final Map<EntityType<?>, Predicate<Entity>> entityFilters = Maps.newLinkedHashMap();
4851

4952
public EntityFilterManager()
5053
{
@@ -56,9 +59,10 @@ public static EntityFilterManager getInstance()
5659
return INSTANCE;
5760
}
5861

62+
@NotNull
5963
public Predicate<Entity> getFilter(@Nullable EntityType<?> entityType)
6064
{
61-
return this.entityFilter.getOrDefault(entityType, DEFAULT_FILTER);
65+
return this.entityFilters.getOrDefault(entityType, DEFAULT_FILTER);
6266
}
6367

6468
/**
@@ -71,30 +75,35 @@ public boolean test(Entity entity)
7175
return this.getFilter(null).test(entity) && this.getFilter(entity.getType()).test(entity);
7276
}
7377

74-
public void setEntityFilter(CommandSourceStack source, @Nullable EntityType<?> entityType, @Nullable EntitySelector entitySelector)
78+
public void setEntityFilter(CommandSourceStack source, @Nullable EntityType<?> entityType, @Nullable List<EntitySelector> selectors)
7579
{
7680
BaseComponent typeName = this.getEntityTypeText(entityType);
77-
if (entitySelector != null)
81+
if (selectors != null && !selectors.isEmpty())
7882
{
79-
if (!entitySelector.includesEntities() || ((EntitySelectorAccessor)entitySelector).getPlayerName() != null)
83+
List<EntityFilter> entityFilters = Lists.newArrayList();
84+
for (int i = 0; i < selectors.size(); i++)
8085
{
81-
Messenger.tell(source, tr("unsupported.0"));
82-
Messenger.tell(source, tr("unsupported.1"));
83-
}
84-
else
85-
{
86-
EntityFilter entityFilter = new EntityFilter(source, entitySelector);
87-
this.entityFilter.put(entityType, entityFilter);
88-
Messenger.tell(source, tr(
89-
"filter_set",
90-
typeName,
91-
entityFilter.toText()
92-
));
86+
EntitySelector entitySelector = selectors.get(i);
87+
if (!entitySelector.includesEntities() || ((EntitySelectorAccessor)entitySelector).getPlayerName() != null)
88+
{
89+
Messenger.tell(source, tr("unsupported.0", i + 1));
90+
Messenger.tell(source, tr("unsupported.1"));
91+
return;
92+
}
93+
entityFilters.add(new EntityFilter(source, entitySelector));
9394
}
95+
96+
LifetimeEntityFilter entityFilter = new LifetimeEntityFilter(entityFilters);
97+
this.entityFilters.put(entityType, entityFilter);
98+
Messenger.tell(source, tr(
99+
"filter_set",
100+
typeName,
101+
entityFilter.toText()
102+
));
94103
}
95104
else
96105
{
97-
this.entityFilter.remove(entityType);
106+
this.entityFilters.remove(entityType);
98107
Messenger.tell(source, tr(
99108
"filter_removed",
100109
typeName
@@ -105,7 +114,23 @@ public void setEntityFilter(CommandSourceStack source, @Nullable EntityType<?> e
105114
public BaseComponent getEntityFilterText(@Nullable EntityType<?> entityType)
106115
{
107116
Predicate<Entity> entityPredicate = this.getFilter(entityType);
108-
return entityPredicate instanceof EntityFilter ? ((EntityFilter)entityPredicate).toText() : tr("none");
117+
if (entityPredicate == DEFAULT_FILTER)
118+
{
119+
return tr("none");
120+
}
121+
else if (entityPredicate instanceof EntityFilter)
122+
{
123+
return ((EntityFilter)entityPredicate).toText();
124+
}
125+
else if (entityPredicate instanceof LifetimeEntityFilter)
126+
{
127+
return ((LifetimeEntityFilter)entityPredicate).toText();
128+
}
129+
// unexpected predicate type
130+
return Messenger.hover(
131+
Messenger.s("?", ChatFormatting.RED),
132+
Messenger.s(entityPredicate.getClass().getSimpleName())
133+
);
109134
}
110135

111136
public BaseComponent getEntityTypeText(@Nullable EntityType<?> entityType)
@@ -124,8 +149,8 @@ public void displayFilter(CommandSourceStack source, @Nullable EntityType<?> ent
124149

125150
public int displayAllFilters(CommandSourceStack source)
126151
{
127-
Messenger.tell(source, tr("display_total", this.entityFilter.size()));
128-
this.entityFilter.keySet().forEach(entityType -> Messenger.tell(
152+
Messenger.tell(source, tr("display_total", this.entityFilters.size()));
153+
this.entityFilters.keySet().forEach(entityType -> Messenger.tell(
129154
source,
130155
Messenger.c(
131156
"f - ",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* This file is part of the Carpet TIS Addition project, licensed under the
3+
* GNU Lesser General Public License v3.0
4+
*
5+
* Copyright (C) 2026 Fallen_Breath and contributors
6+
*
7+
* Carpet TIS Addition is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Lesser General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* Carpet TIS Addition is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with Carpet TIS Addition. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package carpettisaddition.commands.lifetime.filter;
22+
23+
import carpettisaddition.utils.Messenger;
24+
import carpettisaddition.utils.entityfilter.EntityFilter;
25+
import com.google.common.collect.ImmutableList;
26+
import net.minecraft.ChatFormatting;
27+
import net.minecraft.network.chat.BaseComponent;
28+
import net.minecraft.world.entity.Entity;
29+
30+
import java.util.List;
31+
import java.util.function.Predicate;
32+
import java.util.stream.Collectors;
33+
34+
public class LifetimeEntityFilter implements Predicate<Entity>
35+
{
36+
private final List<EntityFilter> filters; // "or" relationship
37+
38+
public LifetimeEntityFilter(List<EntityFilter> filters)
39+
{
40+
if (filters.isEmpty())
41+
{
42+
throw new IllegalArgumentException("Must have at least one selector");
43+
}
44+
this.filters = ImmutableList.copyOf(filters);
45+
}
46+
47+
@Override
48+
public boolean test(Entity entity)
49+
{
50+
for (EntityFilter filter : this.filters)
51+
{
52+
if (filter.test(entity))
53+
{
54+
return true;
55+
}
56+
}
57+
return false;
58+
}
59+
60+
public BaseComponent toText()
61+
{
62+
return Messenger.join(
63+
Messenger.hover(
64+
Messenger.s(" || ", ChatFormatting.DARK_GRAY),
65+
EntityFilterManager.getInstance().getTranslator().tr("or")
66+
),
67+
this.filters.stream().map(EntityFilter::toText).collect(Collectors.toList())
68+
);
69+
}
70+
}

src/main/resources/assets/carpettisaddition/lang/en_us.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,14 +716,15 @@ carpettisaddition:
716716
filter:
717717
global: Global
718718
unsupported:
719-
'0': "Unsupported entity filter"
719+
'0': "Unsupported entity filter (no. %1$s)"
720720
'1': "Please enter a @e style entity selector"
721721
filter_set: 'Entity filter of %1$s is set to %2$s'
722722
filter_removed: 'Entity filter of %1$s removed'
723723
display: "Entity filter of %1$s is %2$s"
724724
display_total: "There are %1$s activated filters"
725725
none: None
726726
click_to_clear: "Click to clear filter"
727+
or: or
727728
recorder:
728729
enabled: Lifetime data recorder enabled
729730
already_enabled: Lifetime data recorder is already enabled

src/main/resources/assets/carpettisaddition/lang/zh_cn.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,14 +716,15 @@ carpettisaddition:
716716
filter:
717717
global: 全局
718718
unsupported:
719-
'0': 不支持的实体筛选器
719+
'0': 不支持的实体筛选器(第%1$s个)
720720
'1': 请输入一个@e类型的实体选择器
721721
filter_set: '%1$s的筛选器已设置为%2$s'
722722
filter_removed: '%1$s的筛选器已移除'
723723
display: '%1$s的筛选器为%2$s'
724724
display_total: 共有%1$s个激活的筛选器
725725
none:
726726
click_to_clear: 点击以清除筛选器
727+
or:
727728
recorder:
728729
enabled: 存活时间数据记录器已启用
729730
already_enabled: 存活时间数据记录器已经处于启用状态了

website/docs/commands.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ For example, `/lifetime creeper` shows all statistic of creeper in detail, and `
116116

117117
```
118118
/lifetime filter <entity_type> set <entity_selector>
119+
/lifetime filter <entity_type> set <entity_selector> [<entity_selector2> [<entity_selector3> [<entity_selector4> ...]]]
119120
/lifetime filter <entity_type> clear
120121
```
121122

@@ -125,6 +126,9 @@ Entities need to be accepted by the related filter to be record by the lifetime
125126

126127
Filter is input as an `@e` style Minecraft entity selector. e.g. `@e[distance=..100,nbt={Item:{id:"minecraft:oak_sapling"}}]`
127128

129+
Supports passing multiple `<entity_selector>` parameters (up to 16). An entity passes the filter if it matches any entity selector.
130+
In other words, the relationship between multiple entity selectors is OR
131+
128132
Use `/lifetime filter` to display current activated filters
129133

130134
### recorder

website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/commands.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ sidebar_position: 3
116116

117117
```
118118
/lifetime filter <实体类型> set <实体选择器>
119+
/lifetime filter <实体类型> set <实体选择器> [<实体选择器2> [<实体选择器3> [<实体选择器4> ...]]]
119120
/lifetime filter <实体类型> clear
120121
```
121122

@@ -125,6 +126,9 @@ sidebar_position: 3
125126

126127
使用 `@e` 类型的 Minecraft 实体选择器来输入实体筛选器,如:`@e[distance=..100,nbt={Item:{id:"minecraft:oak_sapling"}}]`
127128

129+
支持传入多个 `<实体选择器>` 参数(最多 16 个)。实体只要满足任一实体选择器的条件,即可通过筛选。
130+
也即多个实体选择器之间是 **** 的关系
131+
128132
使用 `/lifetime filter` 来显示激活的实体筛选器
129133

130134
### recorder

0 commit comments

Comments
 (0)