mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-27 15:17:59 +01:00
Track which spells are active on which blocks to make tile-by-tile interactions easier. Requireed by #412
This commit is contained in:
parent
a1c64f502c
commit
eb97b80d1c
6 changed files with 264 additions and 8 deletions
|
@ -31,6 +31,7 @@ import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
|
|||
import com.minelittlepony.unicopia.network.Channel;
|
||||
import com.minelittlepony.unicopia.particle.UParticles;
|
||||
import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
|
||||
import com.minelittlepony.unicopia.server.world.Ether;
|
||||
import com.minelittlepony.unicopia.server.world.NocturnalSleepManager;
|
||||
import com.minelittlepony.unicopia.server.world.UGameRules;
|
||||
import com.minelittlepony.unicopia.server.world.UWorldGen;
|
||||
|
@ -71,6 +72,7 @@ public class Unicopia implements ModInitializer {
|
|||
((BlockDestructionManager.Source)w).getDestructionManager().tick();
|
||||
ZapAppleStageStore.get(w).tick();
|
||||
WeatherConditions.get(w).tick();
|
||||
Ether.get(w).tick();
|
||||
if (Debug.SPELLBOOK_CHAPTERS) {
|
||||
SpellbookChapterLoader.INSTANCE.sendUpdate(w.getServer());
|
||||
}
|
||||
|
|
|
@ -13,14 +13,18 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
|
|||
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
|
||||
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
|
||||
import com.minelittlepony.unicopia.entity.EntityReference;
|
||||
import com.minelittlepony.unicopia.server.world.chunk.PositionalDataMap;
|
||||
import com.minelittlepony.unicopia.util.NbtSerialisable;
|
||||
import com.minelittlepony.unicopia.util.Tickable;
|
||||
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.world.PersistentState;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class Ether extends PersistentState {
|
||||
public class Ether extends PersistentState implements Tickable {
|
||||
private static final Identifier ID = Unicopia.id("ether");
|
||||
|
||||
public static Ether get(World world) {
|
||||
|
@ -28,6 +32,7 @@ public class Ether extends PersistentState {
|
|||
}
|
||||
|
||||
private final Map<Identifier, Map<UUID, Map<UUID, Entry<?>>>> endpoints;
|
||||
private final PositionalDataMap<Entry<?>> positionData = new PositionalDataMap<>();
|
||||
|
||||
private final Object locker = new Object();
|
||||
|
||||
|
@ -70,23 +75,36 @@ public class Ether extends PersistentState {
|
|||
markDirty();
|
||||
return new Entry<>(spell, caster);
|
||||
});
|
||||
if (entry.removed) {
|
||||
entry.removed = false;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
if (entry.spell.get() != spell) {
|
||||
entry.spell = new WeakReference<>(spell);
|
||||
markDirty();
|
||||
}
|
||||
if (entry.removed) {
|
||||
entry.removed = false;
|
||||
positionData.update(entry);
|
||||
markDirty();
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
endpoints.values().forEach(byType -> {
|
||||
byType.values().forEach(entries -> {
|
||||
entries.values().forEach(Entry::update);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public <T extends Spell> void remove(SpellType<T> spellType, UUID entityId) {
|
||||
synchronized (locker) {
|
||||
endpoints.computeIfPresent(spellType.getId(), (typeId, entries) -> {
|
||||
if (entries.remove(entityId) != null) {
|
||||
Map<UUID, Entry<?>> data = entries.remove(entityId);
|
||||
if (data != null) {
|
||||
markDirty();
|
||||
data.values().forEach(positionData::remove);
|
||||
}
|
||||
return entries.isEmpty() ? null : entries;
|
||||
});
|
||||
|
@ -150,6 +168,10 @@ public class Ether extends PersistentState {
|
|||
return false;
|
||||
}
|
||||
|
||||
public Set<Entry<?>> getAtPosition(BlockPos pos) {
|
||||
return world.isClient() ? Set.of() : positionData.getState(pos);
|
||||
}
|
||||
|
||||
private void pruneNodes() {
|
||||
this.endpoints.values().removeIf(entities -> {
|
||||
entities.values().removeIf(spells -> {
|
||||
|
@ -160,7 +182,7 @@ public class Ether extends PersistentState {
|
|||
});
|
||||
}
|
||||
|
||||
public class Entry<T extends Spell> implements NbtSerialisable {
|
||||
public class Entry<T extends Spell> implements PositionalDataMap.Hotspot, NbtSerialisable {
|
||||
public final EntityReference<?> entity;
|
||||
|
||||
@Nullable
|
||||
|
@ -176,6 +198,9 @@ public class Ether extends PersistentState {
|
|||
|
||||
private final Set<UUID> claimants = new HashSet<>();
|
||||
|
||||
private BlockPos currentPos = BlockPos.ORIGIN;
|
||||
private BlockPos previousPos = BlockPos.ORIGIN;
|
||||
|
||||
private Entry(NbtElement nbt) {
|
||||
this.entity = new EntityReference<>();
|
||||
this.spell = new WeakReference<>(null);
|
||||
|
@ -186,6 +211,15 @@ public class Ether extends PersistentState {
|
|||
this.entity = new EntityReference<>(caster.asEntity());
|
||||
this.spell = new WeakReference<>(spell);
|
||||
spellId = spell.getUuid();
|
||||
update();
|
||||
}
|
||||
|
||||
void update() {
|
||||
previousPos = currentPos;
|
||||
currentPos = entity.getTarget().map(t -> BlockPos.ofFloored(t.pos())).orElse(BlockPos.ORIGIN);
|
||||
if (!currentPos.equals(previousPos)) {
|
||||
positionData.update(this);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasChanged() {
|
||||
|
@ -216,6 +250,13 @@ public class Ether extends PersistentState {
|
|||
markDirty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BlockPos getCenter() {
|
||||
return currentPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
@ -223,6 +264,9 @@ public class Ether extends PersistentState {
|
|||
public void setRadius(float radius) {
|
||||
if (!MathHelper.approximatelyEquals(this.radius, radius)) {
|
||||
this.radius = radius;
|
||||
if ((int)radius != (int)this.radius) {
|
||||
positionData.update(this);
|
||||
}
|
||||
changed.set(true);
|
||||
}
|
||||
markDirty();
|
||||
|
@ -247,6 +291,7 @@ public class Ether extends PersistentState {
|
|||
public void markDead() {
|
||||
Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead");
|
||||
removed = true;
|
||||
positionData.remove(this);
|
||||
claimants.clear();
|
||||
markDirty();
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ public class WorldOverlay<T extends WorldOverlay.State> extends PersistentState
|
|||
}
|
||||
|
||||
private Chunk getChunk(BlockPos pos) {
|
||||
return chunks.computeIfAbsent(new ChunkPos(pos).toLong(), Chunk::new);
|
||||
return chunks.computeIfAbsent(ChunkPos.toLong(pos), Chunk::new);
|
||||
}
|
||||
|
||||
public void setState(BlockPos pos, @Nullable T state) {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package com.minelittlepony.unicopia.server.world.chunk;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Box;
|
||||
import net.minecraft.util.math.ChunkSectionPos;
|
||||
|
||||
public class Chunk<T extends PositionalDataMap.Hotspot> {
|
||||
private final Long2ObjectMap<Section<T>> sections = new Long2ObjectOpenHashMap<>();
|
||||
private final Map<T, Set<Section<T>>> entryToSections = new WeakHashMap<>();
|
||||
|
||||
Chunk(long pos) { }
|
||||
|
||||
public synchronized Set<T> getState(BlockPos pos) {
|
||||
Section<T> section = sections.get(ChunkSectionPos.getSectionCoord(pos.getY()));
|
||||
return section == null ? Set.of() : section.getState(pos);
|
||||
}
|
||||
|
||||
public synchronized boolean remove(T entry) {
|
||||
Set<Section<T>> sections = entryToSections.remove(entry);
|
||||
if (sections != null) {
|
||||
sections.forEach(section -> {
|
||||
if (section.remove(entry) && section.isEmpty()) {
|
||||
this.sections.remove(section.pos);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized boolean update(T entry, Box entryBox) {
|
||||
Set<Section<T>> oldSections = entryToSections.get(entry);
|
||||
Set<Section<T>> newSections = getIntersectingSections(entryBox);
|
||||
if (oldSections != null) {
|
||||
oldSections.forEach(section -> {
|
||||
if (!newSections.contains(section) && section.remove(entry) && section.isEmpty()) {
|
||||
this.sections.remove(section.pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
newSections.forEach(chunk -> chunk.update(entry, entryBox));
|
||||
entryToSections.put(entry, newSections);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<Section<T>> getIntersectingSections(Box entryBox) {
|
||||
Set<Section<T>> sections = new HashSet<>();
|
||||
|
||||
int minY = ChunkSectionPos.getSectionCoord(entryBox.minY);
|
||||
int maxY = ChunkSectionPos.getSectionCoord(entryBox.maxY);
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
sections.add(this.sections.computeIfAbsent(y, Section::new));
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.minelittlepony.unicopia.server.world.chunk;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Box;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.ChunkSectionPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
|
||||
public class PositionalDataMap<T extends PositionalDataMap.Hotspot> {
|
||||
private final Long2ObjectMap<Chunk<T>> chunks = new Long2ObjectOpenHashMap<>();
|
||||
private final Map<T, Set<Chunk<T>>> entryToChunks = new WeakHashMap<>();
|
||||
|
||||
public Set<T> getState(BlockPos pos) {
|
||||
Chunk<T> chunk = chunks.get(ChunkPos.toLong(pos));
|
||||
return chunk == null ? Set.of() : chunk.getState(pos);
|
||||
}
|
||||
|
||||
public void remove(T entry) {
|
||||
Set<Chunk<T>> chunks = entryToChunks.remove(entry);
|
||||
if (chunks != null) {
|
||||
chunks.forEach(chunk -> chunk.remove(entry));
|
||||
}
|
||||
}
|
||||
|
||||
public void update(T entry) {
|
||||
Box entryBox = new Box(entry.getCenter()).expand(MathHelper.ceil(entry.getRadius()));
|
||||
Set<Chunk<T>> oldChunks = entryToChunks.get(entry);
|
||||
Set<Chunk<T>> newChunks = getIntersectingChunks(entryBox);
|
||||
if (oldChunks != null) {
|
||||
oldChunks.forEach(chunk -> chunk.remove(entry));
|
||||
}
|
||||
newChunks.forEach(chunk -> chunk.update(entry, entryBox));
|
||||
entryToChunks.put(entry, newChunks);
|
||||
}
|
||||
|
||||
private Set<Chunk<T>> getIntersectingChunks(Box entryBox) {
|
||||
int minX = ChunkSectionPos.getSectionCoord(entryBox.minX);
|
||||
int maxX = ChunkSectionPos.getSectionCoord(entryBox.maxX);
|
||||
int minZ = ChunkSectionPos.getSectionCoord(entryBox.minZ);
|
||||
int maxZ = ChunkSectionPos.getSectionCoord(entryBox.maxZ);
|
||||
|
||||
Set<Chunk<T>> chunks = new HashSet<>();
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
chunks.add(this.chunks.computeIfAbsent(ChunkPos.toLong(x, z), Chunk::new));
|
||||
}
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
public interface Hotspot {
|
||||
float getRadius();
|
||||
|
||||
BlockPos getCenter();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.minelittlepony.unicopia.server.world.chunk;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Box;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
|
||||
public class Section<T extends PositionalDataMap.Hotspot> {
|
||||
private final Set<T> entries = weakSet();
|
||||
private Set<T>[] states;
|
||||
|
||||
final long pos;
|
||||
|
||||
public Section(long pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return entries.isEmpty();
|
||||
}
|
||||
|
||||
public boolean remove(T entry) {
|
||||
if (entries.remove(entry)) {
|
||||
states = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean update(T entry, Box box) {
|
||||
entries.add(entry);
|
||||
states = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Set<T> getState(BlockPos pos) {
|
||||
int localPos = toLocalIndex(pos);
|
||||
if (states == null) {
|
||||
states = new Set[16 * 16 * 16];
|
||||
}
|
||||
Set<T> state = states[localPos];
|
||||
return state == null ? (states[localPos] = calculateState(pos)) : state;
|
||||
}
|
||||
|
||||
private Set<T> calculateState(BlockPos pos) {
|
||||
Set<T> state = weakSet();
|
||||
|
||||
for (T entry : entries) {
|
||||
BlockPos center = entry.getCenter();
|
||||
int radius = MathHelper.ceil(entry.getRadius());
|
||||
|
||||
if (pos.equals(center)
|
||||
|| (isInRange(pos.getX(), center.getX(), radius)
|
||||
&& isInRange(pos.getZ(), center.getZ(), radius)
|
||||
&& isInRange(pos.getY(), center.getY(), radius)
|
||||
&& center.isWithinDistance(pos, radius))) {
|
||||
state.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static boolean isInRange(int value, int center, int radius) {
|
||||
return value >= center - radius && value <= center + radius;
|
||||
}
|
||||
|
||||
static int toLocalIndex(BlockPos pos) {
|
||||
int x = pos.getX() % 16;
|
||||
int y = pos.getY() % 16;
|
||||
int z = pos.getZ() % 16;
|
||||
return x + (y * 16) + (z * 16 * 16);
|
||||
}
|
||||
|
||||
static<T> Set<T> weakSet() {
|
||||
return Collections.newSetFromMap(new WeakHashMap<>());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue