Compare commits

...

228 Commits

Author SHA1 Message Date
00d11f837e Added Java extension pack for devcontainer.
All checks were successful
ydeng/islandsurvivalcraft/pipeline/head This commit looks good
2024-08-05 18:28:04 +00:00
1a7a80d74c Added unit test report.
All checks were successful
ydeng/islandsurvivalcraft/pipeline/head This commit looks good
2024-07-22 05:07:01 +00:00
6a37c9c90b Added devcontainer and updated pipeline.
All checks were successful
ydeng/islandsurvivalcraft/pipeline/head This commit looks good
2024-07-22 04:54:33 +00:00
4487a2cb16 Merge branch 'master' into develop
Some checks failed
ydeng/islandsurvivalcraft/pipeline/head There was a failure building this commit
2024-07-21 04:17:12 +00:00
6eff20bb86 Fixed potential null reference.
Some checks failed
RealYHD/islandsurvivalcraft/pipeline/head There was a failure building this commit
ydeng/islandsurvivalcraft/pipeline/head There was a failure building this commit
2022-12-08 16:53:04 +00:00
41938de11b Jenkinsfile now displays test results and archives build. 2022-12-04 05:50:46 +00:00
21ce300b08 Added Jenkinsfile and updated package naming. 2022-12-04 05:24:43 +00:00
1702fe7472 Updated server and minor value changes made. 2020-11-17 13:00:53 -06:00
94dbda6ac6 Map is now toggle instead of crafting. 2020-09-09 13:57:08 -05:00
bde35f8de8 Updated to 1.16.2. 2020-09-09 13:56:52 -05:00
1b4fbd05f4 Increased world value cache and changed map rendering. 2020-05-17 20:23:57 -05:00
df605880b7 Optimized the world island information building.
Island information building is now priority based.

Island information building uses distanced previously built information.

Changed thread safe cache to be more multi-purpose.
2020-05-17 18:10:34 -05:00
a032f007c1 Changed cache value's key to be final. 2020-05-17 14:57:09 -05:00
88d3237d4e Gravelly mountains are less mountainous and don't have dirt. 2020-05-17 01:12:02 -05:00
c420a610a3 No longer waits for island to complete scanning. 2020-05-17 01:11:35 -05:00
69d10d8d5f Clean up in preparation for persistence system. 2020-05-16 19:47:49 -05:00
b1487b500d Changed command name and added size command.
Refactoring performed as well.
2020-05-16 18:50:00 -05:00
f95d0232b9 Made sandy places have more layers of sand. 2020-05-16 18:25:27 -05:00
262a83c7e9 Improved code structure for shading map.
World layering improved by adjusting desert sand thickness and lowering overall mountain peak height.
2020-05-16 18:23:54 -05:00
7d985f4489 Improved the varied item structure.
Map now shows islands within map range without needing player to travel there.
2020-05-16 17:58:51 -05:00
1207693a1d Removed useless import. 2020-05-16 16:21:03 -05:00
56d636c862 Changed load thread count to 2. 2020-05-16 15:46:09 -05:00
3514434fbb Changed chunk island information load structure. 2020-05-16 15:45:44 -05:00
e2f476cd82 Primitive map based island highlighting functioning. 2020-05-16 14:33:40 -05:00
ec006ffbb2 Added docs and made temperature for islands be based on origin. 2020-05-15 01:56:33 -05:00
63ecfedf71 Added better origin highlighting to highlight command. 2020-05-15 01:55:58 -05:00
ea4ba47427 Changed island information creation to be async.
Flooder no longer uses it's own interface for flooding.
2020-05-14 15:07:46 -05:00
78f0a846da A good base for an island highlighting map. 2020-05-13 12:07:16 -05:00
93ea0d3153 Cleaned up imports and unused code. 2020-05-11 17:58:51 -05:00
bd7f591f36 Island info data structure added.
In addition: changed the world info management to use world name as key.
2020-05-11 17:46:50 -05:00
44518270e1 Deleting worlds is now a separate task. 2020-05-11 17:46:44 -05:00
19552f1a2b Made command system and DFS usage more intuitive. 2020-05-11 12:50:32 -05:00
73adefdc7f Made highlight command update quicker. 2020-05-09 22:01:30 -05:00
4f18f25c01 Island highlighting debug now works, albeit, not favorable.
Reworked DFS as well to suit a broader spectrum of needs.

Reworked other classes for easier use.
2020-05-09 21:56:27 -05:00
19f387def9 Merge branch 'feature/command-system' into develop. 2020-05-09 00:31:16 -05:00
d8c0961e2b (Re)added shorelines and fixed both deep and transitional ocean generation. 2020-05-09 00:26:50 -05:00
759a604e4e Added command system structure. 2020-05-09 00:25:43 -05:00
3ab1e05569 Reorganized and added author tag to plugin.yml. 2020-05-07 16:46:30 -05:00
dd89266b4e Preliminary new unique biome generator. 2020-05-07 11:52:23 -05:00
a5cf40913b Implemented better biome system. 2020-05-06 20:05:20 -05:00
d1cae3bf7f Island hashcode feature added. 2020-05-05 16:57:38 -05:00
778b334ceb Implemented concept of island origin coords.
Useful for hashing an island potentially.
2020-05-05 16:49:21 -05:00
46f393c30e Refactoring. 2020-05-05 12:53:38 -05:00
ce4a5899cc Added enumerators for organizing biome information.
Currently unused.
2020-05-05 11:35:18 -05:00
9d77fe3bb6 More controlled chunk existence checking. 2020-05-04 23:20:54 -05:00
10022876d5 Fixed potential biome continuity logic mistake. 2020-05-04 20:35:44 -05:00
785f7a1ea3 Added generation modes. 2020-05-04 20:27:44 -05:00
479e28c090 World info now persist through reloads. 2020-05-04 16:57:57 -05:00
1167efbe4e Large package structure refactoring. 2020-05-04 16:41:46 -05:00
7fb858c7ca Refactoring. 2020-05-04 16:20:09 -05:00
832603cdd7 Value adjustements. 2020-05-04 16:01:45 -05:00
483b8b2ecb Added a world information system. 2020-05-04 15:54:41 -05:00
5ddc11258c Removed snowy biomes from the list of special layered biomes. 2020-05-04 14:08:53 -05:00
3a86bd1a12 Wrote a world information system and management for them.
Also large amounts of refactoring done.

The tests implement the new changes.

Island map test clears the cache now.
2020-05-04 12:53:09 -05:00
fc9aca3bb1 Overworld general terrain generation complete.
Added warning supression for unused as it may be used in the future.
2020-05-03 22:45:42 -05:00
a077ec6c0e Refactoring. 2020-05-03 17:49:48 -05:00
740ca812ae World generation feels more natural now.
Mountain tips after a certain height is stone.

Deep ocean biomes are now actually deeper.

Temperature map no longer calculates in groups of 4.
2020-05-03 16:44:43 -05:00
085827264b Biome selector updates.
Now uses a given seed to produce a biome.

Chooses the deep equivalent of a requested ocean given a parameter.
2020-05-03 16:41:40 -05:00
9aaaede19f Added two new utility datatypes. 2020-05-03 16:38:23 -05:00
dc3ccb6ff0 Added height modifier to height shader as well as difference between deep oceans and normal oceans. 2020-05-03 12:16:37 -05:00
1db4811d66 Height shader and snow blocks work. 2020-05-03 11:42:24 -05:00
d4c0fa259c Further toying with values.
Primitive generation working.
2020-05-02 22:40:56 -05:00
79edd9bf29 Several changes to chunk generation operation.
Multiple values adjusted.

Pipeline changed.

Changed server start script to not wait for debugger.
2020-05-02 19:22:45 -05:00
d8d0744350 Registered chunk loader into bukkit event listener. 2020-05-02 19:21:32 -05:00
486a0f837f Basic terrain shaping added.
World height shader "shades" the primitive island height giving variation to land and sea.

World layer shader "shades" the primitive islands with varying layers dependent on biomes.

Mapper values was changed to perform the more targetted task of generating island locations and masks.

Biome selector now takes a random at call to help with seed consistency (due to threading, this may still be problematic).

Chunk generator implements the new changes.

Tests implement new changes.
2020-05-02 00:06:58 -05:00
a6cebe703b Now generating primitive islands.
Favourable island mapper values.
2020-05-01 18:37:06 -05:00
e4597538b2 Chunk generation now generates sea and island.
Adjusted values for the island mapper.
Island world mapper now takes a seed instead of a random object.
Refactoring of method names.

Reduced cache sizes used by terrain generation.

Reworked height shader to not use it's own noise generator.

Updated tests implementing changes respectively.
2020-04-30 21:42:03 -05:00
d0701ce63d Changed async execution setup.
Two beta threads with lower thread priority and one alpha with normal thread priority.

Implemented use of both in chunk loading.
2020-04-30 16:26:11 -05:00
9b111cc977 Cache value now has convenience constructor.
Implemented the changes.

Also placed putting data in hashmap priority so for faster access.
2020-04-30 15:50:14 -05:00
3e80d1e67d Height shader now takes random object in constructor.
Removed old height shader code having to do with this change.

Implmemented changes to chunk generator.
2020-04-30 15:11:35 -05:00
0eb2a13d14 Adding to usage list before to hashmap for better concurrency. 2020-04-30 14:53:48 -05:00
50ee956ce7 Removed some debugging code, removed instance of double locking. 2020-04-30 14:51:14 -05:00
387393632d Cache now no longer locked from reading while writing.
Fixed another case where writing is followed by consecutive reading.
2020-04-30 14:40:28 -05:00
c60e94728e Added fix for another potential multithreaded case for cache.
Case: consecutive gets.
2020-04-30 01:03:52 -05:00
83297a1d10 Improved cache performance by removing locks.
These sync locks can be removed because of fixing the multithread cases in the previous commit.
2020-04-30 00:20:32 -05:00
f7eb94ae39 Corrected false assumption in caching logic. 2020-04-30 00:09:58 -05:00
f637e79ea5 Refactoring. 2020-04-29 23:13:15 -05:00
64a3e3db27 Slightly faster. 2020-04-29 22:53:28 -05:00
1b80c26348 Clear cache after each test. 2020-04-29 21:29:47 -05:00
21e8e52b9c Changed test parameters, added large sized test. 2020-04-29 21:26:22 -05:00
c963ad140f Chunk generator asyncronously generates block values.
Also added parallel capable override.
2020-04-29 20:12:54 -05:00
538b1c3cae Changed some multithreaded tests to fail if exception is thrown. 2020-04-29 19:53:02 -05:00
c63298bcaa Fixed tests environments.
Added basic islandworldmapper to test for block value consistency.

UniBiomeGenerator tests now produce new random object with same seed.
2020-04-29 19:40:02 -05:00
c6fd9a833c Revert to "Island world chunk generator now pre-generates island values."
This reverts commit e40bf74cb8.
2020-04-29 19:14:20 -05:00
a119e6f596 Removed unneccesary blocking call. 2020-04-29 16:31:38 -05:00
4c93e85078 Fixed error.
The order of checking for null prioritizing was swapped.
2020-04-29 16:28:24 -05:00
6f27e52c6b Improved locking scheme of cache. 2020-04-29 16:25:27 -05:00
e40bf74cb8 Island world chunk generator now pre-generates island values.
Also implements the new biome selector.
2020-04-29 15:06:24 -05:00
5b1bb4288d Biome selector no longer requires random parameter.
Implemented new biome selector changes.
2020-04-29 15:06:14 -05:00
c2fb0ffea8 Added executor to utilities. 2020-04-29 14:00:30 -05:00
14d0375c7e Added threading for building the biome grid. 2020-04-29 13:11:07 -05:00
a102f53365 Changed locking positions. 2020-04-29 13:05:04 -05:00
d62bc696a5 Changed a test to use JUnit's timeout annotation. 2020-04-28 23:37:09 -05:00
b2efe76e27 Renamed test to better describe it. 2020-04-28 23:30:07 -05:00
437a3acdcc Added a more scattered test.
This may be a more realistic representation.
2020-04-28 23:22:43 -05:00
8595ab0c4e Biome generator change in way of caching island info.
Biome cache now stores a 4 biome values: current, main, shore, and shallow.
2020-04-28 23:15:29 -05:00
9c8dd377dd Don't run tests again for packaging.
Since testing is done in a separate task.
2020-04-28 18:13:55 -05:00
7311327d8d Turning cache fairness off. 2020-04-28 17:40:28 -05:00
9883a98aa0 Testing with fairness turned on for cache. 2020-04-28 17:37:10 -05:00
e7f5558aa5 Added doc and changed accessibility of support class. 2020-04-28 17:35:08 -05:00
7a2a2d4ca6 Renamed some tests and changed amount tested. 2020-04-28 17:18:36 -05:00
b49f50f728 Increased chunk count to 6000. 2020-04-28 17:13:11 -05:00
61a6bbaa4f Cleaned up cache tests. 2020-04-28 17:12:36 -05:00
ca1e1c0975 Changed import to latest JUnit import. 2020-04-28 16:57:09 -05:00
b30d689183 Reworked thread safe cache solution.
Also performed refactoring.

And added some threaded tests.
2020-04-28 16:53:23 -05:00
7c073cdc6e Preventing flooder from checking same node.
Added another flooder test.
2020-04-27 23:17:54 -05:00
85ec44a4d7 Flood algorithm added and (basic) tests performed. 2020-04-27 21:10:55 -05:00
2a0f26f8bc Reworked operation order for DFS. 2020-04-27 20:35:32 -05:00
250334e348 Fixed cache locks as they were swapped.
Must've been real tired or something while writing that.
2020-04-27 17:06:10 -05:00
3675074d00 Tested generator system. 2020-04-27 16:06:01 -05:00
49f26f7901 Fixed conversion from world to chunk coords.
Added test that catches this case.
2020-04-27 15:14:33 -05:00
26ebf7af33 Fixed test case. 2020-04-27 14:25:10 -05:00
1e8dc8019a Current iteration of work.
Attempted at fixing the coordinate converter again.

Added more applicable tests for them.

Changing to a more stateless design for biome generator.

Refactored correcting package name to fit conventions.
2020-04-27 14:23:57 -05:00
59d78c9754 Fixed toString() format. 2020-04-27 13:58:39 -05:00
3efc6acf0b Fixed the utilities world to local non perfect positive case. 2020-04-27 13:53:29 -05:00
ec27a9bc10 Fixed perfect chunk coordinates in utilities.
Added respective tests to ensure this doesn't break.
2020-04-27 13:44:49 -05:00
39381f07f9 Fixed incorrect method call.
Added tests to make sure that this doesn't happen.
2020-04-27 13:21:04 -05:00
6ac686b86c Refactoring with different heavy changes to biome generator.
Attempt at making it my async compatible.
2020-04-26 23:07:25 -05:00
0886412cfb Point2 can now overrides toString(). 2020-04-26 20:56:44 -05:00
4f411c42fb Added conversion from world coordinates to utilities.
Respective tests also added.
2020-04-26 20:56:10 -05:00
16618ef439 Added chunk load listener. 2020-04-26 18:23:20 -05:00
b63c39b380 Changed biome cache to cache biomes instead of arrays. 2020-04-26 17:40:18 -05:00
df10a2c298 DFS can now take Point2 as parameters for its searches. 2020-04-26 17:28:57 -05:00
e8340bfc91 DFS's should now run locally on each thread. 2020-04-26 16:47:22 -05:00
faa2cfd79f Changed order of operations. 2020-04-26 16:29:03 -05:00
b492017ef2 Changed to using the constants. 2020-04-26 16:06:26 -05:00
ed4996ff9f Tuned cache values. 2020-04-26 16:05:52 -05:00
b769c83be1 Increased cache size. 2020-04-26 15:42:22 -05:00
2b05dfe4cc Changed to use of read and write locks in cache.
Also added specifics to constructor for the hash map.
2020-04-26 15:42:02 -05:00
8d392cd779 Script now deletes all worlds instead of just one. 2020-04-26 15:09:32 -05:00
02f7de56ed Minor changes to alternator. 2020-04-26 15:09:13 -05:00
60217e4672 World height shader should now be less abrupt. 2020-04-26 15:03:34 -05:00
925296f326 Minor improvement to cache by reducing area under lock. 2020-04-26 02:04:29 -05:00
98e4265db7 Attempted at making all things world gen thread proof.
Specifically, caching is tested thread proof.
2020-04-26 01:28:45 -05:00
f20515fd45 Reworked cache, this time with less hashmap accesses. 2020-04-25 22:32:25 -05:00
dcec9fd7e7 Made sure occurrences are properly tracked. 2020-04-25 18:16:18 -05:00
4b6cfacc47 Fixed clear cache method.
Alternator currently uses threads as identifier (TEMPORARY).
2020-04-25 17:59:30 -05:00
1e0d63e562 Added a magnitiude addition helper function. 2020-04-25 17:54:40 -05:00
629660c8fc Revamped caching again, this time, without sorting.
Implemented cache changes.
2020-04-25 17:54:25 -05:00
91644f9ba0 New cache system.
Caching itself is tested.

Implemented into classes (untested).
2020-04-25 12:06:41 -05:00
1c5baf8762 Removed unnessecary hashmap retrieval from cache. 2020-04-25 02:23:48 -05:00
a7db7d289a Caching now has a fresh importance factor. 2020-04-25 01:32:01 -05:00
28b9a8005c Gave newly cached items usage greater than the lowest. 2020-04-25 01:15:38 -05:00
a412986044 New iteration of biome generator.
Untested.
2020-04-25 00:18:53 -05:00
e93f61c055 Added convenience constructor. 2020-04-25 00:18:29 -05:00
34ccc1e7de Semi-functioning biome generator.
It's faster now, but sometimes hangs, and is still pretty slow.
2020-04-24 22:11:52 -05:00
d0facd268a Fixed world generator not saving generated values. 2020-04-24 22:10:59 -05:00
c5e3c08546 Refactoring. 2020-04-24 17:50:18 -05:00
c415d15a7b Changed load script to work with any version. 2020-04-24 16:02:03 -05:00
491483fc55 Updated .gitignore and untracked files. 2020-04-24 15:49:25 -05:00
8517a20f9f Reworked biome per island generator. 2020-04-24 15:15:44 -05:00
60ab555c57 Added caching solution to temperature map generator. 2020-04-24 15:15:44 -05:00
6c8389d60e Lowered amount of octaves. 2020-04-24 15:15:44 -05:00
2e67cb90ca Increased max cache size and fixed caching on method. 2020-04-24 15:15:44 -05:00
2d9163f5c2 Added two utility functions. 2020-04-24 15:15:44 -05:00
ffc01f0ddf Minor doc and method name changes.
Respective tests updated.
2020-04-24 15:15:24 -05:00
8acc653b5b Attempted to make cache system thread compatible. 2020-04-24 15:12:32 -05:00
88c645e2a5 Some value changes.
Largely untested.
2020-04-24 00:02:11 -05:00
1b5a03d72a Added value caching to world mapper.
Largely untested.
2020-04-24 00:01:03 -05:00
be19db1f9b Separated node class from DFS class. 2020-04-23 23:07:49 -05:00
de3b3a614a Changed DFS order of operation. 2020-04-23 21:16:43 -05:00
e45d642791 Added and changed server scripts.
tasks.json updated accordingly.

Also added a convenience normal start script.
2020-04-23 21:14:18 -05:00
cbe8c30ece Added local_tools folder to .gitignore. 2020-04-23 20:17:57 -05:00
cd864c4d8b Removed old start script. 2020-04-23 20:16:37 -05:00
e50a983b25 Added simple caching system. 2020-04-23 20:15:45 -05:00
5ffea1aa08 DFS max limit check change. 2020-04-23 11:51:54 -05:00
74f5d25dbc Changed furthest island adaption search to 2048 blocks. 2020-04-23 00:02:14 -05:00
e936dff934 Fixed logical fallacies for world mapper.
Method name changed (refactor) as well.
2020-04-23 00:01:35 -05:00
b1699f8f69 Untracked crash logs. 2020-04-22 22:24:02 -05:00
421af33658 Changed test-server scripts and reconfigured tasks.
VSCode task output and cleanup config changed.

Added some more verbosity to scripts.
2020-04-22 21:16:43 -05:00
7d07332ef5 World mapper and biome generator reworked;
World mapper now produces normalized values.

The world height shader uses the values to decide on heights.

The current biome generator was reworked (untested).

Also renamed (refactoring) the mapper.
2020-04-22 21:15:37 -05:00
4ed9d4d272 Tested DFS direction assisted search. 2020-04-22 19:41:25 -05:00
ed48e164b6 Cleaned up DFS testing.
Changed all maps to files.
2020-04-22 18:49:06 -05:00
c3998150f9 Added more tests.
Added a very large map for tests.
Added testing for DFS limiter.
2020-04-22 18:41:33 -05:00
44c6880e07 Added a maximum node count to DFS. 2020-04-22 18:40:17 -05:00
39b5d0e778 Added map file reading to the DFS test. 2020-04-22 17:51:11 -05:00
fd41eff3a0 Minor change in how the commands are executed. 2020-04-22 17:50:36 -05:00
c8715e3dbb DFS target finding will now prioritize user given end node.
Meaning if the general area of the target is known, DFS can use it.
2020-04-22 17:50:19 -05:00
690abbdbe0 Changed script to show running window.
Also added various checks for terminating process.
2020-04-22 03:39:19 -05:00
919c5201e4 Updated plugin.yml to reflect dependency changes. 2020-04-22 03:28:00 -05:00
73b9225a69 Added proper testing server scripts.
Integrated scripts into VSCode metadata.
2020-04-22 03:26:46 -05:00
c902e520c5 Added files to .gitignore. 2020-04-22 03:25:24 -05:00
c3d5ba1683 Added large test map to DFS find end tests. 2020-04-21 23:41:50 -05:00
5fccfd5d38 Changed to Java 11. 2020-04-21 23:24:44 -05:00
6d552820ee DFS instantiation in biome generator is now correct.
Performed refactoring

