SPIGOT-5784, SPIGOT-6858, #1527: Add villager reputation API

This commit is contained in:
Mikołaj Nowak 2024-12-31 11:31:44 +11:00 committed by md_5
parent c905a715e9
commit fa10c5029f
No known key found for this signature in database
GPG Key ID: E8E901AC7C617C11
10 changed files with 699 additions and 10 deletions

View File

@ -0,0 +1,221 @@
--- a/net/minecraft/world/entity/ai/gossip/Reputation.java
+++ b/net/minecraft/world/entity/ai/gossip/Reputation.java
@@ -30,13 +30,27 @@
import net.minecraft.util.VisibleForDebug;
import org.slf4j.Logger;
+// CraftBukkit start
+import net.minecraft.world.entity.npc.EntityVillager;
+import org.bukkit.craftbukkit.entity.CraftVillager.CraftReputationType;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.entity.Villager;
+import org.bukkit.event.entity.VillagerReputationChangeEvent;
+// CraftBukkit end
+
public class Reputation {
private static final Logger LOGGER = LogUtils.getLogger();
public static final int DISCARD_THRESHOLD = 2;
private final Map<UUID, Reputation.a> gossips = Maps.newHashMap();
- public Reputation() {}
+ // CraftBukkit start - store reference to villager entity
+ private final EntityVillager villager;
+
+ public Reputation(EntityVillager villager) {
+ this.villager = villager;
+ }
+ // CraftBukkit end
@VisibleForDebug
public Map<UUID, Object2IntMap<ReputationType>> getGossipEntries() {
@@ -51,15 +65,17 @@
}
public void decay() {
- Iterator<Reputation.a> iterator = this.gossips.values().iterator();
+ Iterator<Map.Entry<UUID, Reputation.a>> iterator = this.gossips.entrySet().iterator(); // CraftBukkit - iterate over entries instead of values to access entity UUID
while (iterator.hasNext()) {
- Reputation.a reputation_a = (Reputation.a) iterator.next();
+ // CraftBukkit start - pass villager and entity UUID to decay method
+ Map.Entry<UUID, Reputation.a> reputation_a = iterator.next();
- reputation_a.decay();
- if (reputation_a.isEmpty()) {
+ reputation_a.getValue().decay(villager, reputation_a.getKey());
+ if (reputation_a.getValue().isEmpty()) {
iterator.remove();
}
+ // CraftBukkit end
}
}
@@ -112,16 +128,27 @@
int j = reputation_b.value - reputation_b.type.decayPerTransfer;
if (j >= 2) {
- this.getOrCreate(reputation_b.target).entries.mergeInt(reputation_b.type, j, Reputation::mergeValuesForTransfer);
+ // CraftBukkit start - redirect to a method which fires an event before setting value
+ this.set(reputation_b.target, reputation_b.type, Reputation.mergeValuesForTransfer(getReputation(reputation_b.target, Predicate.isEqual(reputation_b.type), false), j), Villager.ReputationEvent.GOSSIP);
+ //this.getOrCreate(reputation_b.target).entries.mergeInt(reputation_b.type, j, Reputation::mergeValuesForTransfer);
+ // CraftBukkit end
}
});
}
public int getReputation(UUID uuid, Predicate<ReputationType> predicate) {
+ // CraftBukkit start - add getReputation overload with additional parameter
+ return getReputation(uuid, predicate, true);
+ }
+
+ public int getReputation(UUID uuid, Predicate<ReputationType> predicate, boolean weighted) {
+ // CraftBukkit end
Reputation.a reputation_a = (Reputation.a) this.gossips.get(uuid);
- return reputation_a != null ? reputation_a.weightedValue(predicate) : 0;
+ // CraftBukkit start - handle weighted parameter
+ return reputation_a != null ? (weighted ? reputation_a.weightedValue(predicate) : reputation_a.unweightedValue(predicate)) : 0;
+ // CraftBukkit end
}
public long getCountForType(ReputationType reputationtype, DoublePredicate doublepredicate) {
@@ -131,27 +158,58 @@
}
public void add(UUID uuid, ReputationType reputationtype, int i) {
+ // CraftBukkit start - add change reason parameter
+ add(uuid, reputationtype, i, Villager.ReputationEvent.UNSPECIFIED);
+ }
+
+ public void add(UUID uuid, ReputationType reputationtype, int i, Villager.ReputationEvent changeReason) {
+ // CraftBukkit end
Reputation.a reputation_a = this.getOrCreate(uuid);
+ int oldValue = reputation_a.entries.getInt(reputationtype); // CraftBukkit - store old value
reputation_a.entries.mergeInt(reputationtype, i, (j, k) -> {
return this.mergeValuesForAddition(reputationtype, j, k);
});
- reputation_a.makeSureValueIsntTooLowOrTooHigh(reputationtype);
+ // CraftBukkit start - fire reputation change event
+ int newValue = reputation_a.entries.getInt(reputationtype);
+ newValue = Math.max(0, Math.min(newValue, reputationtype.max));
+ reputation_a.entries.replace(reputationtype, oldValue); // restore old value until the event completed processing
+ VillagerReputationChangeEvent event = CraftEventFactory.callVillagerReputationChangeEvent((Villager) villager.getBukkitEntity(), uuid, changeReason, CraftReputationType.minecraftToBukkit(reputationtype), oldValue, newValue, reputationtype.max);
+ if (!event.isCancelled()) {
+ reputation_a.entries.replace(reputationtype, event.getNewValue());
+ reputation_a.makeSureValueIsntTooLowOrTooHigh(reputationtype);
+ }
+ // CraftBukkit end
if (reputation_a.isEmpty()) {
this.gossips.remove(uuid);
}
}
- public void remove(UUID uuid, ReputationType reputationtype, int i) {
- this.add(uuid, reputationtype, -i);
+ // CraftBukkit start
+ public void set(UUID uuid, ReputationType reputationType, int i, Villager.ReputationEvent changeReason) {
+ int addAmount = i - getReputation(uuid, Predicate.isEqual(reputationType), false);
+ if (addAmount == 0) {
+ return;
+ }
+ this.add(uuid, reputationType, addAmount, changeReason);
}
+ // CraftBukkit end
- public void remove(UUID uuid, ReputationType reputationtype) {
+ // CraftBukkit start - add change reason parameter
+ public void remove(UUID uuid, ReputationType reputationtype, int i, Villager.ReputationEvent changeReason) {
+ this.add(uuid, reputationtype, -i, changeReason);
+ }
+ // CraftBukkit end
+
+ public void remove(UUID uuid, ReputationType reputationtype, Villager.ReputationEvent changeReason) { // CraftBukkit - add change reason parameter
Reputation.a reputation_a = (Reputation.a) this.gossips.get(uuid);
if (reputation_a != null) {
- reputation_a.remove(reputationtype);
+ // CraftBukkit start - redirect - set to 0 instead
+ set(uuid, reputationtype, 0, changeReason);
+ //reputation_a.remove(reputationtype);
+ // CraftBukkit end
if (reputation_a.isEmpty()) {
this.gossips.remove(uuid);
}
@@ -159,7 +217,16 @@
}
- public void remove(ReputationType reputationtype) {
+ public void remove(ReputationType reputationtype, Villager.ReputationEvent changeReason) { // CraftBukkit - add change reason parameter
+ // CraftBukkit start - replace the logic to call the other remove instead
+ Set<UUID> uuids = Sets.newHashSet(this.gossips.keySet());
+ for (UUID uuid : uuids) {
+ remove(uuid, reputationtype, changeReason);
+ }
+ if (true) {
+ return;
+ }
+ // CraftBukkit end
Iterator<Reputation.a> iterator = this.gossips.values().iterator();
while (iterator.hasNext()) {
@@ -174,7 +241,7 @@
}
public <T> T store(DynamicOps<T> dynamicops) {
- Optional optional = Reputation.b.LIST_CODEC.encodeStart(dynamicops, this.unpack().toList()).resultOrPartial((s) -> {
+ Optional<T> optional = Reputation.b.LIST_CODEC.encodeStart(dynamicops, this.unpack().toList()).resultOrPartial((s) -> { // CraftBukkit - missing generic parameter after decompile
Reputation.LOGGER.warn("Failed to serialize gossips: {}", s);
});
@@ -186,7 +253,7 @@
Reputation.b.LIST_CODEC.decode(dynamic).resultOrPartial((s) -> {
Reputation.LOGGER.warn("Failed to deserialize gossips: {}", s);
}).stream().flatMap((pair) -> {
- return ((List) pair.getFirst()).stream();
+ return ((List<Reputation.b>) pair.getFirst()).stream(); // CraftBukkit - missing generic parameter after decompile
}).forEach((reputation_b) -> {
this.getOrCreate(reputation_b.target).entries.put(reputation_b.type, reputation_b.value);
});
@@ -216,18 +283,36 @@
}).sum();
}
+ // CraftBukkit start
+ public int unweightedValue(Predicate<ReputationType> predicate) {
+ return this.entries.object2IntEntrySet().stream().filter((entry) -> {
+ return predicate.test((ReputationType) entry.getKey());
+ }).mapToInt((entry) -> {
+ return entry.getIntValue();
+ }).sum();
+ }
+ // CraftBukkit end
+
public Stream<Reputation.b> unpack(UUID uuid) {
return this.entries.object2IntEntrySet().stream().map((entry) -> {
return new Reputation.b(uuid, (ReputationType) entry.getKey(), entry.getIntValue());
});
}
- public void decay() {
+ public void decay(EntityVillager villager, UUID uuid) { // CraftBukkit - add villager and entity uuid parameters
ObjectIterator<Entry<ReputationType>> objectiterator = this.entries.object2IntEntrySet().iterator();
while (objectiterator.hasNext()) {
Entry<ReputationType> entry = (Entry) objectiterator.next();
int i = entry.getIntValue() - ((ReputationType) entry.getKey()).decayPerDay;
+ // CraftBukkit start - fire event
+ VillagerReputationChangeEvent event = CraftEventFactory.callVillagerReputationChangeEvent((Villager) villager.getBukkitEntity(), uuid, Villager.ReputationEvent.DECAY, CraftReputationType.minecraftToBukkit(entry.getKey()), entry.getIntValue(), i, entry.getKey().max);
+ if (event.isCancelled()) {
+ continue;
+ } else {
+ i = event.getNewValue();
+ }
+ // CraftBukkit end
if (i < 2) {
objectiterator.remove();

View File

@ -0,0 +1,29 @@
--- a/net/minecraft/world/entity/ai/village/ReputationEvent.java
+++ b/net/minecraft/world/entity/ai/village/ReputationEvent.java
@@ -2,14 +2,26 @@
public interface ReputationEvent {
+ java.util.Map<String, ReputationEvent> BY_ID = com.google.common.collect.Maps.newHashMap(); // CraftBukkit - map with all values
ReputationEvent ZOMBIE_VILLAGER_CURED = register("zombie_villager_cured");
ReputationEvent GOLEM_KILLED = register("golem_killed");
ReputationEvent VILLAGER_HURT = register("villager_hurt");
ReputationEvent VILLAGER_KILLED = register("villager_killed");
ReputationEvent TRADE = register("trade");
+ // CraftBukkit start - additional events added in the API
+ ReputationEvent GOSSIP = register("bukkit_gossip");
+ ReputationEvent DECAY = register("bukkit_decay");
+ ReputationEvent UNSPECIFIED = register("bukkit_unspecified");
+ // CraftBukkit end
static ReputationEvent register(final String s) {
return new ReputationEvent() {
+ // CraftBukkit start - add new value to map
+ {
+ BY_ID.put(s, this);
+ }
+ // CraftBukkit end
+
public String toString() {
return s;
}

View File

@ -16,7 +16,28 @@
public class EntityVillager extends EntityVillagerAbstract implements ReputationHandler, VillagerDataHolder {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -150,7 +159,7 @@
@@ -133,6 +142,9 @@
}, MemoryModuleType.MEETING_POINT, (entityvillager, holder) -> {
return holder.is(PoiTypes.MEETING);
});
+ // CraftBukkit start
+ public long gossipDecayInterval = GOSSIP_DECAY_INTERVAL;
+ // CraftBukkit end
public EntityVillager(EntityTypes<? extends EntityVillager> entitytypes, World world) {
this(entitytypes, world, VillagerType.PLAINS);
@@ -140,7 +152,9 @@
public EntityVillager(EntityTypes<? extends EntityVillager> entitytypes, World world, VillagerType villagertype) {
super(entitytypes, world);
- this.gossips = new Reputation();
+ // CraftBukkit start - add constructor parameter in Reputation
+ this.gossips = new Reputation(this);
+ // CraftBukkit end
((Navigation) this.getNavigation()).setCanOpenDoors(true);
this.getNavigation().setCanFloat(true);
this.getNavigation().setRequiredPathLength(48.0F);
@@ -150,7 +164,7 @@
@Override
public BehaviorController<EntityVillager> getBrain() {
@ -25,7 +46,7 @@
}
@Override
@@ -235,7 +244,7 @@
@@ -235,7 +249,7 @@
this.increaseProfessionLevelOnUpdate = false;
}
@ -34,7 +55,7 @@
}
}
@@ -360,7 +369,13 @@
@@ -360,7 +374,13 @@
while (iterator.hasNext()) {
MerchantRecipe merchantrecipe = (MerchantRecipe) iterator.next();
@ -49,7 +70,7 @@
}
this.resendOffersToTradingPlayer();
@@ -429,7 +444,13 @@
@@ -429,7 +449,13 @@
while (iterator.hasNext()) {
MerchantRecipe merchantrecipe = (MerchantRecipe) iterator.next();
@ -64,7 +85,7 @@
}
}
@@ -489,7 +510,7 @@
@@ -489,7 +515,7 @@
@Override
public void addAdditionalSaveData(NBTTagCompound nbttagcompound) {
super.addAdditionalSaveData(nbttagcompound);
@ -73,7 +94,7 @@
Logger logger = EntityVillager.LOGGER;
Objects.requireNonNull(logger);
@@ -512,7 +533,7 @@
@@ -512,7 +538,7 @@
public void readAdditionalSaveData(NBTTagCompound nbttagcompound) {
super.readAdditionalSaveData(nbttagcompound);
if (nbttagcompound.contains("VillagerData", 10)) {
@ -82,7 +103,7 @@
Logger logger = EntityVillager.LOGGER;
Objects.requireNonNull(logger);
@@ -808,7 +829,7 @@
@@ -808,7 +834,7 @@
entitywitch1.finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entitywitch1.blockPosition()), EntitySpawnReason.CONVERSION, (GroupDataEntity) null);
entitywitch1.setPersistenceRequired();
this.releaseAllPois();
@ -91,7 +112,16 @@
if (entitywitch == null) {
super.thunderHit(worldserver, entitylightning);
@@ -906,7 +927,7 @@
@@ -891,7 +917,7 @@
if (this.lastGossipDecayTime == 0L) {
this.lastGossipDecayTime = i;
- } else if (i >= this.lastGossipDecayTime + 24000L) {
+ } else if (i >= this.lastGossipDecayTime + gossipDecayInterval) { // CraftBukkit - use variable for decay interval
this.gossips.decay();
this.lastGossipDecayTime = i;
}
@@ -906,7 +932,7 @@
}).limit(5L).toList();
if (list1.size() >= j) {
@ -100,7 +130,31 @@
list.forEach(SensorGolemLastSeen::golemDetected);
}
}
@@ -963,7 +984,7 @@
@@ -919,15 +945,18 @@
@Override
public void onReputationEventFrom(ReputationEvent reputationevent, Entity entity) {
+ Villager.ReputationEvent bukkitReputationEvent = org.bukkit.craftbukkit.entity.CraftVillager.CraftReputationEvent.minecraftToBukkit(reputationevent); // CraftBukkit - convert event to bukkit
if (reputationevent == ReputationEvent.ZOMBIE_VILLAGER_CURED) {
- this.gossips.add(entity.getUUID(), ReputationType.MAJOR_POSITIVE, 20);
- this.gossips.add(entity.getUUID(), ReputationType.MINOR_POSITIVE, 25);
+ // CraftBukkit start - add change reason parameter
+ this.gossips.add(entity.getUUID(), ReputationType.MAJOR_POSITIVE, 20, bukkitReputationEvent);
+ this.gossips.add(entity.getUUID(), ReputationType.MINOR_POSITIVE, 25, bukkitReputationEvent);
+ // CraftBukkit end
} else if (reputationevent == ReputationEvent.TRADE) {
- this.gossips.add(entity.getUUID(), ReputationType.TRADING, 2);
+ this.gossips.add(entity.getUUID(), ReputationType.TRADING, 2, bukkitReputationEvent); // CraftBukkit - add change reason parameter
} else if (reputationevent == ReputationEvent.VILLAGER_HURT) {
- this.gossips.add(entity.getUUID(), ReputationType.MINOR_NEGATIVE, 25);
+ this.gossips.add(entity.getUUID(), ReputationType.MINOR_NEGATIVE, 25, bukkitReputationEvent); // CraftBukkit - add change reason parameter
} else if (reputationevent == ReputationEvent.VILLAGER_KILLED) {
- this.gossips.add(entity.getUUID(), ReputationType.MAJOR_NEGATIVE, 25);
+ this.gossips.add(entity.getUUID(), ReputationType.MAJOR_NEGATIVE, 25, bukkitReputationEvent); // CraftBukkit - add change reason parameter
}
}
@@ -963,7 +992,7 @@
@Override
public void startSleeping(BlockPosition blockposition) {
super.startSleeping(blockposition);
@ -109,7 +163,7 @@
this.brain.eraseMemory(MemoryModuleType.WALK_TARGET);
this.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
}
@@ -971,7 +992,7 @@
@@ -971,7 +1000,7 @@
@Override
public void stopSleeping() {
super.stopSleeping();

View File

@ -1,9 +1,16 @@
package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.core.BlockPosition;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.ai.gossip.ReputationType;
import net.minecraft.world.entity.monster.EntityZombie;
import net.minecraft.world.entity.monster.EntityZombieVillager;
import net.minecraft.world.entity.npc.EntityVillager;
@ -128,6 +135,79 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
return (entityzombievillager != null) ? (ZombieVillager) entityzombievillager.getBukkitEntity() : null;
}
@Override
public int getReputation(UUID uuid, ReputationType reputationType) {
Preconditions.checkArgument(uuid != null, "UUID cannot be null");
Preconditions.checkArgument(reputationType != null, "Reputation type cannot be null");
return getHandle().getGossips().getReputation(uuid,
Predicate.isEqual(CraftReputationType.bukkitToMinecraft(reputationType)),
false);
}
@Override
public int getWeightedReputation(UUID uuid, ReputationType reputationType) {
Preconditions.checkArgument(uuid != null, "UUID cannot be null");
Preconditions.checkArgument(reputationType != null, "Reputation type cannot be null");
return getHandle().getGossips().getReputation(uuid,
Predicate.isEqual(CraftReputationType.bukkitToMinecraft(reputationType)),
true);
}
@Override
public int getReputation(UUID uuid) {
Preconditions.checkArgument(uuid != null, "UUID cannot be null");
return getHandle().getGossips().getReputation(uuid, reputationType -> true);
}
@Override
public void addReputation(UUID uuid, ReputationType reputationType, int amount) {
addReputation(uuid, reputationType, amount, ReputationEvent.UNSPECIFIED);
}
@Override
public void addReputation(UUID uuid, ReputationType reputationType, int amount, ReputationEvent changeReason) {
Preconditions.checkArgument(uuid != null, "UUID cannot be null");
Preconditions.checkArgument(reputationType != null, "Reputation type cannot be null");
Preconditions.checkArgument(changeReason != null, "Change reason cannot be null");
getHandle().getGossips().add(uuid, CraftReputationType.bukkitToMinecraft(reputationType), amount, changeReason);
}
@Override
public void removeReputation(UUID uuid, ReputationType reputationType, int amount) {
removeReputation(uuid, reputationType, amount, ReputationEvent.UNSPECIFIED);
}
@Override
public void removeReputation(UUID uuid, ReputationType reputationType, int amount, ReputationEvent changeReason) {
Preconditions.checkArgument(uuid != null, "UUID cannot be null");
Preconditions.checkArgument(reputationType != null, "Reputation type cannot be null");
Preconditions.checkArgument(changeReason != null, "Change reason cannot be null");
getHandle().getGossips().remove(uuid, CraftReputationType.bukkitToMinecraft(reputationType), amount, changeReason);
}
@Override
public void setReputation(UUID uuid, ReputationType reputationType, int amount) {
setReputation(uuid, reputationType, amount, ReputationEvent.UNSPECIFIED);
}
@Override
public void setReputation(UUID uuid, ReputationType reputationType, int amount, ReputationEvent changeReason) {
Preconditions.checkArgument(uuid != null, "UUID cannot be null");
Preconditions.checkArgument(reputationType != null, "Reputation type cannot be null");
Preconditions.checkArgument(changeReason != null, "Change reason cannot be null");
getHandle().getGossips().set(uuid, CraftReputationType.bukkitToMinecraft(reputationType), amount, changeReason);
}
@Override
public void setGossipDecayTime(long ticks) {
getHandle().gossipDecayInterval = ticks;
}
@Override
public long getGossipDecayTime() {
return getHandle().gossipDecayInterval;
}
public static class CraftType implements Type, Handleable<VillagerType> {
private static int count = 0;
@ -289,4 +369,78 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
return getKey().hashCode();
}
}
public static class CraftReputationType implements ReputationType, Handleable<net.minecraft.world.entity.ai.gossip.ReputationType> {
public static final Map<String, CraftReputationType> BY_ID = Stream
.of(net.minecraft.world.entity.ai.gossip.ReputationType.values())
.collect(Collectors.toMap(reputationType -> reputationType.id, CraftReputationType::new));
private final net.minecraft.world.entity.ai.gossip.ReputationType handle;
public CraftReputationType(net.minecraft.world.entity.ai.gossip.ReputationType handle) {
this.handle = handle;
}
@Override
public net.minecraft.world.entity.ai.gossip.ReputationType getHandle() {
return handle;
}
@Override
public int getMaxValue() {
return handle.max;
}
@Override
public int getWeight() {
return handle.weight;
}
public static net.minecraft.world.entity.ai.gossip.ReputationType bukkitToMinecraft(ReputationType bukkit) {
Preconditions.checkArgument(bukkit != null);
return ((CraftReputationType) bukkit).getHandle();
}
public static ReputationType minecraftToBukkit(net.minecraft.world.entity.ai.gossip.ReputationType minecraft) {
Preconditions.checkArgument(minecraft != null);
return switch (minecraft) {
case MAJOR_NEGATIVE -> ReputationType.MAJOR_NEGATIVE;
case MINOR_NEGATIVE -> ReputationType.MINOR_NEGATIVE;
case MINOR_POSITIVE -> ReputationType.MINOR_POSITIVE;
case MAJOR_POSITIVE -> ReputationType.MAJOR_POSITIVE;
case TRADING -> ReputationType.TRADING;
};
}
}
public static class CraftReputationEvent implements ReputationEvent, Handleable<net.minecraft.world.entity.ai.village.ReputationEvent> {
private static final Map<String, ReputationEvent> ALL = Maps.newHashMap();
private final net.minecraft.world.entity.ai.village.ReputationEvent handle;
public CraftReputationEvent(net.minecraft.world.entity.ai.village.ReputationEvent handle) {
this.handle = handle;
ALL.put(handle.toString(), this);
}
@Override
public net.minecraft.world.entity.ai.village.ReputationEvent getHandle() {
return handle;
}
public static net.minecraft.world.entity.ai.village.ReputationEvent bukkitToMinecraft(ReputationEvent bukkit) {
Preconditions.checkArgument(bukkit != null);
return ((CraftReputationEvent) bukkit).getHandle();
}
public static ReputationEvent minecraftToBukkit(net.minecraft.world.entity.ai.village.ReputationEvent minecraft) {
Preconditions.checkArgument(minecraft != null);
ReputationEvent bukkit = ALL.get(minecraft.toString());
return bukkit == null ? new CraftReputationEvent(minecraft) : bukkit;
}
}
}

View File

@ -12,6 +12,7 @@ import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
@ -220,6 +221,7 @@ import org.bukkit.event.entity.SpawnerSpawnEvent;
import org.bukkit.event.entity.StriderTemperatureChangeEvent;
import org.bukkit.event.entity.TrialSpawnerSpawnEvent;
import org.bukkit.event.entity.VillagerCareerChangeEvent;
import org.bukkit.event.entity.VillagerReputationChangeEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.PrepareAnvilEvent;
@ -1928,4 +1930,11 @@ public class CraftEventFactory {
Bukkit.getPluginManager().callEvent(new EntityRemoveEvent(entity.getBukkitEntity(), cause));
}
public static VillagerReputationChangeEvent callVillagerReputationChangeEvent(Villager villager, UUID targetUuid, Villager.ReputationEvent reason, Villager.ReputationType reputationType, int oldValue, int newValue, int maxValue) {
VillagerReputationChangeEvent event = new VillagerReputationChangeEvent(villager, targetUuid, reason, reputationType, oldValue, newValue, maxValue);
Bukkit.getPluginManager().callEvent(event);
return event;
}
}

View File

@ -16,6 +16,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.SharedConstants;
@ -32,6 +33,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.util.datafix.DataConverterRegistry;
import net.minecraft.util.datafix.fixes.DataConverterTypes;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.entity.ai.village.ReputationEvent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.alchemy.PotionRegistry;
import net.minecraft.world.level.block.Block;
@ -56,6 +58,7 @@ import org.bukkit.craftbukkit.block.CraftBiome;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.craftbukkit.damage.CraftDamageEffect;
import org.bukkit.craftbukkit.damage.CraftDamageSourceBuilder;
import org.bukkit.craftbukkit.entity.CraftVillager;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.legacy.CraftLegacy;
import org.bukkit.craftbukkit.legacy.FieldRename;
@ -65,6 +68,7 @@ import org.bukkit.damage.DamageSource;
import org.bukkit.damage.DamageType;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Villager;
import org.bukkit.inventory.CreativeCategory;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
@ -422,6 +426,18 @@ public final class CraftMagicNumbers implements UnsafeValues {
return customBiome;
}
@Override
public Villager.ReputationType createReputationType(String key) {
return Optional.ofNullable(CraftVillager.CraftReputationType.BY_ID.get(key))
.orElseThrow(() -> new IllegalArgumentException("Invalid ReputationType key: " + key));
}
@Override
public Villager.ReputationEvent createReputationEvent(String key) {
return Optional.ofNullable(ReputationEvent.BY_ID.get(key)).map(CraftVillager.CraftReputationEvent::new)
.orElseThrow(() -> new IllegalArgumentException("Invalid ReputationEvent key: " + key));
}
/**
* This helper class represents the different NBT Tags.
* <p>

View File

@ -0,0 +1,52 @@
package org.bukkit.entity;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.world.entity.ai.village.ReputationEvent;
import org.bukkit.craftbukkit.entity.CraftVillager;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.api.Test;
@Normal
public class ReputationEventTest {
@Test
public void toBukkit() throws IllegalAccessException {
List<ReputationEvent> reputationEvents = getConstants(ReputationEvent.class);
List<Villager.ReputationEvent> bukkitReputationEvents = getConstants(Villager.ReputationEvent.class);
for (ReputationEvent reputationEvent : reputationEvents) {
Villager.ReputationEvent bukkit = CraftVillager.CraftReputationEvent.minecraftToBukkit(reputationEvent);
assertNotNull(bukkit, "Reputation event " + reputationEvent.toString() + " should have a Bukkit equivalent");
assertTrue(bukkitReputationEvents.contains(bukkit), "Reputation event " + reputationEvent.toString() + " should have a Bukkit equivalent");
}
}
@Test
public void toMinecraft() throws IllegalAccessException {
List<ReputationEvent> reputationEvents = getConstants(ReputationEvent.class);
List<Villager.ReputationEvent> bukkitReputationEvents = getConstants(Villager.ReputationEvent.class);
for (Villager.ReputationEvent reputationEvent : bukkitReputationEvents) {
ReputationEvent minecraft = CraftVillager.CraftReputationEvent.bukkitToMinecraft(reputationEvent);
assertNotNull(minecraft, "Reputation event " + reputationEvent.toString() + " should have a Minecraft equivalent");
assertTrue(reputationEvents.contains(minecraft), "Reputation event " + reputationEvent + " should have a Minecraft equivalent");
}
}
private static boolean isPublicStaticFinal(Field field) {
return Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && Modifier.isFinal(field.getModifiers());
}
private static <T> List<T> getConstants(Class<T> clazz) throws IllegalAccessException {
List<T> list = new ArrayList<>();
for (Field field : clazz.getFields()) {
if (isPublicStaticFinal(field) && clazz.isAssignableFrom(field.getType())) {
list.add((T) field.get(null));
}
}
return list;
}
}

View File

@ -0,0 +1,43 @@
package org.bukkit.entity;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.world.entity.ai.gossip.ReputationType;
import org.bukkit.craftbukkit.entity.CraftVillager;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.api.Test;
@Normal
public class ReputationTypeTest {
@Test
public void toBukkit() {
for (ReputationType reputationType : ReputationType.values()) {
assertNotNull(CraftVillager.CraftReputationType.minecraftToBukkit(reputationType), "ReputationType." + reputationType.name() + ".toBukkit() should not be null");
}
}
@Test
public void fromBukkit() throws IllegalAccessException {
for (Villager.ReputationType reputationType : getConstants(Villager.ReputationType.class)) {
assertNotNull(CraftVillager.CraftReputationType.bukkitToMinecraft(reputationType), "ReputationType.fromBukkit(Villager.ReputationType) should not be null");
}
}
private static boolean isPublicStaticFinal(Field field) {
return Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && Modifier.isFinal(field.getModifiers());
}
private static <T> List<T> getConstants(Class<T> clazz) throws IllegalAccessException {
List<T> list = new ArrayList<>();
for (Field field : clazz.getFields()) {
if (isPublicStaticFinal(field) && clazz.isAssignableFrom(field.getType())) {
list.add((T) field.get(null));
}
}
return list;
}
}

View File

@ -0,0 +1,107 @@
package org.bukkit.entity;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
import java.util.UUID;
import net.minecraft.server.level.WorldServer;
import net.minecraft.world.entity.ai.gossip.Reputation;
import net.minecraft.world.entity.ai.gossip.ReputationType;
import net.minecraft.world.entity.npc.EntityVillager;
import net.minecraft.world.flag.FeatureFlags;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.entity.CraftEntityTypes;
import org.bukkit.support.environment.AllFeatures;
import org.junit.jupiter.api.Test;
@AllFeatures
public class VillagerTest {
@Test
public void getReputation() {
Villager villager = createVillager();
UUID uuid = UUID.randomUUID();
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 20);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 10);
assertEquals(20, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "getReputation should return correct value for a single reputation type");
assertEquals(10, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "getReputation should return correct value for a single reputation type");
assertEquals(10, villager.getReputation(uuid), "getReputation should return correct value for total weighted reputation");
}
@Test
public void getWeightedReputation() {
Villager villager = createVillager();
UUID uuid = UUID.randomUUID();
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 20);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 10);
assertEquals(20, villager.getWeightedReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "getWeightedReputation should return correct value for a single reputation type");
assertEquals(-10, villager.getWeightedReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "getWeightedReputation should return correct value for a single reputation type");
}
@Test
public void addReputation() {
Villager villager = createVillager();
UUID uuid = UUID.randomUUID();
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 20);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 10);
villager.addReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 3);
villager.addReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 20);
assertEquals(23, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "addReputation should increase value by given amount");
assertEquals(30, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "addReputation should increase value by given amount");
villager.addReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, ReputationType.MINOR_POSITIVE.max);
villager.addReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, ReputationType.MINOR_NEGATIVE.max);
assertEquals(ReputationType.MINOR_POSITIVE.max, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "addReputation should not exceed maximum value");
assertEquals(ReputationType.MINOR_NEGATIVE.max, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "addReputation should not exceed maximum value");
}
@Test
public void removeReputation() {
Villager villager = createVillager();
UUID uuid = UUID.randomUUID();
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 20);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 10);
villager.removeReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 5);
villager.removeReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 2);
assertEquals(15, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "removeReputation should decrease value by given amount");
assertEquals(8, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "removeReputation should decrease value by given amount");
villager.removeReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 15 - Reputation.DISCARD_THRESHOLD + 1);
villager.removeReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, Integer.MAX_VALUE);
assertEquals(0, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "removeReputation should cause reputation removal if value drops below discard threshold");
assertEquals(0, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "removeReputation should cause reputation removal if value drops below discard threshold");
}
@Test
public void setReputation() {
Villager villager = createVillager();
UUID uuid = UUID.randomUUID();
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 20);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 10);
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, 5);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, 2);
assertEquals(5, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "setReputation should set value to given amount");
assertEquals(2, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "setReputation should set value to given amount");
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, Reputation.DISCARD_THRESHOLD - 1);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, Integer.MIN_VALUE);
assertEquals(0, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "setReputation should cause reputation removal if value drops below discard threshold");
assertEquals(0, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "setReputation should cause reputation removal if value drops below discard threshold");
villager.setReputation(uuid, Villager.ReputationType.MINOR_POSITIVE, ReputationType.MINOR_POSITIVE.max + 1);
villager.setReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE, Integer.MAX_VALUE);
assertEquals(ReputationType.MINOR_POSITIVE.max, villager.getReputation(uuid, Villager.ReputationType.MINOR_POSITIVE), "setReputation should be clamped to reputation type maximum value");
assertEquals(ReputationType.MINOR_NEGATIVE.max, villager.getReputation(uuid, Villager.ReputationType.MINOR_NEGATIVE), "setReputation should be clamped to reputation type maximum value");
}
private static Villager createVillager() {
World world = mock(withSettings().stubOnly());
WorldServer worldServer = mock(withSettings().stubOnly());
when(worldServer.getMinecraftWorld()).thenReturn(worldServer);
when(worldServer.enabledFeatures()).thenReturn(FeatureFlags.VANILLA_SET);
Location location = new Location(world, 0, 0, 0, 0, 0);
CraftEntityTypes.SpawnData spawnData = new CraftEntityTypes.SpawnData(worldServer, location, false, false);
EntityVillager entityVillager = (EntityVillager) CraftEntityTypes.getEntityTypeData(EntityType.VILLAGER).spawnFunction().apply(spawnData);
return (Villager) entityVillager.getBukkitEntity();
}
}

View File

@ -10,6 +10,7 @@ import org.bukkit.Keyed;
import org.bukkit.Registry;
import org.bukkit.Server;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.plugin.PluginManager;
import org.bukkit.support.DummyServerHelper;
import org.bukkit.support.RegistryHelper;
import org.junit.jupiter.api.extension.ExtensionContext;
@ -56,6 +57,9 @@ public class AllFeaturesExtension extends BaseExtension {
return spy;
});
PluginManager pluginManager = mock(withSettings().stubOnly());
when(server.getPluginManager()).thenReturn(pluginManager);
CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry());
}