1
1
package org .mctourney .autoreferee ;
2
2
3
- import java .util .List ;
4
3
import java .util .LinkedList ;
5
4
import java .util .Collections ;
6
5
import java .util .Comparator ;
7
6
8
7
import org .bukkit .ChatColor ;
9
8
import org .bukkit .Chunk ;
9
+ import org .bukkit .Location ;
10
10
import org .bukkit .World ;
11
11
import org .bukkit .entity .Player ;
12
12
import org .bukkit .command .CommandSender ;
19
19
20
20
public class UHCMatch extends AutoRefMatch
21
21
{
22
- // size of a default
22
+ // size of a default UHC match in blocks
23
23
public static final int DEFAULT_SIZE = 1024 ;
24
24
25
+ // convert players who are eliminated into spectators?
25
26
protected boolean losersBecomeSpectators = false ;
26
27
28
+ // is this world currently accepting players (preload complete?)
27
29
protected boolean acceptingPlayers = false ;
28
30
31
+ // match creator - receives updates on load progress
32
+ private CommandSender creator = null ;
33
+
34
+ // world region (all players share this region)
35
+ private AutoRefRegion matchRegion = null ;
36
+
37
+ // world generation task
29
38
private WorldPregenerationTask worldgen = null ;
30
39
31
- public class WorldPregenerationTask extends BukkitRunnable
40
+ /**
41
+ * Reason for world pregeneration being paused.
42
+ */
43
+ private enum PauseReason
44
+ {
45
+ MEMORY , USER ;
46
+ }
47
+
48
+ private class WorldPregenerationTask extends BukkitRunnable
32
49
{
33
- private static final int CHUNKS_PER_STEP = 1 ;
50
+ private static final int CHUNKS_PER_STEP = 4 ;
51
+
52
+ public static final int MINIMUM_MEMORY = 10 * 1024 ; // 10 kb
53
+
54
+ private static final int PRELOAD_CHUNK_RADIUS = 3 ;
55
+
56
+ private PauseReason pause = null ;
34
57
35
58
private int totalChunks = 0 ;
36
59
37
- public CommandSender recipient = null ;
60
+ public int speed = CHUNKS_PER_STEP ;
61
+
62
+ public long startTime = 0L ;
38
63
39
64
private LinkedList <Chunk > chunkQueue = Lists .newLinkedList ();
40
65
@@ -57,15 +82,19 @@ public WorldPregenerationTask(World w, int radius)
57
82
{
58
83
for (int x = -radius ; x <= radius ; ++x )
59
84
for (int z = -radius ; z <= radius ; ++z )
60
- this .chunkQueue .add (w .getChunkAt (x , z ));
85
+ {
86
+ Chunk chunk = w .getChunkAt (x , z );
87
+ this .chunkQueue .add (chunk );
88
+ }
61
89
62
90
this .totalChunks = this .chunkQueue .size ();
63
91
64
92
// sort chunks on distance to spawn
65
93
Collections .sort (chunkQueue , new ChunkSorter ());
94
+ this .startTime = System .currentTimeMillis ();
66
95
}
67
96
68
- private WorldPregenerationTask (AutoRefRegion region )
97
+ public WorldPregenerationTask (AutoRefRegion region )
69
98
{
70
99
CuboidRegion bound = region .getBoundingCuboid ();
71
100
@@ -77,28 +106,74 @@ private WorldPregenerationTask(AutoRefRegion region)
77
106
78
107
for (int x = bmin .getX (); x <= bmax .getX (); ++x )
79
108
for (int z = bmin .getZ (); z <= bmax .getZ (); ++z )
80
- this .chunkQueue .add (w .getChunkAt (x , z ));
109
+ {
110
+ Chunk chunk = w .getChunkAt (x , z );
111
+ this .chunkQueue .add (chunk );
112
+ }
81
113
82
114
this .totalChunks = this .chunkQueue .size ();
83
115
84
116
// sort chunks on distance to spawn
85
117
Collections .sort (chunkQueue , new ChunkSorter ());
118
+ this .startTime = System .currentTimeMillis ();
86
119
}
87
120
121
+ public void setPaused (boolean pause )
122
+ { this .pause = pause ? PauseReason .USER : null ; }
123
+
88
124
@ Override
89
125
public void run ()
90
126
{
127
+ // just quit immediately if paused by user
128
+ if (pause == PauseReason .USER ) return ;
129
+
130
+ if (Runtime .getRuntime ().freeMemory () < MINIMUM_MEMORY )
131
+ {
132
+ // notify the users that we are pausing the world generation
133
+ if (pause != PauseReason .MEMORY ) this .notify (
134
+ ChatColor .DARK_GRAY + "Waiting for additional memory." );
135
+
136
+ pause = PauseReason .MEMORY ;
137
+ return ;
138
+ }
139
+
140
+ if (pause != null ) this .notify (
141
+ ChatColor .DARK_GRAY + "Resuming world generation..." );
142
+ pause = null ;
143
+
91
144
// load a batch of chunks
145
+ int prc_before = (this .totalChunks - this .chunkQueue .size ()) * 100 / this .totalChunks ;
92
146
for (int i = CHUNKS_PER_STEP ; i > 0 && this .chunkQueue .size () > 0 ; --i )
93
- this .chunkQueue .removeFirst ().load (true );
147
+ {
148
+ // load and unload a chunk to force it to generate
149
+ Chunk chunk = this .chunkQueue .removeFirst ();
150
+ chunk .load (true ); chunk .unload ();
151
+
152
+ if (!UHCMatch .this .acceptingPlayers )
153
+ {
154
+ int distance = Math .max (Math .abs (chunk .getX ()), Math .abs (chunk .getZ ()));
155
+ if (this .chunkQueue .isEmpty ()) UHCMatch .this .worldPreloadComplete ();
156
+ }
157
+ }
158
+
159
+ float taken = (System .currentTimeMillis () - startTime ) / 1000.0f ;
160
+ float workremaining = this .chunkQueue .size () / (float ) this .totalChunks ;
161
+ int sec = (int ) Math .floor (taken * workremaining / (1.0f - workremaining ));
94
162
95
- int percent = (this .totalChunks - this .chunkQueue .size ()) * 100 / this .totalChunks ;
96
- String update = String .format ("%d%% chunks generated" , percent );
97
- UHCMatch .this .broadcast (ChatColor .DARK_GRAY + update );
163
+ int prc_after = (this .totalChunks - this .chunkQueue .size ()) * 100 / this .totalChunks ;
164
+ String update = String .format ("%d%% chunks generated (~%02d:%02d remaining)" , prc_after , sec /60 , sec %60 );
165
+ if (prc_after / 10 != prc_before / 10 ) this .notify (ChatColor .GRAY + update );
166
+ }
167
+
168
+ private void notify (String message )
169
+ {
170
+ message += String .format (" [%d KB remaining]" , Runtime .getRuntime ().freeMemory ());
171
+ UHCMatch .this .broadcast (message );
98
172
99
- if (this .recipient != null && (!(this .recipient instanceof Player )
100
- || ((Player ) this .recipient ).getWorld () != UHCMatch .this .getWorld ()))
101
- this .recipient .sendMessage (ChatColor .DARK_GRAY + update );
173
+ // if the creator is not yet in this world, send update directly
174
+ if (UHCMatch .this .creator instanceof Player &&
175
+ ((Player ) UHCMatch .this .creator ).getWorld () != UHCMatch .this .getWorld ())
176
+ UHCMatch .this .creator .sendMessage (message );
102
177
}
103
178
}
104
179
@@ -112,8 +187,45 @@ public UHCMatch(World world, int size, boolean tmp)
112
187
// chunks), then divide by 2 to convert diameter to radius.
113
188
this .worldgen = new WorldPregenerationTask (world , size /32 );
114
189
this .worldgen .runTaskTimer (AutoRefereeUHC .getInstance (), 0L , 20L );
190
+
191
+ this .addStartRegion (new CuboidRegion (this .getWorld (),
192
+ -20 , 20 , -20 , 20 , 0 , this .getWorld ().getMaxHeight ()));
193
+ this .setWorldSpawn (new Location (this .getWorld (),
194
+ 0 , this .getWorld ().getHighestBlockYAt (0 , 0 ), 0 ));
195
+
196
+ this .addRegion (this .matchRegion = new CuboidRegion (this .getWorld (),
197
+ -size /2 , size /2 , -size /2 , size /2 , 0 , this .getWorld ().getMaxHeight ()));
198
+ }
199
+
200
+ @ Override
201
+ protected void loadWorldConfiguration ()
202
+ {
115
203
}
116
204
117
- public void setNotificationRecipient (CommandSender recp )
118
- { if (this .worldgen != null ) this .worldgen .recipient = recp ; }
205
+ @ Override
206
+ public void saveWorldConfiguration ()
207
+ {
208
+ }
209
+
210
+ public UHCMatch setCreator (CommandSender creator )
211
+ { this .creator = creator ; return this ; }
212
+
213
+ public UHCMatch setLoadSpeed (int speed )
214
+ { if (this .worldgen != null ) this .worldgen .speed = speed ; return this ; }
215
+
216
+ public void pauseWorldGen ()
217
+ { if (this .worldgen != null ) this .worldgen .setPaused (true ); }
218
+
219
+ public void unpauseWorldGen ()
220
+ { if (this .worldgen != null ) this .worldgen .setPaused (false ); }
221
+
222
+ private void worldPreloadComplete ()
223
+ {
224
+ if (this .creator instanceof Player )
225
+ this .joinMatch ((Player ) this .creator );
226
+ this .acceptingPlayers = true ;
227
+
228
+ this .worldgen .cancel ();
229
+ this .worldgen = null ;
230
+ }
119
231
}
0 commit comments