aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CreeperBeams.java
blob: 28678e1910eeb9c6896d2ae41e29d1c3b9d999c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package me.xmrvizzy.skyblocker.skyblock.dungeon;

import java.util.ArrayList;
import java.util.List;

import org.joml.Intersectiond;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
import me.xmrvizzy.skyblocker.utils.render.RenderHelper;
import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.mob.CreeperEntity;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;

public class CreeperBeams {

    private static final Logger LOGGER = LoggerFactory.getLogger(CreeperBeams.class.getName());

    // "missing, this palette looks like you stole it from a 2018 bootstrap webapp!"
    private static final float[][] COLORS = {
            { 0.33f, 1f, 1f },
            { 1f, 0.33f, 0.33f },
            { 1f, 0.66f, 0f },
            { 1f, 0.33f, 1f },
    };

    private static final int FLOOR_Y = 68;
    private static final int BASE_Y = 74;

    private static ArrayList<Beam> beams = new ArrayList<>();
    private static BlockPos base = null;
    private static boolean solved = false;

    public static void init() {
        Scheduler.INSTANCE.scheduleCyclic(CreeperBeams::update, 20);
        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(CreeperBeams::render);
    }

    private static void update() {

        MinecraftClient client = MinecraftClient.getInstance();
        ClientWorld world = client.world;
        ClientPlayerEntity player = client.player;

        // clear state if not in dungeon
        if (world == null || player == null /* || !Utils.isInDungeons() */) {
            beams.clear();
            base = null;
            solved = false;
            return;
        }

        // don't do anything if the room is solved
        if (solved) {
            player.sendMessage(Text.of("Room is solved"));
            return;
        }

        // try to find base if not found
        if (base == null) {
            base = findCreeperBase(player, world);
            if (base == null) {
                return;
            }
        }

        // try to solve if we haven't already
        if (beams.size() == 0) {

            Vec3d creeperPos = new Vec3d(base.getX() + 0.5, BASE_Y + 3.5, base.getZ() + 0.5);
            ArrayList<BlockPos> targets = findTargets(player, world, base);
            LOGGER.info("targets2 = {}", targets);
            beams = findLines(player, world, creeperPos, targets);
        }

        // check if the room is solved
        if (world.getBlockState(base).getBlock() != Blocks.SEA_LANTERN) {
            solved = true;
        }

        // update the beam states
        beams.forEach(b -> b.updateState(world));
    }

    // find the sea lantern block beneath the creeper
    private static BlockPos findCreeperBase(ClientPlayerEntity player, ClientWorld world) {

        // find all creepers
        List<CreeperEntity> creepers = world.getEntitiesByClass(
                CreeperEntity.class,
                player.getBoundingBox().expand(50D),
                EntityPredicates.VALID_ENTITY);

        if (creepers.size() == 0) {
            return null;
        }

        // (sanity) check:
        // if the creeper isn't above a sea lantern, it's not the target.
        for (CreeperEntity ce : creepers) {
            Vec3d creeperPos = ce.getPos();
            BlockPos potentialBase = BlockPos.ofFloored(creeperPos.x, BASE_Y, creeperPos.z);
            Block block = world.getBlockState(potentialBase).getBlock();
            if (block == Blocks.SEA_LANTERN || block == Blocks.PRISMARINE) {
                player.sendMessage(Text.of(String.format("Base found at %s", potentialBase.toString())));
                return potentialBase;
            }
        }

        player.sendMessage(Text.of("Base not found"));
        return null;

    }

    // find the sea lanterns (and the ONE prismarine ty hypixel) in the room
    private static ArrayList<BlockPos> findTargets(ClientPlayerEntity player, ClientWorld world, BlockPos basePos) {
        ArrayList<BlockPos> targets = new ArrayList<>();

        BlockPos start = new BlockPos(basePos.getX() - 15, BASE_Y + 12, basePos.getZ() - 15);
        BlockPos end = new BlockPos(basePos.getX() + 16, FLOOR_Y, basePos.getZ() + 16);

        for (BlockPos bp : BlockPos.iterate(start, end)) {
            Block b = world.getBlockState(bp).getBlock();
            if (b == Blocks.SEA_LANTERN || b == Blocks.PRISMARINE) {
                targets.add(new BlockPos(bp));
                player.sendMessage(Text.of(String.format("Found a target at %s", bp.toString())));
            }
        }
        LOGGER.info("targets = {}", targets);
        return targets;
    }