Began work on world height shader (untested).
2020-04-21 20:59:03 -05:00
e2c3e46849 Refactor. 2020-04-21 19:32:54 -05:00
e306d828e9 Added the shape shader, also refactored. 2020-04-21 19:31:09 -05:00
66673e2f52 Made sure alternator was semi-multithread safe. 2020-04-21 17:01:53 -05:00
533a758799 Finished untested biome generator.
Also:
Changed plugin structure to adapt better to server API
Refactored by reorganizing packages.
2020-04-21 16:40:29 -05:00
35e52ae61b Refactoring. 2020-04-21 11:40:23 -05:00
67a93fff5b Reworked biome generation and used DFS for continuity.
Also made changes to structure to better support Bukkit's structure.
2020-04-21 02:59:19 -05:00
4fc824f94f Removed the bedrock metadata system. 2020-04-21 02:55:18 -05:00
d44b059457 Changed DFS algorithm to use hashset. 2020-04-21 00:42:00 -05:00
f5e3435d8b Added a target coordinate finder.
Implemented DFS name changes.
2020-04-20 23:26:44 -05:00
cf6a371428 Added some getters to the metadata pack. 2020-04-20 20:26:04 -05:00
c860ba1a15 Tested biome selector.
Also deleted launch.json.
2020-04-20 19:13:01 -05:00
623ee1a544 Removed use of mountain edge. 2020-04-20 19:12:29 -05:00
c6001bdfea Refactored and updated to JUnit 5. Fixed biome selector. 2020-04-20 18:57:37 -05:00
78cd32106f Added another invalid map test case to DFS tests. 2020-04-20 17:37:14 -05:00
81fb18ab3d Tested DFS search algorithm. 2020-04-20 17:34:24 -05:00
a3024f6e91 Implemented changes to world generator and plugin class. 2020-04-20 16:28:30 -05:00
5b0942202a Finished untested biome per island generator. 2020-04-20 16:27:53 -05:00
44f183cca0 Improved island map generator.
Added documentation as well.

Untested.
2020-04-20 16:27:15 -05:00
6cf1f6dcc1 Deleted old code. 2020-04-20 16:26:28 -05:00
d506b60f07 Added simple bedrock layer generator. 2020-04-20 16:25:57 -05:00
4b9c5ca440 Implemented DFS algorithm.
Untested.
2020-04-20 16:24:49 -05:00
85dd6e6e3d Added bedrock metadata.
Stores:
* A temporary island id.
* The main biome of the island.
* The shore biome of the island.
* The temperature of the island.
* The owner UUID of the island.
2020-04-20 16:24:11 -05:00
c9dd2a1c4b Changed temperature generator to return float. 2020-04-20 16:18:33 -05:00
37aa598d8f Adding to plugin base.
Untested.
2020-04-19 20:32:22 -05:00
943feece04 Added a few more generators to aid map gen.
Untested.
2020-04-19 20:32:16 -05:00
4b644159cb Clean up. 2020-04-19 20:31:21 -05:00
2ace74972e Added biome generator and interface.
untested.
2020-04-19 20:23:10 -05:00
3c41444dfa added selector that helps with selecting which biome to generate.
Untested.
2020-04-19 20:22:48 -05:00
23f761c36f Added utilities and a hashmap inverter. 2020-04-19 18:17:17 -05:00
08a23c1bf2 Ran with some changed values. 2020-04-19 03:24:38 -05:00
1dbb329dc1 Basic terrain generator implemented.
Needs fine tuning.
2020-04-19 03:13:08 -05:00
784edb0ecf Untracking WorldGuard config files. 2020-04-19 00:59:34 -05:00
4efd583e56 Added more test server stuff to .gitignore. 2020-04-19 00:56:50 -05:00
66480084d7 Basic island generation occurring. 2020-04-18 23:22:21 -05:00
823c139d2d Server properties. 2020-04-18 22:17:34 -05:00
be133a11c4 Basic structuring and added WorldGeneratorAPI.
Also fixed typo.
2020-04-18 22:07:56 -05:00
ccbb9380e5 Further updated README. 2020-04-18 02:04:52 -05:00
63b90f6512 Added text regarding nether in README. 2020-04-18 01:52:56 -05:00
fbb3f0441b Deleted extra line at end of readme. 2020-04-18 01:39:26 -05:00
d796fbbe59 Fixed typo. 2020-04-18 01:35:53 -05:00
22429cd759 Changed name for Zany's Minigames. 2020-04-18 01:33:24 -05:00
4228ef102d Initial README. 2020-04-18 01:27:21 -05:00
86d63bfee5 Created template bukkit plugin.
Generated using maven.
2020-04-18 00:08:05 -05:00
80 changed files with 4859 additions and 69 deletions

View File

@ -1,11 +0,0 @@
FROM mcr.microsoft.com/devcontainers/anaconda:1-3
# Copy environment.yml (if found) to a temp location so we update the environment. Also
# copy "noop.txt" so the COPY instruction does not fail if no environment.yml exists.
COPY environment.yml* .devcontainer/noop.txt /tmp/conda-tmp/
RUN if [ -f "/tmp/conda-tmp/environment.yml" ]; then umask 0002 && /opt/conda/bin/conda env update -n base -f /tmp/conda-tmp/environment.yml; fi \
&& rm -rf /tmp/conda-tmp
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>

View File

@ -1,31 +1,31 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/anaconda
// README at: https://github.com/devcontainers/templates/tree/main/src/java
{
"name": "Anaconda (Python 3)",
"build": {
"context": "..",
"dockerfile": "Dockerfile"
"name": "Java",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/java:1-11-bullseye",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installMaven": "true",
"installGradle": "true"
},
"ghcr.io/devcontainers/features/powershell:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"vscjava.vscode-java-test",
"vscjava.vscode-java-dependency",
"vscjava.vscode-maven",
"redhat.java",
"vscjava.vscode-java-debug"
"vscjava.vscode-java-pack"
]
}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "python --version",
// "postCreateCommand": "java -version",
// Configure tool-specific properties.
// "customizations": {},

View File

@ -1,3 +0,0 @@
This file copied into the container along with environment.yml* from the parent
folder. This file is included to prevents the Dockerfile COPY instruction from
failing if no environment.yml is found.

30
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,30 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickJavaProcess}"
},
{
"type": "java",
"name": "Debug (Launch) - Current File",
"request": "launch",
"mainClass": "${file}"
},
{
"type": "java",
"name": "Debug With MC Server",
"request": "attach",
"hostName": "localhost",
"port": 25566,
"sourcePaths": [
"src/main/java"
],
}
]
}

View File

@ -1,3 +1,10 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
"java.configuration.updateBuildConfiguration": "automatic",
"cSpell.words": [
"Bukkit",
"islandsurvivalcraft",
"Minecraft",
"QUICKBAR"
],
"java.compile.nullAnalysis.mode": "automatic"
}

86
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,86 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "verify",
"type": "shell",
"command": "mvn -B verify",
"group": "build"
},
{
"label": "test",
"type": "shell",
"command": "mvn -B test",
"group": "test"
},
{
"label": "package",
"type": "shell",
"command": "mvn package -DskipTests",
"group": "build"
},
{
"label": "delete server world",
"type": "shell",
"options": {"cwd": "${workspaceFolder}/test-server/"},
"args": ["-ExecutionPolicy", "Bypass", "-File", "./delete_worlds.ps1"],
"command": "pwsh",
"problemMatcher": []
},
{
"dependsOn": ["package"],
"label": "load build",
"type": "shell",
"args": ["-ExecutionPolicy", "Bypass", "-File", "./load_build.ps1"],
"options": {"cwd": "${workspaceFolder}/test-server/"},
"command": "pwsh",
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"dependsOn": ["load build"],
"label": "start test server",
"type": "shell",
"runOptions": {"instanceLimit": 1},
"args": ["-ExecutionPolicy", "Bypass", "-File", "./start_process.ps1"],
"options": {"cwd": "${workspaceFolder}/test-server/"},
"command": "pwsh",
"problemMatcher": [],
"group": "test",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
}
},
{
"label": "stop test server",
"type": "shell",
"args": ["-ExecutionPolicy", "Bypass", "-File", "./end_process.ps1"],
"options": {"cwd": "${workspaceFolder}/test-server/"},
"command": "pwsh",
"problemMatcher": [],
"group": "test",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
}
]
}

35
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,35 @@
pipeline {
agent {
kubernetes {
cloud 'Reslate Systems'
defaultContainer 'conda'
}
}
stages {
stage ("Install") {
steps {
sh 'conda update conda -y -q'
sh 'conda env update -n base --file environment.yml -q'
sh "conda run -n base mvn validate"
}
}
stage ("Build") {
steps {
sh "conda run -n base mvn compile"
}
}
stage ("Test") {
steps {
sh "conda run -n base mvn -Dmaven.test.skip=false test"
xunit checksName: '', tools: [JUnit(excludesPattern: '', pattern: 'target/surefire-reports/TEST-*.xml', stopProcessingIfError: true)]
recordCoverage(tools: [[parser: 'JUNIT', pattern: 'target/surefire-reports/TEST-*.xml']])
}
}
stage ("Package") {
steps {
sh "conda run -n base mvn package"
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true, followSymlinks: false
}
}
}
}

6
environment.yml Normal file
View File

@ -0,0 +1,6 @@
name: islandsurvivalcraft
channels:
- conda-forge
dependencies:
- openjdk=21
- maven

57
pom.xml
View File

@ -1,7 +1,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ca.recrown.islandsurvivalcraft</groupId>
<groupId>net.reslate.islandsurvivalcraft</groupId>
<artifactId>IslandSurvivalCraft</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
@ -9,37 +9,56 @@
<url>http://maven.apache.org</url>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>sk89q-repo</id>
<url>https://maven.enginehub.org/repo/</url>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.15.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sk89q.worldguard</groupId>
<artifactId>worldguard-bukkit</artifactId>
<version>7.0.2</version>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.16.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release>
<maven.test.skip>true</maven.test.skip>
</properties>
</project>

View File

@ -1,21 +0,0 @@
package ca.recrown.islandsurvivalcraft;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Hello world!
*
*/
public class IslandSurvivalCraftPlugin extends JavaPlugin {
@Override
public void onEnable() {
// TODO Auto-generated method stub
super.onEnable();
}
@Override
public void onDisable() {
// TODO Auto-generated method stub
super.onDisable();
}
}

View File

@ -0,0 +1,79 @@
package net.reslate.islandsurvivalcraft;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import net.reslate.islandsurvivalcraft.interaction.commands.CommandProcessor;
import net.reslate.islandsurvivalcraft.interaction.items.ItemVariantManager;
import net.reslate.islandsurvivalcraft.world.WorldInfoManager;
import net.reslate.islandsurvivalcraft.world.generation.GeneratorModes;
public class IslandSurvivalCraft extends JavaPlugin implements Listener {
private PluginManager pluginManager;
private WorldInfoManager worldInfoManager;
private CommandProcessor commandProcessor;
private ItemVariantManager variedItemManager;
public IslandSurvivalCraft() {
worldInfoManager = new WorldInfoManager();
variedItemManager = new ItemVariantManager(this);
commandProcessor = new CommandProcessor(this);
}
@Override
public void onEnable() {
pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(worldInfoManager, this);
pluginManager.registerEvents(variedItemManager, this);
pluginManager.registerEvents(this, this);
super.onEnable();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onReady(ServerLoadEvent e) {
variedItemManager.loadAllItems();
}
@Override
public void onDisable() {
HandlerList.unregisterAll(worldInfoManager);
HandlerList.unregisterAll(variedItemManager);
HandlerList.unregisterAll((Listener) this);
variedItemManager.unloadAllItems();
super.onDisable();
}
@Override
public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
GeneratorModes gID = null;
try {
gID = GeneratorModes.valueOf(id);
} catch (NullPointerException | IllegalArgumentException e) {
gID = GeneratorModes.UNIQUE;
}
return worldInfoManager.getChunkGenerator(worldName, gID);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
return commandProcessor.onCommand(sender, command, label, args);
}
/**
* @return the world information manager.
*/
public WorldInfoManager getWorldInfoManager() {
return worldInfoManager;
}
}

View File

@ -0,0 +1,37 @@
package net.reslate.islandsurvivalcraft.interaction.commands;
import java.util.Arrays;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
public class CommandProcessor implements CommandExecutor {
private IslandSurvivalCraft islandSurvivalCraft;
public CommandProcessor(IslandSurvivalCraft islandSurvivalCraft) {
this.islandSurvivalCraft = islandSurvivalCraft;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length < 1 || args[0].isEmpty()) return false;
RegisteredCommands currentCommand = null;
try {
currentCommand = RegisteredCommands.valueOf(args[0].toUpperCase());
if (currentCommand.getCommandRunnable().requiresInitialization()) currentCommand.getCommandRunnable().initialize(islandSurvivalCraft);
String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
boolean success = currentCommand.getCommandRunnable().invoke(sender, commandArguments);
if (!success) {
sender.sendMessage(String.format("The command arguments were incorrect. Please refer to \"/IslandSurvivalCraft help %s\" for help.", currentCommand.toString()));
return false;
}
} catch (IllegalArgumentException e) {
sender.sendMessage(ChatColor.LIGHT_PURPLE + "This command was not understood. Refer to \"/IslandSurvivalCraft help\" for more info.");
}
return true;
}
}

View File

@ -0,0 +1,30 @@
package net.reslate.islandsurvivalcraft.interaction.commands;
import net.reslate.islandsurvivalcraft.interaction.commands.runnables.IslandCommand;
import net.reslate.islandsurvivalcraft.interaction.commands.runnables.*;
public enum RegisteredCommands {
ISLAND(new IslandCommand()),
HELP(new HelpCommand()),
VALUE(new ValueCommand()),
;
private final CommandRunnable commandRunnable;
private RegisteredCommands(CommandRunnable runnable) {
this.commandRunnable = runnable;
}
@Override
public String toString() {
return name().toLowerCase();
}
/**
* @return the commandRunnable
*/
public CommandRunnable getCommandRunnable() {
return commandRunnable;
}
}

View File

@ -0,0 +1,38 @@
package net.reslate.islandsurvivalcraft.interaction.commands.runnables;
import org.bukkit.command.CommandSender;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
public interface CommandRunnable {
/**
* @return a string that gives users an idea of what this command does. Should be short and not include details on arguments.
*/
public String getDescription();
/**
* @return A more detailed description. Doesn't need to contain information already present in normal description as will print with that one. This string should describe proper usage arguments required.
*/
public String getDetailedDescription();
/**
* Invokes this particular command.
* Should check for correct arguments.
* @param sender The sender of this command.
* @param args The arguments given for this command. May be length of 0, in which case there were no arguments.
* @return True if the execution of this command was a success, and false to display the help message for this command.
*/
public boolean invoke(CommandSender sender, String[] args);
/**
* Initializes the command. Is run the first time this command is run.
* @param islandSurvivalCraft
*/
public void initialize(IslandSurvivalCraft islandSurvivalCraft);
/**
*
* @return True if on the next call, should initialize.
*/
public boolean requiresInitialization();
}

View File

@ -0,0 +1,63 @@
package net.reslate.islandsurvivalcraft.interaction.commands.runnables;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
import net.reslate.islandsurvivalcraft.interaction.commands.RegisteredCommands;
public class HelpCommand implements CommandRunnable {
private String helpMessage;
@Override
public String getDescription() {
return "Displays these help messages.";
}
@Override
public boolean invoke(CommandSender sender, String[] args) {
if (args == null || args.length == 0) {
sender.sendMessage(helpMessage);
return true;
} else {
RegisteredCommands command = null;
try {
command = RegisteredCommands.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException e) {
return false;
}
StringBuilder sb = new StringBuilder();
sb.append(ChatColor.YELLOW + args[0]);
sb.append(": " + ChatColor.GRAY);
sb.append(command.getCommandRunnable().getDescription());
sb.append("\n" + ChatColor.WHITE);
sb.append(command.getCommandRunnable().getDetailedDescription());
sender.sendMessage(sb.toString().trim());
}
return true;
}
@Override
public void initialize(IslandSurvivalCraft islandSurvivalCraft) {
StringBuilder stringBuilder = new StringBuilder();
RegisteredCommands[] commands = RegisteredCommands.values();
stringBuilder.append(ChatColor.YELLOW + "IslandSurvivalCraft Commands:\n");
for (int i = 0; i < commands.length; i++) {
stringBuilder.append(ChatColor.GREEN + commands[i].toString());
stringBuilder.append(": " + ChatColor.WHITE);
stringBuilder.append(commands[i].getCommandRunnable().getDescription());
stringBuilder.append("\n");
}
helpMessage = stringBuilder.toString().trim();
}
@Override
public String getDetailedDescription() {
return " \"islandsurvivalcraft help [command]\". Where [command] is a listed command, can optionally be added to get a detailed look at the command. Help is a command that lists all the other potential commands that may be run. Can also look at the detailed commands. But you knew this since your using help with help...";
}
@Override
public boolean requiresInitialization() {
return helpMessage == null;
}
}

View File

@ -0,0 +1,124 @@
package net.reslate.islandsurvivalcraft.interaction.commands.runnables;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
import net.reslate.islandsurvivalcraft.utilities.GeneralUtilities;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.world.WorldInfo;
import net.reslate.islandsurvivalcraft.world.Information.IslandInformation;
public class IslandCommand implements CommandRunnable {
private IslandSurvivalCraft islandSurvivalCraft;
private final Particle.DustOptions EDGE_DUST_OPTIONS = new Particle.DustOptions(Color.RED, 1f);
private final Particle.DustOptions ORIGIN_DUST_OPTIONS = new Particle.DustOptions(Color.LIME, 1.5f);
private final int PARTICLE_AMOUNT = 1;
private final double OFFSET_X = 0.0d, OFFSET_Y = 6.0d, OFFSET_Z = 0.0d;
private final double EXTRA = 0d;
HashSet<Player> playersHighlighting = new HashSet<>();
@Override
public String getDescription() {
return "Various debugging information about islands.";
}
@Override
public boolean invoke(CommandSender sender, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("This command can only be run as a player!");
return false;
}
Player player = (Player) sender;
if (args.length == 0 || args[0].isEmpty()) {
return false;
}
if (args[0].toLowerCase().equals("highlight")) {
if (playersHighlighting.add(player)) {
sender.sendMessage("You are now highlighting islands.");
} else {
sender.sendMessage("You are already highlighting islands.");
}
} else if (args[0].toLowerCase().equals("stop")) {
if (playersHighlighting.remove(player)) {
sender.sendMessage("You are no longer highlighting islands.");
} else {
sender.sendMessage("You weren't highlighting islands.");
}
} else if (args[0].toLowerCase().equals("origin")) {
Point2 playerLoc = new Point2(player.getLocation().getBlockX(), player.getLocation().getBlockZ());
sender.sendMessage("Current island's origin point: " + islandSurvivalCraft.getWorldInfoManager().retrieve(player.getWorld().getName()).getIslandInfoManager().getIslandInformation(playerLoc, true).getIslandOrigin());
} else if (args[0].toLowerCase().equals("size")) {
Point2 playerLoc = new Point2(player.getLocation().getBlockX(), player.getLocation().getBlockZ());
IslandInformation info = islandSurvivalCraft.getWorldInfoManager().retrieve(player.getWorld().getName()).getIslandInfoManager().getIslandInformation(playerLoc, true);
if (info != null) {
sender.sendMessage("Current island's raw size in blocks: " + info.getIslandSize());
} else {
sender.sendMessage("Current location is not an island!");
}
} else {
return false;
}
return true;
}
@Override
public void initialize(IslandSurvivalCraft islandsurvivalcraft) {
this.islandSurvivalCraft = islandsurvivalcraft;
Bukkit.getScheduler().scheduleSyncRepeatingTask(islandsurvivalcraft, new Runnable() {
@Override
public void run() {
for (Player player : playersHighlighting) {
if (player == null) continue;
if (!player.isOnline()) {
playersHighlighting.remove(player);
continue;
}
World world = player.getWorld();
WorldInfo worldInfo = islandsurvivalcraft.getWorldInfoManager().retrieve(world.getName());
Location playerLocation = player.getLocation();
double playerX = playerLocation.getX(), playerY = playerLocation.getY(), playerZ = playerLocation.getZ();
Point2 playerCoords = new Point2(playerLocation.getBlockX(), playerLocation.getBlockZ());
IslandInformation islandInfo = worldInfo.getIslandInfoManager().getIslandInformation(playerCoords, true);
if (islandInfo != null) {
Set<Point2> islandBorder = islandInfo.getEdgeCoordinates();
for (Point2 current : islandBorder) {
spawnParticle(false, true, world, current.x, playerY, current.y, EDGE_DUST_OPTIONS);
}
Point2 islandOrigin = islandInfo.getIslandOrigin();
double xDirection = GeneralUtilities.addMagnitude(Math.max(Math.min(1, islandOrigin.x - playerCoords.x), -1), 1) + playerX;
double yDirection = GeneralUtilities.addMagnitude(Math.max(Math.min(1, islandOrigin.y - playerCoords.y), -1), 1) + playerZ;
spawnParticle(false, false, world, xDirection, playerY + 1, yDirection, ORIGIN_DUST_OPTIONS);
spawnParticle(true, true, world, islandOrigin.x, playerY, islandOrigin.y, ORIGIN_DUST_OPTIONS);
}
}
}
}, 0, 10);
}
private void spawnParticle(boolean focus, boolean column, World world, double x, double y, double z, Particle.DustOptions dustOptions) {
world.spawnParticle(Particle.REDSTONE, x, y, z, focus ? PARTICLE_AMOUNT * 2 : PARTICLE_AMOUNT, OFFSET_X, column ? OFFSET_Y : 0.2, OFFSET_Z, EXTRA, dustOptions, false);
}
@Override
public String getDetailedDescription() {
return "Usage: \"island <arg>\". replacing <args> with \"highlight\" will start highlighting the island by " +
"displaying red particles that indicate the islands border, and green particles indicate the origin. \"stop\" will stop the highlighting. " +
"\"origin\" will print the current island's origin point. " +
"\"size\" will print the raw island size in blocks including the shallow portions.";
}
@Override
public boolean requiresInitialization() {
return islandSurvivalCraft == null;
}
}

