· 6 years ago · Jun 24, 2019, 05:34 AM
1package com.michaelbmorris.generator;
2
3import java.io.File;
4import java.sql.Connection;
5import java.sql.DriverManager;
6import java.sql.PreparedStatement;
7import java.sql.ResultSet;
8import java.sql.SQLException;
9import java.sql.Statement;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.Random;
13
14import org.bukkit.Chunk;
15import org.bukkit.World;
16import org.bukkit.generator.BlockPopulator;
17
18/**
19 * Populates a chunk once all surrounding chunks within a radius are generated.
20 */
21public abstract class SafeBlockPopulator extends BlockPopulator {
22 private static final HashSet<String> DatabaseUrls = new HashSet<String>();
23 private static final int DEFAULT_RADIUS = 1;
24
25 /*
26 * Statuses
27 */
28
29 private static final int STATUS_GENERATED = 0;
30 private static final int STATUS_POPULATED = 1;
31
32 /*
33 * SQL
34 */
35
36 private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS chunkCoordinate (x INTEGER NOT NULL, z INTEGER NOT NULL, status INTEGER NOT NULL, PRIMARY KEY (x, z));";
37 private static final String DELETE_GENERATED_CHUNKS = "DELETE FROM chunkCoordinate WHERE status = " + STATUS_GENERATED + ";";
38 private static final String GET_CHUNK = "SELECT * FROM chunkCoordinate WHERE x = ? AND z = ?;";
39 private static final String GET_GENERATED_CHUNKS = "SELECT * FROM chunkCoordinate WHERE status = " + STATUS_GENERATED + ";";
40 private static final String INSERT_CHUNK = "INSERT INTO chunkCoordinate (x, z, status) VALUES (?, ?, 0);";
41 private static final String RESET_CHUNK = "UPDATE chunkCoordinate SET status = " + STATUS_GENERATED + " WHERE x = ? AND z = ?;";
42 private static final String SET_CHUNK_POPULATED = "UPDATE chunkCoordinate SET status = " + STATUS_POPULATED + " WHERE x = ? AND z = ?;";
43
44 private static ResultSet getGeneratedChunks(Connection connection) throws SQLException {
45 PreparedStatement getGeneratedChunks = connection.prepareStatement(GET_GENERATED_CHUNKS);
46 return getGeneratedChunks.executeQuery();
47 }
48
49 private static void insertOrResetChunk(int x, int z, Connection connection) throws SQLException {
50 PreparedStatement getChunk = connection.prepareStatement(GET_CHUNK);
51 getChunk.setInt(1, x);
52 getChunk.setInt(2, z);
53 ResultSet chunk = getChunk.executeQuery();
54
55 if (!chunk.next()) {
56 PreparedStatement insertChunk = connection.prepareStatement(INSERT_CHUNK);
57 insertChunk.setInt(1, x);
58 insertChunk.setInt(2, z);
59 insertChunk.executeUpdate();
60 } else {
61 PreparedStatement resetChunk = connection.prepareStatement(RESET_CHUNK);
62 resetChunk.setInt(1, x);
63 resetChunk.setInt(2, z);
64 resetChunk.executeUpdate();
65 }
66 }
67
68 private final HashMap<String, Chunk> chunks;
69 private final int radius;
70 private final String databaseUrl;
71
72 /**
73 * Creates a SafeBlockPopulator with the default radius of 1.
74 */
75 protected SafeBlockPopulator(String databaseUrl, boolean isNew) {
76 this(databaseUrl, isNew, DEFAULT_RADIUS);
77 }
78
79 /**
80 * Creates a SafeBlockPopulator with a specified radius.
81 */
82 protected SafeBlockPopulator(String databaseUrl, boolean isNew, int radius) {
83 if (databaseUrl == null || databaseUrl.isEmpty()) {
84 throw new IllegalArgumentException("Inheriting block populator must supply a URL for the SQLite database.");
85 }
86
87 if (DatabaseUrls.contains(databaseUrl)) {
88 throw new IllegalArgumentException("Each populator must have a unique database URL.");
89 }
90
91 if (radius < 1) {
92 throw new IllegalArgumentException("The radius must be at least 1.");
93 }
94
95 DatabaseUrls.add(databaseUrl);
96 this.radius = radius;
97 this.databaseUrl = "jdbc:sqlite:" + databaseUrl;
98
99
100 if (isNew) {
101 File database = new File(databaseUrl);
102 database.delete();
103 }
104
105 try (Connection connection = DriverManager.getConnection(this.databaseUrl)) {
106 Statement statement = connection.createStatement();
107 statement.execute(CREATE_TABLE);
108 statement.executeUpdate(DELETE_GENERATED_CHUNKS);
109 } catch (SQLException e) {
110 System.out.println(e.getMessage());
111 }
112
113 chunks = new HashMap<String, Chunk>();
114 }
115
116 @Override
117 public final void populate(World world, Random random, Chunk chunk) {
118 int x = chunk.getX();
119 int z = chunk.getZ();
120 chunks.put(x + " " + z, chunk);
121
122 try (Connection connection = DriverManager.getConnection(databaseUrl)) {
123 insertOrResetChunk(x, z, connection);
124 attemptPopulate(world, random, connection);
125 } catch (SQLException e) {
126 System.out.println(e.getMessage());
127 }
128 }
129
130 private void attemptPopulate(World world, Random random, Connection connection) throws SQLException {
131 ResultSet unpopulatedChunks = getGeneratedChunks(connection);
132 PreparedStatement setChunkPopulated = connection.prepareStatement(SET_CHUNK_POPULATED);
133
134 while (unpopulatedChunks.next()) {
135 if (unpopulatedChunks.getInt("status") == STATUS_GENERATED) {
136 int chunkX = unpopulatedChunks.getInt("x");
137 int chunkZ = unpopulatedChunks.getInt("z");
138
139 if (hasSurrounding(connection, chunkX, chunkZ)) {
140 Chunk chunk;
141 String key = chunkX + " " + chunkZ;
142
143 if (chunks.containsKey(key)) {
144 chunk = chunks.get(key);
145 chunks.remove(key);
146 } else {
147 chunk = world.getChunkAt(chunkX, chunkZ);
148 }
149
150 actuallyPopulate(world, random, chunk);
151 setChunkPopulated.setInt(1, unpopulatedChunks.getInt("x"));
152 setChunkPopulated.setInt(2, unpopulatedChunks.getInt("z"));
153 setChunkPopulated.executeUpdate();
154 }
155 }
156 }
157 }
158
159 private boolean hasSurrounding(Connection connection, int x, int z) throws SQLException {
160 PreparedStatement getChunk = connection.prepareStatement(GET_CHUNK);
161 ResultSet resultSet;
162
163 for (int i = 0 - radius; i <= radius; i++) {
164 for (int j = 0 - radius; j <= radius; j++) {
165 getChunk.setInt(1, x + i);
166 getChunk.setInt(2, z + j);
167 resultSet = getChunk.executeQuery();
168
169 if (!resultSet.next()) {
170 return false;
171 }
172 }
173 }
174
175 return true;
176 }
177
178 /**
179 * Actually populates this chunk once all surrounding chunks within the radius
180 * are generated.
181 */
182 protected abstract void actuallyPopulate(World world, Random random, Chunk chunk);
183}