That's one heck of a commit you've got there...

I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
Electroblob77
2019-08-18 00:04:18 +01:00
parent 2680d02304
commit f37812be3e
1564 changed files with 47652 additions and 12984 deletions
@@ -1,28 +1,15 @@
package electroblob.wizardry.entity.living;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.UUID;
import javax.annotation.Nullable;
import com.google.common.base.Predicate;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.IndirectMinionDamage;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.MinionDamage;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.*;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
@@ -36,28 +23,33 @@ import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.UUID;
/**
* Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 2.1, and this
* interface allows summoned creatures to extend vanilla (or indeed modded) entity classes, so
* <code>EntitySummonedZombie</code> now extends <code>EntityZombie</code>, for example. This change has two major
* benefits:
* <p>
* <p></p>
* - There is no longer any need for separate render classes, because summoned creatures are now instances of vanilla
* types. <i>You don't even need to assign a render class</i> because the supertype should already be assigned the
* correct one.<br>
* - Summoned creature classes are now much more robust when it comes to changes between Minecraft versions, since none
* of the vanilla code needs to be copied.
* <p>
* <p></p>
* <b>Summoned creatures that do not emulate vanilla entities do not directly implement this interface</b>. Instead,
* they should extend the abstract base implementation, {@link EntitySummonedCreature}.
* <p>
* <p></p>
* All damage dealt by ISummonedCreature instances is redirected via
* {@link ISummonedCreature#onLivingAttackEvent(net.minecraftforge.event.entity.living.LivingAttackEvent)
* {@link ISummonedCreature#onLivingAttackEvent(LivingAttackEvent)
* ISummonedCreature.onLivingAttackEvent(LivingAttackEvent)} and replaced by an instance of
* {@link electroblob.wizardry.util.IElementalDamage IElementalDamage} with the summoner of that creature as the source
* {@link IElementalDamage IElementalDamage} with the summoner of that creature as the source
* rather than the creature itself. This means that kills by summoned creatures register as kills for their owner,
* dropping xp and rare loot if that owner is a player.
* <p>
* <p></p>
* Though this system is a lot better than the previous system, <i>it is not a perfect solution</i>. The old
* EntitySummonedCreature class overrode some methods from Entity in order to add shared functionality, but this cannot
* be done with an interface. To get around this problem, this interface contains 5 delegate methods that do the same
@@ -67,12 +59,12 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
* work properly unless it is adhered to. <i>The position of the delegate method call is unimportant, but by convention
* it is usually at the start of the calling method, which avoids it being unintentionally skipped by a return
* statement (except for methods where the result of the delegate method should itself be returned).</i>
* <p>
* <p></p>
* It is recommended that when implementing this interface, you begin by copying {@link EntitySummonedCreature} to
* ensure all the relevant methods are duplicated. You can then change the superclass, override any additional methods
* and add functionality to any that are already overridden. You will always want to override the AI methods at the very
* least.
* <p>
* <p></p>
* Due to the limitations of interfaces, some methods that really ought to be protected are public. These are clearly
* marked as 'Internal, DO NOT CALL'. <b>Don't call them, only implement them.</b>
*
@@ -83,7 +75,7 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
* sacrifices have to be made when it comes to Java style - because adding on to a pre-existing program is not a good
* way of doing this sort of thing anyway, but we have no choice about that! */
@Mod.EventBusSubscriber
public interface ISummonedCreature extends IEntityAdditionalSpawnData {
public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOwnable {
// Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that.
String NAMEPLATE_TRANSLATION_KEY = "entity." + Wizardry.MODID + ":summonedcreature.nameplate";
@@ -101,43 +93,51 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
*/
int getLifetime();
/**
* Sets the WeakReference object which refers to the owner of this summoned creature. Internal, don't call unless
* you know what you are doing.
*/
void setCasterReference(WeakReference<EntityLivingBase> reference);
/** Internal, do not use. Implementing classes should implement this to set their owner UUID field. */
void setOwnerId(UUID uuid);
/**
* Returns a WeakReference object which refers to the owner of this summoned creature. Subclasses should store this
* as a private field. This may be null; as such it is preferable to use {@link ISummonedCreature#getCaster()} to
* get the caster object itself.
*/
/** Returns the UUID of the owner of this summoned creature, or null if it does not have an owner.
* Implementing classes should implement this to return their owner UUID field. */
@Nullable
WeakReference<EntityLivingBase> getCasterReference();
@Override
UUID getOwnerId(); // Only overridden because I wanted to add javadoc!
/** Internal, DO NOT CALL. */
void setCasterUUID(UUID uuid);
/** Internal, DO NOT CALL. This is for loading purposes only and is not usually synchronised. */
UUID getCasterUUID();
@Nullable
@Override
default Entity getOwner(){
return getCaster(); // Delegate to getCaster
}
/**
* Returns the EntityLivingBase that summoned this creature, or null if it no longer exists. Cases where the entity
* may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to
* another dimension, or this creature simply had no caster in the first place. <i>This is the correct method to use
* to get the owner of this summoned creature.
* another dimension, or this creature simply had no caster in the first place.
*/
@Nullable
default EntityLivingBase getCaster(){
return getCasterReference() == null ? null : getCasterReference().get();
default EntityLivingBase getCaster(){ // Kept despite the above method because it returns an EntityLivingBase
if(this instanceof Entity){ // Bit of a cheat but it saves having yet another method just to get the world
Entity entity = WizardryUtilities.getEntityByUUID(((Entity)this).world, getOwnerId());
if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen
Wizardry.logger.warn("{} has a non-living owner!", this);
return null;
}
return (EntityLivingBase)entity;
}else{
Wizardry.logger.warn("{} implements ISummonedCreature but is not an SoundLoopSpellEntity!", this.getClass());
return null;
}
}
/**
* Sets the EntityLivingBase that summoned this creature. <i>This is the correct method to use to set the owner of
* this summoned creature.
* Sets the EntityLivingBase that summoned this creature.
*/
default void setCaster(@Nullable EntityLivingBase caster){
setCasterReference(new WeakReference<EntityLivingBase>(caster));
setOwnerId(caster == null ? null : caster.getUniqueID());
}
// Miscellaneous
@@ -164,17 +164,56 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
default void readSpawnData(ByteBuf buffer){
int id = buffer.readInt();
// We're on the client side here, so we can safely use Minecraft.getMinecraft().world via proxies.
if(id > -1) setCasterReference(
new WeakReference<EntityLivingBase>((EntityLivingBase)Wizardry.proxy.getTheWorld().getEntityByID(id)));
if(id > -1){
Entity entity = Wizardry.proxy.getTheWorld().getEntityByID(id);
if(entity instanceof EntityLivingBase) setCaster((EntityLivingBase)entity);
else Wizardry.logger.warn("Received a spawn packet for entity {}, but no living entity matched the supplied ID", this);
}
setLifetime(buffer.readInt());
}
/**
* Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this creature as the
* attacker. Also allows implementors to override it if they wish to do so.
* Determines whether the given target is valid. Used by the default target selector (see
* {@link ISummonedCreature#getTargetSelector()}) and revenge targeting checks. This method is responsible for the
* ally designation system, default classes that may be targeted and the config whitelist/blacklist.
* Implementors may override this if they want to do something different or add their own checks.
* @see AllyDesignationSystem#isValidTarget(Entity, Entity)
*/
default boolean isValidTarget(Entity target){
return WizardryUtilities.isValidTarget(this.getCaster(), target);
// If the target is valid based on the ADS...
if(AllyDesignationSystem.isValidTarget(this.getCaster(), target)){
// ...and is a player, they can be attacked, since players can't be in the whitelist or the
// blacklist...
if(target instanceof EntityPlayer){
// ...unless the creature was summoned by a good wizard who the player has not angered.
if(getCaster() instanceof EntityWizard){
if(getCaster().getRevengeTarget() != target
&& ((EntityWizard)getCaster()).getAttackTarget() != target) {
return false;
}
}
return true;
}
// ...and is a mob, a summoned creature, a wizard...
if((target instanceof IMob || target instanceof ISummonedCreature
|| (target instanceof EntityWizard && !(getCaster() instanceof EntityWizard))
// ...or something that's attacking the owner...
|| (target instanceof EntityLiving && ((EntityLiving)target).getAttackTarget() == getCaster())
// ...or in the whitelist...
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist)
.contains(EntityList.getKey(target.getClass())))
// ...and isn't in the blacklist...
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
.contains(EntityList.getKey(target.getClass()))){
// ...it can be attacked.
return true;
}
}
return false;
}
/**
@@ -182,45 +221,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* possible for implementors to override this in order to do something special when selecting a target.
*/
default Predicate<Entity> getTargetSelector(){
return new Predicate<Entity>(){
public boolean apply(Entity entity){
// TODO: Backport invisibility check (also in wizards)
// If the target is valid and not invisible...
if(!entity.isInvisible() && isValidTarget(entity)){
// ... and is a player, they can be attacked, since players can't be in the whitelist or the
// blacklist ...
if(entity instanceof EntityPlayer){
// ... unless the creature was summoned by a good wizard who the player has not angered.
if(getCaster() instanceof EntityWizard){
if(((EntityWizard)getCaster()).getRevengeTarget() != entity
&& ((EntityWizard)getCaster()).getAttackTarget() != entity) {
return false;
}
}
return true;
}
// ... and is a mob, a summoned creature, a wizard ...
if((entity instanceof IMob || entity instanceof ISummonedCreature
|| (entity instanceof EntityWizard && !(getCaster() instanceof EntityWizard))
// ... or in the whitelist ...
|| Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist)
.contains(EntityList.getKey(entity.getClass())))
// ... and isn't in the blacklist ...
&& !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist)
.contains(EntityList.getKey(entity.getClass()))){
// ... it can be attacked.
return true;
}
}
return false;
}
};
return entity -> getCaster() == null ? entity instanceof EntityPlayer : !entity.isInvisible() && isValidTarget(entity);
}
/**
@@ -241,7 +242,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* Called from the event handler after the damage change is applied. Does nothing by default, but can be overridden
* to do something when a successful attack is made. This was added because the event-based damage source system can
* cause parts of attackEntityAsMob not to fire, since attackEntityFrom is intercepted and canceled.
* <p>
* <p></p>
* Usage examples: {@link EntitySilverfishMinion} uses this to summon more silverfish if the target is killed,
* {@link EntitySkeletonMinion} and {@link EntitySpiderMinion} use this to add potion effects to the target.
*/
@@ -266,7 +267,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* very little point in doing that since anything extra could just be added to readEntityFromNBT anyway.
*/
default void readNBTDelegate(NBTTagCompound tagcompound){
this.setCasterUUID(tagcompound.getUniqueId("casterUUID"));
this.setOwnerId(tagcompound.getUniqueId("casterUUID"));
this.setLifetime(tagcompound.getInteger("lifetime"));
}
@@ -275,8 +276,8 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
* returns <b>true</b>.
*/
default boolean shouldRevengeTarget(EntityLivingBase entity){
// Allows the config to prevent minions from revenge-targeting their owners.
return entity != this.getCaster() || Wizardry.settings.minionRevengeTargeting;
// Allows the config to prevent minions from revenge-targeting their owners (or anything else, for that matter)
return Wizardry.settings.minionRevengeTargeting || isValidTarget(entity);
}
/**
@@ -286,22 +287,17 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
default void updateDelegate(){
if(!(this instanceof Entity))
throw new ClassCastException("Implementations of ISummonedCreature must extend Entity!");
throw new ClassCastException("Implementations of ISummonedCreature must extend SoundLoopSpellEntity!");
Entity thisEntity = ((Entity)this);
if(this.getCaster() == null && this.getCasterUUID() != null){
Entity entity = WizardryUtilities.getEntityByUUID(thisEntity.world, getCasterUUID());
if(entity instanceof EntityLivingBase){
this.setCasterReference(new WeakReference<EntityLivingBase>((EntityLivingBase)entity));
}
}
if(thisEntity.ticksExisted == 1){
this.onSpawn();
}
if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() != -1){
// For some reason Minecraft reads the entity from NBT just after the entity is created, so setting -1 as a
// default lifetime doesn't work. The easiest way around this is to use 0 - nobody's going to need it!
if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() > 0){
this.onDespawn();
thisEntity.setDead();
}
@@ -322,20 +318,20 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
ItemStack stack = player.getHeldItem(hand);
WizardData properties = WizardData.get(player);
WizardData data = WizardData.get(player);
// Selects one of the player's minions.
if(player.isSneaking() && stack.getItem() instanceof ItemWand){
if(player.isSneaking() && stack.getItem() instanceof ISpellCastingItem){
if(!player.world.isRemote && properties != null && this.getCaster() == player){
if(!player.world.isRemote && data != null && this.getCaster() == player){
if(properties.selectedMinion != null && properties.selectedMinion.get() == this){
if(data.selectedMinion != null && data.selectedMinion.get() == this){
// Deselects the selected minion if right-clicked again
properties.selectedMinion = null;
data.selectedMinion = null;
}else{
// Selects this minion
properties.selectedMinion = new WeakReference<ISummonedCreature>(this);
data.selectedMinion = new WeakReference<>(this);
}
properties.sync();
data.sync();
}
return true;
}
@@ -346,7 +342,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
// Damage system
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
static void onLivingAttackEvent(LivingAttackEvent event){
// Rather than bother overriding entire attack methods in ISummonedCreature implementations, it's easier (and
// more robust) to use LivingAttackEvent to modify the damage source.