View File

@ -0,0 +1,69 @@
package net.reslate.islandsurvivalcraft.interaction.commands.runnables;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
public class ValueCommand implements CommandRunnable {
private IslandSurvivalCraft isc;
@Override
public String getDescription() {
return "Gives the world value this plugin is using.";
}
@Override
public String getDetailedDescription() {
return "Player only command. Usage: \"IslandSurvivalCraft value [x] [z]\". Gives the current world value at the given x and z coordinates. If the two coordinates are not provided, player location is used.";
}
@Override
public boolean invoke(CommandSender sender, String[] args) {
if (!(sender instanceof Player)) return false;
Player p = (Player) sender;
int worldX = 0, worldZ = 0;
if (args.length > 0) {
if (args.length != 2) return false;
try {
worldX = Integer.valueOf(args[0]);
worldZ = Integer.valueOf(args[1]);
} catch (NumberFormatException e) {
return false;
}
} else {
worldX = p.getLocation().getBlockX();
worldZ = p.getLocation().getBlockZ();
}
IslandWorldMap map = isc.getWorldInfoManager().retrieve(p.getWorld().getName()).getIslandMap();
p.sendMessage(String.format("world: %s, World value (%d, %d): %s",
p.getWorld().getName(),
worldX, worldZ,
map.getWorldValue(worldX, worldZ)));
p.sendMessage(String.format("Terrain: \nIsland: %b, Shallow: %b, Hill: %b, Land: %b, Shore: %b, Edge: %b, Transitional: %b, Deep: %b",
map.isIsland(worldX, worldZ),
map.isShallowPortion(worldX, worldZ),
map.isHill(worldX, worldZ),
map.isLand(worldX, worldZ),
map.isShore(worldX, worldZ),
map.isEdgeOfIsland(worldX, worldZ),
map.isTransitional(worldX, worldZ),
map.isDeep(worldX, worldZ)));
return true;
}
@Override
public void initialize(IslandSurvivalCraft islandSurvivalCraft) {
isc = islandSurvivalCraft;
}
@Override
public boolean requiresInitialization() {
return isc == null;
}
}

View File

@ -0,0 +1,103 @@
package net.reslate.islandsurvivalcraft.interaction.items;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
public interface ItemVariant {
/**
* Should be used to perform anything that need only be called once.
* Is called only when the needInitialization() method returns true.
* Is called after assigning identity.
* @param islandSurvivalCraft The IslandSurvivalCraft plugin object that contains references to various managers that may be used.
*/
public void initialize(IslandSurvivalCraft islandSurvivalCraft);
/**
* The recipe of the item variant. If null, these features will hook directly into the original map.
* @return the recipe to be registered with bukkit.
*/
public Recipe getRecipe();
/**
* @return a string representing the key name of the item.
*/
public String getItemName();
/**
*
* @return The material item this item is a variation of.
*/
public Material[] getBaseItems();
/**
* Assigns a persistent metadata identifier.
* This identifier should be saved to any itemstack that is considered this item.
* @param identifier The identifier.
*/
public void assignIdentifier(ItemVariantIdentifier identifier);
/**
* Should give the identifier used identify this variation of the base item from the original.
* This usually should just return the assigned identifier.
* @return The identifier metadata.
*/
public ItemVariantIdentifier getIdentifier();
/**
* If this item needs initializing.
* If returning true, the initialize method will be called before being registered as a recipe.
* @return True if the initialize method should be called.
*/
public boolean needInitialization();
/**
* Called when this variety of the item is crafted.
* @param item The resulting itemstack.
* @param crafter The entity performing the crafting.
* @param recipe The recipe used to craft this.
* @return true if the remaining chain of events for this crafting should not occur. Otherwise, false.
*/
public boolean onItemCrafted(ItemStack item, HumanEntity crafter);
/**
* Called when this item is right clicked with.
* @param item The itemstack that was right clicked with.
* @param clicker The player that did the clicking.
* @param hand The hand that it was in.
* @param block The block the item was right clicked on.
* @param blockFace The face of the block that was clicked.
* @return True if the vanilla chain of events should no longer run. False otherwise.
*/
public boolean onItemRightClicked(ItemStack item, HumanEntity clicker, EquipmentSlot hand, Block block, BlockFace blockFace);
/**
* Called once when the player changes to holding the item.
* @param heldItem The itemstack representing the held item.
* @param holder The player holding the item.
*/
public void onItemHeld(ItemStack heldItem, HumanEntity holder);
/**
* Called when this item is put away.
* @param item The itemstack that was put away.
* @param holder The player that put away the itemstack.
*/
public void onItemPutAway(ItemStack item, HumanEntity holder);
/**
* Called when a player joins and called when player picks up this variation of the item.
* Can be used to initialize any data the item doesn't save on server shutdown.
* You may need to check if the item already has been instantiated.
* @param item The itemstack that represents the item in question.
*/
public void initializeInstanceOfItem(ItemStack item);
}

View File

@ -0,0 +1,41 @@
package net.reslate.islandsurvivalcraft.interaction.items;
import java.nio.charset.StandardCharsets;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataType;
public class ItemVariantIdentifier implements PersistentDataType<byte[], String> {
private final String ID;
public ItemVariantIdentifier(String ID) {
this.ID = ID;
}
@Override
public Class<byte[]> getPrimitiveType() {
return byte[].class;
}
@Override
public Class<String> getComplexType() {
return String.class;
}
@Override
public byte [] toPrimitive(String complex, PersistentDataAdapterContext context) {
return complex.getBytes(StandardCharsets.UTF_8);
}
@Override
public String fromPrimitive(byte [] primitive, PersistentDataAdapterContext context) {
return new String(primitive, StandardCharsets.UTF_8);
}
/**
* @return the iD
*/
public String getID() {
return ID;
}
}

View File

