Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New mana transfer effect between pools and items #4710

Open
wants to merge 1 commit into
base: 1.20.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions Xplat/src/main/java/vazkii/botania/client/fx/FXWisp.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public class FXWisp extends TextureSheetParticle {
private final int moteHalfLife;

public FXWisp(ClientLevel world, double d, double d1, double d2, double xSpeed, double ySpeed, double zSpeed,
float size, float red, float green, float blue, boolean depthTest, float maxAgeMul, boolean noClip) {
super(world, d, d1, d2, 0, 0, 0);
float size, float red, float green, float blue, boolean depthTest, float maxAgeMul, boolean noClip, float g) {
super(world, d, d1, d2);
// super applies wiggle to motion so set it here instead
xd = xSpeed;
yd = ySpeed;
Expand All @@ -43,7 +43,7 @@ public FXWisp(ClientLevel world, double d, double d1, double d2, double xSpeed,
gCol = green;
bCol = blue;
alpha = 0.375F;
gravity = 0;
gravity = g;
quadSize = (this.random.nextFloat() * 0.5F + 0.5F) * 2.0F * size;
moteParticleScale = quadSize;
lifetime = (int) (28D / (Math.random() * 0.3D + 0.7D) * maxAgeMul);
Expand Down Expand Up @@ -91,11 +91,13 @@ public void tick() {
this.remove();
}

this.yd -= 0.04D * (double) this.gravity;
this.yd -= this.gravity;
this.move(this.xd, this.yd, this.zd);
this.xd *= 0.9800000190734863D;
this.yd *= 0.9800000190734863D;
this.zd *= 0.9800000190734863D;
if (gravity == 0.0f) {
this.xd *= 0.98;
this.yd *= 0.98;
this.zd *= 0.98;
}
}

public void setGravity(float value) {
Expand Down
31 changes: 24 additions & 7 deletions Xplat/src/main/java/vazkii/botania/client/fx/WispParticleData.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ public class WispParticleData implements ParticleOptions {
Codec.FLOAT.fieldOf("b").forGetter(d -> d.b),
Codec.FLOAT.fieldOf("maxAgeMul").forGetter(d -> d.maxAgeMul),
Codec.BOOL.fieldOf("depthTest").forGetter(d -> d.depthTest),
Codec.BOOL.fieldOf("noClip").forGetter(d -> d.noClip)
Codec.BOOL.fieldOf("noClip").forGetter(d -> d.noClip),
Codec.FLOAT.optionalFieldOf("gravity", 0.0f).forGetter(d -> d.gravity)
)
.apply(instance, WispParticleData::new));
public final float size;
public final float r, g, b;
public final float maxAgeMul;
public final boolean depthTest;
public final boolean noClip;
public final float gravity;

public static WispParticleData wisp(float size, float r, float g, float b) {
return wisp(size, r, g, b, 1);
Expand All @@ -52,24 +54,33 @@ public static WispParticleData wisp(float size, float r, float g, float b, boole
}

public static WispParticleData wisp(float size, float r, float g, float b, float maxAgeMul, boolean depthTest) {
return new WispParticleData(size, r, g, b, maxAgeMul, depthTest, false);
return wisp(size, r, g, b, maxAgeMul, depthTest, 0.0f);
}

private WispParticleData(float size, float r, float g, float b, float maxAgeMul, boolean depthTest, boolean noClip) {
public static WispParticleData wisp(float size, float r, float g, float b, float maxAgeMul, float gravity) {
return wisp(size, r, g, b, maxAgeMul, true, gravity);
}

public static WispParticleData wisp(float size, float r, float g, float b, float maxAgeMul, boolean depthTest, float gravity) {
return new WispParticleData(size, r, g, b, maxAgeMul, depthTest, false, gravity);
}

private WispParticleData(float size, float r, float g, float b, float maxAgeMul, boolean depthTest, boolean noClip, float gravity) {
this.size = size;
this.r = r;
this.g = g;
this.b = b;
this.maxAgeMul = maxAgeMul;
this.depthTest = depthTest;
this.noClip = noClip;
this.gravity = gravity;
}

public WispParticleData withNoClip(boolean v) {
if (noClip == v) {
return this;
} else {
return new WispParticleData(size, r, g, b, maxAgeMul, depthTest, v);
return new WispParticleData(size, r, g, b, maxAgeMul, depthTest, v, gravity);
}
}

Expand All @@ -88,6 +99,7 @@ public void writeToNetwork(FriendlyByteBuf buf) {
buf.writeFloat(maxAgeMul);
buf.writeBoolean(depthTest);
buf.writeBoolean(noClip);
buf.writeFloat(gravity);
}

@NotNull
Expand Down Expand Up @@ -119,14 +131,19 @@ public WispParticleData fromCommand(@NotNull ParticleType<WispParticleData> type
boolean noClip = false;
if (reader.canRead()) {
reader.expect(' ');
depth = reader.readBoolean();
noClip = reader.readBoolean();
}
float gravity = 0;
if (reader.canRead()) {
reader.expect(' ');
gravity = reader.readFloat();
}
return new WispParticleData(size, r, g, b, mam, depth, noClip);
return new WispParticleData(size, r, g, b, mam, depth, noClip, gravity);
}

@Override
public WispParticleData fromNetwork(@NotNull ParticleType<WispParticleData> type, FriendlyByteBuf buf) {
return new WispParticleData(buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readBoolean(), buf.readBoolean());
return new WispParticleData(buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readBoolean(), buf.readBoolean(), buf.readFloat());
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public Factory(SpriteSet sprite) {

@Override
public Particle createParticle(WispParticleData data, ClientLevel world, double x, double y, double z, double mx, double my, double mz) {
FXWisp ret = new FXWisp(world, x, y, z, mx, my, mz, data.size, data.r, data.g, data.b, data.depthTest, data.maxAgeMul, data.noClip);
FXWisp ret = new FXWisp(world, x, y, z, mx, my, mz, data.size, data.r, data.g, data.b, data.depthTest, data.maxAgeMul, data.noClip, data.gravity);
ret.pickSprite(sprite);
return ret;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
import com.google.common.base.Predicates;
import com.mojang.blaze3d.systems.RenderSystem;

import it.unimi.dsi.fastutil.ints.*;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
Expand All @@ -28,6 +31,7 @@
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;

import org.apache.commons.lang3.mutable.MutableInt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11;
Expand Down Expand Up @@ -57,7 +61,6 @@
import vazkii.botania.common.helper.EntityHelper;
import vazkii.botania.common.item.BotaniaItems;
import vazkii.botania.common.item.ManaTabletItem;
import vazkii.botania.common.proxy.Proxy;
import vazkii.botania.xplat.BotaniaConfig;
import vazkii.botania.xplat.XplatAbstractions;

Expand All @@ -70,6 +73,9 @@
public class ManaPoolBlockEntity extends BotaniaBlockEntity implements ManaPool, KeyLocked, SparkAttachable,
ThrottledPacket, Wandable {
public static final int PARTICLE_COLOR = 0x00C6FF;
public static final float PARTICLE_COLOR_BLUE = (PARTICLE_COLOR & 0xFF) / 255F;
public static final float PARTICLE_COLOR_GREEN = (PARTICLE_COLOR >> 8 & 0xFF) / 255F;
public static final float PARTICLE_COLOR_RED = (PARTICLE_COLOR >> 16 & 0xFF) / 255F;
public static final int MAX_MANA = 1000000;
private static final int MAX_MANA_DILLUTED = 10000;

Expand All @@ -82,6 +88,8 @@ public class ManaPoolBlockEntity extends BotaniaBlockEntity implements ManaPool,
private static final String TAG_OUTPUT_KEY = "outputKey";
private static final int CRAFT_EFFECT_EVENT = 0;
private static final int CHARGE_EFFECT_EVENT = 1;
private static final int DRAIN_EFFECT_EVENT = 2;
private static final float CHARGING_GRAVITY = 0.003f;

private boolean outputting = false;

Expand All @@ -100,6 +108,8 @@ public class ManaPoolBlockEntity extends BotaniaBlockEntity implements ManaPool,

private int ticks = 0;
private boolean sendPacket = false;
private final Int2ObjectMap<MutableInt> chargingParticles = new Int2ObjectOpenHashMap<>();
private final Int2ObjectMap<MutableInt> drainingParticles = new Int2ObjectOpenHashMap<>();

public ManaPoolBlockEntity(BlockPos pos, BlockState state) {
super(BotaniaBlockEntities.POOL, pos, state);
Expand Down Expand Up @@ -217,14 +227,14 @@ public boolean triggerEvent(int event, int param) {
return true;
}
case CHARGE_EFFECT_EVENT: {
if (level.isClientSide) {
if (BotaniaConfig.common().chargingAnimationEnabled()) {
boolean outputting = param == 1;
Vec3 itemVec = Vec3.atLowerCornerOf(worldPosition).add(0.5, 0.5 + Math.random() * 0.3, 0.5);
Vec3 tileVec = Vec3.atLowerCornerOf(worldPosition).add(0.2 + Math.random() * 0.6, 0, 0.2 + Math.random() * 0.6);
Proxy.INSTANCE.lightningFX(level, outputting ? tileVec : itemVec,
outputting ? itemVec : tileVec, 80, level.random.nextLong(), 0x4400799c, 0x4400C6FF);
}
if (level.isClientSide && BotaniaConfig.common().chargingAnimationEnabled()) {
chargingParticles.computeIfAbsent(param, i -> new MutableInt(15)).setValue(15);
}
return true;
}
case DRAIN_EFFECT_EVENT: {
if (level.isClientSide && BotaniaConfig.common().chargingAnimationEnabled()) {
drainingParticles.computeIfAbsent(param, i -> new MutableInt(15)).setValue(15);
}
return true;
}
Expand All @@ -246,12 +256,84 @@ public static void clientTick(Level level, BlockPos worldPosition, BlockState st
self.initManaCapAndNetwork();
double particleChance = 1F - (double) self.getCurrentMana() / (double) self.getMaxMana() * 0.1;
if (Math.random() > particleChance) {
float red = (PARTICLE_COLOR >> 16 & 0xFF) / 255F;
float green = (PARTICLE_COLOR >> 8 & 0xFF) / 255F;
float blue = (PARTICLE_COLOR & 0xFF) / 255F;
WispParticleData data = WispParticleData.wisp((float) Math.random() / 3F, red, green, blue, 2F);
level.addParticle(data, worldPosition.getX() + 0.3 + Math.random() * 0.5, worldPosition.getY() + 0.6 + Math.random() * 0.25, worldPosition.getZ() + Math.random(), 0, (float) Math.random() / 25F, 0);
WispParticleData data = WispParticleData.wisp((float) Math.random() / 3F,
PARTICLE_COLOR_RED, PARTICLE_COLOR_GREEN, PARTICLE_COLOR_BLUE, 2F);
level.addParticle(data, worldPosition.getX() + 0.3 + Math.random() * 0.5,
worldPosition.getY() + 0.6 + Math.random() * 0.25, worldPosition.getZ() + Math.random(),
0, (float) Math.random() / 25F, 0);
}

if (self.getCurrentMana() == 0) {
self.chargingParticles.clear();
} else {
displayChargingParticles(level, worldPosition, self, self.chargingParticles, true);
}
displayChargingParticles(level, worldPosition, self, self.drainingParticles, false);
}

private static void displayChargingParticles(Level level, BlockPos worldPosition, ManaPoolBlockEntity self,
Int2ObjectMap<MutableInt> particles, boolean charging) {
int bellowCount = charging ? getBellowCount(level, worldPosition, self) : 0;
float relativeMana = (float) self.getCurrentMana() / self.getMaxMana();
var particlesIterator = particles.int2ObjectEntrySet().iterator();
while (particlesIterator.hasNext()) {
var entry = particlesIterator.next();
int ticksRemaining = entry.getValue().decrementAndGet();
if (ticksRemaining % 2 == 0) {
int encodedPos = entry.getIntKey();
Vec3 itemPosRelBase = decodeRelativeItemPosition(encodedPos, relativeMana);
if (charging) {
for (int i = 0; i <= bellowCount; i++) {
Vec3 itemPosRel = randomizeItemPos(itemPosRelBase);
Vec3 poolPosRel = new Vec3(0.1 + 0.8 * Math.random(), 0.1 + 0.4 * relativeMana,
0.1 + 0.8 * Math.random());
addManaFlowParticle(level, worldPosition, poolPosRel, itemPosRel);
}
} else {
Vec3 itemPosRel = randomizeItemPos(itemPosRelBase);
Vec3 poolPosRel =
new Vec3(0.05 + 0.9 * Math.random(), 0.35 * relativeMana, 0.05 + 0.9 * Math.random());
addManaFlowParticle(level, worldPosition, itemPosRel, poolPosRel);
}
}
if (ticksRemaining <= 0) {
particlesIterator.remove();
}
}
}

@NotNull
private static Vec3 randomizeItemPos(Vec3 itemPosRelBase) {
return itemPosRelBase.add(0.1 * Math.random() - 0.05, 0.1 * Math.random() + 0.25, 0.1 * Math.random() - 0.05);
}

private static int getBellowCount(Level level, BlockPos worldPosition, ManaPoolBlockEntity self) {
int bellowCount = 0;
for (Direction dir : Direction.Plane.HORIZONTAL) {
BlockEntity tile = level.getBlockEntity(worldPosition.relative(dir));
if (tile instanceof BellowsBlockEntity bellows && bellows.getLinkedTile() == self) {
bellowCount++;
}
}
return bellowCount;
}

private static void addManaFlowParticle(Level level, BlockPos worldPosition, Vec3 startPos, Vec3 endPos) {
double maxHeight = Math.max(startPos.y, endPos.y) - endPos.y + 0.05 * Math.random();
Vec3 horizontalDiff = new Vec3(endPos.x - startPos.x, 0, endPos.z - startPos.z);
double horizontalDistance = horizontalDiff.horizontalDistance();
Vec3 horizontalDir = horizontalDiff.scale(1 / horizontalDistance);
double startHeight = startPos.y - endPos.y;
double vY0Squared = 2 * CHARGING_GRAVITY * (maxHeight - startHeight);
double vY0 = Math.sqrt(vY0Squared);
double lifetime = (vY0 + Math.sqrt(vY0Squared + 2 * CHARGING_GRAVITY * startHeight)) / CHARGING_GRAVITY;
double vX0 = horizontalDistance / lifetime;
Vec3 v0 = horizontalDir.scale(vX0).with(Direction.Axis.Y, vY0);

WispParticleData data = WispParticleData.wisp(0.1f, PARTICLE_COLOR_RED, PARTICLE_COLOR_GREEN,
PARTICLE_COLOR_BLUE, (float) (0.025 * lifetime), CHARGING_GRAVITY).withNoClip(true);
level.addParticle(data, worldPosition.getX() + startPos.x, worldPosition.getY() + startPos.y,
worldPosition.getZ() + startPos.z, v0.x, v0.y, v0.z);
}

public static void serverTick(Level level, BlockPos worldPosition, BlockState state, ManaPoolBlockEntity self) {
Expand Down Expand Up @@ -287,15 +369,7 @@ public static void serverTick(Level level, BlockPos worldPosition, BlockState st
if (self.outputting && mana.canReceiveManaFromPool(self) || !self.outputting && mana.canExportManaToPool(self)) {
boolean didSomething = false;

int bellowCount = 0;
if (self.outputting) {
for (Direction dir : Direction.Plane.HORIZONTAL) {
BlockEntity tile = level.getBlockEntity(worldPosition.relative(dir));
if (tile instanceof BellowsBlockEntity bellows && bellows.getLinkedTile() == self) {
bellowCount++;
}
}
}
int bellowCount = self.outputting ? getBellowCount(level, worldPosition, self) : 0;
int transfRate = 1000 * (bellowCount + 1);

if (self.outputting) {
Expand Down Expand Up @@ -324,8 +398,10 @@ public static void serverTick(Level level, BlockPos worldPosition, BlockState st
}

if (didSomething) {
if (BotaniaConfig.common().chargingAnimationEnabled() && level.random.nextInt(20) == 0) {
level.blockEvent(worldPosition, state.getBlock(), CHARGE_EFFECT_EVENT, self.outputting ? 1 : 0);
if (BotaniaConfig.common().chargingAnimationEnabled() && self.ticks % 10 == 0) {
level.blockEvent(worldPosition, state.getBlock(),
self.outputting ? CHARGE_EFFECT_EVENT : DRAIN_EFFECT_EVENT,
encodeRelativeItemPosition(worldPosition, item));
}
EntityHelper.syncItem(item);
self.isDoingTransfer = self.outputting;
Expand All @@ -346,6 +422,39 @@ public static void serverTick(Level level, BlockPos worldPosition, BlockState st
self.ticks++;
}

/**
* Somehow squeeze a relative position within a block cube into 8 bits.
* This conversion reserves 2 bits (i.e. four different values) for the vertical position and two times 3 bits
* (i.e. eight different values per axis) for the horizontal position. The vertical position is assumed to be either
* on the bottom of the pool or at least about halfway up, so the four possible values are distributed accordingly.
*/
private static int encodeRelativeItemPosition(BlockPos worldPosition, ItemEntity item) {
double relX = Mth.clamp(item.position().x() - worldPosition.getX(), 0, 1);
double relY = Mth.clamp(0.125 + 0.875 * (item.position().y() - worldPosition.getY()), 0.125, 0.9);
double relZ = Mth.clamp(item.position().z() - worldPosition.getZ(), 0, 1);

int compressedX = (int) Math.round(7.0 * relX);
int compressedY = 4 - Mth.ceillog2(14 - (int) (14.0 * relY));
int compressedZ = (int) Math.round(7.0 * relZ);

return compressedX | compressedY << 3 | compressedZ << 5;
}

/**
* Decodes a position from the parameter that roughly matches the originally encoded one.
*/
private static Vec3 decodeRelativeItemPosition(int param, float relativeMana) {
int compressedX = param & 0x7;
int compressedY = param >> 3 & 0x3;
int compressedZ = param >> 5 & 0x7;

double relX = compressedX / 7.0;
double relY = 1.0 - (14.0 / 16.0) / (1 << compressedY);
double relZ = compressedZ / 7.0;

return new Vec3(relX, Math.max(relY, 0.5 * relativeMana), relZ);
}

@Override
public void writePacketNBT(CompoundTag cmp) {
cmp.putInt(TAG_MANA, getCurrentMana());
Expand Down