    // generate lines between targets and finally find the solution
    private static ArrayList<Beam> findLines(ClientPlayerEntity player, ClientWorld world, Vec3d creeperPos,
            ArrayList<BlockPos> targets) {

        LOGGER.info("targets3 = {}", targets);

        ArrayList<ObjectDoublePair<Beam>> allLines = new ArrayList<>();

        // optimize this a little bit by
        // only generating lines "one way", i.e. 1 -> 2 but not 2 -> 1
        for (int i = 0; i < targets.size(); i++) {
            for (int j = i + 1; j < targets.size(); j++) {
                Beam beam = new Beam(targets.get(i), targets.get(j));
                double dist = Intersectiond.distancePointLine(
                        creeperPos.x, creeperPos.y, creeperPos.z,
                        beam.line[0].x, beam.line[0].y, beam.line[0].z,
                        beam.line[1].x, beam.line[1].y, beam.line[1].z);
                allLines.add(ObjectDoublePair.of(beam, dist));
                player.sendMessage(Text.of(String.format("Adding line from %s to %s", beam.blockOne, beam.blockTwo)));
            }
        }

        // this feels a bit heavy-handed, but it works for now.

        ArrayList<Beam> result = new ArrayList<>();
        allLines.sort((a, b) -> Double.compare(a.rightDouble(), b.rightDouble()));

        while (result.size() < 4 && !allLines.isEmpty()) {
            Beam solution = allLines.get(0).left();
            result.add(solution);
            player.sendMessage(
                    Text.of(String.format("Drawing line from %s to %s", solution.blockOne, solution.blockTwo)));

            // remove the line we just added and other lines that use blocks we're using for
            // that line
            allLines.remove(0);
            player.sendMessage(Text.of(String.format("Deduplicating pre, %d left", allLines.size())));
            allLines.removeIf(beam -> solution.containsComponentOf(beam.left()));
            player.sendMessage(Text.of(String.format("Deduplicating post, %d left", allLines.size())));
        }

        if (result.size() != 4) {
            LOGGER.error("Not enough solutions found. This is bad...");
        }

        return result;
    }

    private static void render(WorldRenderContext wrc) {

        // don't render if solved
        if (solved) {
            return;
        }

        // lines.size() is always <= 4 so no issues OOB issues with the colors here.
        for (int i = 0; i < beams.size(); i++) {
            beams.get(i).render(wrc, COLORS[i]);
        }
    }

    // helper class to hold all the things needed to render a beam
    private static class Beam {

        // raw block pos of target
        public BlockPos blockOne;
        public BlockPos blockTwo;

        // middle of targets used for rendering the line
        public Vec3d[] line = new Vec3d[2];

        // boxes used for rendering the block outline
        public Box outlineOne;
        public Box outlineTwo;

        // state: is this beam created/inputted or not?
        private boolean toDo = true;

        public Beam(BlockPos a, BlockPos b) {
            blockOne = a;
            blockTwo = b;
            line[0] = new Vec3d(a.getX() + 0.5, a.getY() + 0.5, a.getZ() + 0.5);
            line[1] = new Vec3d(b.getX() + 0.5, b.getY() + 0.5, b.getZ() + 0.5);
            outlineOne = new Box(a);
            outlineTwo = new Box(b);
        }

        // used to filter the list of all beams so that no two beams share a target
        public boolean containsComponentOf(Beam other) {
            return this.blockOne.equals(other.blockOne)
                    || this.blockOne.equals(other.blockTwo)
                    || this.blockTwo.equals(other.blockOne)
                    || this.blockTwo.equals(other.blockTwo);
        }

        // update the state: is the beam created or not?
        public void updateState(ClientWorld world) {
            toDo = !(world.getBlockState(blockOne).getBlock() == Blocks.PRISMARINE
                    && world.getBlockState(blockTwo).getBlock() == Blocks.PRISMARINE);
        }

        // render either in a color if not created or faintly green if created
        public void render(WorldRenderContext wrc, float[] color) {
            if (toDo) {
                RenderHelper.renderOutline(wrc, outlineOne, color, 3);
                RenderHelper.renderOutline(wrc, outlineTwo, color, 3);
                RenderHelper.renderLinesFromPoints(wrc, line, color, 1, 2);
            } else {
                RenderHelper.renderOutline(wrc, outlineOne, new float[] { 0.33f, 1f, 0.33f }, 1);
                RenderHelper.renderOutline(wrc, outlineTwo, new float[] { 0.33f, 1f, 0.33f }, 1);
                RenderHelper.renderLinesFromPoints(wrc, line, new float[] { 0.33f, 1f, 0.33f }, 0.75f, 1);
            }
        }

    }

}