@ -0,0 +1,192 @@
package net.reslate.islandsurvivalcraft.interaction.items;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map.Entry;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.Event.Result;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType.SlotType;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.ItemMeta;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
public class ItemVariantManager implements Listener {
private final IslandSurvivalCraft islandSurvivalCraft;
public ItemVariantManager(IslandSurvivalCraft islandSurvivalCraft) {
this.islandSurvivalCraft = islandSurvivalCraft;
}
public void loadAllItems() {
RegisteredItemVariants[] items = RegisteredItemVariants.values();
for (int i = 0; i < items.length; i++) {
if (items[i].getVariedItem().needInitialization()) {
items[i].getVariedItem().initialize(islandSurvivalCraft);
}
Recipe currentRecipe = items[i].getVariedItem().getRecipe();
if (currentRecipe != null) {
islandSurvivalCraft.getServer().addRecipe(currentRecipe);
}
}
Collection<? extends Player> players = islandSurvivalCraft.getServer().getOnlinePlayers();
for (Player player : players) {
initializeInventoryVariedItems(player.getInventory());
}
}
public void unloadAllItems() {
RegisteredItemVariants[] items = RegisteredItemVariants.values();
for (int i = 0; i < items.length; i++) {
Bukkit.removeRecipe(new NamespacedKey(islandSurvivalCraft, items[i].getVariedItem().getItemName()));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onHeld(PlayerItemHeldEvent e) {
ItemStack item = e.getPlayer().getInventory().getItem(e.getNewSlot());
ItemVariant variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) {
variedItem.onItemHeld(item, e.getPlayer());
} else {
item = e.getPlayer().getInventory().getItem(e.getPreviousSlot());
variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) {
variedItem.onItemPutAway(item, e.getPlayer());
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onInventoryInteract(InventoryClickEvent e) {
ItemStack item = e.getCurrentItem();
ItemVariant variedItem = getVariedItemFromItemStack(item);
if (e.getSlotType() == SlotType.RESULT) {
if (variedItem != null) {
if (variedItem.onItemCrafted(item, e.getWhoClicked())) {
e.setCancelled(true);
}
}
} else if (e.getSlotType() == SlotType.QUICKBAR) {
if (e.getWhoClicked().getInventory().getHeldItemSlot() == e.getSlot()) {
if (variedItem != null) {
variedItem.onItemPutAway(item, e.getWhoClicked());
} else if ((variedItem = getVariedItemFromItemStack(e.getCursor())) != null) {
variedItem.onItemHeld(item, e.getWhoClicked());
}
}
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerDropItem(PlayerDropItemEvent e) {
ItemStack item = e.getItemDrop().getItemStack();
ItemVariant variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) {
variedItem.onItemPutAway(item, e.getPlayer());
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onEntityPickup(EntityPickupItemEvent e) {
ItemStack item = e.getItem().getItemStack();
if (e.getEntityType() == EntityType.PLAYER) {
ItemVariant variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) {
Player player = (Player) e.getEntity();
if (player.getInventory().getHeldItemSlot() == player.getInventory().firstEmpty()) {
variedItem.onItemHeld(item, player);
}
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerInteraction(PlayerInteractEvent e) {
if (e.useItemInHand() == Result.DENY || (e.getAction() != Action.RIGHT_CLICK_AIR && e.getAction() != Action.RIGHT_CLICK_BLOCK)) return;
ItemStack item = e.getItem();
ItemVariant variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) {
if(variedItem.onItemRightClicked(item, e.getPlayer(), e.getHand(), e.getClickedBlock(), e.getBlockFace())) {
e.setCancelled(true);
e.setUseItemInHand(Result.DENY);
}
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent e) {
initializeInventoryVariedItems(e.getPlayer().getInventory());
ItemStack item = e.getPlayer().getInventory().getItemInMainHand();
ItemVariant variedItem = getVariedItemFromItemStack(item); //Check to see of we can find a variation of the item.
if (variedItem != null) {
variedItem.onItemHeld(item, e.getPlayer());
}
}
private void initializeInventoryVariedItems(Inventory inv) {
RegisteredItemVariants[] registeredVariedItems = RegisteredItemVariants.values();
for (RegisteredItemVariants registeredVariedItem : registeredVariedItems) {
ItemVariant variedItem = registeredVariedItem.getVariedItem();
Material[] bases = variedItem.getBaseItems();
for (Material base : bases) {
HashMap<Integer, ? extends ItemStack> potentials = inv.all(base);
for (Entry<Integer, ? extends ItemStack> entry : potentials.entrySet()) {
if (registeredVariedItem.getVariedItem().getRecipe() != null) {
NamespacedKey key = new NamespacedKey(islandSurvivalCraft, variedItem.getItemName() + ".identifier");
String identifier = entry.getValue().getItemMeta().getPersistentDataContainer().get(key, variedItem.getIdentifier());
if (identifier != null) {
variedItem.initializeInstanceOfItem(entry.getValue());
}
} else {
variedItem.initializeInstanceOfItem(entry.getValue());
}
}
}
}
}
private ItemVariant getVariedItemFromItemStack(ItemStack item) {
if (item == null || item.getType() == Material.AIR) return null;
ItemMeta meta = item.getItemMeta();
RegisteredItemVariants[] items = RegisteredItemVariants.values();
for (int i = 0; i < items.length; i++) {
ItemVariant current = items[i].getVariedItem();
if (current.getRecipe() == null) {
for (Material material : current.getBaseItems()) {
if (item.getType() == material) {
return current;
}
}
}
NamespacedKey key = new NamespacedKey(islandSurvivalCraft, current.getItemName());
String identifier = meta.getPersistentDataContainer().get(key, current.getIdentifier());
if (identifier != null) {
if (current.getIdentifier().getID().equals(identifier)) {
return current;
}
}
}
return null;
}
}

View File

@ -0,0 +1,21 @@
package net.reslate.islandsurvivalcraft.interaction.items;
import net.reslate.islandsurvivalcraft.interaction.items.variations.*;
public enum RegisteredItemVariants {
ISLAND_MAP(new IslandMapItem())
;
private final ItemVariant variedItem;
private RegisteredItemVariants(ItemVariant alternateItem) {
this.variedItem = alternateItem;
this.variedItem.assignIdentifier(new ItemVariantIdentifier(this.name() + ".identifier"));
}
/**
* @return the variedItem
*/
public ItemVariant getVariedItem() {
return variedItem;
}
}

View File

@ -0,0 +1,149 @@
package net.reslate.islandsurvivalcraft.interaction.items.variations;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapPalette;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import net.reslate.islandsurvivalcraft.IslandSurvivalCraft;
import net.reslate.islandsurvivalcraft.interaction.items.ItemVariant;
import net.reslate.islandsurvivalcraft.interaction.items.ItemVariantIdentifier;
import net.reslate.islandsurvivalcraft.utilities.GeneralUtilities;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.world.WorldInfo;
import net.reslate.islandsurvivalcraft.world.Information.IslandInformation;
import net.reslate.islandsurvivalcraft.world.Information.IslandInformationManager;
import java.awt.Color;
public class IslandMapItem extends MapRenderer implements ItemVariant {
IslandSurvivalCraft plugin;
ItemVariantIdentifier identifier;
Color mainColor = new Color(0, 180, 0);
@SuppressWarnings("deprecation")
@Override
public void render(MapView map, MapCanvas canvas, Player player) {
WorldInfo worldInfo = plugin.getWorldInfoManager().retrieve(map.getWorld().getName());
IslandInformationManager islandInformationManager = worldInfo.getIslandInfoManager();
int blocksPerPixel = (int) (1 * Math.pow(2, map.getScale().getValue()));
for (int x = 0; x < 128; x += 1) {
for (int y = x % 2; y < 128; y += 2) {
if (canvas.getPixel(x, y) != MapPalette.matchColor(mainColor)) {
int actualX = blocksPerPixel * (x - Byte.MAX_VALUE/2) + map.getCenterX();
int actualZ = blocksPerPixel * (y - Byte.MAX_VALUE/2) + map.getCenterZ();
Point2 pCoords = new Point2(actualX, actualZ);
if (islandInformationManager.isChunkIslandInformationLoaded(GeneralUtilities.worldToChunkCoordinates(pCoords), false)) {
IslandInformation info = islandInformationManager.getIslandInformation(pCoords, false);
if (info != null) {
canvas.setPixel(x, y, MapPalette.matchColor(mainColor));
}
} else if (worldInfo.getIslandMap().isIsland(actualX, actualZ)) {
canvas.setPixel(x, y, MapPalette.matchColor(mainColor));
}
}
}
}
}
@Override
public void initialize(IslandSurvivalCraft islandSurvivalCraft) {
this.plugin = islandSurvivalCraft;
}
@Override
public Recipe getRecipe() {
return null;
}
@Override
public String getItemName() {
return "Island_Map";
}
@Override
public Material[] getBaseItems() {
return new Material[] {Material.FILLED_MAP};
}
@Override
public void assignIdentifier(ItemVariantIdentifier identifier) {
this.identifier = identifier;
}
@Override
public ItemVariantIdentifier getIdentifier() {
return this.identifier;
}
@Override
public boolean needInitialization() {
return this.plugin == null;
}
@Override
public boolean onItemCrafted(ItemStack item, HumanEntity crafter) {
return false;
}
@Override
public boolean onItemRightClicked(ItemStack item, HumanEntity clicker, EquipmentSlot hand, Block block,
BlockFace blockFace) {
if (((Player)clicker).isSneaking()) {
ItemMeta meta = item.getItemMeta();
PersistentDataContainer container = meta.getPersistentDataContainer();
NamespacedKey key = new NamespacedKey(plugin, getItemName() + ".state");
if (!container.has(key, PersistentDataType.BYTE)) {
container.set(key, PersistentDataType.BYTE, (byte) 0);
}
if (container.get(key, PersistentDataType.BYTE) == 0) {
container.set(key, PersistentDataType.BYTE, (byte) 1);
MapMeta mapMeta = (MapMeta) item.getItemMeta();
mapMeta.getMapView().addRenderer(this);
} else {
container.set(key, PersistentDataType.BYTE, (byte) 0);
MapMeta mapMeta = (MapMeta) item.getItemMeta();
mapMeta.getMapView().removeRenderer(this);
}
item.setItemMeta(meta);
}
return false;
}
@Override
public void onItemHeld(ItemStack heldItem, HumanEntity holder) {
}
@Override
public void onItemPutAway(ItemStack item, HumanEntity holder) {
}
@Override
public void initializeInstanceOfItem(ItemStack item) {
MapMeta mapMeta = (MapMeta) item.getItemMeta();
PersistentDataContainer container = mapMeta.getPersistentDataContainer();
NamespacedKey key = new NamespacedKey(plugin, getItemName() + ".state");
if (container.has(key, PersistentDataType.BYTE)) {
if (container.get(key, PersistentDataType.BYTE) == 1) {
mapMeta = (MapMeta) item.getItemMeta();
mapMeta.getMapView().addRenderer(this);
item.setItemMeta(mapMeta);
}
}
}
}

View File

@ -0,0 +1,5 @@
package net.reslate.islandsurvivalcraft.persistence;
public enum Settings {
}

View File

@ -0,0 +1,90 @@
package net.reslate.islandsurvivalcraft.utilities;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public class GeneralUtilities {
public final static int CHUNK_SIZE = 16;
public final static ExecutorService ISC_EXECUTOR_ALPHA = Executors.newFixedThreadPool(1, createThreadFactory("ALPHA", Thread.NORM_PRIORITY + 1));
public final static ExecutorService ISC_EXECUTOR_BETA = Executors.newFixedThreadPool(2, createThreadFactory("BETA", Thread.NORM_PRIORITY));
public static <K, V> HashMap<V, ArrayList<K>> invertHashMap(HashMap<K, V> hashMap) {
HashMap<V, ArrayList<K>> res = new HashMap<>();
for (Entry<K, V> entry : hashMap.entrySet()) {
if (!res.containsKey(entry.getValue())) {
ArrayList<K> l = new ArrayList<K>();
l.add(entry.getKey());
res.put(entry.getValue(), l);
} else {
res.get(entry.getValue()).add(entry.getKey());
}
}
return res;
}
public static int addMagnitude(int value, int add) {
boolean negative = false;
if (value < 0) negative = true;
int res = Math.abs(value) + add;
return negative ? - res : res;
}
public static Point2 worldToChunkCoordinates(int x, int y) {
int xRes = 0;
if (x < 0) {
xRes = (int) Math.floor((float) x / CHUNK_SIZE);
} else {
xRes = x / CHUNK_SIZE;
}
int yRes = 0;
if (y < 0) {
yRes = (int) Math.floor((float) y / CHUNK_SIZE);
} else {
yRes = y / CHUNK_SIZE;
}
return new Point2(xRes, yRes);
}
public static Point2 worldToChunkCoordinates(Point2 worldCoordinates) {
return worldToChunkCoordinates(worldCoordinates.x, worldCoordinates.y);
}
public static Point2 worldToLocalChunkCoordinates(int x, int y) {
int xRes = 0;
if (x < 0) {
xRes = CHUNK_SIZE + (x % CHUNK_SIZE);
xRes %= CHUNK_SIZE;
} else {
xRes = x % CHUNK_SIZE;
}
int yRes = 0;
if (y < 0) {
yRes = CHUNK_SIZE + (y % CHUNK_SIZE);
yRes %= CHUNK_SIZE;
} else {
yRes = y % CHUNK_SIZE;
}
return new Point2(xRes, yRes);
}
public static Point2 worldToLocalChunkCoordinates(Point2 worldCoordinates) {
return worldToLocalChunkCoordinates(worldCoordinates.x, worldCoordinates.y);
}
public static ThreadFactory createThreadFactory(final String identifier, final int priority) {
return new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, String.format("ISC-worker-%s", identifier));
thread.setPriority(priority);
return thread;
}
};
}
}

View File

@ -0,0 +1,93 @@
package net.reslate.islandsurvivalcraft.utilities.biomes;
import java.util.Arrays;
import org.bukkit.block.Biome;
import net.reslate.islandsurvivalcraft.utilities.weighting.Weightable;
import net.reslate.islandsurvivalcraft.utilities.weighting.WeightedSelector;
public enum BiomeClassifications implements Weightable {
SNOWY(0.2f, new OceanBiomeInfo(Biome.FROZEN_OCEAN, Biome.COLD_OCEAN, Biome.DEEP_FROZEN_OCEAN),
new LandBiomeInfo(Biome.SNOWY_TAIGA, null, Biome.SNOWY_TAIGA_HILLS, Biome.SNOWY_BEACH, 0.3f),
new LandBiomeInfo(Biome.SNOWY_TAIGA_MOUNTAINS, Biome.SNOWY_TAIGA, null, Biome.SNOWY_BEACH, 0.25f),
new LandBiomeInfo(Biome.SNOWY_TUNDRA, null, null, Biome.SNOWY_BEACH, 0.3f),
new LandBiomeInfo(Biome.ICE_SPIKES, null, null, Biome.SNOWY_BEACH, 0.15f)
),
COLD(0.3f, new OceanBiomeInfo(Biome.COLD_OCEAN, null, Biome.DEEP_COLD_OCEAN),
new LandBiomeInfo(Biome.MOUNTAINS, null, null, Biome.STONE_SHORE, 0.14f),
new LandBiomeInfo(Biome.GRAVELLY_MOUNTAINS, 0.1f),
new LandBiomeInfo(Biome.WOODED_MOUNTAINS, 0.08f),
new LandBiomeInfo(Biome.MODIFIED_GRAVELLY_MOUNTAINS, 0.08f),
new LandBiomeInfo(Biome.TAIGA, null, Biome.TAIGA_HILLS, Biome.BEACH, 0.15f),
new LandBiomeInfo(Biome.TAIGA_MOUNTAINS, 0.15f),
new LandBiomeInfo(Biome.GIANT_TREE_TAIGA, null, Biome.GIANT_TREE_TAIGA_HILLS, Biome.BEACH, 0.15f),
new LandBiomeInfo(Biome.GIANT_SPRUCE_TAIGA, null, Biome.GIANT_SPRUCE_TAIGA_HILLS, Biome.BEACH, 0.15f)
),
LUSH(0.3f, new OceanBiomeInfo(Biome.WARM_OCEAN, Biome.LUKEWARM_OCEAN, Biome.DEEP_LUKEWARM_OCEAN),
new LandBiomeInfo(Biome.PLAINS, 0.15f),
new LandBiomeInfo(Biome.SUNFLOWER_PLAINS, 0.075f),
new LandBiomeInfo(Biome.FOREST, null, Biome.WOODED_HILLS, Biome.BEACH, 0.1f),
new LandBiomeInfo(Biome.FLOWER_FOREST, 0.05f),
new LandBiomeInfo(Biome.BIRCH_FOREST, null, Biome.BIRCH_FOREST_HILLS, Biome.BEACH, 0.1f),
new LandBiomeInfo(Biome.TALL_BIRCH_FOREST, null, Biome.TALL_BIRCH_HILLS, Biome.BEACH, 0.075f),
new LandBiomeInfo(Biome.DARK_FOREST, null, Biome.DARK_FOREST_HILLS, Biome.BEACH, 0.075f),
new LandBiomeInfo(Biome.SWAMP, null, Biome.SWAMP_HILLS, null, 0.1f),
new LandBiomeInfo(Biome.JUNGLE, Biome.JUNGLE_EDGE, Biome.JUNGLE_HILLS, Biome.BEACH, 0.075f),
new LandBiomeInfo(Biome.MODIFIED_JUNGLE, Biome.MODIFIED_JUNGLE_EDGE, null, Biome.BEACH, 0.075f),
new LandBiomeInfo(Biome.BAMBOO_JUNGLE, null, Biome.BAMBOO_JUNGLE_HILLS, Biome.BEACH, 0.075f),
new LandBiomeInfo(Biome.MUSHROOM_FIELDS, null, null, Biome.MUSHROOM_FIELD_SHORE, 0.05f)
),
WARM(0.2f, new OceanBiomeInfo(Biome.OCEAN, null, Biome.DEEP_OCEAN),
new LandBiomeInfo(Biome.DESERT, null, Biome.DESERT_HILLS, Biome.BEACH, 0.2f),
new LandBiomeInfo(Biome.SAVANNA, 0.2f),
new LandBiomeInfo(Biome.SHATTERED_SAVANNA, 0.075f),
new LandBiomeInfo(Biome.BADLANDS, 0.075f),
new LandBiomeInfo(Biome.ERODED_BADLANDS, 0.075f),
new LandBiomeInfo(Biome.WOODED_BADLANDS_PLATEAU, 0.075f),
new LandBiomeInfo(Biome.MODIFIED_WOODED_BADLANDS_PLATEAU, 0.075f),
new LandBiomeInfo(Biome.BADLANDS_PLATEAU, 0.075f),
new LandBiomeInfo(Biome.SAVANNA_PLATEAU, 0.05f),
new LandBiomeInfo(Biome.MODIFIED_BADLANDS_PLATEAU, 0.05f),
new LandBiomeInfo(Biome.SHATTERED_SAVANNA_PLATEAU, 0.05f)
),
;
private final LandBiomeInfo[] biomeInfos;
private final float weight;
private final OceanBiomeInfo associatedOceanBiomeInfo;
private final WeightedSelector<LandBiomeInfo> selector;
BiomeClassifications(float weight, OceanBiomeInfo oceanBiomeInfo, LandBiomeInfo... biomeInfos) {
this.biomeInfos = biomeInfos;
this.weight = weight;
this.associatedOceanBiomeInfo = oceanBiomeInfo;
selector = new WeightedSelector<>(Arrays.asList(biomeInfos));
}
/**
* @return the biomes associated with this classification.
*/
public LandBiomeInfo[] getBiomeInfos() {
return biomeInfos;
}
@Override
public float getWeight() {
return weight;
}
/**
* @return the associatedOceanBiomeInfo
*/
public OceanBiomeInfo getAssociatedOceanBiomeInfo() {
return associatedOceanBiomeInfo;
}
/**
* @return the selector
*/
public WeightedSelector<LandBiomeInfo> getSelector() {
return selector;
}
}

View File

@ -0,0 +1,8 @@
package net.reslate.islandsurvivalcraft.utilities.biomes;
import org.bukkit.block.Biome;
public interface BiomeInfo {
public Biome getMainBiome();
public Biome getTransitionBiome();
}

View File

@ -0,0 +1,76 @@
package net.reslate.islandsurvivalcraft.utilities.biomes;
import org.bukkit.block.Biome;
import net.reslate.islandsurvivalcraft.utilities.weighting.Weightable;
public class LandBiomeInfo implements Weightable, BiomeInfo {
private final Biome biome;
private final float weight;
private final Biome hillBiome;
private final Biome transitionBiome;
private final Biome preferredShore;
/**
* Creates a land biome information object.
* @param biome the main biome this object represents.
* @param transitionBiome the transition biome to go with this biome. May be null.
* @param hill the hill biome that goes with this biome. May be null.
* @param shore the shore that goes with this biome. May be null.
* @param weight the weight, relative to the others, chance this biome spawns.
*/
public LandBiomeInfo(Biome biome, Biome transitionBiome, Biome hill, Biome shore, float weight) {
if (biome == null) throw new IllegalArgumentException("Argument biome may never be null.");
this.weight = weight;
this.biome = biome;
this.hillBiome = hill;
this.preferredShore = shore;
this.transitionBiome = transitionBiome;
}
/**
* Creates a land biome information object.
* Leaves the transition biome and hill biomes as null.
* The shore is set to beach.
* @param biome The biome this object represents.
* @param weight The probability of selecting this biome.
*/
public LandBiomeInfo(Biome biome, float weight) {
this(biome, null, null, Biome.BEACH, weight);
}
/**
* @return the weight
*/
public float getWeight() {
return weight;
}
/**
* @return the hill biome. Will return the normal biome if there is no special hill biome for this biome.
*/
public Biome getHillBiome() {
if (hillBiome == null) return getMainBiome();
return hillBiome;
}
/**
* @return the shore biome. Will return the normal biome if there is no shore specificied.
*/
public Biome getShoreBiome() {
if (preferredShore != null) return preferredShore;
return getMainBiome();
}
/**
* @return the transition biome if it has one. May be null.
*/
public Biome getTransitionBiome() {
return transitionBiome;
}
@Override
public Biome getMainBiome() {
return biome;
}
}

View File

@ -0,0 +1,41 @@
package net.reslate.islandsurvivalcraft.utilities.biomes;
import org.bukkit.block.Biome;
public class OceanBiomeInfo implements BiomeInfo {
private final Biome ocean;
private final Biome transitionOcean;
private final Biome deepAlternative;
public OceanBiomeInfo(Biome ocean, Biome transitionOcean, Biome deepAlternative) {
if (ocean == null || deepAlternative == null) throw new IllegalArgumentException("Neither argument ocean nor deepAlternative may be null.");
this.ocean = ocean;
this.transitionOcean = transitionOcean;
this.deepAlternative = deepAlternative;
}
/**
* @return the main ocean.
*/
public Biome getOcean() {
return ocean;
}
/**
* @return the deep version of this ocean.
*/
public Biome getDeepAlternative() {
return deepAlternative;
}
@Override
public Biome getMainBiome() {
return getOcean();
}
@Override
public Biome getTransitionBiome() {
if (transitionOcean == null) return getOcean();
return transitionOcean;
}
}

View File

@ -0,0 +1,109 @@
package net.reslate.islandsurvivalcraft.utilities.caching;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class Cache<K, V> {
private final int maxSize;
private final ConcurrentHashMap<K, CacheValue<K, V>> data;
private final CacheUsageStack<K, V> usage = new CacheUsageStack<>();
private final ReentrantLock lock = new ReentrantLock();
/**
* Creates a thread safe cache.
* @param maxSize If this is greater than 0, this cache will operate as an LRU cache. Otherwise, the user will need to remove objects manually.
*/
public Cache(int maxSize) {
data = new ConcurrentHashMap<>(maxSize + 1, 0.75f, 16);
this.maxSize = maxSize;
}
/**
* Creates a thread safe cache.
* This creates the cache without a limit, and therefore, does not operate as a LSU.
*/
public Cache() {
this(-1);
}
/**
* Sets the value with the respective associated key.
* If the addition of this value and key cause the cache to be bigger than its designated max size,
* It will remove one of the least used values in the cache to accomodate.
* @param key The key to associate with the value.
* @param value The value to store.
*/
public void set(K key, V value) {
if (data.containsKey(key)) {
data.get(key).value = value;
} else {
lock.lock();
try {
if (data.containsKey(key)) return;
CacheValue<K, V> val = new CacheValue<>(key, value);
data.put(key, val);
usage.add(val);
if (maxSize > 0 && data.size() > maxSize) {
data.remove(usage.pop().key);
}
} finally {
lock.unlock();
}
}
}
/**
* Retrieves cached value associated to the key.
* Uses equals.
* If does not exist, returns null.
* @param key the key associated with the value.
* @return the value associated to the key.
*/
public V get(K key) {
CacheValue<K, V> cacheValue = data.get(key);
if (cacheValue == null) return null;
usage.tryMoveToTop(cacheValue);
return cacheValue.value;
}
/**
* Removes the associated key and value pair.
* @param key The associated key.
* @return The associated value.
*/
public V remove(K key) {
CacheValue<K, V> cacheValue = data.remove(key);
if (cacheValue == null) return null;
usage.remove(cacheValue);
return cacheValue.value;
}
public int size() {
return data.size();
}
public boolean contains(K key) {
return data.containsKey(key);
}
public V getMostRecent() {
return usage.getTop().value;
}
public K getMostRecentKey() {
return usage.getTop().key;
}
/**
* Clears the cache of all values.
*/
public void clearCache() {
lock.lock();
try {
data.clear();
usage.clear();
} finally {
lock.unlock();
}
}
}

View File

@ -0,0 +1,119 @@
package net.reslate.islandsurvivalcraft.utilities.caching;
import java.util.concurrent.locks.ReentrantLock;
class CacheUsageStack<K, V> {
private volatile int size;
private volatile CacheValue<K, V> first, last;
private final ReentrantLock lock = new ReentrantLock();
private void addValueToStackTop(CacheValue<K, V> value) {
if (value == first) return;
if (first != null) {
first.front = value;
value.back = first;
} else {
last = value;
}
value.front = null;
first = value;
size++;
}
private void removeValueFromStack(CacheValue<K, V> value) {
if (value.front != null) {
value.front.back = value.back;
} else {
first = value.back;
}
if (value.back != null) {
value.back.front = value.front;
} else {
last = value.front;
}
value.front = null;
value.back = null;
size--;
}
public void moveToTop(CacheValue<K, V> value) {
lock.lock();
try {
if (!value.detached) {
removeValueFromStack(value);
addValueToStackTop(value);
}
} finally {
lock.unlock();
}
}
public boolean tryMoveToTop(CacheValue<K, V> value) {
boolean success = false;
success = lock.tryLock();
if (success) {
try {
if (!value.detached) {
removeValueFromStack(value);
addValueToStackTop(value);
}
} finally {
lock.unlock();
}
}
return success;
}
public void add(CacheValue<K, V> value) {
lock.lock();
try {
addValueToStackTop(value);
value.detached = false;
} finally {
lock.unlock();
}
}
public void remove(CacheValue<K, V> value) {
lock.lock();
try {
removeValueFromStack(value);
value.detached = true;
} finally {
lock.unlock();
}
}
public CacheValue<K, V> pop() {
CacheValue<K, V> cacheValue;
lock.lock();
try {
cacheValue = last;
removeValueFromStack(last);
cacheValue.detached = true;
} finally {
lock.unlock();
}
return cacheValue;
}
public void clear() {
lock.lock();
try {
first = null;
last = null;
} finally {
lock.unlock();
}
size = 0;
}
public int size() {
return size;
}
public CacheValue<K, V> getTop() {
return first;
}
}

View File

@ -0,0 +1,29 @@
package net.reslate.islandsurvivalcraft.utilities.caching;
class CacheValue<KeyType, ValueType> {
/**
* The key that represents this cache value in the caches hashmap.
*/
public final KeyType key;
/**
* The actual value that this cache value stores.
*/
public volatile ValueType value;
/**
* The cache value in this position.
*/
public volatile CacheValue<KeyType, ValueType> front, back;
/**
* Whether or not this value is detached from the cache's hashmap.
*/
public volatile boolean detached = true;
public CacheValue(KeyType key, ValueType value) {
this.key = key;
this.value = value;
}
}

View File

@ -0,0 +1,49 @@
package net.reslate.islandsurvivalcraft.utilities.datatypes;
import java.util.Objects;
public class Point2 {
public final int x, y;
private final int hash;
public Point2(int x, int y) {
this.x = x;
this.y = y;
this.hash = Objects.hash(x, y);
}
public Point2(Point2 point) {
this(point.x, point.y);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Point2) {
Point2 p = (Point2) obj;
return p.x == this.x && p.y == this.y;
}
return false;
}
public boolean fastEquals(Point2 point) {
return point.hashCode() == this.hash && x == point.x && y == point.y;
}
public double distance(Point2 point) {
return Math.hypot(point.x - this.x, point.y - this.y);
}
@Override
public int hashCode() {
return hash;
}
@Override
public String toString() {
return String.format("(%d, %d)", x, y);
}
public Point2 shift(int x, int y) {
return new Point2(this.x + x, this.y + y);
}
}

View File

@ -0,0 +1,38 @@
package net.reslate.islandsurvivalcraft.utilities.datatypes;
import java.util.Objects;
public class Point3 {
public final int x, y, z;
private final int hash;
public Point3(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
this.hash = Objects.hash(x, y, z);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Point3) {
Point3 p = (Point3) obj;
return p.x == this.x && p.y == this.y && p.z == this.z;
}
return false;
}
public boolean fastEquals(Point3 point) {
return point.hashCode() == this.hash && x == point.x && y == point.y && z == point.z;
}
@Override
public int hashCode() {
return hash;
}
@Override
public String toString() {
return String.format("(%d, %d, %d)", x, y, z);
}
}

View File

@ -0,0 +1,5 @@
package net.reslate.islandsurvivalcraft.utilities.datatypes;
public class Reference<T> {
public T value;
}

View File

@ -0,0 +1,38 @@
package net.reslate.islandsurvivalcraft.utilities.datatypes;
import java.util.Objects;
public class Vector3 {
public final float x, y, z;
private final int hash;
public Vector3(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
this.hash = Objects.hash(x, y, z);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Point3) {
Point3 p = (Point3) obj;
return p.x == this.x && p.y == this.y && p.z == this.z;
}
return false;
}
public boolean fastEquals(Point3 point) {
return point.hashCode() == this.hash && x == point.x && y == point.y && z == point.z;
}
@Override
public int hashCode() {
return hash;
}
@Override
public String toString() {
return String.format("(%d, %d, %d)", x, y, z);
}
}

View File

@ -0,0 +1,58 @@
package net.reslate.islandsurvivalcraft.utilities.drawing;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
public class Circle {
public final Point2 center;
public final CoordinateValidatable drawer;
public final int r;
int p, x, y;
/**
* A circle drawing object.
* @param center The center point.
* @param radius The radius of the circle to be drawn.
* @param drawer The coordinate validator to be used to draw. The value the validator returns is ignored.
*/
public Circle(Point2 center, int radius, CoordinateValidatable drawer) {
if (center == null) throw new NullPointerException("Center cannot be null.");
if (radius <= 0) throw new IllegalArgumentException("radius cannot be 0 or less.");
if (drawer == null) throw new NullPointerException("Drawer cannot be null.");
this.center = center;
this.r = radius;
this.drawer = drawer;
p = 1 - radius;
x = 0;
y = this.r;
}
/**
*
* @return true if done.
*/
public boolean step() {
if (x > y) return true;
drawer.validate(new Point2(center.x + x, center.y + y));
drawer.validate(new Point2(center.x + x, center.y - y));
drawer.validate(new Point2(center.x - x, center.y + y));
drawer.validate(new Point2(center.x - x, center.y - y));
drawer.validate(new Point2(center.x + y, center.y + x));
drawer.validate(new Point2(center.x + y, center.y - x));
drawer.validate(new Point2(center.x - y, center.y + x));
drawer.validate(new Point2(center.x - y, center.y - x));
if (p < 0) {
p += 2*x + 3;
} else {
p += 2 * x - 2 * y + 5;
y--;
}
x++;
return x > y;
}
public void draw() {
while (!step());
}
}

View File

@ -0,0 +1,46 @@
package net.reslate.islandsurvivalcraft.utilities.drawing;
import java.util.HashSet;
import java.util.LinkedList;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
public class Flooder {
private final Point2 start;
private final CoordinateValidatable floodable;
public Flooder(Point2 start, CoordinateValidatable floodable) {
this.start = start;
this.floodable = floodable;
}
public Flooder(int xStart, int yStart, CoordinateValidatable floodable) {
this(new Point2(xStart, yStart), floodable);
}
public void start() {
HashSet<Point2> checked = new HashSet<>();
LinkedList<Point2> queue = new LinkedList<>();
if (!checked.add(start)) return;
if (!floodable.validate(start)) return;
queue.add(start);
while (!queue.isEmpty()) {
Point2 p = queue.pop();
Point2 pE = new Point2(p.x + 1, p.y);
if (checked.add(pE) && floodable.validate(pE)) queue.add(pE);
Point2 pW = new Point2(p.x - 1, p.y);
if (checked.add(pW) && floodable.validate(pW)) queue.add(pW);
Point2 pN = new Point2(p.x, p.y + 1);
if (checked.add(pN) && floodable.validate(pN)) queue.add(pN);
Point2 pS = new Point2(p.x, p.y - 1);
if (checked.add(pS) && floodable.validate(pS)) queue.add(pS);
}
}
}

View File

@ -0,0 +1,21 @@
package net.reslate.islandsurvivalcraft.utilities.noise;
import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
public class FractalOctaveGenerator {
private final SimplexOctaveGenerator simplex;
private final double amplitude = 0.5d;
private final double frequency = 0.5d;
private final double scale = 0.05D;
public FractalOctaveGenerator(Random random) {
simplex = new SimplexOctaveGenerator(random, 4);
simplex.setScale(scale);
}
public double noise(int x, int y) {
return 1d - Math.abs(simplex.noise(x, y, frequency, amplitude, true));
}
}

View File

@ -0,0 +1,7 @@
package net.reslate.islandsurvivalcraft.utilities.pathfinding;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public interface CoordinateValidatable {
public boolean validate(Point2 point);
}

View File

@ -0,0 +1,112 @@
package net.reslate.islandsurvivalcraft.utilities.pathfinding;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Queue;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public class DepthFirstSearch {
private final HashSet<Node> checkedNodes;
private final NodeComparable comparable;
private final Queue<Node> queue;
private final Node startNode;
private final Point2 endGoal;
private final int maxNodesSearched;
private Node currentValidNode;
private Node results;
/**
* Instantiates a DFS search object.
* @param start The starting point for this search.
* @param endGoal The objective. May be null, but a target validator will then be required.
* @param customComparable The comparable to use to compare points. May be null, where then there will be no prioritization of direction.
* @param maxNodesSearched The maximum amount of allowed searched nodes. set to 0 for infinite.
*/
public DepthFirstSearch(Point2 start, Point2 endGoal, NodeComparable customComparable, int maxNodesSearched) {
if (start == null) throw new NullPointerException("Start point cannot be null.");
checkedNodes = new HashSet<>();
queue = new PriorityQueue<>();
this.comparable = customComparable;
this.endGoal = endGoal;
this.startNode = new Node(null, start, endGoal, customComparable);
queue.add(startNode);
this.maxNodesSearched = maxNodesSearched;
}
/**
* Instantiates a DFS search object.
* Where there is no comparable set and distance to goal will be used if there is one,
* and where there is no limit on amount of searched nodes.
* @param start The starting point of the search.
* @param endGoal The objective of the search, may be null.
*/
public DepthFirstSearch(Point2 start, Point2 endGoal) {
this(start, endGoal, null, 0);
}
public Node getResult() {
return results;
}
/**
* @return the currentNode
*/
public Node getCurrentNode() {
return currentValidNode;
}
/**
* Makes a search step.
* @param validity the validator.
* @param objective used to determine whether or not this DFS has founds its objective. Will use this over using the end goal.
* @return true if done, whether or not it found the objective is determined by the result.
*/
public boolean step(CoordinateValidatable validity, CoordinateValidatable objective) {
if (validity == null) throw new NullPointerException("The validator cannot be null.");
Node node = queue.poll();
if (node == null) return true;
if (checkedNodes.add(node) && validity.validate(node)) {
currentValidNode = node;
if (objective != null) {
if (objective.validate(currentValidNode)) {
results = currentValidNode;
return true;
}
} else {
if (endGoal == null) throw new IllegalStateException("Cannot build path without a target or goal.");
if (endGoal.fastEquals(currentValidNode)) {
results = currentValidNode;
return true;
}
}
if (maxNodesSearched > 0 && getSearchedCount() >= maxNodesSearched) return true;
Node north = new Node(currentValidNode, currentValidNode.x, currentValidNode.y + 1, endGoal, comparable);
queue.add(north);
Node south = new Node(currentValidNode, currentValidNode.x, currentValidNode.y - 1, endGoal, comparable);
queue.add(south);
Node west = new Node(currentValidNode, currentValidNode.x - 1, currentValidNode.y, endGoal, comparable);
queue.add(west);
Node east = new Node(currentValidNode, currentValidNode.x + 1, currentValidNode.y, endGoal, comparable);
queue.add(east);
}
return false;
}
/**
* Searches until complete. Completion is defined such that when a call to the step function does not return true.
* @param validator The validator to use to check if a node is considered allowed to be used.
* @param targetValidatable The validator to be used to determine whether or not the current node is the objective. May be null, however, the end goal must not be null in such a case.
* @return
*/
public boolean buildToGoal(CoordinateValidatable validator, CoordinateValidatable targetValidatable) {
while (!step(validator, targetValidatable));
return results != null;
}
public int getSearchedCount() {
return checkedNodes.size();
}
}

View File

@ -0,0 +1,20 @@
package net.reslate.islandsurvivalcraft.utilities.pathfinding;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public class LinkedCoordinateValidator implements CoordinateValidatable {
private final CoordinateValidatable[] CoordinateValidatables;
public LinkedCoordinateValidator(CoordinateValidatable...coordinateValidatables) {
this.CoordinateValidatables = coordinateValidatables;
}
@Override
public boolean validate(Point2 point) {
for (int i = 0; i < CoordinateValidatables.length; i++) {
if (CoordinateValidatables[i].validate(point)) return true;
}
return false;
}
}

View File

@ -0,0 +1,61 @@
package net.reslate.islandsurvivalcraft.utilities.pathfinding;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public class Node extends Point2 implements Comparable<Point2> {
private final NodeComparable nodeComparable;
private final Point2 parent;
private final Point2 goal;
private Point2 child;
public Node(Point2 parent, int x, int y, Point2 goal, NodeComparable nodeComparable) {
super(x, y);
this.parent = parent;
this.goal = goal;
this.nodeComparable = nodeComparable;
}
public Node(Point2 parent, Point2 location, Point2 goal, NodeComparable nodeComparable) {
this(parent, location.x, location.y, goal, nodeComparable);
}
/**
* Sets the child of this node. Can only be set once.
* @param child the child to set.
*/
public void setChild(Point2 child) {
if (this.child != null) throw new IllegalStateException("Child has already been set.");
this.child = child;
}
@Override
public int compareTo(Point2 o) {
if (nodeComparable != null) return nodeComparable.compare(this, o);
if (goal == null) return 0;
double distDiff = distance(goal) - distance(o);
if (distDiff == 0) {
return 0;
} else if (distDiff < 0) {
return -1;
} else {
return 1;
}
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
/**
* @return the parent
*/
public Point2 getParent() {
return parent;
}
}

View File

@ -0,0 +1,7 @@
package net.reslate.islandsurvivalcraft.utilities.pathfinding;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public interface NodeComparable {
public int compare(Point2 current, Point2 other);
}

View File

@ -0,0 +1,5 @@
package net.reslate.islandsurvivalcraft.utilities.weighting;
public interface Weightable {
public float getWeight();
}

View File

@ -0,0 +1,27 @@
package net.reslate.islandsurvivalcraft.utilities.weighting;
import java.util.Random;
import java.util.TreeMap;
public class WeightedSelector<T extends Weightable> {
private final TreeMap<Float, T> map;
private final float totalWeights;
public WeightedSelector(Iterable<T> set) {
float totalWeights = 0;
map = new TreeMap<>();
for (T t : set) {
totalWeights += t.getWeight();
map.put(totalWeights, t);
}
this.totalWeights = totalWeights;
}
public T selectRandom(Random random) {
return map.ceilingEntry(random.nextFloat() * totalWeights).getValue();
}
public T selectRandom(float selector) {
return map.ceilingEntry(selector * totalWeights).getValue();
}
}

View File

@ -0,0 +1,73 @@
package net.reslate.islandsurvivalcraft.world;
import java.util.Random;
import org.bukkit.block.Biome;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import net.reslate.islandsurvivalcraft.utilities.biomes.BiomeClassifications;
import net.reslate.islandsurvivalcraft.utilities.biomes.LandBiomeInfo;
import net.reslate.islandsurvivalcraft.utilities.biomes.OceanBiomeInfo;
public class BiomeMap {
private final SimplexOctaveGenerator noise;
public BiomeMap(Random random) {
this(random, 0.02d);
}
public BiomeMap(Random random, double scale) {
noise = new SimplexOctaveGenerator(random, 2);
noise.setScale(scale);
}
/**
* Attempts to procure a biome that helps transition from one biome to another biome
* for given biome.
* @param from the biome to transition from.
* @return the resulting transition biome.
*/
public Biome getTransitionalBiome(Biome from) {
String biomeName = from.name().toLowerCase();
if (biomeName.contains("jungle")) {
if (biomeName.contains("modified")) {
return Biome.MODIFIED_JUNGLE_EDGE;
}
return Biome.JUNGLE_EDGE;
} else if (biomeName.contains("mountain")) {
return null;
}
return null;
}
public BiomeClassifications getClassification(float temperature) {
if (temperature <= 0.1f) {
return BiomeClassifications.SNOWY;
} else if (temperature <= 0.3f) {
return BiomeClassifications.COLD;
} else if (temperature <= 1f) {
return BiomeClassifications.LUSH;
} else {
return BiomeClassifications.WARM;
}
}
public LandBiomeInfo getLandBiomeInfo(float temperature, int worldX, int worldZ) {
return getClassification(temperature).getSelector().selectRandom((float) noise.noise(worldX, worldZ, 0.5d, 0.5d, true));
}
/**
* Randomly selects an ocean biome that fits in given temperature.
*
* @param temperature Minecraft temperature to select biome from.
* @param isDeep If this ocean should be of the deep variant.
* @param seed The seed to use to select the biome.
* @return The randomly selected biome.
*/
public OceanBiomeInfo getOceanBiome(float temperature) {
BiomeClassifications classification = getClassification(temperature);
return classification.getAssociatedOceanBiomeInfo();
}
}

View File

@ -0,0 +1,93 @@
package net.reslate.islandsurvivalcraft.world.Information;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public class IslandInformation {
private final Set<Point2> chunkSpan;
private final Set<Point2> islandCoordinates;
private final Set<Point2> edgeCoordinates;
private final Point2 islandOrigin;
private UUID ownerUUID;
public IslandInformation(Point2 islandOrigin, Set<Point2> islandCoordinates, Set<Point2> edgeCoordinates, Set<Point2> chunksUsed) {
this.islandCoordinates = Collections.unmodifiableSet(islandCoordinates);
this.chunkSpan = Collections.unmodifiableSet(chunksUsed);
this.edgeCoordinates = Collections.unmodifiableSet(edgeCoordinates);
this.islandOrigin = islandOrigin;
}
/**
* @return a set that represents all coordinates within this island.
*/
public Set<Point2> getIslandCoordinates() {
return islandCoordinates;
}
/**
* @return a set of chunks that this island spans.
*/
public Set<Point2> getChunkSpan() {
return chunkSpan;
}
/**
* @return a set of points that represent all the edges of this island.
*/
public Set<Point2> getEdgeCoordinates() {
return edgeCoordinates;
}
/**
* @param ownerUUID the UUID of the player owner of this island. May be null.
*/
public void setOwnerUUID(UUID ownerUUID) {
this.ownerUUID = ownerUUID;
}
/**
* @return the islandOrigin
*/
public Point2 getIslandOrigin() {
return islandOrigin;
}
/**
* @return the UUID of the player owner of this island. May be null.
*/
public UUID getOwnerUUID() {
return ownerUUID;
}
public boolean isWithinIsland(Point2 coordinates) {
return islandCoordinates.contains(coordinates);
}
public boolean isEdgeOfIsland(Point2 coordinates) {
return edgeCoordinates.contains(coordinates);
}
public boolean isIslandInChunk(Point2 chunkCoords) {
return chunkSpan.contains(chunkCoords);
}
public int getIslandSize() {
return islandCoordinates.size();
}
@Override
public int hashCode() {
return islandOrigin.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof IslandInformation) {
return islandOrigin.equals(((IslandInformation) obj).getIslandOrigin());
}
return false;
}
}

View File

@ -0,0 +1,39 @@
package net.reslate.islandsurvivalcraft.world.Information;
import java.util.HashSet;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public class IslandInformationBuilder {
private final HashSet<Point2> islandCoordinates = new HashSet<>();
private final HashSet<Point2> edgeCoordinates = new HashSet<>();
private final HashSet<Point2> chunksUsed = new HashSet<>();
private boolean built = false;
public boolean addCoordinate(Point2 coordinate, boolean isEdge) {
if (coordinate == null) throw new NullPointerException("Coordinate cannot be null!");
if (built) throw new IllegalStateException("Island information has already been built!");
if (isEdge) edgeCoordinates.add(coordinate);
return islandCoordinates.add(coordinate);
}
public boolean addChunk(Point2 chunkCoordinate) {
if (chunkCoordinate == null) throw new NullPointerException("Chunk coordinate cannot be null!");
if (built) throw new IllegalStateException("Island information has already been built!");
return chunksUsed.add(chunkCoordinate);
}
public IslandInformation build(Point2 islandOrigin) {
if (islandOrigin == null) throw new NullPointerException("IslandOrigin cannot be null.");
if (built) throw new IllegalStateException("This island informationh as already been built!");
built = true;
return new IslandInformation(islandOrigin, islandCoordinates, edgeCoordinates, chunksUsed);
}
/**
* @return the chunksUsed
*/
public HashSet<Point2> getChunksUsed() {
return chunksUsed;
}
}

View File

@ -0,0 +1,155 @@
package net.reslate.islandsurvivalcraft.world.Information;
import java.lang.Thread.State;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import net.reslate.islandsurvivalcraft.utilities.GeneralUtilities;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
public class IslandInformationManager implements Runnable {
private final IslandWorldMap islandMap;
private final Thread loader;
private final Cache<Point2, CompletableFuture<HashSet<IslandInformation>>> buildingCache = new Cache<>();
private final ConcurrentHashMap<Point2, HashSet<IslandInformation>> islandSets = new ConcurrentHashMap<>();
public IslandInformationManager(IslandWorldMap islandWorldMap) {
this.islandMap = islandWorldMap;
loader = new Thread(this);
loader.setDaemon(true);
loader.setName("Island-Info-Builder");
loader.setPriority(Thread.MIN_PRIORITY);
loader.start();
}
private HashSet<IslandInformation> buildChunkIslandInformation(Point2 chunkCoords) {
HashSet<IslandInformation> result = new HashSet<>();
Point2[] surroundings = new Point2[4];
surroundings[0] = chunkCoords.shift(1, 0);
surroundings[1] = chunkCoords.shift(-1, 0);
surroundings[2] = chunkCoords.shift(0, -1);
surroundings[3] = chunkCoords.shift(0, 1);
HashSet<Point2> checkedCoordinates = new HashSet<>();
for (Point2 surrounding : surroundings) {
if (Thread.currentThread().isInterrupted())
return null;
HashSet<IslandInformation> chunkIslandSet = islandSets.get(surrounding);
if (chunkIslandSet != null) {
for (IslandInformation islandInformation : chunkIslandSet) {
if (islandInformation.isIslandInChunk(chunkCoords)) {
checkedCoordinates.addAll(islandInformation.getIslandCoordinates());
result.add(islandInformation);
}
}
}
}
for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) {
for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) {
if (Thread.currentThread().isInterrupted())
return null;
int worldX = chunkCoords.x * GeneralUtilities.CHUNK_SIZE + x;
int worldZ = chunkCoords.y * GeneralUtilities.CHUNK_SIZE + z;
if (islandMap.isIsland(worldX, worldZ)) {
IslandInformationBuilder infoBuilder = new IslandInformationBuilder();
Point2 origin = islandMap.findIslandOrigin(worldX, worldZ, (p) -> {
if (Thread.currentThread().isInterrupted())
return true;
if (!checkedCoordinates.add(p))
return true;
HashSet<IslandInformation> chunkIslandSet = islandSets.get(GeneralUtilities.worldToChunkCoordinates(p));
if (chunkIslandSet != null) {
for (IslandInformation islandInformation : chunkIslandSet) {
if (islandInformation.isIslandInChunk(chunkCoords)) {
checkedCoordinates.addAll(islandInformation.getIslandCoordinates());
result.add(islandInformation);
return true;
}
}
}
infoBuilder.addCoordinate(p, islandMap.isEdgeOfIsland(p.x, p.y));
infoBuilder.addChunk(GeneralUtilities.worldToChunkCoordinates(p));
return false;
});
if (origin != null) {
IslandInformation islandInfo = infoBuilder.build(origin);
result.add(islandInfo);
}
}
}
}
return result;
}
public void loadChunkIslandInformation(Point2 chunkCoords) {
CompletableFuture<HashSet<IslandInformation>> completableFuture = new CompletableFuture<>();
buildingCache.set(chunkCoords, completableFuture);
if (loader.getState() == State.WAITING) {
synchronized (loader) {
loader.notifyAll();
}
}
}
public void unloadChunkIslandInformation(Point2 chunkCoords) {
if (buildingCache.contains(chunkCoords))
buildingCache.remove(chunkCoords).cancel(true);
islandSets.remove(chunkCoords);
}
/**
* Gets the island information for the given coordinates.
*
* @param coords The coordinates to check for island information.
* @param checkLoading Whether or not to check for the currently loading chunks.
* @return The island information or null if there is no island at those
* coordinates.
*/
public IslandInformation getIslandInformation(Point2 coords, boolean checkLoading) {
Point2 chunkCoords = GeneralUtilities.worldToChunkCoordinates(coords);
if (!islandSets.containsKey(chunkCoords)) {
if (checkLoading && buildingCache.contains(chunkCoords)) {
try {
buildingCache.get(chunkCoords).get();
} catch (InterruptedException | ExecutionException e) {
}
}
}
HashSet<IslandInformation> chunkIslands = islandSets.get(chunkCoords);
if (chunkIslands != null) {
for (IslandInformation islandInformation : chunkIslands) {
if (islandInformation.isWithinIsland(coords))
return islandInformation;
}
}
return null;
}
public boolean isChunkIslandInformationLoaded(Point2 chunkCoords, boolean checkLoading) {
return islandSets.containsKey(chunkCoords) || buildingCache.contains(chunkCoords);
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
while (buildingCache.size() > 0) {
Point2 chunkCoords = buildingCache.getMostRecentKey();
CompletableFuture<HashSet<IslandInformation>> future = buildingCache.remove(chunkCoords);
HashSet<IslandInformation> islandInfos = buildChunkIslandInformation(chunkCoords);
islandSets.put(chunkCoords, islandInfos);
future.complete(islandInfos);
}
try {
synchronized (loader) {
loader.wait();
}
} catch (InterruptedException e) {
}
}
}
}

View File

@ -0,0 +1,205 @@
package net.reslate.islandsurvivalcraft.world;
import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Reference;
import net.reslate.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
import net.reslate.islandsurvivalcraft.utilities.pathfinding.DepthFirstSearch;
public class IslandWorldMap {
private final float SHALLOW_PORTION = 0.06f;
private final float TRANSITION_DEPTH_PORTION = 0.1f;
private final float DEEP_OCEAN_PORTION = 0.5f;
private final float HILL_PORTION = 0.4f;
private final float SHORE_PORTION = 0.095f;
private final Cache<Point2, Double> blockValueCache;
private final SimplexOctaveGenerator noiseGenerator;
public final islandValidator islandValidator = new islandValidator();
private final int NOISE_OCTAVES = 4;
private final float ISLAND_PERCENT = 0.36f;
private final double NOISE_FREQ = 1.78D;
private final double NOISE_AMP = 0.47D;
private final double SCALE = 0.005D;
public IslandWorldMap(Random random) {
this.noiseGenerator = new SimplexOctaveGenerator(random, NOISE_OCTAVES);
noiseGenerator.setScale(SCALE);
this.blockValueCache = new Cache<>(256000);
}
/**
* Considers this land if the world block value is greater than or equal to 0.
* @param worldX the X coordinate of location in world.
* @param worldZ the Z coordinate of location in world.
* @return
*/
public boolean isLand(int worldX, int worldZ) {
if (getWorldValue(worldX, worldZ) >= 0) {
return true;
}
return false;
}
/**
* Calculates if this is the edge of the island by checking if it's surrounding blocks are part of this island.
* @param worldX The world X coordinate.
* @param worldZ The world Z coordinate.
* @return true if it is the edge of the island and false if it isn't.
*/
public boolean isEdgeOfIsland(int worldX, int worldZ) {
return isIsland(worldX, worldZ) &&
!(isIsland(worldX + 1, worldZ) &&
isIsland(worldX - 1, worldZ) &&
isIsland(worldX , worldZ + 1) &&
isIsland(worldX, worldZ - 1));
}
/**
* Considers these coordinates to be part of an island if:
* It is land, or if it is a shallow portion of land.
* @param worldX the X coordinate of location in world.
* @param worldZ the Y coordinate of location in world.
* @return true if these coordinates represent an island.
*/
public boolean isIsland(int worldX, int worldZ) {
return isLand(worldX, worldZ) || isShallowPortion(worldX, worldZ);
}
/**
* Checks coordinates for if this location is a hill.
* It is considered a hill when its world value is greater than the HILL_PORTION.
* @param worldX
* @param worldZ
* @return
*/
public boolean isHill(int worldX, int worldZ) {
return getWorldValue(worldX, worldZ) >= HILL_PORTION;
}
public boolean isShore(int worldX, int worldZ) {
return isLand(worldX, worldZ) && getWorldValue(worldX, worldZ) <= SHORE_PORTION;
}
public boolean isTransitional(int worldX, int worldZ) {
return !isIsland(worldX, worldZ) && getWorldValue(worldX, worldZ) >= -TRANSITION_DEPTH_PORTION;
}
public boolean isDeep(int worldX, int worldZ) {
return getWorldValue(worldX, worldZ) <= -DEEP_OCEAN_PORTION;
}
/**
* This block is considered a shallow portion of the island:
* The block height is less than the sea level,
* and that the block height is greater than the shallow portion.
* If it is land, it is not shallow.
* @param worldX the X coordinate of location in world.
* @param worldZ the Z coordinate of location in world.
* @return true if it is considered the shallow portion.
*/
public boolean isShallowPortion(int worldX, int worldZ) {
if (!isLand(worldX, worldZ) && getWorldValue(worldX, worldZ) >= -SHALLOW_PORTION) {
return true;
}
return false;
}
/**
* World block value will be 0 or positive if it is a part of an island.
* If less than, it is considered under the sea.
* Does not factor in a shallow depth.
* The value is normalized to [-1, 1].
* @param worldX the x world coordinate to obtain this value for.
* @param worldZ the z world coordinate to obtain this value for.
* @return a value representing the island at the given point.
*/
public double getWorldValue(int worldX, int worldZ) {
Point2 p = new Point2(worldX, worldZ);
Double res = blockValueCache.get(p);
if (res == null) {
double shift = 1f - 2f * ISLAND_PERCENT;
double rawNoise = noiseGenerator.noise(worldX, worldZ, NOISE_FREQ, NOISE_AMP, true) - shift;
double maxNeg = -1 - shift;
double maxPos = 1 - shift;
if (rawNoise < 0) {
res = - rawNoise / maxNeg;
} else {
res = rawNoise / maxPos;
}
blockValueCache.set(p, res);
return res;
}
return res;
}
/**
* Determines of the two columns are part of the same island.
* If both points are connected consistently by island, than both are considered part of the same island.
* @param firstColumn The column of the first island.
* @param secondColumn The column of the second island.
* @return If the two islands are connected.
*/
public boolean isSameIsland(Point2 firstColumn, Point2 secondColumn) {
DepthFirstSearch dfs = new DepthFirstSearch(firstColumn, secondColumn);
boolean same = dfs.buildToGoal(islandValidator, null);
return same;
}
/**
* Find the island's origin block. Will call the coordinate target validatable to end early if nessecary.
* @param worldX The x coordinate of the island block in question.
* @param worldZ The y coordinate of the island block in question.
* @param targetValidator The coordinate target validator to use. May be null.
* @return The island origin point. Will be null if the passed target target validator stops search prematurely.
*/
public Point2 findIslandOrigin(int worldX, int worldZ, CoordinateValidatable targetValidator) {
if (!isIsland(worldX, worldZ)) throw new IllegalArgumentException("The given coordinates are not part is an island.");
final Point2 goal = new Point2(0, 0);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(worldX, worldZ), goal);
Reference<Point2> closest = new Reference<>();
dfs.buildToGoal(islandValidator, new CoordinateValidatable() {
Double closestDistance = null;
@Override
public boolean validate(Point2 point) {
if (targetValidator != null && targetValidator.validate(point)) {
closest.value = null;
return true;
}
double currentDistance = point.distance(goal);
if (closestDistance == null || (currentDistance) < closestDistance) {
closestDistance = currentDistance;
closest.value = point;
}
return false;
}
});
return closest.value;
}
public Point2 findIslandOrigin(int worldX, int worldZ) {
return findIslandOrigin(worldX, worldZ, null);
}
public int calculateIslandHash(int worldX, int worldZ) {
return findIslandOrigin(worldX, worldZ).hashCode();
}
private class islandValidator implements CoordinateValidatable {
@Override
public boolean validate(Point2 point) {
return isIsland(point.x, point.y);
}
}
}

View File

@ -0,0 +1,33 @@
package net.reslate.islandsurvivalcraft.world;
import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
public class TemperatureMap {
private final Cache<Point2, Float> temperatureCache;
private final double frequency = 0.5D;
private final double amplitude = 0.5D;
private volatile SimplexOctaveGenerator noiseGenerator;
public TemperatureMap(Random random) {
temperatureCache = new Cache<>(1024);
noiseGenerator = new SimplexOctaveGenerator(random, 2);
noiseGenerator.setScale(0.001D);
}
public float getTemperature(int worldX, int worldZ) {
Point2 loc = new Point2(worldX, worldZ);
Float val = temperatureCache.get(loc);
if (val == null) {
val = (float) (((noiseGenerator.noise(worldX, worldZ, frequency, amplitude, true) + 1D) / 2D) * 3D - 1D);
temperatureCache.set(loc, val);
}
return val;
}
}

View File

@ -0,0 +1,127 @@
package net.reslate.islandsurvivalcraft.world;
import java.util.Random;
import org.bukkit.World;
import net.reslate.islandsurvivalcraft.world.Information.IslandInformationManager;
import net.reslate.islandsurvivalcraft.world.generation.chunks.IslandWorldChunkGenerator;
public class WorldInfo {
private final WorldInfoManager manager;
private volatile IslandInformationManager islandInfoManager;
private volatile Random random;
private volatile int seaLevel;
private volatile int worldHeight;
private volatile boolean initialized;
private volatile long seed;
private volatile BiomeMap biomeMap;
private volatile TemperatureMap tempMap;
private volatile IslandWorldMap islandMap;
private volatile IslandWorldChunkGenerator generator;
/**
* Will initialize with the known information extracted from the given world if
* world is not null. If the world is null, this world info will not be
* initialized and awaits initialization from the generator.
*
* @param world The world this object is describing. May be null to create a
* non-initialized info object.
*/
public WorldInfo(WorldInfoManager manager) {
this.manager = manager;
this.generator = new IslandWorldChunkGenerator(this);
}
public void initialize(World world) {
if (initialized)
throw new IllegalStateException("This world information object has already been initialized.");
if (world == null) throw new NullPointerException("The world argument cannot be null.");
this.initialized = true;
this.seed = world.getSeed();
this.random = new Random(seed);
this.biomeMap = new BiomeMap(random);
this.tempMap = new TemperatureMap(random);
this.islandMap = new IslandWorldMap(random);
this.islandInfoManager = new IslandInformationManager(islandMap);
this.worldHeight = world.getMaxHeight();
this.seaLevel = world.getSeaLevel();
}
/**
* @return whether or not this world info is initialized.
*/
public boolean isInitialized() {
return initialized;
}
/**
* @return the islandInfoManager
*/
public IslandInformationManager getIslandInfoManager() {
return islandInfoManager;
}
/**
* @return the biomeMap
*/
public BiomeMap getBiomeMap() {
return biomeMap;
}
/**
* @return the generator
*/
public IslandWorldChunkGenerator getGenerator() {
return generator;
}
/**
* @return the islandMap
*/
public IslandWorldMap getIslandMap() {
return islandMap;
}
/**
* @return the manager
*/
public WorldInfoManager getManager() {
return manager;
}
/**
* @return the random
*/
public Random getRandom() {
return random;
}
/**
* @return the seaLevel
*/
public int getSeaLevel() {
return seaLevel;
}
/**
* @return the seed
*/
public long getSeed() {
return seed;
}
/**
* @return the tempMap
*/
public TemperatureMap getTempMap() {
return tempMap;
}
/**
* @return the worldHeight
*/
public int getWorldHeight() {
return worldHeight;
}
}

View File

@ -0,0 +1,66 @@
package net.reslate.islandsurvivalcraft.world;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.NullArgumentException;
import org.bukkit.Chunk;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.generator.ChunkGenerator;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.world.generation.GeneratorModes;
import net.reslate.islandsurvivalcraft.world.generation.chunks.IslandWorldChunkGenerator;
public class WorldInfoManager implements Listener {
public final ConcurrentHashMap<String, WorldInfo> worldInformation = new ConcurrentHashMap<>();
public WorldInfoManager() {
}
/**
* Return the world info requested for the given world.
* Should not be null.
* @param world The world the associated info is to be requested for.
* @return The world info.
*/
public WorldInfo retrieve(String worldName) {
if (worldName == null) throw new NullArgumentException("world");
return worldInformation.computeIfAbsent(worldName, (name) -> {
return new WorldInfo(this);
});
}
public ChunkGenerator getChunkGenerator(String worldName, GeneratorModes mode) {
IslandWorldChunkGenerator islandWorldChunkGenerator = retrieve(worldName).getGenerator();
if (!islandWorldChunkGenerator.isInitialized()) islandWorldChunkGenerator.setGeneratorType(mode);
return islandWorldChunkGenerator;
}
/**
* Resets the manager deleting all currently loaded world info from memory.
*/
public void reset() {
worldInformation.clear();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
if (!worldInformation.containsKey(event.getWorld().getName())) return;
WorldInfo worldInfo = retrieve(event.getWorld().getName());
if (!worldInfo.isInitialized()) worldInfo.initialize(event.getWorld());
worldInfo.getIslandInfoManager().loadChunkIslandInformation(new Point2(chunk.getX(), chunk.getZ()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void onChunkUnload(ChunkUnloadEvent event) {
Chunk chunk = event.getChunk();
if (!worldInformation.containsKey(event.getWorld().getName())) return;
WorldInfo worldInfo = retrieve(event.getWorld().getName());
if (!worldInfo.isInitialized()) worldInfo.initialize(event.getWorld());
worldInfo.getIslandInfoManager().unloadChunkIslandInformation(new Point2(chunk.getX(), chunk.getZ()));
}
}

View File

@ -0,0 +1,5 @@
package net.reslate.islandsurvivalcraft.world.generation;
public enum GeneratorModes {
UNIQUE
}

View File

@ -0,0 +1,29 @@
package net.reslate.islandsurvivalcraft.world.generation.biomes;
import net.reslate.islandsurvivalcraft.utilities.biomes.BiomeInfo;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.world.BiomeMap;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
import net.reslate.islandsurvivalcraft.world.TemperatureMap;
public interface BiomeGenerator {
/**
* Given a biome array, requests for it to be saturated by the IslandWorldChunkGenerator.
* The array should store the columns of biomes for the entire chunk.
* It doesn't need to be populated on the first call as this method will be called once for every column in the chunk.
* However, if some biomes can be set without repetative calls, doing so will prevent this method from being called for those locals.
*
* @param localBiomeInfo The array of biomes that are to be saturated. First two arrays represent the x and y values, and the third represents the biome set.
* @param chunkX The X coordinate of the chunk.
* @param chunkZ The Z coordinate of the chunk.
* @param localX The X coordinate of the column within the chunk.
* @param localZ The Z coordinate of the column within the chunk.
* @param islandMap The island mapper to be used.
* @param tempGenerator The temperature generator to be used.
* @param biomeCache Cache for biomes.
* @param chunkGenCache Cache for whether or not the chunk is generated.
*/
public void generateBiomeColumn(BiomeInfo[][] localBiomeInfo, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMap islandMap, BiomeMap biomeSelector, TemperatureMap tempGenerator, Cache<Point2, BiomeInfo> biomeCache);
}

View File

@ -0,0 +1,63 @@
package net.reslate.islandsurvivalcraft.world.generation.biomes;
import net.reslate.islandsurvivalcraft.utilities.GeneralUtilities;
import net.reslate.islandsurvivalcraft.utilities.biomes.BiomeInfo;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Reference;
import net.reslate.islandsurvivalcraft.utilities.drawing.Flooder;
import net.reslate.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
import net.reslate.islandsurvivalcraft.world.BiomeMap;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
import net.reslate.islandsurvivalcraft.world.TemperatureMap;
public class UniqueBiomeGenerator implements BiomeGenerator {
@Override
public void generateBiomeColumn(BiomeInfo[][] localBiomeInfo, int chunkX, int chunkZ, int localX, int localZ,
IslandWorldMap islandMap, BiomeMap biomeSelector, TemperatureMap tempGenerator,
Cache<Point2, BiomeInfo> biomeCache) {
int worldX = 16 * chunkX + localX;
int worldZ = 16 * chunkZ + localZ;
Point2 worldCoords = new Point2(worldX, worldZ);
localBiomeInfo[localX][localZ] = biomeCache.get(worldCoords);
if (localBiomeInfo[localX][localZ] != null) return;
float temperature = tempGenerator.getTemperature(worldX, worldZ);
if (!islandMap.isIsland(worldX, worldZ)) {
BiomeInfo oceanInfo = biomeSelector.getOceanBiome(temperature);
localBiomeInfo[localX][localZ] = oceanInfo;
biomeCache.set(worldCoords, oceanInfo);
return;
}
Reference<BiomeInfo> currentIslandInfo = new Reference<>();
Point2 islandOrigin = islandMap.findIslandOrigin(worldX, worldZ, new CoordinateValidatable() {
@Override
public boolean validate(Point2 coord) {
BiomeInfo info = biomeCache.get(coord);
if (info == null) return false;
currentIslandInfo.value = info;
return true;
}
});
if (currentIslandInfo.value == null) {
temperature = tempGenerator.getTemperature(islandOrigin.x, islandOrigin.y);
currentIslandInfo.value = biomeSelector.getLandBiomeInfo(temperature, islandOrigin.x, islandOrigin.y);
}
Flooder flooder = new Flooder(worldCoords, new CoordinateValidatable() {
@Override
public boolean validate(Point2 point) {
Point2 chunkCoords = GeneralUtilities.worldToChunkCoordinates(point);
if (chunkCoords.x != chunkX || chunkCoords.y != chunkZ || !islandMap.isIsland(point.x, point.y)) return false;
biomeCache.set(point, currentIslandInfo.value);
Point2 localCoords = GeneralUtilities.worldToLocalChunkCoordinates(point);
localBiomeInfo[localCoords.x][localCoords.y] = currentIslandInfo.value;
return true;
}
});
flooder.start();
}
}

View File

@ -0,0 +1,200 @@
package net.reslate.islandsurvivalcraft.world.generation.chunks;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.apache.commons.lang.NotImplementedException;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.Listener;
import org.bukkit.generator.ChunkGenerator;
import net.reslate.islandsurvivalcraft.utilities.GeneralUtilities;
import net.reslate.islandsurvivalcraft.utilities.biomes.BiomeInfo;
import net.reslate.islandsurvivalcraft.utilities.biomes.LandBiomeInfo;
import net.reslate.islandsurvivalcraft.utilities.biomes.OceanBiomeInfo;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.world.BiomeMap;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
import net.reslate.islandsurvivalcraft.world.TemperatureMap;
import net.reslate.islandsurvivalcraft.world.WorldInfo;
import net.reslate.islandsurvivalcraft.world.generation.GeneratorModes;
import net.reslate.islandsurvivalcraft.world.generation.biomes.BiomeGenerator;
import net.reslate.islandsurvivalcraft.world.generation.biomes.UniqueBiomeGenerator;
import net.reslate.islandsurvivalcraft.world.generation.shaders.WorldHeightShader;
import net.reslate.islandsurvivalcraft.world.generation.shaders.WorldLayerShader;
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
private final int BEDROCK_HEIGHT = 5;
private volatile GeneratorModes generatorType;
private volatile boolean initialized = false;
private volatile WorldInfo worldInfo;
private volatile IslandWorldMap islandMap;
private volatile TemperatureMap temperatureMap;
private volatile BiomeMap biomeMap;
private volatile BiomeGenerator biomeGenerator;
private volatile WorldHeightShader heightShader;
private volatile WorldLayerShader layerShader;
private volatile Cache<Point2, BiomeInfo> biomeCache = new Cache<>(131072);
private final ExecutorService exAlpha = GeneralUtilities.ISC_EXECUTOR_ALPHA;
public void initialize() {
if (initialized) throw new IllegalStateException("This generator has already been initialized.");
initialized = true;
this.islandMap = worldInfo.getIslandMap();
this.temperatureMap = worldInfo.getTempMap();
this.biomeMap = worldInfo.getBiomeMap();
this.heightShader = new WorldHeightShader(worldInfo.getRandom(), islandMap, worldInfo.getSeaLevel(), worldInfo.getWorldHeight(), BEDROCK_HEIGHT + 1);
this.layerShader = new WorldLayerShader(worldInfo.getRandom(), worldInfo.getSeaLevel(), worldInfo.getWorldHeight(), islandMap);
if (generatorType == GeneratorModes.UNIQUE) {
biomeGenerator = new UniqueBiomeGenerator();
} else {
throw new NotImplementedException();
}
}
/**
* @param generatorType the gID to set to.
*/
public void setGeneratorType(GeneratorModes generatorType) {
if (initialized) throw new IllegalStateException("This generator has already been initialized.");
this.generatorType = generatorType;
}
public IslandWorldChunkGenerator(WorldInfo worldInfo) {
this.worldInfo = worldInfo;
}
@Override
public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {
if (!worldInfo.isInitialized()) {
worldInfo.initialize(world);
}
if (!isInitialized()) initialize();
if (!initialized) throw new IllegalStateException("This generator has not been initialized.");
Future<Boolean> preLoader = exAlpha.submit(() -> {
for (int x = GeneralUtilities.CHUNK_SIZE - 1; x >= 0; x--) {
for (int z = GeneralUtilities.CHUNK_SIZE - 1; z >= 0; z--) {
if (Thread.currentThread().isInterrupted()) return false;
islandMap.getWorldValue(GeneralUtilities.CHUNK_SIZE * chunkX + x, GeneralUtilities.CHUNK_SIZE * chunkZ + z);
}
}
return true;
});
ChunkData chunkData = createChunkData(world);
BiomeInfo[][] localBiomeInfos = new BiomeInfo[GeneralUtilities.CHUNK_SIZE][GeneralUtilities.CHUNK_SIZE];
for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) {
for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) {
final int localX = x;
final int localZ = z;
final int worldX = GeneralUtilities.CHUNK_SIZE * chunkX + localX;
final int worldZ = GeneralUtilities.CHUNK_SIZE * chunkZ + localZ;
if (localBiomeInfos[localX][localZ] == null) {
biomeGenerator.generateBiomeColumn(localBiomeInfos, chunkX, chunkZ, localX, localZ, islandMap, biomeMap,
temperatureMap, biomeCache);
}
if (localBiomeInfos[localX][localZ] == null) throw new IllegalStateException("Biome column produced was null.");
Biome currentBiome = null;
BiomeInfo biomeInfo = localBiomeInfos[localX][localZ];
if (islandMap.isHill(worldX, worldZ)) {
currentBiome = ((LandBiomeInfo) biomeInfo).getHillBiome();
} else if (islandMap.isShore(worldX, worldZ)) {
currentBiome = ((LandBiomeInfo) biomeInfo).getShoreBiome();
} else if (islandMap.isShallowPortion(worldX, worldZ)) {
currentBiome = ((LandBiomeInfo) biomeInfo).getShoreBiome();
} else if (islandMap.isLand(worldX, worldZ)) {
currentBiome = biomeInfo.getMainBiome();
} else if (islandMap.isTransitional(worldX, worldZ)) {
currentBiome = biomeInfo.getTransitionBiome();
} else if (islandMap.isDeep(worldX, worldZ)) {
currentBiome = ((OceanBiomeInfo) biomeInfo).getDeepAlternative();
} else {
currentBiome = biomeInfo.getMainBiome();
}
for (int y = 0; y < worldInfo.getWorldHeight(); y++) {
biomeGrid.setBiome(localX, y, localZ, currentBiome);
}
BiomeInfo currentBiomeInfo = localBiomeInfos[localX][localZ];
int terrainHeight = heightShader.getTerrainHeight(worldX, worldZ, currentBiomeInfo);
int currentTerrainHeight = terrainHeight - 1;
int bedrockHeight = random.nextInt(5) + 1;
if (layerShader.hasSpecialLayers(currentBiome)) {
BlockData currentMaterial = layerShader.getMaterialForHeight(worldInfo.getSeed(), worldX, worldZ, currentTerrainHeight, terrainHeight - 1, currentBiome);
while (currentMaterial != null) {
chunkData.setBlock(localX, currentTerrainHeight, localZ, currentMaterial);
currentTerrainHeight--;
currentMaterial = layerShader.getMaterialForHeight(worldInfo.getSeed(), worldX, worldZ, currentTerrainHeight, terrainHeight - 1, currentBiome);
}
} else {
int surfaceThickness = layerShader.getSurfaceThickness(worldX, worldZ, currentBiome);
currentTerrainHeight = currentTerrainHeight - surfaceThickness;
chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + surfaceThickness + 1, localZ + 1, layerShader.getSurfaceMaterial(currentBiome));
int transitionThickness = layerShader.getTransitionMaterialThickness(worldX, worldZ, currentBiome);
currentTerrainHeight = currentTerrainHeight - transitionThickness;
chunkData.setRegion(localX, currentTerrainHeight, localZ, localX + 1, currentTerrainHeight + transitionThickness + 1, localZ + 1, layerShader.getTransitionMaterial(currentBiome));
}
chunkData.setRegion(localX, bedrockHeight, localZ, localX + 1, currentTerrainHeight + 1, localZ + 1, Material.STONE);
if (terrainHeight < worldInfo.getSeaLevel()) {
chunkData.setRegion(localX, terrainHeight, localZ, localX + 1, worldInfo.getSeaLevel(), localZ + 1, Material.WATER);
}
chunkData.setRegion(localX, 0, localZ, localX + 1, bedrockHeight, localZ + 1, Material.BEDROCK);
}
}
preLoader.cancel(false);
return chunkData;
}
/**
* @return if this generator has been initialized.
*/
public boolean isInitialized() {
return initialized;
}
@Override
public boolean canSpawn(World world, int x, int z) {
return islandMap.isLand(x, z);
}
@Override
public Location getFixedSpawnLocation(World world, Random random) {
return null;
}
@Override
public boolean shouldGenerateCaves() {
return true;
}
@Override
public boolean shouldGenerateDecorations() {
return true;
}
@Override
public boolean shouldGenerateStructures() {
return true;
}
@Override
public boolean isParallelCapable() {
return true;
}
@Override
public boolean shouldGenerateMobs() {
return true;
}
}

View File

@ -0,0 +1,80 @@
package net.reslate.islandsurvivalcraft.world.generation.shaders;
import java.util.Random;
import org.bukkit.block.Biome;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import net.reslate.islandsurvivalcraft.utilities.biomes.BiomeInfo;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
public class WorldHeightShader {
private final IslandWorldMap mapper;
private final SimplexOctaveGenerator noiseGenerator;
private final int seaLevel;
private final int worldHeight;
private final int minimumHeight;
public WorldHeightShader(Random random, IslandWorldMap islandLocator, int seaLevel, int worldHeight, int minimumHeight) {
this.mapper = islandLocator;
this.seaLevel = seaLevel;
this.worldHeight = worldHeight;
this.minimumHeight = minimumHeight;
this.noiseGenerator = new SimplexOctaveGenerator(random, 8);
this.noiseGenerator.setScale(0.0225d);
}
public int getTerrainHeight(int worldX, int worldZ, BiomeInfo biomeInfo) {
int height = 0;
if (!mapper.isLand(worldX, worldZ)) {
height = (int) Math.floor(calculateTerrainFactor(worldX, worldZ, seaLevel * 0.8d, 1.7d, 0.5d, 1d, 0.09d));
} else {
height = getIslandBiomeHeight(worldX, worldZ, biomeInfo.getMainBiome(), 0d);
if (!mapper.isShore(worldX, worldZ)) height ++;
}
height = Math.max(minimumHeight, height);
if (height > worldHeight) throw new IllegalStateException(String.format("Resulting height is greater than world height! Current biome info: %s, maximum height: %d, minimum height %d", biomeInfo, worldHeight, minimumHeight));
return height;
}
private int getIslandBiomeHeight(int worldX, int worldZ, Biome biome, double heightModifier) {
int res = 0;
String biomeName = biome.name().toLowerCase();
if (biomeName.contains("mountains")) {
if (biomeName.contains("gravelly")) {
res = (int) calculateTerrainFactor(worldX, worldZ, 100d + heightModifier, 1.8d, 0.8d);
} else {
res = (int) calculateTerrainFactor(worldX, worldZ, 180d + heightModifier, 1.88d, 0.5d);
}
} else if (biomeName.contains("plateau")) {
res = (int) Math.min(calculateTerrainFactor(worldX, worldZ, 60d + heightModifier), seaLevel + 30d);
} else if (biomeName.contains("modified")) {
res = (int) calculateTerrainFactor(worldX, worldZ, 80d + heightModifier);
} else if (biomeName.contains("shattered")) {
res = (int) calculateTerrainFactor(worldX, worldZ, 90d + heightModifier);
} else if (biomeName.contains("tall")) {
res = (int) calculateTerrainFactor(worldX, worldZ, 80d + heightModifier);
} else {
res = (int) calculateTerrainFactor(worldX, worldZ, 35d + heightModifier);
}
return res;
}
private double calculateTerrainFactor(int worldX, int worldZ, double heightFactor, double freq, double amplitude, double shift, double exaggerationFactor) {
double res = seaLevel;
double shapeValue = (noiseGenerator.noise(worldX, worldZ, freq, amplitude, true) + shift) / (shift + 1d);
if (shift == 1d && exaggerationFactor != 1d) shapeValue = Math.pow(shapeValue, exaggerationFactor);
double islandValue = mapper.getWorldValue(worldX, worldZ);
res += (islandValue) * (shapeValue * heightFactor);
return res;
}
private double calculateTerrainFactor(int worldX, int worldZ, double heightFactor, double freq, double amp) {
return calculateTerrainFactor(worldX, worldZ, heightFactor, freq, amp, 0.75d, 1d);
}
private double calculateTerrainFactor(int worldX, int worldZ, double heightFactor) {
return calculateTerrainFactor(worldX, worldZ, heightFactor, 1.6d, 0.5d);
}
}

View File

@ -0,0 +1,145 @@
package net.reslate.islandsurvivalcraft.world.generation.shaders;
import java.util.Random;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Vector3;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
@SuppressWarnings("unused")
public class WorldLayerShader {
private final Cache<Vector3, Double> noiseCache = new Cache<>(256);
private final SimplexOctaveGenerator noiseGenerator;
private final IslandWorldMap islandMap;
private final int seaLevel, maxHeight;
private final Material[] badlandTerrocota = new Material[] {
Material.BLACK_TERRACOTTA,
Material.BROWN_TERRACOTTA,
Material.GRAY_TERRACOTTA,
Material.LIGHT_GRAY_TERRACOTTA,
Material.YELLOW_TERRACOTTA,
Material.WHITE_TERRACOTTA
};
public WorldLayerShader(Random random, int seaLevel, int maxHeight, IslandWorldMap islandMap) {
this.islandMap = islandMap;
this.seaLevel = seaLevel;
this.maxHeight = maxHeight;
this.noiseGenerator = new SimplexOctaveGenerator(random, 4);
this.noiseGenerator.setScale(0.1d);
}
public boolean hasSpecialLayers(Biome biome) {
String biomeName = biome.toString().toLowerCase();
return
biomeName.contains("badlands") ||
biomeName.contains("mountain");
}
/**
* Returns the material for the height. If there are no more special materials, this will return null.
*
* @param seed The seed to use for special layer generation.
* @param worldX The x world coordinate.
* @param worldZ The z world coordinate.
* @param y The current layer (AKA the y world coordinate).
* @param highestPoint The y level of the top layer.
* @param biomeSet The biomeset for the current column.
* @return The block data for the material, or null if there are no further special materials.
*/
public BlockData getMaterialForHeight(long seed, int worldX, int worldZ, int y, int highestPoint, Biome currentBiome) {
Biome mainBiome = currentBiome;
String mainBiomeName = mainBiome.toString().toLowerCase();
BlockData res = null;
if (mainBiomeName.contains("badlands")) {
int seedOffset = (worldX + worldZ) / 1000;
Random random = new Random(seed + seedOffset);
random.setSeed(seed + seedOffset);
int yInterval = (int) (random.nextFloat() * 9) + 1;
int yBandInitial = (int) (seaLevel + random.nextDouble() * 50);
if (y > yBandInitial) {
int amountOfLayers = Math.min(Math.max(1, random.nextInt(badlandTerrocota.length)), 4);
int subLayer = y % yInterval;
if (subLayer < amountOfLayers) {
Material[] selected = new Material[amountOfLayers];
for (int i = 0; i < selected.length; i++) {
selected[i] = badlandTerrocota[random.nextInt(amountOfLayers)];
}
res = selected[subLayer].createBlockData();
} else {
res = Material.TERRACOTTA.createBlockData();
}
}
} else if (mainBiomeName.contains("mountain")) {
if (y > (maxHeight * 0.4d) - getNormalizedNoise(worldX, worldZ, 2.8f) * 8d) {
res = Material.STONE.createBlockData();
} else if (y > highestPoint - getSurfaceThickness(worldX, worldZ, mainBiome)) {
res = getSurfaceMaterial(mainBiome).createBlockData();
} else if (y > highestPoint - getSurfaceThickness(worldX, worldZ, mainBiome) - getTransitionMaterialThickness(worldX, worldZ, mainBiome)) {
res = getTransitionMaterial(mainBiome).createBlockData();
}
}
return res;
}
public Material getSurfaceMaterial(Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach") || biomeName.contains("desert") || biomeName.contains("warm_ocean")) {
return Material.SAND;
} else if (biome == Biome.ICE_SPIKES) {
return Material.SNOW_BLOCK;
} else if (biomeName.contains("ocean") || biomeName.contains("gravelly")) {
return Material.GRAVEL;
} else if (biomeName.contains("stone")) {
return Material.STONE;
}
return Material.GRASS_BLOCK;
}
public int getSurfaceThickness(int worldX, int worldZ, Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach") || biomeName.contains("desert")) {
return (int) (getNormalizedNoise(worldX, worldZ, 1.3f) * 8) + 5;
} else if (biomeName.contains("gravelly")) {
return (int) (getNormalizedNoise(worldX, worldZ, 1.3f) * 5);
}
return 1;
}
public Material getTransitionMaterial(Biome biome) {
String biomeName = biome.toString().toLowerCase();
if (biomeName.contains("beach") || biomeName.contains("desert")) {
return Material.SANDSTONE;
} else if (biomeName.contains("gravelly") || biome == Biome.STONE_SHORE) {
return Material.STONE;
} else if (biomeName.contains("ocean")) {
return Material.GRAVEL;
}
return Material.DIRT;
}
public int getTransitionMaterialThickness(int worldX, int worldZ, Biome biome) {
return (int) (getNormalizedNoise(worldX, worldZ, 1.3f) * 4) + 4;
}
private double getNormalizedNoise(int worldX, int worldZ, float freq) {
Vector3 key = new Vector3(worldX, worldZ, freq);
Double res = noiseCache.get(key);
if (res == null) {
res = (noiseGenerator.noise(worldX, worldZ, freq, 0.5d) + 1d) / 2d;
noiseCache.set(key, res);
}
return res;
}
}

View File

@ -0,0 +1,19 @@
name: IslandSurvivalCraft
main: net.reslate.islandsurvivalcraft.IslandSurvivalCraft
version: 1.0.0
author: Reslate
description: Adds the gamemode IslandSurvivalCraft.
depend: []
load: startup
api-version: 1.15
commands:
IslandSurvivalCraft:
aliases: [isc]
description: The administration command for Island Survival Craft.
usage: Type "/IslandSurvivalCraft help" (or any of its aliases) for a list of commands.
permission: islandsurvivalcraft.admin
permission-message: This command isn't needed for normal gameplay, and therefore, by default, only operators have access to it.
permissions:
islandsurvivalcraft.admin:
description: Gives ability to configure and test Island Survival Craft features in game.
default: op

View File

@ -0,0 +1,129 @@
package net.reslate.islandsurvivalcraft.utilities;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.HashMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
/**
* Unit test for simple App.
*/
@TestInstance(Lifecycle.PER_CLASS)
public class GeneralUtilitiesTest {
/**
* Basic hashmap inversion test.
*/
@Test
public void testInvertHashMap()
{
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("a", 1);
hashMap.put("b", 1);
hashMap.put("c", 2);
hashMap.put("d", 2);
HashMap<Integer, ArrayList<String>> res = GeneralUtilities.invertHashMap(hashMap);
ArrayList<String> first = res.get(1);
ArrayList<String> expectedFirst = new ArrayList<>();
expectedFirst.add("a");
expectedFirst.add("b");
ArrayList<String> second = res.get(2);
ArrayList<String> expectedSecond = new ArrayList<>();
expectedSecond.add("c");
expectedSecond.add("d");
assertEquals(first, expectedFirst);
assertEquals(second, expectedSecond);
}
@Test
public void testMagnitudeAddPositive() {
assertEquals(2, GeneralUtilities.addMagnitude(1, 1));
}
@Test
public void testMagnitudeAddNegative() {
assertEquals(-2, GeneralUtilities.addMagnitude(-1, 1));
}
@Test
public void testWorldToChunkCoordinatesNegativePerfect() {
assertEquals(new Point2(-4, -3), GeneralUtilities.worldToChunkCoordinates(-64, -48));
}
@Test
public void testWorldToChunkCoordinatesNegativeOff() {
assertEquals(new Point2(-4, -3), GeneralUtilities.worldToChunkCoordinates(-55, -33));
}
@Test
public void testWorldToChunkCoordinatesNegativeOffClose() {
assertEquals(new Point2(15, -15), GeneralUtilities.worldToChunkCoordinates(255, -227));
}
@Test
public void testWorldToChunkCoordinatesPositivePerfect() {
assertEquals(new Point2(4, 5), GeneralUtilities.worldToChunkCoordinates(64, 80));
}
@Test
public void testWorldToChunkCoordinatesPositiveOff() {
assertEquals(new Point2(4, 5), GeneralUtilities.worldToChunkCoordinates(67, 84));
}
@Test
public void testWorldToLocalCoordinatesNegative() {
assertEquals(new Point2(2, 5), GeneralUtilities.worldToLocalChunkCoordinates(-62, -27));
assertEquals(new Point2(2, 5), GeneralUtilities.worldToLocalChunkCoordinates(new Point2(-62, -27)));
}
@Test
public void testWorldToLocalCoordinatesNegativePerfect() {
assertEquals(new Point2(0, 0), GeneralUtilities.worldToLocalChunkCoordinates(-128, -32));
assertEquals(new Point2(0, 0), GeneralUtilities.worldToLocalChunkCoordinates(new Point2(-256, -16)));
}
@Test
public void testWorldToLocalCoordinatesPositive() {
assertEquals(new Point2(7, 3), GeneralUtilities.worldToLocalChunkCoordinates(39, 83));
assertEquals(new Point2(7, 3), GeneralUtilities.worldToLocalChunkCoordinates(new Point2(39, 83)));
}
@Test
public void testWorldToLocalCoordinatesPositivePerfect() {
assertEquals(new Point2(0, 0), GeneralUtilities.worldToLocalChunkCoordinates(1024, 32));
assertEquals(new Point2(0, 0), GeneralUtilities.worldToLocalChunkCoordinates(new Point2(16, 80)));
}
@Test
public void testWorldToLocalCoordinatesPositiveEntirety() {
int chunkX = 4;
int chunkZ = 3;
for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) {
for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) {
int worldX = GeneralUtilities.CHUNK_SIZE * chunkX + x;
int worldZ = GeneralUtilities.CHUNK_SIZE * chunkZ + z;
assertEquals(new Point2(x, z), GeneralUtilities.worldToLocalChunkCoordinates(new Point2(worldX, worldZ)));
}
}
}
@Test
public void testWorldToLocalCoordinatesNegativeEntirety() {
int chunkX = -42;
int chunkZ = -3;
for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) {
for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) {
int worldX = GeneralUtilities.CHUNK_SIZE * chunkX + x;
int worldZ = GeneralUtilities.CHUNK_SIZE * chunkZ + z;
assertEquals(new Point2(x, z), GeneralUtilities.worldToLocalChunkCoordinates(new Point2(worldX, worldZ)));
}
}
}
}

View File

@ -0,0 +1,256 @@
package net.reslate.islandsurvivalcraft.utilities.caching;
import static org.junit.Assert.assertSame;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
@TestInstance(Lifecycle.PER_CLASS)
public class CacheTest {
private volatile Cache<Integer, String> integerCache;
private final int LARGE_CACHE_SIZE = 262144;
private final int[] answers = new int[LARGE_CACHE_SIZE];
private final Random rand = new Random();
Cache<Integer, Integer> lCache = new Cache<>(LARGE_CACHE_SIZE);
@BeforeAll
public void setUp() {
integerCache = new Cache<>(3);
for (int i = 0; i < answers.length; i++) {
answers[i] = rand.nextInt();
}
}
@AfterEach
public void cleanUp() {
lCache.clearCache();
integerCache.clearCache();
}
@Test
public void testBasicCaching() {
String val = integerCache.get(0);
assertTrue(val == null);
val = "first";
integerCache.set(0, val);
assertTrue(integerCache.get(0) != null);
assertSame(val, integerCache.get(0));
val = integerCache.get(1);
assertTrue(val == null);
val = "second";
integerCache.set(1, val);
assertTrue(integerCache.get(1) != null);
assertSame(val, integerCache.get(1));
}
@Test
public void testUsageBasedClean() {
integerCache.set(0, "first");
integerCache.set(1, "second");
integerCache.set(2, "third");
assertEquals("first", integerCache.get(0));
integerCache.set(3, "fourth");
assertEquals("first", integerCache.get(0));
integerCache.set(4, "fifth");
assertTrue(integerCache.get(3) != null);
assertTrue(integerCache.get(0) != null);
}
@Test
public void testUsageLargeData() {
int amount = 1024;
Random random = new Random();
Cache<Integer, Integer> largeCache = new Cache<>(amount / 2);
int[] values = new int[amount];
for (int i = 0; i < amount; i++) {
values[i] = random.nextInt();
largeCache.set(i, values[i]);
}
for (int i = 0; i < amount / 2; i++) {
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
}
for (int i = amount / 2; i < amount; i++) {
assertEquals(values[i], largeCache.get(i), "Current accessor: " + i);
}
}
@Test
public void testUsageLargeDataImportance() {
int amount = 1024;
Random random = new Random();
Cache<Integer, Integer> largeCache = new Cache<>(amount / 2);
int[] values = new int[amount];
for (int i = 0; i < amount; i++) {
values[i] = random.nextInt();
largeCache.set(i, values[i]);
largeCache.get(0);
}
for (int i = 1; i < (amount / 2) + 1; i++) {
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
}
for (int i = (amount / 2) + 1; i < amount; i++) {
assertEquals(values[i], largeCache.get(i), "Current accessor: " + i);
}
assertEquals(values[0], largeCache.get(0));
}
@Test
public void testMultithreadingWriteConsistency() {
Runnable write = new Runnable() {
@Override
public void run() {
for (int i = 0; i < LARGE_CACHE_SIZE; i++) {
lCache.set(i, answers[i]);
}
}
};
Thread firstThread = new Thread(write);
firstThread.start();
Thread secondThread = new Thread(write);
secondThread.start();
Thread thirdThread = new Thread(write);
thirdThread.start();
try {
firstThread.join();
secondThread.join();
thirdThread.join();
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
for (int i = 0; i < LARGE_CACHE_SIZE; i++) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
@Test
public void testMultithreadingReadConsistency() {
for (int i = 0; i < answers.length; i++) {
lCache.set(i, answers[i]);
}
Runnable read = new Runnable() {
@Override
public void run() {
for (int i = 0; i < LARGE_CACHE_SIZE; i++) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
};
Thread firstThread = new Thread(read);
firstThread.start();
Thread secondThread = new Thread(read);
secondThread.start();
Thread thirdThread = new Thread(read);
thirdThread.start();
try {
firstThread.join();
secondThread.join();
thirdThread.join();
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
}
@Test
public void testMulthreadedReadWrite() {
Runnable readWrite = new Runnable() {
@Override
public void run() {
for (int i = 0; i < LARGE_CACHE_SIZE; i++) {
if (lCache.get(i) == null) {
lCache.set(i, answers[i]);
} else {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
}
};
Runnable readAll = new Runnable() {
@Override
public void run() {
for (int i = 0; i < LARGE_CACHE_SIZE; i++) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
};
Thread firstThread = new Thread(readWrite);
firstThread.start();
Thread secondThread = new Thread(readWrite);
secondThread.start();
Thread thirdThread = new Thread(readAll);
try {
firstThread.join();
secondThread.join();
thirdThread.start();
thirdThread.join();
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
}
@Test
public void testMultiThreadConsistency() {
Runnable readWriteCheck = new Runnable(){
@Override
public void run() {
for (int i = 0; i < LARGE_CACHE_SIZE; i++) {
if (lCache.get(i) != null) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
} else {
lCache.set(i, answers[i]);
}
}
}
};
ExecutorService executorService = Executors.newFixedThreadPool(6);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
try {
executorService.shutdown();
assertTrue(executorService.awaitTermination(1, TimeUnit.MINUTES), "Timed out.");
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
}
}

View File

@ -0,0 +1,118 @@
package net.reslate.islandsurvivalcraft.utilities.drawing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Scanner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
@TestInstance(Lifecycle.PER_CLASS)
public class FloodFillTest {
public byte[][] mapA;
public byte[][] mapB;
private class Flood implements CoordinateValidatable {
private final byte[][] map;
public Flood(byte[][] map) {
this.map = map;
}
@Override
public boolean validate(Point2 point) {
try {
if (map[point.y][point.x] < 1) return false;
} catch (IndexOutOfBoundsException e) {
return false;
}
map[point.y][point.x] = 0;
return true;
}
}
private byte[][] readMap(String filename) throws IOException {
byte[][] map = null;
InputStream stream = getClass().getClassLoader().getResourceAsStream((filename));
Scanner scanner = new Scanner(stream);
String line = null;
ArrayList<byte[]> rows = new ArrayList<>();
while (scanner.hasNextLine()) {
line = scanner.nextLine();
char[] chars = line.toCharArray();
byte[] rowBytes = new byte[chars.length];
for (int i = 0; i < chars.length; i++) {
rowBytes[i] = (byte) Character.getNumericValue(chars[i]);
}
rows.add(rowBytes);
}
scanner.close();
map = new byte[rows.size()][rows.get(0).length];
for (int row = 0; row < rows.size(); row++) {
map[row] = rows.get(row);
}
return map;
}
@BeforeEach
public void reset() {
try {
mapA = readMap("DFSTestMapLargeA.txt");
mapB = readMap("DFSTestMapLargeB.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testFillMapA() {
Flooder flooder = new Flooder(new Point2(0, 0), new Flood(mapA));
flooder.start();
for (int y = 0; y < mapA.length; y++) {
for (int x = 0; x < mapA[y].length; x++) {
assertEquals(0, mapA[y][x], String.format("At: %d, %d", x, y));
}
}
}
@Test
public void testFillMapB() {
Flooder flooder = new Flooder(new Point2(0, 0), new Flood(mapB));
flooder.start();
for (int y = 0; y < mapB.length; y++) {
for (int x = 0; x < mapB[y].length; x++) {
if (x > 21 && y > 15 && x < 27) {
assertTrue(mapB[y][x] > 0, String.format("At: %d, %d", x, y));
} else {
assertEquals(0, mapB[y][x], String.format("At: %d, %d", x, y));
}
}
}
}
@Test
public void testFillMapBNonCorner() {
Flooder flooder = new Flooder(new Point2(43, 11), new Flood(mapB));
flooder.start();
for (int y = 0; y < mapB.length; y++) {
for (int x = 0; x < mapB[y].length; x++) {
if (x > 21 && y > 15 && x < 27) {
assertTrue(mapB[y][x] > 0, String.format("At: %d, %d", x, y));
} else {
assertEquals(0, mapB[y][x], String.format("At: %d, %d", x, y));
}
}
}
}
}

View File

@ -0,0 +1,239 @@
package net.reslate.islandsurvivalcraft.utilities.pathfinding;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Scanner;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
/**
* Unit test for simple App.
*/
@TestInstance(Lifecycle.PER_CLASS)
public class DepthFirstSearchTest {
private class Validator implements CoordinateValidatable {
private byte[][] map;
public Validator(byte[][] map) {
this.map = map;
}
@Override
public boolean validate(Point2 point) {
try {
return map[point.y][point.x] >= 1;
} catch (IndexOutOfBoundsException e) {
return false;
}
}
}
private class TargetValidator implements CoordinateValidatable {
private byte[][] map;
public TargetValidator(byte[][] map) {
this.map = map;
}
@Override
public boolean validate(Point2 point) {
try {
return map[point.y][point.x] == 2;
} catch (IndexOutOfBoundsException e) {
return false;
}
}
}
private byte[][] mapA;
private byte[][] mapB;
private byte[][] mapC;
private byte[][] mapD;
private byte[][] mapE;
private byte[][] mapF;
private byte[][] mapG;
private byte[][] mapH;
@BeforeAll
public void setUp() throws IOException {
mapA = readMap("DFSTestMapSmallA.txt");
mapB = readMap("DFSTestMapSmallB.txt");
mapC = readMap("DFSTestMapSmallC.txt");
mapD = readMap("DFSTestMapSmallD.txt");
mapE = readMap("DFSTestMapSmallE.txt");
mapF = readMap("DFSTestMapLargeA.txt");
mapG = readMap("DFSTestMapLargeB.txt");
mapH = readMap("DFSTestMapLargeC.txt");
}
private byte[][] readMap(String filename) throws IOException {
byte[][] map = null;
InputStream stream = getClass().getClassLoader().getResourceAsStream((filename));
Scanner scanner = new Scanner(stream);
String line = null;
ArrayList<byte[]> rows = new ArrayList<>();
while (scanner.hasNextLine()) {
line = scanner.nextLine();
char[] chars = line.toCharArray();
byte[] rowBytes = new byte[chars.length];
for (int i = 0; i < chars.length; i++) {
rowBytes[i] = (byte) Character.getNumericValue(chars[i]);
}
rows.add(rowBytes);
}
scanner.close();
map = new byte[rows.size()][rows.get(0).length];
for (int row = 0; row < rows.size(); row++) {
map[row] = rows.get(row);
}
return map;
}
@Test
public void testDFSBuildPathToEndNodeMapAValid()
{
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(1, 2), new Point2(1, 0));
dfs.buildToGoal(new Validator(mapA), null);
assertTrue(dfs.getResult() != null);
}
@Test
public void testDFSBuildPathToEndNodeMapBValid()
{
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(3, 2));
dfs.buildToGoal(new Validator(mapB), null);
assertTrue(dfs.getResult() != null);
}
@Test
public void testDFSBuildPathToEndNodeMapDValid()
{
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 3));
dfs.buildToGoal(new Validator(mapD), null);
assertTrue(dfs.getResult() != null);
}
@Test
public void testDFSBuildPathToEndNodeMapCInvalid()
{
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(3, 2));
dfs.buildToGoal(new Validator(mapC), null);
assertFalse(dfs.getResult() != null);
}
@Test
public void testDFSBuildPathToEndNodeMapEInvalid()
{
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(3, 2));
dfs.buildToGoal(new Validator(mapE), null);
assertFalse(dfs.getResult() != null);
}
@Test
public void testDFSFindEndNodeMapBValid() {
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0));
Validator validator = new Validator(mapB);
TargetValidator targetValidator = new TargetValidator(mapB);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(1, dfs.getResult().x);
assertEquals(2, dfs.getResult().y);
}
@Test
public void testDFSFindEndNodeMapDValid() {
Validator validator = new Validator(mapD);
TargetValidator targetValidator = new TargetValidator(mapD);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0));
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(0, dfs.getResult().x);
assertEquals(3, dfs.getResult().y);
}
@Test
public void testDFSFindEndNodeMapHValid() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0));
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getResult().y);
}
@Test
public void testDFSFindEndNodeMapHInvalidLimited() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0), null, 128);
dfs.buildToGoal(validator, targetValidator);
assertFalse(dfs.getResult() != null);
}
@Test
public void testDFSFindEndNodeMapEInvalid() {
Validator validator = new Validator(mapE);
TargetValidator targetValidator = new TargetValidator(mapE);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 128);
dfs.buildToGoal(validator, targetValidator);
assertFalse(dfs.getResult() != null);
}
@Test
public void testDFSFindEndNodeMapFValid() {
Validator validator = new Validator(mapF);
TargetValidator targetValidator = new TargetValidator(mapF);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 1024);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(26, dfs.getResult().x);
assertEquals(32, dfs.getResult().y);
}
@Test
public void testDFSFindEndNodeMapGInvalid() {
Validator validator = new Validator(mapG);
TargetValidator targetValidator = new TargetValidator(mapG);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 128);
dfs.buildToGoal(validator, targetValidator);
assertFalse(dfs.getResult() != null);
}
@Test
public void testDFSFindEndNodeMapHWithAssistValid() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(95, 49), null, 0);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getResult().y);
}
@Test
public void testDFSFindEndNodeMapHWithoutAssistValid() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0), null, 0);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getResult().y);
}
}

View File

@ -0,0 +1,94 @@
package net.reslate.islandsurvivalcraft.world;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Random;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
@TestInstance(Lifecycle.PER_CLASS)
public class IslandWorldMapTest {
public final int SEED = 102385923;
public final int GRIDSIZE = 2048;
public Random random = new Random(SEED);
public IslandWorldMap mapper = new IslandWorldMap(random);
public final double[][] answers = new double[GRIDSIZE][GRIDSIZE];
@BeforeAll
public void setUp() {
for (int x = 0; x < answers.length; x++) {
for (int y = 0; y < answers[x].length; y++) {
answers[x][y] = mapper.getWorldValue(x, y);
}
}
}
@Test
public void testBlockValueConsistency() {
for (int x = 0; x < answers.length; x++) {
for (int y = 0; y < answers[x].length; y++) {
assertEquals(answers[x][y], mapper.getWorldValue(x, y), String.format("Occurred at (%d, %d)", x, y));
}
}
}
@Test
public void testBlockValueConsistencyRandom() {
for (int amount = 0; amount < GRIDSIZE; amount++) {
int x = random.nextInt(answers.length);
int y = random.nextInt(answers[x].length);
assertEquals(answers[x][y], mapper.getWorldValue(x, y), String.format("Occurred at (%d, %d)", x, y));
}
}
@Test
public void testValuesValidity() {
for (int i = 0; i < 1024; i++) {
int x = random.nextInt();
int y = random.nextInt();
assertTrue((mapper.getWorldValue(x, y) <= 1));
assertTrue((mapper.getWorldValue(x, y) >= -1));
}
}
@Test
public void testIslandOriginConsistency() {
Point2 origin = null;
for (int i = 0; i < GRIDSIZE; i++) {
if (origin != null) {
if (mapper.isSameIsland(origin, new Point2(i, 0))) {
assertEquals(origin, mapper.findIslandOrigin(i, 0), String.format("Looking at (%d, 0)", i));
} else {
origin = null;
}
} else if (mapper.isIsland(i, 0)) {
origin = mapper.findIslandOrigin(i, 0);
}
}
}
@Test
public void testIslandOriginConsistency2D() {
int gridSize = 32;
Point2 origin = null;
for (int y = 0; y < gridSize; y++) {
for (int x = 0; x < gridSize; x++) {
if (origin != null) {
if (mapper.isSameIsland(origin, new Point2(x, y))) {
assertEquals(origin, mapper.findIslandOrigin(x, y), String.format("Looking at (%d, %d)", x, y));
} else {
origin = null;
}
} else if (mapper.isIsland(x, y)) {
origin = mapper.findIslandOrigin(x, y);
}
}
}
}
}

View File

@ -0,0 +1,278 @@
package net.reslate.islandsurvivalcraft.world.generation.biomes;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.reslate.islandsurvivalcraft.utilities.GeneralUtilities;
import net.reslate.islandsurvivalcraft.utilities.biomes.BiomeInfo;
import net.reslate.islandsurvivalcraft.utilities.caching.Cache;
import net.reslate.islandsurvivalcraft.utilities.datatypes.Point2;
import net.reslate.islandsurvivalcraft.world.BiomeMap;
import net.reslate.islandsurvivalcraft.world.IslandWorldMap;
import net.reslate.islandsurvivalcraft.world.TemperatureMap;
@TestInstance(Lifecycle.PER_CLASS)
public class UniqueBiomeGeneratorTest {
private final int SEED = 249398015;
private volatile Cache<Point2, BiomeInfo> biomeCache;
private final BiomeMap biomeSelector = new BiomeMap(new Random(SEED));
private class BiomeGenTask implements Runnable {
private final int amount;
private final int shift;
public BiomeGenTask(int amount, int shift) {
this.shift = shift;
this.amount = amount;
}
@Override
public void run() {
for (int x = 0; x < amount; x++) {
generateBiome(x, shift);
}
}
public void generateBiome(int chunkX, int chunkZ) {
Random random = new Random(SEED);
IslandWorldMap mapper = new IslandWorldMap(random);
TemperatureMap temperatureMapGenerator = new TemperatureMap(random);
BiomeGenerator biomeGenerator = new UniqueBiomeGenerator();
BiomeInfo[][] biomes = new BiomeInfo[GeneralUtilities.CHUNK_SIZE][GeneralUtilities.CHUNK_SIZE];
for (int localX = 0; localX < GeneralUtilities.CHUNK_SIZE; localX++) {
for (int localZ = 0; localZ < GeneralUtilities.CHUNK_SIZE; localZ++) {
if (biomes[localX][localZ] == null) {
biomeGenerator.generateBiomeColumn(biomes, chunkX, chunkZ, localX, localZ, mapper,
biomeSelector, temperatureMapGenerator, biomeCache);
}
if (biomes[localX][localZ] == null)
throw new IllegalStateException("Biome was null.");
}
}
}
}
@BeforeAll
public void setup() {
}
@BeforeEach
public void individualSetup() {
biomeCache = new Cache<>(524288);
}
@AfterEach
public void individualCleanup() {
biomeCache.clearCache();
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread1608Chunks() {
int chunksToDoEach = 268;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 1);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 2);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 3);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 4);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 5);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread1608ChunksSmallCache() {
this.biomeCache = new Cache<>(1024);
int chunksToDoEach = 268;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 1);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 2);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 3);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 4);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 5);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationSingleThread1608Chunks() {
Runnable g1 = new BiomeGenTask(1608, 0);
g1.run();
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread1608ChunksScatteredColumns() {
int chunksToDoEach = 268;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 2);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 4);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 6);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 8);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 10);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread1608ChunksScatteredColumnsSmallCache() {
this.biomeCache = new Cache<>(1024);
int chunksToDoEach = 268;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 2);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 4);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 6);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 8);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 10);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread6000ChunksScatteredColumns() {
int chunksToDoEach = 1000;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 3);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 6);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 9);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 12);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 15);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
public void testBiomeGenerationMultithread6000ChunksScatteredColumnsSmallCache() {
this.biomeCache = new Cache<>(1024);
int chunksToDoEach = 1000;
Runnable g1 = new BiomeGenTask(chunksToDoEach, 0);
Runnable g2 = new BiomeGenTask(chunksToDoEach, 3);
Runnable g3 = new BiomeGenTask(chunksToDoEach, 6);
Runnable g4 = new BiomeGenTask(chunksToDoEach, 9);
Runnable g5 = new BiomeGenTask(chunksToDoEach, 12);
Runnable g6 = new BiomeGenTask(chunksToDoEach, 15);
ExecutorService ex = Executors.newFixedThreadPool(6);
LinkedList<Future<?>> tasks = new LinkedList<>();
tasks.add(ex.submit(g1));
tasks.add(ex.submit(g2));
tasks.add(ex.submit(g3));
tasks.add(ex.submit(g4));
tasks.add(ex.submit(g5));
tasks.add(ex.submit(g6));
while (!tasks.isEmpty()) {
try {
tasks.pop().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
assertFalse(false, e.getCause().getMessage());
}
}
}
}

View File

@ -0,0 +1,33 @@
11111111111111111111111111111111
11111111111111111110000000000000
11111111111111111111111111111111
00000000000000000000000000111111
00000000000000000000000000001111
00000000000000000000000111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111110000000000000000000
11111111111100000000000000000000
11111111100000000000000000000000
11111111110000000000000000000000
11111111110000000000000000000000
11111111110000000000000000000000
11111111111000000000000000000000
11111111111100000000000000000000
11111111111110000000000000000000
11111111111111000000000000000000
11111111111111110000000000000000
11111111111111111100000000000000
11111111111111111111100000000000
11111111111111111111111000000000
11111111111111111111111100000000
11111111111111111111111110000000
11111111111111111111111110000000
11111111111111111111111100000000
11111111111111111111111100000000
11111111111111111111111000000000
11111111111111111111110000000000
11111111111111111111100000000000
11111111111111111111100000000000
11111111111111111110000000000000
11111111111111111111111111200000

View File

@ -0,0 +1,17 @@
1111111111111111111111111111111111111111111111111110000000000000
1111111111111111111111111111111100000000000000000000000000111111
0000000000000000000000000000111100000000000000000000000111111111
1111111111111111111111111111111111111111111111111111111111111111
1111111111111000000000000000000011111111111100000000000000000000
1111111110000000000000000000000011111111110000000000000000000000
1111111111000000000000000000000011111111110000000000000000000000
1111111111100000000000000000000011111111111100000000000000000000
1111111111111000000000000000000011111111111111000000000000000000
1111111111111111000000000000000011111111111111111100000000000000
1111111111111111111110000000000011111111111111111111111000000000
1111111111111111111111110000000011111111111111111111111110000000
1111111111111111111111111000000011111111111111111111111100000000
1111111111111111111111110000000011111111111111111111111000000000
1111111111111111111111000000000011111111111111111111100000000000
1111111111111111111110000000000011111111111111111110000000000000
1111111111111111111110111120000000000000000000000000000000000000

View File

@ -0,0 +1,50 @@
0011111111110101010101110111101101000001011110010110111010100111101111111010111000100010010110000100
0011111110111001000011011101111001001100000011100110010000011101100111000110100001111110010101001101
0011111010000110100011100110101101011101001101011100010101101011111111000100110011000101110000010111
1011100000110001011101001111000100011001010110010000001111100111000010010101001111011011011000111101
0011110001100010111110110100011110110010101011111010110000001101100111010011111101000001001010001110
0111100010000011111100010110010010111111110001010001111001011110000100110101100011001001001111010010
1111100101001010101001011101001000101100000011011101110011110111110001101011101010000011001111000111
0111111111100001100101000100110001101000111110000111101111110111011001101111011010001100000001111101
1111101101110111101000110010100000000000011101010110010100100100101001101001010001110101000110010110
1011110000110110000001011011010010111011010010101000001101101101111100100000000000011000100111011111
0101111111111111101001101111011111100011111110001111000110101001100110010010001011011111001010110111
0011101101111101101101110000001001000000000001011010101100011011110010011111111110100010011101111111
1111001011110110001100001110101000110111100100010110000010111001010111001101101101010101010111011111
0111100111010010110010111011100010101101010111101001101011011100010010000100010111101100010101111110
1011000101101100110111110010010110100100011100101000111101101010110111010100000100011000101010001110
0101010001000101100111010100000010010011000011111000000000011100010011000110001010000100011110110011
1100111001011111001011001110011111110010101100100100010100010110110000100101100001000001011000101010
1010111101111111100011100011111001010000000111111010100011001100011001001000110100010010011010010010
0111001100100111110010100101111111101100000001101101001011000000101111001101101010110001001010100001
1110010000100011111111110000110110101001000111101101010010111001000111001101100010111010101100111101
0010100010010101111111110010111011001101110001001100111001110100100000101011000011110101000100001110
0000001100101000111111111110110001011110010011001111001000011010010010101001001100101011000100011111
1101101111000101100100111111111111111000000011010000100101111000101011110110110110110010010111111010
1110101010110100111110110111111110001101001001100110110111011101000111001011101010101010010110101000
0100011000100110110101000000111111100100011110010011011011101001001111011101011001011110100000110010
1100100100100000110101101110011111110101111111110111111011001000101010110110110001010001001010011000
0101110110101011100100100100011111111111000101111110111110100101010101110101101000110010011111100101
0111100001010100100101010101101001101110000111000100111111110000101000000111010001000111011011011010
1011010100010010011101001011010110001110111101000110111111110101111101010000001111110110110111010000
1011001011010101000110110001111011010111000001101001111011111111111011110001000100110100101110101110
0011011010110111001110110110010110100101110000010010101100111111110101101111101010110101110110111000
1001100100000101101000110011011010111110100001011100000101101001110001010001001011100010100010110011
0110010101110110110111011010101000011010010000000110001110010101111010100011000011000100011100110000
0111000111100001101110101110110011001100000010101001111111010110111111011101000100101000011110110011
0100110001000001001111011011011101000101111001110101110010111011011110010011101101010100000110001101
1111000110001111010011101100110001101101011111100101010001101001101111011111000010010110011100100101
0011001101110010110001101001100100101101110010110100100110101101100111111001100101011101111010101010
1001001101110111010000011000011100011011110011100011100111010110111101111111101011111100011100110101
1000011000011101010100001010000101000010111010101011010101001110000010001111111111110111111011000000
1011010011111010101001011001110011111110001000000111101110001001110000001111111111100011111001000110
0110110111001100000001100101110111101011000011000111001100110111101000011110001011111111011110011101
0000101101011000100110001111100101101100011111101001110000110111100010010101010101110100000100100000
0011011100101110101000111110110011001101000110000110010010010000101001001111001111111111100011110001
1011000011010010000100000010110000101001000101010000100011100110100111011010011000010111111101100101
1001011000111111111010011010100001100111000111000001011111011110101100011000001110000111111011101101
1110101001110011111110101110001110111100010100010110111001000100010100110011101001111101111110110100
1010000100000100101101111001111101111110111001101000000110111100000110011111000001011011111110111001
1010011010111001010111011101000011010100001001101101110011101100000110100011111011101001111111101101
0011011110111000111101000110000111101000111101001011011100110011010000111011010000011110110111111101
1100010110001111010011101000001011010011000011111010010011011000110101011000001110100000110111120011

View File

@ -0,0 +1,3 @@
11
10
11

View File

@ -0,0 +1,3 @@
1111
1000
1211

View File

@ -0,0 +1,3 @@
1111
0000
1111

View File

@ -0,0 +1,4 @@
1111
0001
1111
2000

View File

@ -0,0 +1,4 @@
1111
0000
1111
1020

44
test-server/bukkit.yml Normal file
View File

@ -0,0 +1,44 @@
# This is the main configuration file for Bukkit.
# As you can see, there's actually not that much to configure without any plugins.
# For a reference for any variable inside this file, check out the Bukkit Wiki at
# https://www.spigotmc.org/go/bukkit-yml
#
# If you need help on this file, feel free to join us on irc or leave a message
# on the forums asking for advice.
#
# IRC: #spigot @ irc.spi.gt
# (If this means nothing to you, just go to https://www.spigotmc.org/go/irc )
# Forums: https://www.spigotmc.org/
# Bug tracker: https://www.spigotmc.org/go/bugs
settings:
allow-end: true
warn-on-overload: true
permissions-file: permissions.yml
update-folder: update
plugin-profiling: false
connection-throttle: 4000
query-plugins: true
deprecated-verbose: default
shutdown-message: Server closed
minimum-api: none
spawn-limits:
water-ambient: 20
monsters: 70
animals: 10
water-animals: 15
ambient: 15
chunk-gc:
period-in-ticks: 600
ticks-per:
water-ambient-spawns: 1
animal-spawns: 400
monster-spawns: 1
water-spawns: 1
ambient-spawns: 1
autosave: 6000
aliases: now-in-commands.yml
worlds:
world:
generator: IslandSurvivalCraft

View File

@ -0,0 +1,2 @@
write-Output "Deleting previous world if there was one..."
remove-Item -Recurse world* -Force -ErrorAction Ignore

View File

@ -0,0 +1,9 @@
if (Test-Path ./test-server.pid) {
$sID = Get-Item -Path ./test-server.pid | Get-Content -Tail 1
if (Get-Process -Id $sID -ErrorAction SilentlyContinue) {
Stop-Process -Id $sID
}
Remove-Item "./test-server.pid"
} else {
Write-Output "Couldn't find running server to stop."
}

View File

@ -0,0 +1,3 @@
New-Item -Path .\plugins -ItemType Directory -Force
write-Output "Attempting to copy plugin jar to plugins folder..."
copy-Item -Path "..\target\IslandSurvivalCraft*.jar" -Destination "plugins\IslandSurvivalCraft.jar"

View File

@ -0,0 +1 @@
Start-Process java -ArgumentList "-Xms512M", "-Xmx1G", "-jar", "paper.jar", "nogui"

View File

@ -0,0 +1,10 @@
if (!(Test-Path -Path "paper.jar" -PathType Leaf)) {
Invoke-WebRequest -Uri "https://api.papermc.io/v2/projects/paper/versions/1.16.5/builds/794/downloads/paper-1.16.5-794.jar" -OutFile "paper.jar"
}
write-Output "Attempting to start Paper test server."
$SID = Start-Process java -ArgumentList "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=25566", "-Xms512M", "-Xmx1G", "-jar", "paper.jar", "nogui" -PassThru
$SID = $SID.Id
write-Output "Process started. PID is: $SID"
$SID | Out-File -FilePath "test-server.pid"