diff --git a/.classpath b/.classpath index b9ad5b8a7..a91252366 100644 --- a/.classpath +++ b/.classpath @@ -1,14 +1,37 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index be5440803..3b90e44e1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,7 @@ /logs /nbproject /build + +history.txt +target/ +/dependency-reduced-pom.xml \ No newline at end of file diff --git a/.project b/.project index 315a65dfa..c60d78bfd 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - AMIDST + amidst @@ -10,8 +10,14 @@ + + org.eclipse.m2e.core.maven2Builder + + + + org.eclipse.m2e.core.maven2Nature org.eclipse.jdt.core.javanature diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 000000000..dc1b41496 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 8000cd6ca..13b3428ac 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,11 +1,13 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 000000000..f897a7f1c --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..fb3f72c9f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: java +jdk: +- oraclejdk8 +addons: + apt: + packages: + - libc6:i386 # these two packages are needed by the launch4j maven plugin to work + - libstdc++6:i386 +before_install: +- mvn clean # this actually installs a dependency +before_deploy: +- bash travis-ci/scripts/create-mac-icon.sh +- bash travis-ci/scripts/create-windows-icon.sh +- mvn package -DskipTests=true -f travis-ci/wrapper-for-mac/pom.xml +- mvn package -DskipTests=true -f travis-ci/wrapper-for-windows/pom.xml +- bash travis-ci/scripts/zip-and-move-wrapper-for-mac.sh +- export filename=$(bash travis-ci/scripts/get-amidst-filename.sh) +deploy: + provider: releases + api-key: + secure: QMXa1QpW+mlTLlxCbKOAWIRUi2cR+Y2vMQDlwiV8BoUyZchvTBR+stDYuEdxd2fK8/aSRUWEgfUhKMJ4cIZEOOwz/UA/1RQmzumbFLexwTwDXfkTfCcDQFLtzywVoep2P3s1lpheo4f1G3z2sZTCxO5yRnpd/sBtWnaq3j5uifYZB+mv84eH+EsrQL7f+0KjycOPrAdiJ8xh/2twUtGMhaStrnhcH+RCi6qm6D5Dv4mwYM4wRxG3H9i2NceGa6lHIE4bU+kKY9lc3V6EzmTnnShwEcrf+4/ZfCe4A9aU3jzFhKCHKpJ4PlqLEtMeQ08ijaMrF4LaBqwRZ5il5nqKkFdfKOtx7EHqjxrQ1dT7JUXU/3lTfEX/DLbhPa3z5Tl9LgCu9flCS+ltQoPvx7MR1leaQeHNoFCfzWJQ8ytW2CEhnOqx3nr4AIW8q5akJxQB7USfHV5F8kw2T6QmzuniQRZex+15j9SLedaDXZCvno6C0+j59sABpiF9lRVww8jW6BxqklxDcjEtkMxyHiBCG0EgGlKJvVSyGGzDsjb8FweC1KQKBHuG+5UYNE1rJesj6rwfIIrbInjJRTPndb4GpuzzbqbTGxwHd2zqzkQhnxSjCoKIzyhpBzfyCJaqTv/DfimSc95sXH1w1d6zfcO01MkopXYUSlHvr/Rjxh8vE8U= + file: + - "target/${filename}.jar" + - "target/${filename}.zip" + - "target/${filename}.exe" + skip_cleanup: true + on: + repo: stefandollase/amidst + tags: true diff --git a/Default-sorted.json b/Default-sorted.json deleted file mode 100644 index 835b4c208..000000000 --- a/Default-sorted.json +++ /dev/null @@ -1,81 +0,0 @@ -{ "name":"default-sorted", "colorMap":[ -[ "Beach M", { "r":255, "g":255, "b":125 } ], -[ "Beach", { "r":250, "g":222, "b":85 } ], -[ "Birch Forest Hills M", { "r":71, "g":135, "b":90 } ], -[ "Birch Forest Hills", { "r":31, "g":95, "b":50 } ], -[ "Birch Forest M", { "r":88, "g":156, "b":108 } ], -[ "Birch Forest", { "r":48, "g":116, "b":68 } ], -[ "Cold Beach M", { "r":255, "g":255, "b":232 } ], -[ "Cold Beach", { "r":250, "g":240, "b":192 } ], -[ "Cold Taiga Hills M", { "r":76, "g":103, "b":94 } ], -[ "Cold Taiga Hills", { "r":36, "g":63, "b":54 } ], -[ "Cold Taiga M", { "r":89, "g":125, "b":114 } ], -[ "Cold Taiga", { "r":49, "g":85, "b":74 } ], -[ "Deep Ocean M", { "r":40, "g":40, "b":88 } ], -[ "Deep Ocean", { "r":0, "g":0, "b":48 } ], -[ "Desert Hills M", { "r":250, "g":135, "b":58 } ], -[ "Desert Hills", { "r":210, "g":95, "b":18 } ], -[ "Desert M", { "r":255, "g":188, "b":64 } ], -[ "Desert", { "r":250, "g":148, "b":24 } ], -[ "Extreme Hills Edge M", { "r":154, "g":160, "b":194 } ], -[ "Extreme Hills Edge", { "r":114, "g":120, "b":154 } ], -[ "Extreme Hills M", { "r":136, "g":136, "b":136 } ], -[ "Extreme Hills", { "r":96, "g":96, "b":96 } ], -[ "Extreme Hills+ M", { "r":120, "g":152, "b":120 } ], -[ "Extreme Hills+", { "r":80, "g":112, "b":80 } ], -[ "Flower Forest", { "r":45, "g":142, "b":73 } ], -[ "Forest Hills M", { "r":74, "g":125, "b":68 } ], -[ "Forest Hills", { "r":34, "g":85, "b":28 } ], -[ "Forest", { "r":5, "g":102, "b":33 } ], -[ "Frozen Ocean M", { "r":184, "g":184, "b":200 } ], -[ "Frozen Ocean", { "r":144, "g":144, "b":160 } ], -[ "Frozen River M", { "r":200, "g":200, "b":255 } ], -[ "Frozen River", { "r":160, "g":160, "b":255 } ], -[ "Hell M", { "r":255, "g":40, "b":40 } ], -[ "Hell", { "r":255, "g":0, "b":0 } ], -[ "Ice Mountains M", { "r":200, "g":200, "b":200 } ], -[ "Ice Mountains", { "r":160, "g":160, "b":160 } ], -[ "Ice Plains Spikes", { "r":180, "g":220, "b":220 } ], -[ "Ice Plains", { "r":255, "g":255, "b":255 } ], -[ "Jungle Edge M", { "r":138, "g":179, "b":63 } ], -[ "Jungle Edge", { "r":98, "g":139, "b":23 } ], -[ "Jungle Hills M", { "r":84, "g":106, "b":45 } ], -[ "Jungle Hills", { "r":44, "g":66, "b":5 } ], -[ "Jungle M", { "r":123, "g":163, "b":49 } ], -[ "Jungle", { "r":83, "g":123, "b":9 } ], -[ "Mega Spruce Taiga (Hills)", { "r":109, "g":119, "b":102 } ], -[ "Mega Spruce Taiga", { "r":129, "g":142, "b":121 } ], -[ "Mega Taiga Hills", { "r":69, "g":79, "b":62 } ], -[ "Mega Taiga", { "r":89, "g":102, "b":81 } ], -[ "Mesa (Bryce)", { "r":255, "g":109, "b":61 } ], -[ "Mesa Plateau F M", { "r":216, "g":191, "b":141 } ], -[ "Mesa Plateau F", { "r":176, "g":151, "b":101 } ], -[ "Mesa Plateau M", { "r":242, "g":180, "b":141 } ], -[ "Mesa Plateau", { "r":202, "g":140, "b":101 } ], -[ "Mesa", { "r":217, "g":69, "b":21 } ], -[ "Mushroom Island M", { "r":255, "g":40, "b":255 } ], -[ "Mushroom Island Shore M", { "r":200, "g":40, "b":255 } ], -[ "Mushroom Island Shore", { "r":160, "g":0, "b":255 } ], -[ "Mushroom Island", { "r":255, "g":0, "b":255 } ], -[ "Ocean M", { "r":40, "g":40, "b":152 } ], -[ "Ocean", { "r":0, "g":0, "b":112 } ], -[ "Plains", { "r":141, "g":179, "b":96 } ], -[ "River M", { "r":40, "g":40, "b":255 } ], -[ "River", { "r":0, "g":0, "b":255 } ], -[ "Roofed Forest M", { "r":104, "g":121, "b":66 } ], -[ "Roofed Forest", { "r":64, "g":81, "b":26 } ], -[ "Savanna M", { "r":229, "g":218, "b":135 } ], -[ "Savanna Plateau M", { "r":207, "g":197, "b":140 } ], -[ "Savanna Plateau", { "r":167, "g":157, "b":100 } ], -[ "Savanna", { "r":189, "g":178, "b":95 } ], -[ "Sky M", { "r":168, "g":168, "b":255 } ], -[ "Sky", { "r":128, "g":128, "b":255 } ], -[ "Stone Beach M", { "r":202, "g":202, "b":172 } ], -[ "Stone Beach", { "r":162, "g":162, "b":132 } ], -[ "Sunflower Plains", { "r":181, "g":219, "b":136 } ], -[ "Swampland M", { "r":47, "g":255, "b":218 } ], -[ "Swampland", { "r":7, "g":249, "b":178 } ], -[ "Taiga Hills M", { "r":62, "g":97, "b":91 } ], -[ "Taiga Hills", { "r":22, "g":57, "b":51 } ], -[ "Taiga M", { "r":51, "g":142, "b":129 } ], -[ "Taiga", { "r":11, "g":102, "b":89 } ] ] } diff --git a/README.md b/README.md index 964f95223..0cfac67f0 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,98 @@ -AMIDST +Amidst ====== +[![Build Status](https://travis-ci.org/stefandollase/amidst.svg)](https://travis-ci.org/stefandollase/amidst) + Advanced Minecraft Interface and Data/Structure Tracking +Where can I get Amidst? +----------------------- + +You can download Amidst [here](https://github.com/skiphs/amidst/releases/latest). If you find any bugs, please [report](https://github.com/skiphs/amidst/issues/new) them so we can fix them. If you want to request a feature, you can to this [here](https://github.com/skiphs/amidst/issues/new). If you want to help develop Amidst, please get in contact [here](https://github.com/skiphs/amidst/issues/new). Lastly, [here](https://github.com/stefandollase/amidst/blob/refactoring/docs/BUILDING.md) is a description how you can build Amidst by yourself. + +What is Amidst? +--------------- + +What it **can** do for you: + +* generate an overview of a Minecraft world from a given seed and a given Minecraft version +* display biome information +* display slime chunks +* display structures + * default world spawn + * strongholds + * desert temples + * jungle temples + * witch huts + * villages + * ocean monuments + * nether fortresses + +When the world is loaded from a Minecraft world file, Amidst **can** also: + +* display singleplayer and multiplayer player locations +* load player skins +* move players to another location, including the y-coordinate + +What it **cannot** do for you: + +* display changes to the world, that are made after the world generator was finished, this includes + * changes made by world editors like MCEdit + * changes made while loading the world in Minecraft +* find individual blocks or mobs in the world like e.g. + * diamond ore + * cows + +More features include: + +* saving an image of the map + +Which Minecraft versions are supported? +--------------------------------------- + +We support Minecraft versions from 1.0 up to the latest snapshot. If you find an issue with a specific Minecraft version, [please report it](https://github.com/skiphs/amidst/issues/new). + +How can I move a player? +------------------------ + +You can move players in a world that was loaded from a Minecraft world file like so: + +* scroll to and right-click on the new player location, this opens a popup menu +* select the player you want to move +* enter the new y-coordinate +* save player locations + +**WARNING: This will change the contents of the save folder, so there is a chance that the world gets corrupted. We try to minimize the risk by creating a backup of the changed file, before it is changed. If the backup fails, we will not write the changes. You can find the backup files in a sub folder of the world, named 'amidst_backup'. Especially, make sure to not have the world loaded in minecraft during this process.** + +When I load a world file I am asked what whether I want to load the Singleplayer of Multiplayer players. What does it mean? +--------------------------------------------------------------------------------------------------------------------------- + +Minecraft worlds have three different locations to store player information: + +* the `level.dat` file contains the singleplayer player +* the `players` directory contains all multiplayer players by name, this was used before Minecraft 1.7.6 +* the `playerdata` directory contains all multiplayers players by uuid, this is used since Minecraft 1.7.6 + +If the `players` and the `playerdata` directory exist, we will simply ignore the `players` directory, since it contains outdated information. However, other situations cannot be decided automatically. If the world was only used by a server, there will be no player information in the `level.dat`, so we will just load the multiplayer players. However, if the map was ever loaded as a singleplayer world, the `level.dat` file will create singleplayer information. Also, the `playerdata` directory will contain information about all the players that used the world as singleplayer world. Of course we could just display the singleplayer player and the multiplayer players, however this might lead to an issue when you want to move the singleplayer player. When the world is loaded as singleplayer world, Minecraft will simply ignore and overwrite the information in the multiplayer directory, that belongs to the player that opened the world. Thus, if you move your player instead of the singleplayer player, this will have no effect. + +**tl;dr** If you use the world just as a singleplayer world, simply choose Singleplayer. + +What is the internet used for? +------------------- + +* Amidst v3.7 was the last version that used google analytics, so we do no longer track you +* Amidst will check for updates on every start +* Amidst will use web services provided by mojang, e.g. to + * display information about minecraft versions + * display information about players like the name or the skin + +General information +------------------- + +Amidst is not owned by or related to mojang in any way. + License and warranty -------------------- -AMIDST comes with ABSOLUTELY NO WARRANTY. It is free software, and you are -welcome to redistribute it under certain conditions. See LICENSE.txt for more -details on both of these points. +Amidst comes with ABSOLUTELY NO WARRANTY. It is free and open source software, license under the +[GPL v3](https://github.com/skiphs/amidst/blob/master/LICENSE.txt), and you are welcome to redistribute it under certain conditions. diff --git a/biome/default.json b/biome/default.json index 8faa004b5..e1aaf5646 100644 --- a/biome/default.json +++ b/biome/default.json @@ -1,81 +1,81 @@ -{ "name":"default", "colorMap":[ -[ "Cold Taiga M", { "r":89, "g":125, "b":114 } ], -[ "Desert", { "r":250, "g":148, "b":24 } ], -[ "Mushroom Island Shore", { "r":160, "g":0, "b":255 } ], -[ "Taiga", { "r":11, "g":102, "b":89 } ], -[ "Extreme Hills Edge M", { "r":154, "g":160, "b":194 } ], -[ "Cold Beach M", { "r":255, "g":255, "b":232 } ], -[ "Mesa Plateau", { "r":202, "g":140, "b":101 } ], -[ "Birch Forest Hills M", { "r":71, "g":135, "b":90 } ], -[ "Ice Mountains", { "r":160, "g":160, "b":160 } ], -[ "Swampland M", { "r":47, "g":255, "b":218 } ], -[ "Mushroom Island M", { "r":255, "g":40, "b":255 } ], -[ "Birch Forest Hills", { "r":31, "g":95, "b":50 } ], -[ "Extreme Hills+ M", { "r":120, "g":152, "b":120 } ], -[ "Taiga M", { "r":51, "g":142, "b":129 } ], -[ "Jungle M", { "r":123, "g":163, "b":49 } ], -[ "Mega Spruce Taiga (Hills)", { "r":109, "g":119, "b":102 } ], -[ "Savanna", { "r":189, "g":178, "b":95 } ], -[ "Roofed Forest M", { "r":104, "g":121, "b":66 } ], -[ "Mesa Plateau F", { "r":176, "g":151, "b":101 } ], -[ "Mesa Plateau F M", { "r":216, "g":191, "b":141 } ], -[ "Ice Mountains M", { "r":200, "g":200, "b":200 } ], -[ "Mega Taiga Hills", { "r":69, "g":79, "b":62 } ], -[ "Ice Plains Spikes", { "r":180, "g":220, "b":220 } ], -[ "Mushroom Island Shore M", { "r":200, "g":40, "b":255 } ], -[ "Deep Ocean M", { "r":40, "g":40, "b":88 } ], -[ "Ice Plains", { "r":255, "g":255, "b":255 } ], -[ "Mesa Plateau M", { "r":242, "g":180, "b":141 } ], -[ "Cold Taiga Hills M", { "r":76, "g":103, "b":94 } ], -[ "Frozen River", { "r":160, "g":160, "b":255 } ], -[ "Frozen River M", { "r":200, "g":200, "b":255 } ], -[ "Forest", { "r":5, "g":102, "b":33 } ], -[ "Mesa (Bryce)", { "r":255, "g":109, "b":61 } ], -[ "Frozen Ocean", { "r":144, "g":144, "b":160 } ], -[ "Forest Hills", { "r":34, "g":85, "b":28 } ], -[ "Mega Spruce Taiga", { "r":129, "g":142, "b":121 } ], -[ "Beach", { "r":250, "g":222, "b":85 } ], -[ "Desert Hills", { "r":210, "g":95, "b":18 } ], -[ "Roofed Forest", { "r":64, "g":81, "b":26 } ], -[ "Hell M", { "r":255, "g":40, "b":40 } ], -[ "Stone Beach", { "r":162, "g":162, "b":132 } ], -[ "Extreme Hills M", { "r":136, "g":136, "b":136 } ], -[ "Desert M", { "r":255, "g":188, "b":64 } ], -[ "Deep Ocean", { "r":0, "g":0, "b":48 } ], -[ "Extreme Hills", { "r":96, "g":96, "b":96 } ], -[ "Jungle Hills M", { "r":84, "g":106, "b":45 } ], -[ "Jungle", { "r":83, "g":123, "b":9 } ], -[ "Taiga Hills M", { "r":62, "g":97, "b":91 } ], -[ "Ocean M", { "r":40, "g":40, "b":152 } ], -[ "Savanna Plateau", { "r":167, "g":157, "b":100 } ], -[ "Extreme Hills Edge", { "r":114, "g":120, "b":154 } ], -[ "Sky", { "r":128, "g":128, "b":255 } ], -[ "Mushroom Island", { "r":255, "g":0, "b":255 } ], -[ "Birch Forest", { "r":48, "g":116, "b":68 } ], -[ "Mesa", { "r":217, "g":69, "b":21 } ], -[ "Mega Taiga", { "r":89, "g":102, "b":81 } ], -[ "Savanna M", { "r":229, "g":218, "b":135 } ], -[ "River", { "r":0, "g":0, "b":255 } ], -[ "Swampland", { "r":7, "g":249, "b":178 } ], -[ "Sunflower Plains", { "r":181, "g":219, "b":136 } ], -[ "Extreme Hills+", { "r":80, "g":112, "b":80 } ], -[ "River M", { "r":40, "g":40, "b":255 } ], -[ "Flower Forest", { "r":45, "g":142, "b":73 } ], -[ "Ocean", { "r":0, "g":0, "b":112 } ], -[ "Plains", { "r":141, "g":179, "b":96 } ], -[ "Beach M", { "r":255, "g":255, "b":125 } ], -[ "Sky M", { "r":168, "g":168, "b":255 } ], -[ "Hell", { "r":255, "g":0, "b":0 } ], -[ "Taiga Hills", { "r":22, "g":57, "b":51 } ], -[ "Jungle Edge M", { "r":138, "g":179, "b":63 } ], -[ "Cold Taiga", { "r":49, "g":85, "b":74 } ], -[ "Forest Hills M", { "r":74, "g":125, "b":68 } ], -[ "Jungle Edge", { "r":98, "g":139, "b":23 } ], -[ "Birch Forest M", { "r":88, "g":156, "b":108 } ], -[ "Jungle Hills", { "r":44, "g":66, "b":5 } ], -[ "Stone Beach M", { "r":202, "g":202, "b":172 } ], -[ "Desert Hills M", { "r":250, "g":135, "b":58 } ], -[ "Frozen Ocean M", { "r":184, "g":184, "b":200 } ], -[ "Savanna Plateau M", { "r":207, "g":197, "b":140 } ], -[ "Cold Taiga Hills", { "r":36, "g":63, "b":54 } ], -[ "Cold Beach", { "r":250, "g":240, "b":192 } ] ] } +{ "name":"default", "colorMap":[ +[ "Ocean", { "r":0, "g":0, "b":112 } ], +[ "Plains", { "r":141, "g":179, "b":96 } ], +[ "Desert", { "r":250, "g":148, "b":24 } ], +[ "Extreme Hills", { "r":96, "g":96, "b":96 } ], +[ "Forest", { "r":5, "g":102, "b":33 } ], +[ "Taiga", { "r":11, "g":102, "b":89 } ], +[ "Swampland", { "r":7, "g":249, "b":178 } ], +[ "River", { "r":0, "g":0, "b":255 } ], +[ "Hell", { "r":255, "g":0, "b":0 } ], +[ "Sky", { "r":128, "g":128, "b":255 } ], +[ "Frozen Ocean", { "r":144, "g":144, "b":160 } ], +[ "Frozen River", { "r":160, "g":160, "b":255 } ], +[ "Ice Plains", { "r":255, "g":255, "b":255 } ], +[ "Ice Mountains", { "r":160, "g":160, "b":160 } ], +[ "Mushroom Island", { "r":255, "g":0, "b":255 } ], +[ "Mushroom Island Shore", { "r":160, "g":0, "b":255 } ], +[ "Beach", { "r":250, "g":222, "b":85 } ], +[ "Desert Hills", { "r":210, "g":95, "b":18 } ], +[ "Forest Hills", { "r":34, "g":85, "b":28 } ], +[ "Taiga Hills", { "r":22, "g":57, "b":51 } ], +[ "Extreme Hills Edge", { "r":114, "g":120, "b":154 } ], +[ "Jungle", { "r":83, "g":123, "b":9 } ], +[ "Jungle Hills", { "r":44, "g":66, "b":5 } ], +[ "Jungle Edge", { "r":98, "g":139, "b":23 } ], +[ "Deep Ocean", { "r":0, "g":0, "b":48 } ], +[ "Stone Beach", { "r":162, "g":162, "b":132 } ], +[ "Cold Beach", { "r":250, "g":240, "b":192 } ], +[ "Birch Forest", { "r":48, "g":116, "b":68 } ], +[ "Birch Forest Hills", { "r":31, "g":95, "b":50 } ], +[ "Roofed Forest", { "r":64, "g":81, "b":26 } ], +[ "Cold Taiga", { "r":49, "g":85, "b":74 } ], +[ "Cold Taiga Hills", { "r":36, "g":63, "b":54 } ], +[ "Mega Taiga", { "r":89, "g":102, "b":81 } ], +[ "Mega Taiga Hills", { "r":69, "g":79, "b":62 } ], +[ "Extreme Hills+", { "r":80, "g":112, "b":80 } ], +[ "Savanna", { "r":189, "g":178, "b":95 } ], +[ "Savanna Plateau", { "r":167, "g":157, "b":100 } ], +[ "Mesa", { "r":217, "g":69, "b":21 } ], +[ "Mesa Plateau F", { "r":176, "g":151, "b":101 } ], +[ "Mesa Plateau", { "r":202, "g":140, "b":101 } ], +[ "Ocean M", { "r":40, "g":40, "b":152 } ], +[ "Sunflower Plains", { "r":181, "g":219, "b":136 } ], +[ "Desert M", { "r":255, "g":188, "b":64 } ], +[ "Extreme Hills M", { "r":136, "g":136, "b":136 } ], +[ "Flower Forest", { "r":45, "g":142, "b":73 } ], +[ "Taiga M", { "r":51, "g":142, "b":129 } ], +[ "Swampland M", { "r":47, "g":255, "b":218 } ], +[ "River M", { "r":40, "g":40, "b":255 } ], +[ "Hell M", { "r":255, "g":40, "b":40 } ], +[ "Sky M", { "r":168, "g":168, "b":255 } ], +[ "Frozen Ocean M", { "r":184, "g":184, "b":200 } ], +[ "Frozen River M", { "r":200, "g":200, "b":255 } ], +[ "Ice Plains Spikes", { "r":180, "g":220, "b":220 } ], +[ "Ice Mountains M", { "r":200, "g":200, "b":200 } ], +[ "Mushroom Island M", { "r":255, "g":40, "b":255 } ], +[ "Mushroom Island Shore M", { "r":200, "g":40, "b":255 } ], +[ "Beach M", { "r":255, "g":255, "b":125 } ], +[ "Desert Hills M", { "r":250, "g":135, "b":58 } ], +[ "Forest Hills M", { "r":74, "g":125, "b":68 } ], +[ "Taiga Hills M", { "r":62, "g":97, "b":91 } ], +[ "Extreme Hills Edge M", { "r":154, "g":160, "b":194 } ], +[ "Jungle M", { "r":123, "g":163, "b":49 } ], +[ "Jungle Hills M", { "r":84, "g":106, "b":45 } ], +[ "Jungle Edge M", { "r":138, "g":179, "b":63 } ], +[ "Deep Ocean M", { "r":40, "g":40, "b":88 } ], +[ "Stone Beach M", { "r":202, "g":202, "b":172 } ], +[ "Cold Beach M", { "r":255, "g":255, "b":232 } ], +[ "Birch Forest M", { "r":88, "g":156, "b":108 } ], +[ "Birch Forest Hills M", { "r":71, "g":135, "b":90 } ], +[ "Roofed Forest M", { "r":104, "g":121, "b":66 } ], +[ "Cold Taiga M", { "r":89, "g":125, "b":114 } ], +[ "Cold Taiga Hills M", { "r":76, "g":103, "b":94 } ], +[ "Mega Spruce Taiga", { "r":129, "g":142, "b":121 } ], +[ "Mega Spruce Taiga (Hills)", { "r":109, "g":119, "b":102 } ], +[ "Extreme Hills+ M", { "r":120, "g":152, "b":120 } ], +[ "Savanna M", { "r":229, "g":218, "b":135 } ], +[ "Savanna Plateau M", { "r":207, "g":197, "b":140 } ], +[ "Mesa (Bryce)", { "r":255, "g":109, "b":61 } ], +[ "Mesa Plateau F M", { "r":216, "g":191, "b":141 } ], +[ "Mesa Plateau M", { "r":242, "g":180, "b":141 } ] ] } diff --git a/biome/test.json b/biome/test.json index 8cc5f6a92..eaa9380af 100644 --- a/biome/test.json +++ b/biome/test.json @@ -1,81 +1,81 @@ { "name":"test", "colorMap":[ -[ "Cold Taiga M", { "r":89, "g":125, "b":114 } ], +[ "Ocean", { "r":0, "g":0, "b":112 } ], +[ "Plains", { "r":141, "g":179, "b":96 } ], [ "Desert", { "r":250, "g":148, "b":24 } ], -[ "Mushroom Island Shore", { "r":160, "g":0, "b":255 } ], -[ "Taiga", { "r":11, "g":2, "b":89 } ], -[ "Extreme Hills Edge M", { "r":154, "g":160, "b":194 } ], -[ "Cold Beach M", { "r":255, "g":255, "b":232 } ], -[ "Mesa Plateau", { "r":202, "g":140, "b":101 } ], -[ "Birch Forest Hills M", { "r":71, "g":15, "b":90 } ], -[ "Ice Mountains", { "r":160, "g":160, "b":160 } ], -[ "Swampland M", { "r":47, "g":255, "b":18 } ], -[ "Mushroom Island M", { "r":255, "g":40, "b":255 } ], -[ "Birch Forest Hills", { "r":31, "g":5, "b":50 } ], -[ "Extreme Hills+ M", { "r":120, "g":52, "b":120 } ], -[ "Taiga M", { "r":51, "g":142, "b":19 } ], -[ "Jungle M", { "r":123, "g":13, "b":49 } ], -[ "Mega Spruce Taiga (Hills)", { "r":109, "g":119, "b":102 } ], -[ "Savanna", { "r":189, "g":18, "b":95 } ], -[ "Roofed Forest M", { "r":104, "g":121, "b":66 } ], -[ "Mesa Plateau F", { "r":17, "g":151, "b":101 } ], -[ "Mesa Plateau F M", { "r":216, "g":191, "b":141 } ], -[ "Ice Mountains M", { "r":20, "g":20, "b":200 } ], -[ "Mega Taiga Hills", { "r":69, "g":7, "b":62 } ], -[ "Ice Plains Spikes", { "r":180, "g":20, "b":220 } ], -[ "Mushroom Island Shore M", { "r":200, "g":40, "b":255 } ], -[ "Deep Ocean M", { "r":40, "g":40, "b":88 } ], -[ "Ice Plains", { "alias":"test", "r":255, "g":255, "b":255 } ], -[ "Mesa Plateau M", { "r":242, "g":180, "b":141 } ], -[ "Cold Taiga Hills M", { "r":76, "g":103, "b":94 } ], -[ "Frozen River", { "r":160, "g":160, "b":255 } ], -[ "Frozen River M", { "r":200, "g":200, "b":255 } ], +[ "Extreme Hills", { "r":96, "g":96, "b":96 } ], [ "Forest", { "r":5, "g":102, "b":33 } ], -[ "Mesa (Bryce)", { "r":255, "g":109, "b":61 } ], +[ "Taiga", { "r":11, "g":2, "b":89 } ], +[ "Swampland", { "r":7, "g":249, "b":178 } ], +[ "River", { "r":0, "g":0, "b":255 } ], +[ "Hell", { "r":255, "g":0, "b":0 } ], +[ "Sky", { "r":128, "g":128, "b":255 } ], [ "Frozen Ocean", { "r":144, "g":144, "b":160 } ], -[ "Forest Hills", { "r":34, "g":85, "b":28 } ], -[ "Mega Spruce Taiga", { "r":129, "g":142, "b":121 } ], +[ "Frozen River", { "r":160, "g":160, "b":255 } ], +[ "Ice Plains", { "r":255, "g":255, "b":255 } ], +[ "Ice Mountains", { "r":160, "g":160, "b":160 } ], +[ "Mushroom Island", { "r":255, "g":0, "b":255 } ], +[ "Mushroom Island Shore", { "r":160, "g":0, "b":255 } ], [ "Beach", { "r":250, "g":222, "b":85 } ], [ "Desert Hills", { "r":210, "g":95, "b":18 } ], -[ "Roofed Forest", { "r":64, "g":81, "b":26 } ], -[ "Hell M", { "r":255, "g":40, "b":40 } ], -[ "Stone Beach", { "r":162, "g":162, "b":132 } ], -[ "Extreme Hills M", { "r":136, "g":136, "b":136 } ], -[ "Desert M", { "r":255, "g":188, "b":64 } ], -[ "Deep Ocean", { "r":0, "g":0, "b":48 } ], -[ "Extreme Hills", { "r":96, "g":96, "b":96 } ], -[ "Jungle Hills M", { "r":84, "g":106, "b":45 } ], -[ "Jungle", { "r":83, "g":123, "b":9 } ], -[ "Taiga Hills M", { "r":62, "g":97, "b":91 } ], -[ "Ocean M", { "r":40, "g":40, "b":152 } ], -[ "Savanna Plateau", { "r":167, "g":157, "b":100 } ], +[ "Forest Hills", { "r":34, "g":85, "b":28 } ], +[ "Taiga Hills", { "r":22, "g":57, "b":51 } ], [ "Extreme Hills Edge", { "r":114, "g":120, "b":154 } ], -[ "Sky", { "r":128, "g":128, "b":255 } ], -[ "Mushroom Island", { "r":255, "g":0, "b":255 } ], +[ "Jungle", { "r":83, "g":123, "b":9 } ], +[ "Jungle Hills", { "r":44, "g":66, "b":5 } ], +[ "Jungle Edge", { "r":98, "g":139, "b":23 } ], +[ "Deep Ocean", { "r":0, "g":0, "b":48 } ], +[ "Stone Beach", { "r":162, "g":162, "b":132 } ], +[ "Cold Beach", { "r":250, "g":240, "b":192 } ], [ "Birch Forest", { "r":48, "g":116, "b":68 } ], -[ "Mesa", { "r":217, "g":69, "b":21 } ], +[ "Birch Forest Hills", { "r":31, "g":5, "b":50 } ], +[ "Roofed Forest", { "r":64, "g":81, "b":26 } ], +[ "Cold Taiga", { "r":49, "g":85, "b":74 } ], +[ "Cold Taiga Hills", { "r":36, "g":63, "b":54 } ], [ "Mega Taiga", { "r":89, "g":102, "b":81 } ], -[ "Savanna M", { "r":229, "g":218, "b":135 } ], -[ "River", { "r":0, "g":0, "b":255 } ], -[ "Swampland", { "r":7, "g":249, "b":178 } ], -[ "Sunflower Plains", { "r":181, "g":219, "b":136 } ], +[ "Mega Taiga Hills", { "r":69, "g":7, "b":62 } ], [ "Extreme Hills+", { "r":80, "g":112, "b":80 } ], -[ "River M", { "r":40, "g":40, "b":255 } ], +[ "Savanna", { "r":189, "g":18, "b":95 } ], +[ "Savanna Plateau", { "r":167, "g":157, "b":100 } ], +[ "Mesa", { "r":217, "g":69, "b":21 } ], +[ "Mesa Plateau F", { "r":17, "g":151, "b":101 } ], +[ "Mesa Plateau", { "r":202, "g":140, "b":101 } ], +[ "Ocean M", { "r":40, "g":40, "b":152 } ], +[ "Sunflower Plains", { "r":181, "g":219, "b":136 } ], +[ "Desert M", { "r":255, "g":188, "b":64 } ], +[ "Extreme Hills M", { "r":136, "g":136, "b":136 } ], [ "Flower Forest", { "r":45, "g":142, "b":73 } ], -[ "Ocean", { "r":0, "g":0, "b":112 } ], -[ "Plains", { "r":141, "g":179, "b":96 } ], -[ "Beach M", { "r":255, "g":255, "b":125 } ], +[ "Taiga M", { "r":51, "g":142, "b":19 } ], +[ "Swampland M", { "r":47, "g":255, "b":18 } ], +[ "River M", { "r":40, "g":40, "b":255 } ], +[ "Hell M", { "r":255, "g":40, "b":40 } ], [ "Sky M", { "r":168, "g":168, "b":255 } ], -[ "Hell", { "r":255, "g":0, "b":0 } ], -[ "Taiga Hills", { "r":22, "g":57, "b":51 } ], -[ "Jungle Edge M", { "r":138, "g":179, "b":63 } ], -[ "Cold Taiga", { "r":49, "g":85, "b":74 } ], +[ "Frozen Ocean M", { "r":184, "g":184, "b":200 } ], +[ "Frozen River M", { "r":200, "g":200, "b":255 } ], +[ "Ice Plains Spikes", { "r":180, "g":20, "b":220 } ], +[ "Ice Mountains M", { "r":20, "g":20, "b":200 } ], +[ "Mushroom Island M", { "r":255, "g":40, "b":255 } ], +[ "Mushroom Island Shore M", { "r":200, "g":40, "b":255 } ], +[ "Beach M", { "r":255, "g":255, "b":125 } ], +[ "Desert Hills M", { "r":250, "g":135, "b":58 } ], [ "Forest Hills M", { "r":74, "g":125, "b":68 } ], -[ "Jungle Edge", { "r":98, "g":139, "b":23 } ], -[ "Birch Forest M", { "r":88, "g":156, "b":108 } ], -[ "Jungle Hills", { "r":44, "g":66, "b":5 } ], +[ "Taiga Hills M", { "r":62, "g":97, "b":91 } ], +[ "Extreme Hills Edge M", { "r":154, "g":160, "b":194 } ], +[ "Jungle M", { "r":123, "g":13, "b":49 } ], +[ "Jungle Hills M", { "r":84, "g":106, "b":45 } ], +[ "Jungle Edge M", { "r":138, "g":179, "b":63 } ], +[ "Deep Ocean M", { "r":40, "g":40, "b":88 } ], [ "Stone Beach M", { "r":202, "g":202, "b":172 } ], -[ "Desert Hills M", { "r":250, "g":135, "b":58 } ], -[ "Frozen Ocean M", { "r":184, "g":184, "b":200 } ], +[ "Cold Beach M", { "r":255, "g":255, "b":232 } ], +[ "Birch Forest M", { "r":88, "g":156, "b":108 } ], +[ "Birch Forest Hills M", { "r":71, "g":15, "b":90 } ], +[ "Roofed Forest M", { "r":104, "g":121, "b":66 } ], +[ "Cold Taiga M", { "r":89, "g":125, "b":114 } ], +[ "Cold Taiga Hills M", { "r":76, "g":103, "b":94 } ], +[ "Mega Spruce Taiga", { "r":129, "g":142, "b":121 } ], +[ "Mega Spruce Taiga (Hills)", { "r":109, "g":119, "b":102 } ], +[ "Extreme Hills+ M", { "r":120, "g":52, "b":120 } ], +[ "Savanna M", { "r":229, "g":218, "b":135 } ], [ "Savanna Plateau M", { "r":207, "g":197, "b":140 } ], -[ "Cold Taiga Hills", { "r":36, "g":63, "b":54 } ], -[ "Cold Beach", { "r":250, "g":240, "b":192 } ] ] } +[ "Mesa (Bryce)", { "r":255, "g":109, "b":61 } ], +[ "Mesa Plateau F M", { "r":216, "g":191, "b":141 } ], +[ "Mesa Plateau M", { "r":242, "g":180, "b":141 } ] ] } diff --git a/build.xml b/build.xml deleted file mode 100644 index cdfa7571f..000000000 --- a/build.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - Build AMIDST - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 000000000..d2c1117bd --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,36 @@ +Building Amidst +===== + +The build process is configured via the file `src/main/resources/amidst/metadata.properties`. For example the `` is taken from the variable `amidst.build.filename`. + +Amidst uses maven for its build process. Here are the steps to build from source: + +* `mvn clean` +* `mvn install` + +This will place the jar file under `target/.jar`. + +To build the wrapper for mac, follow these steps: + +* `mvn clean` +* `mvn install` +* `bash travis-ci/scripts/create-mac-icon.sh` +* `mvn package -DskipTests=true -f travis-ci/wrapper-for-mac/pom.xml` +* `bash travis-ci/scripts/zip-and-move-wrapper-for-mac.sh` + +This will place the zip file under `target/.zip`. + +To build the wrapper for windows, follow these steps: + +* `mvn clean` +* `mvn install` +* `bash travis-ci/scripts/create-windows-icon.sh` +* `mvn package -DskipTests=true -f travis-ci/wrapper-for-windows/pom.xml` + +This will place the exe file under `target/.exe`. + +You will need imagemagick installed to create the icons. Of course, you can also create the icon files by yourself and place them as `target/icon.icns` (mac) or `target/icon.ico` (windows) to the expected location. The `zip-and-move-wrapper-for-mac.sh` bash script simply creates a zip file from the directory located at `travis-ci/wrapper-for-mac/target//`. You can also do this by yourself. + +All of these steps are also executed by travis-ci to create a new release. However the regular travis-ci build will not create the wrappers for mac and windows. + +The command `mvn clean` will actually install a dependency that is not available from a public maven repository to the local maven repository, so it is necessary to execute. diff --git a/lib/JGoogleAnalytics_0.4.jar b/lib/JGoogleAnalytics_0.4.jar deleted file mode 100644 index cceba1c88..000000000 Binary files a/lib/JGoogleAnalytics_0.4.jar and /dev/null differ diff --git a/lib/args4j-2.0.21.jar b/lib/args4j-2.0.21.jar deleted file mode 100644 index e188b3dfe..000000000 Binary files a/lib/args4j-2.0.21.jar and /dev/null differ diff --git a/lib/gson-2.2.4.jar b/lib/gson-2.2.4.jar deleted file mode 100644 index 9478253e8..000000000 Binary files a/lib/gson-2.2.4.jar and /dev/null differ diff --git a/lib/js.jar b/lib/js.jar deleted file mode 100644 index 6f0dafbbc..000000000 Binary files a/lib/js.jar and /dev/null differ diff --git a/lib/kryonet-2.21-all.jar b/lib/kryonet-2.21-all.jar deleted file mode 100644 index e7b0c94ef..000000000 Binary files a/lib/kryonet-2.21-all.jar and /dev/null differ diff --git a/lib/miglayout-4.0-swing.jar b/lib/miglayout-4.0-swing.jar deleted file mode 100644 index 7b6a22cac..000000000 Binary files a/lib/miglayout-4.0-swing.jar and /dev/null differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..1fe96b8c3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,134 @@ + + 4.0.0 + amidst + amidst + 0.0.1-SNAPSHOT + + yyyy-MM-dd HH:mm + + + ${amidst.build.filename} + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + initialize + + read-project-properties + + + + ${basedir}/src/main/resources/amidst/metadata.properties + + + + + + + org.apache.maven.plugins + maven-install-plugin + 2.5.2 + + + install-jnbt + clean + + install-file + + + ${basedir}/lib/JNBT_1.3.jar + default + jnbt + jnbt + 1.3 + jar + true + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${amidst.build.jdk.version} + ${amidst.build.jdk.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + + + amidst.Amidst + + ${amidst.build.filename} + ${maven.build.timestamp} + + + + + + + + + + + + com.google.code.gson + gson + 2.5 + + + com.miglayout + miglayout-swing + 4.2 + + + args4j + args4j + 2.32 + + + jnbt + jnbt + 1.3 + + + junit + junit + 4.11 + test + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + provided + maven-plugin + + + \ No newline at end of file diff --git a/src/MoF/FinderWindow.java b/src/MoF/FinderWindow.java deleted file mode 100644 index ebbbe1be3..000000000 --- a/src/MoF/FinderWindow.java +++ /dev/null @@ -1,60 +0,0 @@ -package MoF; - - -import amidst.Amidst; -import amidst.gui.menu.AmidstMenu; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.event.*; -import javax.swing.JFrame; - -public class FinderWindow extends JFrame { - private static final long serialVersionUID = 196896954675968191L; - public static FinderWindow instance; - private Container pane; - public Project curProject; //TODO - public static boolean dataCollect; - private final AmidstMenu menuBar; - public FinderWindow() { - //Initialize window - super("Amidst v" + Amidst.version()); - - setSize(1000,800); - //setLookAndFeel(); - pane = getContentPane(); - //UI Manager: - pane.setLayout(new BorderLayout()); - new UpdateManager(this, true).start(); - setJMenuBar(menuBar = new AmidstMenu(this)); - setVisible(true); - setIconImage(Amidst.icon); - instance = this; - - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - dispose(); - System.exit(0); - } - }); - } - - public void clearProject() { - // FIXME Release resources. - if (curProject != null) { - removeKeyListener(curProject.getKeyListener()); - curProject.dispose(); - pane.remove(curProject); - System.gc(); - } - } - public void setProject(Project ep) { - menuBar.mapMenu.setEnabled(true); - curProject = ep; - - addKeyListener(ep.getKeyListener()); - pane.add(curProject, BorderLayout.CENTER); - - this.validate(); - } -} diff --git a/src/MoF/Google.java b/src/MoF/Google.java deleted file mode 100644 index 8966bce47..000000000 --- a/src/MoF/Google.java +++ /dev/null @@ -1,20 +0,0 @@ -package MoF; - -import amidst.Amidst; - -import com.boxysystems.jgoogleanalytics.*; - - -public class Google { - private static JGoogleAnalyticsTracker tracker; - public static void startTracking() { - tracker = new JGoogleAnalyticsTracker("AMIDST", Amidst.version(), "UA-27092717-1"); - - } - - public static void track(String s) { - FocusPoint focusPoint = new FocusPoint(s); - tracker.trackAsynchronously(focusPoint); - } - -} diff --git a/src/MoF/MapViewer.java b/src/MoF/MapViewer.java deleted file mode 100644 index a6ce03a92..000000000 --- a/src/MoF/MapViewer.java +++ /dev/null @@ -1,395 +0,0 @@ -package MoF; - - -import amidst.Options; -import amidst.gui.menu.PlayerMenuItem; -import amidst.logging.Log; -import amidst.map.FragmentManager; -import amidst.map.IconLayer; -import amidst.map.ImageLayer; -import amidst.map.LiveLayer; -import amidst.map.Map; -import amidst.map.MapObject; -import amidst.map.MapObjectPlayer; -import amidst.map.layers.BiomeLayer; -import amidst.map.layers.GridLayer; -import amidst.map.layers.NetherFortressLayer; -import amidst.map.layers.OceanMonumentLayer; -import amidst.map.layers.PlayerLayer; -import amidst.map.layers.SlimeLayer; -import amidst.map.layers.SpawnLayer; -import amidst.map.layers.StrongholdLayer; -import amidst.map.layers.TempleLayer; -import amidst.map.layers.VillageLayer; -import amidst.map.widget.BiomeToggleWidget; -import amidst.map.widget.BiomeWidget; -import amidst.map.widget.CursorInformationWidget; -import amidst.map.widget.DebugWidget; -import amidst.map.widget.FpsWidget; -import amidst.map.widget.ScaleWidget; -import amidst.map.widget.PanelWidget.CornerAnchorPoint; -import amidst.map.widget.SeedWidget; -import amidst.map.widget.SelectedObjectWidget; -import amidst.map.widget.Widget; -import amidst.minecraft.MinecraftUtil; -import amidst.resources.ResourceLoader; - -import java.awt.AlphaComposite; -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.RenderingHints; -import java.awt.event.ActionEvent; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.awt.geom.Point2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; - -import javax.imageio.ImageIO; -import javax.swing.JComponent; -import javax.swing.JPopupMenu; - -public class MapViewer extends JComponent implements MouseListener, MouseWheelListener, KeyListener { - private static final long serialVersionUID = -8309927053337294612L; - // TODO: This should likely be moved somewhere else. - private static FragmentManager fragmentManager; - private static PlayerLayer playerLayer; - - private Widget mouseOwner; - private static BufferedImage - dropShadowBottomLeft = ResourceLoader.getImage("dropshadow/inner_bottom_left.png"), - dropShadowBottomRight = ResourceLoader.getImage("dropshadow/inner_bottom_right.png"), - dropShadowTopLeft = ResourceLoader.getImage("dropshadow/inner_top_left.png"), - dropShadowTopRight = ResourceLoader.getImage("dropshadow/inner_top_right.png"), - dropShadowBottom = ResourceLoader.getImage("dropshadow/inner_bottom.png"), - dropShadowTop = ResourceLoader.getImage("dropshadow/inner_top.png"), - dropShadowLeft = ResourceLoader.getImage("dropshadow/inner_left.png"), - dropShadowRight = ResourceLoader.getImage("dropshadow/inner_right.png"); - static { - fragmentManager = new FragmentManager( - new ImageLayer[] { - new BiomeLayer(), - new SlimeLayer() - }, - new LiveLayer[] { - new GridLayer() - }, - new IconLayer[] { - new VillageLayer(), - new OceanMonumentLayer(), - new StrongholdLayer(), - new TempleLayer(), - new SpawnLayer(), - new NetherFortressLayer(), - playerLayer = new PlayerLayer() - }); - } - - private Project proj; - - private JPopupMenu menu = new JPopupMenu(); - public int strongholdCount, villageCount; - - private Map worldMap; - private MapObject selectedObject = null; - private Point lastMouse; - public Point lastRightClick = null; - private Point2D.Double panSpeed; - - private static int zoomLevel = 0, zoomTicksRemaining = 0; - private static double targetZoom = 0.25f, curZoom = 0.25f; - private Point zoomMouse = new Point(); - - private Font textFont = new Font("arial", Font.BOLD, 15); - - private FontMetrics textMetrics; - - private ArrayList widgets = new ArrayList(); - private long lastTime; - - public void dispose() { - Log.debug("Disposing of map viewer."); - worldMap.dispose(); - menu.removeAll(); - proj = null; - } - - MapViewer(Project proj) { - panSpeed = new Point2D.Double(); - this.proj = proj; - if (playerLayer.isEnabled = proj.saveLoaded) { - playerLayer.setPlayers(proj.save); - for (MapObjectPlayer player : proj.save.getPlayers()) { - menu.add(new PlayerMenuItem(this, player, playerLayer)); - } - } - - worldMap = new Map(fragmentManager); //TODO: implement more layers - worldMap.setZoom(curZoom); - - widgets.add(new FpsWidget(this).setAnchorPoint(CornerAnchorPoint.BOTTOM_LEFT)); - widgets.add(new ScaleWidget(this).setAnchorPoint(CornerAnchorPoint.BOTTOM_CENTER)); - widgets.add(new SeedWidget(this).setAnchorPoint(CornerAnchorPoint.TOP_LEFT)); - widgets.add(new DebugWidget(this).setAnchorPoint(CornerAnchorPoint.BOTTOM_RIGHT)); - widgets.add(new SelectedObjectWidget(this).setAnchorPoint(CornerAnchorPoint.TOP_LEFT)); - widgets.add(new CursorInformationWidget(this).setAnchorPoint(CornerAnchorPoint.TOP_RIGHT)); - widgets.add(new BiomeToggleWidget(this).setAnchorPoint(CornerAnchorPoint.BOTTOM_RIGHT)); - widgets.add(BiomeWidget.get(this).setAnchorPoint(CornerAnchorPoint.NONE)); - addMouseListener(this); - addMouseWheelListener(this); - - setFocusable(true); - lastTime = System.currentTimeMillis(); - - textMetrics = getFontMetrics(textFont); - } - - @Override - public void paint(Graphics g) { - Graphics2D g2d = (Graphics2D)g.create(); - - long currentTime = System.currentTimeMillis(); - float time = Math.min(Math.max(0, currentTime - lastTime), 100) / 1000.0f; - lastTime = currentTime; - - g2d.setColor(Color.black); - g2d.fillRect(0, 0, this.getWidth(), this.getHeight()); - - if (zoomTicksRemaining-- > 0) { - double lastZoom = curZoom; - curZoom = (targetZoom + curZoom) * 0.5; - - Point2D.Double targetZoom = worldMap.getScaled(lastZoom, curZoom, zoomMouse); - worldMap.moveBy(targetZoom); - worldMap.setZoom(curZoom); - } - - Point curMouse = getMousePosition(); - if (lastMouse != null) { - if (curMouse != null) { - double difX = curMouse.x - lastMouse.x; - double difY = curMouse.y - lastMouse.y; - // TODO : Scale with time - panSpeed.setLocation(difX * 0.2, difY * 0.2); - } - - lastMouse.translate((int) panSpeed.x, (int)panSpeed.y); - } - - worldMap.moveBy((int)panSpeed.x, (int)panSpeed.y); - if (Options.instance.mapFlicking.get()) { - panSpeed.x *= 0.95f; - panSpeed.y *= 0.95f; - } else { - panSpeed.x *= 0.f; - panSpeed.y *= 0.f; - } - - worldMap.width = getWidth(); - worldMap.height = getHeight(); - - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - worldMap.draw((Graphics2D)g2d.create(), time); - g2d.drawImage(dropShadowTopLeft, 0, 0, null); - g2d.drawImage(dropShadowTopRight, getWidth() - 10, 0, null); - g2d.drawImage(dropShadowBottomLeft, 0, getHeight() - 10, null); - g2d.drawImage(dropShadowBottomRight, getWidth() - 10, getHeight() - 10, null); - - g2d.drawImage(dropShadowTop, 10, 0, getWidth() - 20, 10, null); - g2d.drawImage(dropShadowBottom, 10, getHeight() - 10, getWidth() - 20, 10, null); - g2d.drawImage(dropShadowLeft, 0, 10, 10, getHeight() - 20, null); - g2d.drawImage(dropShadowRight, getWidth() - 10, 10, 10, getHeight() - 20, null); - - g2d.setFont(textFont); - for (Widget widget : widgets) { - if (widget.isVisible()) { - g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, widget.getAlpha())); - widget.draw(g2d, time); - } - } - } - - - public void centerAt(long x, long y) { - worldMap.centerOn(x, y); - } - - public void adjustZoom(Point position, int notches) { - zoomMouse = position; - if (notches > 0) { - if (zoomLevel < (Options.instance.maxZoom.get()?10:10000)) { - targetZoom /= 1.1; - zoomLevel++; - zoomTicksRemaining = 100; - } - } else { - if (zoomLevel > -20) { - targetZoom *= 1.1; - zoomLevel--; - zoomTicksRemaining = 100; - } - } - } - - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - int notches = e.getWheelRotation(); - Point mouse = e.getPoint(); // Don't use getMousePosition() because when computer is swapping/grinding, mouse may have moved out of window before execution reaches here. - for (Widget widget : widgets) { - if ((widget.isVisible()) && - (mouse.x > widget.getX()) && - (mouse.y > widget.getY()) && - (mouse.x < widget.getX() + widget.getWidth()) && - (mouse.y < widget.getY() + widget.getHeight())) { - if (widget.onMouseWheelMoved(mouse.x - widget.getX(), mouse.y - widget.getY(), notches)) - return; - } - } - adjustZoom(getMousePosition(), notches); - } - @Override - public void mouseClicked(MouseEvent e) { - if (!e.isMetaDown()) { - Point mouse = e.getPoint(); // Don't use getMousePosition() because when computer is swapping/grinding, mouse may have moved out of window before execution reaches here. - for (Widget widget : widgets) { - if ((widget.isVisible()) && - (mouse.x > widget.getX()) && - (mouse.y > widget.getY()) && - (mouse.x < widget.getX() + widget.getWidth()) && - (mouse.y < widget.getY() + widget.getHeight())) { - if (widget.onClick(mouse.x - widget.getX(), mouse.y - widget.getY())) - return; - } - } - MapObject object = worldMap.getObjectAt(mouse, 50.0); - - if (selectedObject != null) - selectedObject.localScale = 1.0; - - if (object != null) - object.localScale = 1.5; - selectedObject = object; - } - } - - - @Override - public void mouseEntered(MouseEvent arg0) { - } - @Override - public void mouseExited(MouseEvent arg0) { - } - @Override - public void mousePressed(MouseEvent e) { - if (e.isMetaDown()) - return; - Point mouse = e.getPoint(); // Don't use getMousePosition() because when computer is swapping/grinding, mouse may have moved out of window before execution reaches here. - for (Widget widget : widgets) { - if ((widget.isVisible()) && - (mouse.x > widget.getX()) && - (mouse.y > widget.getY()) && - (mouse.x < widget.getX() + widget.getWidth()) && - (mouse.y < widget.getY() + widget.getHeight())) { - if (widget.onMousePressed(mouse.x - widget.getX(), mouse.y - widget.getY())) { - mouseOwner = widget; - return; - } - } - } - lastMouse = mouse; - } - - @Override - public void mouseReleased(MouseEvent e) { - if (e.isPopupTrigger() && MinecraftUtil.getVersion().saveEnabled()) { - lastRightClick = getMousePosition(); - if (proj.saveLoaded) { - menu.show(e.getComponent(), e.getX(), e.getY()); - } - } else { - if (mouseOwner != null) { - mouseOwner.onMouseReleased(); - mouseOwner = null; - } else { - lastMouse = null; - } - } - } - - public MapObject getSelectedObject() { - return selectedObject; - } - - - public void movePlayer(String name, ActionEvent e) { - //PixelInfo p = getCursorInformation(new Point(tempX, tempY)); - - //proj.movePlayer(name, p); - } - - public void saveToFile(File f) { - BufferedImage image = new BufferedImage(worldMap.width, worldMap.height, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = image.createGraphics(); - - worldMap.draw(g2d, 0); - - for (Widget widget : widgets) - if (widget.isVisible()) - widget.draw(g2d, 0); - - try { - ImageIO.write(image, "png", f); - } catch (IOException e) { - e.printStackTrace(); - } - - g2d.dispose(); - image.flush(); - } - - @Override - public void keyPressed(KeyEvent e) { - // TODO Auto-generated method stub - Point mouse = getMousePosition(); - if (mouse == null) - mouse = new Point(getWidth() >> 1, getHeight () >> 1); - if (e.getKeyCode() == KeyEvent.VK_EQUALS) - adjustZoom(mouse, -1); - else if (e.getKeyCode() == KeyEvent.VK_MINUS) - adjustZoom(mouse, 1); - } - - @Override - public void keyReleased(KeyEvent e) { - // TODO Auto-generated method stub - - } - - @Override - public void keyTyped(KeyEvent e) { - // TODO Auto-generated method stub - - } - - public FragmentManager getFragmentManager() { - return fragmentManager; - } - - public Map getMap() { - return worldMap; - } - - public FontMetrics getFontMetrics() { - return textMetrics; - } -} diff --git a/src/MoF/Project.java b/src/MoF/Project.java deleted file mode 100644 index a98aae34b..000000000 --- a/src/MoF/Project.java +++ /dev/null @@ -1,158 +0,0 @@ -package MoF; - -import amidst.Options; -import amidst.logging.Log; -import amidst.map.MapObject; -import amidst.minecraft.MinecraftUtil; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.event.KeyListener; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Timer; -import java.util.TimerTask; -import java.sql.Timestamp; -import java.util.Date; - -import javax.swing.JPanel; - -@Deprecated //TODO: we should remove this and integrate it into Options -public class Project extends JPanel { - private static final long serialVersionUID = 1132526465987018165L; - - public MapViewer map; - public static int FRAGMENT_SIZE = 256; - private Timer timer; - public MapObject curTarget; - - public boolean saveLoaded; - public SaveLoader save; - - public Project(String seed) { - this(stringToLong(seed)); - Options.instance.seedText = seed; - - Google.track("seed/" + seed + "/" + Options.instance.seed); - } - - public Project(long seed) { - this(seed, SaveLoader.Type.DEFAULT.getName()); - } - - public Project(SaveLoader file) { - this(file.seed, SaveLoader.genType.getName(), file); - - Google.track("seed/file/" + Options.instance.seed); - } - - public Project(String seed, String type) { - this(stringToLong(seed), type); - - Google.track("seed/" + seed + "/" + Options.instance.seed); - } - - public Project(long seed, String type) { - this(seed, type, null); - } - - private void logSeedHistory(long seed) { - File historyFile = new File("./history.txt"); - if (Options.instance.historyPath != null) { - historyFile = new File(Options.instance.historyPath); - if (!historyFile.exists()) { - try { - historyFile.createNewFile(); - } catch (IOException e) { - Log.w("Unable to create history file: " + historyFile); - e.printStackTrace(); - return; - } - } - } - - if (historyFile.exists() && historyFile.isFile()) { - FileWriter writer = null; - try { - writer = new FileWriter(historyFile, true); - writer.append(new Timestamp(new Date().getTime()).toString() + " " + seed + "\r\n"); - } catch (IOException e) { - Log.w("Unable to write to history file."); - e.printStackTrace(); - } finally { - try { - if (writer != null) - writer.close(); - } catch (IOException e) { - Log.w("Unable to close writer for history file."); - e.printStackTrace(); - } - } - } - - - } - - public Project(long seed, String type, SaveLoader saveLoader) { - logSeedHistory(seed); - saveLoaded = !(saveLoader == null); - save = saveLoader; - //Enter seed data: - Options.instance.seed = seed; - - BorderLayout layout = new BorderLayout(); - this.setLayout(layout); - if (saveLoaded) - MinecraftUtil.createWorld(seed, type, save.getGeneratorOptions()); - else - MinecraftUtil.createWorld(seed, type); - //Create MapViewer - map = new MapViewer(this); - add(map, BorderLayout.CENTER); - //Debug - this.setBackground(Color.BLUE); - - //Timer: - timer = new Timer(); - - timer.scheduleAtFixedRate(new TimerTask() { - public void run() { - tick(); - } - }, 20, 20); - - } - - public void tick() { - map.repaint(); - } - - public void dispose() { - map.dispose(); - map = null; - timer.cancel(); - timer = null; - curTarget = null; - save = null; - System.gc(); - } - - private static long stringToLong(String seed) { - long ret; - try { - ret = Long.parseLong(seed); - } catch (NumberFormatException err) { - ret = seed.hashCode(); - } - return ret; - } - - - public KeyListener getKeyListener() { - return map; - } - public void moveMapTo(long x, long y) { - map.centerAt(x, y); - } -} diff --git a/src/MoF/SaveLoader.java b/src/MoF/SaveLoader.java deleted file mode 100644 index 0588d71f1..000000000 --- a/src/MoF/SaveLoader.java +++ /dev/null @@ -1,209 +0,0 @@ -package MoF; -import amidst.Util; -import amidst.logging.Log; -import amidst.map.MapObjectPlayer; -import java.io.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import javax.swing.filechooser.FileFilter; - -import org.jnbt.CompoundTag; -import org.jnbt.DoubleTag; -import org.jnbt.ListTag; -import org.jnbt.NBTInputStream; -import org.jnbt.NBTOutputStream; -import org.jnbt.Tag; - -public class SaveLoader { - public static Type genType = Type.DEFAULT; - - public enum Type { - DEFAULT("Default", "default"), FLAT("Flat", "flat"), LARGE_BIOMES("Large Biomes", "largeBiomes"), AMPLIFIED("Amplified", "amplified"), CUSTOMIZED("Customized", "customized"); - private final String name; - private final String value; - Type(String name, String value) { - this.name = name; - this.value = value; - } - - @Override - public String toString() { - return name; - } - - public String getName() { - return name; - } - - public String getValue() { - return value; - } - - public static Type fromMixedCase(String name) { - name = name.toLowerCase(); - for (Type t : values()) - if (t.name.toLowerCase().equals(name) || t.value.toLowerCase().equals(name)) - return t; - Log.crash("Unable to find World Type: " + name); - return null; - } - - } - public static Type[] selectableTypes = new Type[] { Type.DEFAULT, Type.FLAT, Type.LARGE_BIOMES, Type.AMPLIFIED }; - public static FileFilter getFilter() { - return (new FileFilter() { - public boolean accept(File f) { - if (f.isDirectory()) - return true; - String[] st = f.getName().split("\\/"); - return st[st.length - 1].equalsIgnoreCase("level.dat"); - } - - @Override - public String getDescription() { - return "Minecraft Data File (level.dat)"; - } - }); - } - - private File file; - private List players; - public long seed; - private boolean multi; - private List back; - private String generatorOptions = ""; - - public List getPlayers() { - return players; - } - public void movePlayer(String name, int x, int y) { - File out; - if (multi) { - String outPath = file.getParent() + "/players/" + name +".dat"; - out = new File(outPath); - backupFile(out); - try { - NBTInputStream inStream = new NBTInputStream(new FileInputStream(out)); - CompoundTag root = (CompoundTag)inStream.readTag(); - inStream.close(); - - HashMap rootMap = new HashMap(root.getValue()); - ArrayList posTag = new ArrayList(((ListTag)rootMap.get("Pos")).getValue()); - posTag.set(0, new DoubleTag("x", x)); - posTag.set(1, new DoubleTag("y", 120)); - posTag.set(2, new DoubleTag("z", y)); - rootMap.put("Pos", new ListTag("Pos", DoubleTag.class, posTag)); - root = new CompoundTag("Data", rootMap); - NBTOutputStream outStream = new NBTOutputStream(new FileOutputStream(out)); - outStream.writeTag(root); - outStream.close(); - } catch (Exception e) { - e.printStackTrace(); - } - - } else { - out = file; - backupFile(out); - try { - NBTInputStream inStream = new NBTInputStream(new FileInputStream(out)); - CompoundTag root = (CompoundTag)(((CompoundTag)inStream.readTag()).getValue().get("Data")); - inStream.close(); - - HashMap rootMap = new HashMap(root.getValue()); - HashMap playerMap = new HashMap(((CompoundTag)rootMap.get("Player")).getValue()); - ArrayList posTag = new ArrayList(((ListTag)playerMap.get("Pos")).getValue()); - posTag.set(0, new DoubleTag("x", x)); - posTag.set(1, new DoubleTag("y", 120)); - posTag.set(2, new DoubleTag("z", y)); - rootMap.put("Player", new CompoundTag("Player", playerMap)); - playerMap.put("Pos", new ListTag("Pos", DoubleTag.class, posTag)); - root = new CompoundTag("Data", rootMap); - HashMap base = new HashMap(); - base.put("Data", root); - root = new CompoundTag("Base", base); - NBTOutputStream outStream = new NBTOutputStream(new FileOutputStream(out)); - outStream.writeTag(root); - outStream.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void backupFile(File inputFile) { - File backupFolder = new File(inputFile.getParentFile() + "/amidst_backup/"); - if (!backupFolder.exists()) - backupFolder.mkdir(); - - File outputFile = new File(backupFolder + "/" + inputFile.getName()); - if (!back.contains(outputFile.toString())) { - try { - FileReader in = new FileReader(inputFile); - FileWriter out = new FileWriter(outputFile); - int c; - - while ((c = in.read()) != -1) - out.write(c); - - in.close(); - out.close(); - back.add(outputFile.toString()); - } catch (Exception ignored) {} - } - } - - public SaveLoader(File f) { - file = f; - players = new ArrayList(); - back = new ArrayList(); - try { - NBTInputStream inStream = new NBTInputStream(new FileInputStream(f)); - CompoundTag root = (CompoundTag) ((CompoundTag)inStream.readTag()).getValue().get("Data"); - inStream.close(); - seed = (Long)(root.getValue().get("RandomSeed").getValue()); - if (root.getValue().get("generatorName") != null) { - genType = Type.fromMixedCase((String)(root.getValue().get("generatorName").getValue())); - - if (genType == Type.CUSTOMIZED) - generatorOptions = (String)root.getValue().get("generatorOptions").getValue(); - } - CompoundTag playerTag = (CompoundTag)root.getValue().get("Player"); - - File playersFolder = new File(f.getParent(), "players"); - boolean multi = (playersFolder.exists() && (playersFolder.listFiles().length > 0)); - - if (multi) - Log.i("Multiplayer map detected."); - else - Log.i("Singleplayer map detected."); - - if (!multi) { - addPlayer("Player", playerTag); - } else { - File[] listing = playersFolder.listFiles(); - for (int i = 0; i < (listing != null ? listing.length : 0); i++) { - if (listing[i].isFile()) { - NBTInputStream playerInputStream = new NBTInputStream(new FileInputStream(listing[i])); - addPlayer(listing[i].getName().split("\\.")[0], (CompoundTag) ((CompoundTag)playerInputStream.readTag())); - playerInputStream.close(); - } - } - - } - } catch (Exception e) { - Util.showError(e); - } - } - - private void addPlayer(String name, CompoundTag ps) { - List pos = ((ListTag)(ps.getValue().get("Pos"))).getValue(); - double x = (Double)pos.get(0).getValue(); - double z = (Double)pos.get(2).getValue(); - players.add(new MapObjectPlayer(name, (int) x, (int) z)); - } - - public String getGeneratorOptions() { - return generatorOptions; - } -} diff --git a/src/MoF/SkinManager.java b/src/MoF/SkinManager.java deleted file mode 100644 index 7e5c25ef1..000000000 --- a/src/MoF/SkinManager.java +++ /dev/null @@ -1,62 +0,0 @@ -package MoF; - -import amidst.map.MapObjectPlayer; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Stack; - -import javax.imageio.ImageIO; - -public class SkinManager extends Thread { - private Stack players; - public boolean active; - public SkinManager() { - players = new Stack(); - active = true; - } - - public void addPlayer(MapObjectPlayer p) { - players.push(p); - } - public void run() { - while (this.active) { - try { - if (players.isEmpty()) { - Thread.sleep(50L); - } else { - MapObjectPlayer p = players.pop(); - try { - URL url = new URL("http://s3.amazonaws.com/MinecraftSkins/" + p.getName() + ".png"); - BufferedImage img = ImageIO.read(url); - BufferedImage pimg = new BufferedImage(20,20,BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = pimg.createGraphics(); - g2d.setColor(Color.black); - g2d.fillRect(0, 0, 20, 20); - g2d.drawImage(img, 2, 2, 18, 18, 8, 8, 16, 16, null); - g2d.dispose(); - img.flush(); - p.setMarker(pimg); - Thread.sleep(20L); - } catch (MalformedURLException e2) { - } catch (IOException e) { - } - } - } catch (InterruptedException e) { - - } - } - if (!this.active) { - dispose(); - } - } - - public void dispose() { - players.clear(); - players = null; - } -} diff --git a/src/MoF/UpdateManager.java b/src/MoF/UpdateManager.java deleted file mode 100644 index f3f959e97..000000000 --- a/src/MoF/UpdateManager.java +++ /dev/null @@ -1,117 +0,0 @@ -package MoF; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; - -import javax.swing.JFrame; -import javax.swing.JOptionPane; - -import amidst.Amidst; -import org.w3c.dom.*; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.ParserConfigurationException; - -import org.xml.sax.SAXException; - -public class UpdateManager extends Thread { - public static final String updateURL = "https://sites.google.com/site/mothfinder/update.xml"; - public static final String updateUnstableURL = "https://sites.google.com/site/mothfinder/update_unstable.xml"; - private JFrame window; - private boolean silent; - public UpdateManager(JFrame window) { - this.setWindow(window); - silent = false; - } - public UpdateManager(JFrame window, boolean silence) { - this.setWindow(window); - silent = silence; - } - public void run() { - - try { - URL url = new URL(updateURL); - DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); - Document doc = docBuilder.parse(url.openStream()); - - doc.getDocumentElement().normalize(); - NodeList vlist = doc.getDocumentElement().getElementsByTagName("version"); - NodeList version = vlist.item(0).getChildNodes(); - - int major = 0; - int minor = 0; - String updateURL = doc.getFirstChild().getAttributes().item(0).getNodeValue(); - for (int i = 0; i < version.getLength(); i++) { - Node v = version.item(i); - if (v.getNodeType() == Node.ELEMENT_NODE) { - if (v.getNodeName().toLowerCase().equals("major")) { - major = Integer.parseInt(v.getAttributes().item(0).getNodeValue()); - } else if (v.getNodeName().toLowerCase().equals("minor")) { - minor = Integer.parseInt(v.getAttributes().item(0).getNodeValue()); - } - } - } - int n = JOptionPane.NO_OPTION; - - if (major > Amidst.version_major) { - n = JOptionPane.showConfirmDialog( - window, - "A new version was found. Would you like to update?", - "Update Found", - JOptionPane.YES_NO_OPTION); - } else if ((major == Amidst.version_major) && (minor > Amidst.version_minor)) { - n = JOptionPane.showConfirmDialog( - window, - "A minor revision was found. Update?", - "Update Found", - JOptionPane.YES_NO_OPTION); - } else if (!silent) - JOptionPane.showMessageDialog(window, "There are no new updates."); - - if (n==0) { - if( !java.awt.Desktop.isDesktopSupported()) { - JOptionPane.showMessageDialog(window, "Error unable to open browser."); - } - - java.awt.Desktop desktop = java.awt.Desktop.getDesktop(); - - if( !desktop.isSupported( java.awt.Desktop.Action.BROWSE ) ) { - JOptionPane.showMessageDialog(window, "Error unable to open browser page."); - } - java.net.URI uri = new java.net.URI(updateURL); - desktop.browse(uri); - } - } catch (MalformedURLException e1) { - if (!silent) - JOptionPane.showMessageDialog(window, "Error connecting to update server: Malformed URL."); - } catch (IOException e1) { - if (!silent) - JOptionPane.showMessageDialog(window, "Error reading update data."); - } catch (ParserConfigurationException e) { - if (!silent) - JOptionPane.showMessageDialog(window, "Error with XML parser configuration."); - } catch (SAXException e) { - if (!silent) - JOptionPane.showMessageDialog(window, "Error parsing update file."); - } catch (NumberFormatException e) { - if (!silent) - JOptionPane.showMessageDialog(window, "Error parsing version numbers."); - } catch (NullPointerException e) { - if (!silent) - JOptionPane.showMessageDialog(window, "Error \"NullPointerException\" in update."); - } catch (URISyntaxException e) { - if (!silent) - JOptionPane.showMessageDialog(window, "Error parsing update URL."); - } - - } - public JFrame getWindow() { - return window; - } - public void setWindow(JFrame window) { - this.window = window; - } -} diff --git a/src/amidst/Amidst.java b/src/amidst/Amidst.java deleted file mode 100644 index 7e2ec33c7..000000000 --- a/src/amidst/Amidst.java +++ /dev/null @@ -1,85 +0,0 @@ -package amidst; - -import java.awt.Image; -import java.io.File; -import java.net.MalformedURLException; - -import org.kohsuke.args4j.CmdLineException; -import org.kohsuke.args4j.CmdLineParser; - -import com.google.gson.Gson; - -import MoF.FinderWindow; -import MoF.Google; -import amidst.gui.version.VersionSelectWindow; -import amidst.logging.FileLogger; -import amidst.logging.Log; -import amidst.minecraft.Minecraft; -import amidst.minecraft.MinecraftUtil; -import amidst.preferences.BiomeColorProfile; -import amidst.resources.ResourceLoader; - -public class Amidst { - public final static int version_major = 3; - public final static int version_minor = 7; - public final static String versionOffset = ""; - public static Image icon = ResourceLoader.getImage("icon.png"); - public static final Gson gson = new Gson(); - - - public static void main(String args[]) { - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable e) { - Log.crash(e, "Amidst has encounted an uncaught exception on thread: " + thread); - } - }); - CmdLineParser parser = new CmdLineParser(Options.instance); - try { - parser.parseArgument(args); - } catch (CmdLineException e) { - Log.w("There was an issue parsing command line options."); - e.printStackTrace(); - } - Util.setMinecraftDirectory(); - Util.setMinecraftLibraries(); - - if (Options.instance.logPath != null) - Log.addListener("file", new FileLogger(new File(Options.instance.logPath))); - - - if (!isOSX()) { Util.setLookAndFeel(); } - Google.startTracking(); - Google.track("Run"); - System.setProperty("sun.java2d.opengl","True"); - System.setProperty("sun.java2d.accthreshold", "0"); - BiomeColorProfile.scan(); - - if (Options.instance.minecraftJar != null) - { - try { - Util.setProfileDirectory(Options.instance.minecraftPath); - MinecraftUtil.setBiomeInterface(new Minecraft(new File(Options.instance.minecraftJar)).createInterface()); - new FinderWindow(); - } catch (MalformedURLException e) { - Log.crash(e, "MalformedURLException on Minecraft load."); - } - } - else - { - new VersionSelectWindow(); - } - } - - public static boolean isOSX() { - String osName = System.getProperty("os.name"); - return osName.contains("OS X"); - } - - public static String version() { - if (MinecraftUtil.hasInterface()) - return version_major + "." + version_minor + versionOffset + " [Using Minecraft version: " + MinecraftUtil.getVersion() + "]"; - return version_major + "." + version_minor + versionOffset; - } - -} diff --git a/src/amidst/Options.java b/src/amidst/Options.java deleted file mode 100644 index 834e8c284..000000000 --- a/src/amidst/Options.java +++ /dev/null @@ -1,103 +0,0 @@ -package amidst; - -import amidst.preferences.BiomeColorProfile; -import amidst.preferences.BooleanPrefModel; -import amidst.preferences.FilePrefModel; -import amidst.preferences.SelectPrefModel; -import amidst.preferences.StringPreference; - -import java.io.File; -import java.util.prefs.Preferences; - -import org.kohsuke.args4j.Option; - -/** Currently selected options that change AMIDST’s behavior - */ -public enum Options { - instance; - - //per-run preferences. TODO: store elsewhere? - public long seed; - public String seedText; - - //permanent preferences - public final FilePrefModel jar; - public final BooleanPrefModel showSlimeChunks; - public final BooleanPrefModel showGrid; - public final BooleanPrefModel showNetherFortresses; - public final BooleanPrefModel showTemples, showPlayers, showStrongholds, showVillages, showOceanMonuments, showSpawn; - public final BooleanPrefModel mapFlicking, mapFading, showFPS, showScale, showDebug; - public final BooleanPrefModel updateToUnstable; - public final BooleanPrefModel maxZoom; - - public final StringPreference lastProfile; - - public final SelectPrefModel worldType; - public BiomeColorProfile biomeColorProfile; - private Preferences preferences; - - //CLI - @Option (name="-history", usage="Sets the path to seed history file.", metaVar="") - public String historyPath; - - @Option (name="-log", usage="Sets the path to logging file.", metaVar="") - public String logPath; - - @Option (name="-mcpath", usage="Sets the path to the .minecraft directory.", metaVar="") - public String minecraftPath; - - @Option (name="-mcjar", usage="Sets the path to the minecraft .jar", metaVar="") - public String minecraftJar; - - @Option (name="-mcjson", usage="Sets the path to the minecraft .json", metaVar="") - public String minecraftJson; - - @Option (name="-mclibs", usage="Sets the path to the libraries/ folder", metaVar="") - public String minecraftLibraries; - - private Options() { - seed = 0L; - seedText = null; - - - Preferences pref = Preferences.userNodeForPackage(Amidst.class); - preferences = pref; - jar = new FilePrefModel( pref, "jar", new File(Util.minecraftDirectory, "bin/minecraft.jar")); - showSlimeChunks = new BooleanPrefModel(pref, "slimeChunks", false); - showGrid = new BooleanPrefModel(pref, "grid", false); - showNetherFortresses = new BooleanPrefModel(pref, "netherFortressIcons", false); - mapFlicking = new BooleanPrefModel(pref, "mapFlicking", true); - mapFading = new BooleanPrefModel(pref, "mapFading", true); - maxZoom = new BooleanPrefModel(pref, "maxZoom", true); - showStrongholds = new BooleanPrefModel(pref, "strongholdIcons", true); - showPlayers = new BooleanPrefModel(pref, "playerIcons", true); - showTemples = new BooleanPrefModel(pref, "templeIcons", true); - showVillages = new BooleanPrefModel(pref, "villageIcons", true); - showOceanMonuments = new BooleanPrefModel(pref, "oceanMonumentIcons", true); - showSpawn = new BooleanPrefModel(pref, "spawnIcon", true); - showFPS = new BooleanPrefModel(pref, "showFPS", true); - showScale = new BooleanPrefModel(pref, "showScale", true); - showDebug = new BooleanPrefModel(pref, "showDebug", false); - updateToUnstable = new BooleanPrefModel(pref, "updateToUnstable", false); - lastProfile = new StringPreference(pref, "profile", null); - biomeColorProfile = new BiomeColorProfile(); - worldType = new SelectPrefModel( pref, "worldType", "Prompt each time", new String[] { "Prompt each time", "Default", "Flat", "Large Biomes", "Amplified" }); - biomeColorProfile.fillColorArray(); - - - } - - public Preferences getPreferences() { - return preferences; - } - - public File getJar() { - return jar.get(); - } - - public String getSeedMessage() { - if (seedText == null) - return "Seed: " + seed; - return "Seed: \"" + seedText + "\" (" + seed + ")"; - } -} diff --git a/src/amidst/Util.java b/src/amidst/Util.java deleted file mode 100644 index befc06cb9..000000000 --- a/src/amidst/Util.java +++ /dev/null @@ -1,183 +0,0 @@ -package amidst; - -import javax.swing.*; - -import amidst.logging.Log; - -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintStream; - -public class Util { - /** Shows an error message for an exception - * @param e the exception for which the stachtrace is to be shown - */ - public static final String REMOTE_VERSION_LIST_URL = "https://s3.amazonaws.com/Minecraft.Download/versions/versions.json"; - private static String osString; - - public static String getOs() { - if (osString == null) { - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("win")) - osString = "windows"; - else if (os.contains("mac")) - osString = "osx"; - else - osString = "linux"; - } - return osString; - } - - public static void showError(Exception e) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(baos); - e.printStackTrace(ps); - String trace = baos.toString(); - - e.printStackTrace(); - - JOptionPane.showMessageDialog( - null, - trace, - e.toString(), - JOptionPane.ERROR_MESSAGE); - } - - public static void setLookAndFeel() { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - Log.printTraceStack(e); - } - } - - public static File minecraftDirectory; - public static void setMinecraftDirectory() { - if (Options.instance.minecraftPath != null) { - minecraftDirectory = new File(Options.instance.minecraftPath); - if (minecraftDirectory.exists() && minecraftDirectory.isDirectory()) - return; - Log.w("Unable to set Minecraft directory to: " + minecraftDirectory + " as that location does not exist or is not a folder."); - } - File mcDir = null; - File homeDirectory = new File(System.getProperty("user.home", ".")); - String os = System.getProperty("os.name").toLowerCase(); - - if (os.contains("win")) { - File appData = new File(System.getenv("APPDATA")); - if (appData.isDirectory()) - mcDir = new File(appData, ".minecraft"); - } else if (os.contains("mac")) { - mcDir = new File(homeDirectory, "Library/Application Support/minecraft"); - } - minecraftDirectory = (mcDir != null) ? mcDir : new File(homeDirectory, ".minecraft"); - } - - public static File minecraftLibraries; - public static void setMinecraftLibraries() { - minecraftLibraries = (Options.instance.minecraftLibraries == null) ? new File(minecraftDirectory, "libraries") : new File(Options.instance.minecraftLibraries); - } - - public static File profileDirectory; - public static void setProfileDirectory(String gameDir) { - if (gameDir != null && !gameDir.isEmpty()) { - profileDirectory = new File(gameDir); - if (profileDirectory.exists() && profileDirectory.isDirectory()) - return; - Log.w("Unable to set Profile directory to: " + profileDirectory + " as that location does not exist or is not a folder."); - } - profileDirectory = null; - } - - public static int makeColor(int r, int g, int b) { - int color = 0xFF000000; - color |= 0xFF0000 & (r << 16); - color |= 0xFF00 & (g << 8); - color |= 0xFF & b; - return color; - } - public static int mcColor(int color) { - return 0xFF000000 | color; - } - - private static final int TEMP_DIR_ATTEMPTS = 1000; - - /** Guava's method, moved here to avoid a huge dependency - * TODO: maybe switch to JDK 7 to use its java.nio.file.Files#createTempDirectory() - */ - public static File createTempDir() { - return getTempDir(System.currentTimeMillis() + ""); - } - - public static File getTempDir(String name) { - File baseDir = new File(System.getProperty("java.io.tmpdir")); - String baseName = name + "-"; - for (int counter=0; counter T readObject(BufferedReader reader, final Class clazz) throws JsonIOException, JsonSyntaxException { - return Amidst.gson.fromJson(reader, clazz); - } - - public static T readObject(File path, final Class clazz) throws IOException, JsonIOException, JsonSyntaxException { - final BufferedReader reader = new BufferedReader(new FileReader(path)); - T object = Amidst.gson.fromJson(reader, clazz); - reader.close(); - return object; - } - - public static T readObject(String path, final Class clazz) throws IOException { - return readObject(new File(path), clazz); - } - - public static int deselectColor(int color) { - int r = (color & 0x00FF0000) >> 16; - int g = (color & 0x0000FF00) >> 8; - int b = (color & 0x000000FF); - - int average = (r + g + b); - r = (r + average) / 30; - g = (g + average) / 30; - b = (b + average) / 30; - return makeColor(r, g, b); - } - - public static int lightenColor(int color, int brightness) { - int r = (color & 0x00FF0000) >> 16; - int g = (color & 0x0000FF00) >> 8; - int b = (color & 0x000000FF); - - r += brightness; - g += brightness; - b += brightness; - - if (r > 0xFF) r = 0xFF; - if (g > 0xFF) g = 0xFF; - if (b > 0xFF) b = 0xFF; - - return makeColor(r, g, b); - } - - public static int greyScale(int color) { - int r = (color & 0x00FF0000) >> 16; - int g = (color & 0x0000FF00) >> 8; - int b = (color & 0x000000FF); - int average = (r + g + b) / 3; - return makeColor(average, average, average); - } -} diff --git a/src/amidst/bytedata/ByteClass.java b/src/amidst/bytedata/ByteClass.java deleted file mode 100644 index d06643e47..000000000 --- a/src/amidst/bytedata/ByteClass.java +++ /dev/null @@ -1,357 +0,0 @@ -package amidst.bytedata; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.util.Stack; -import java.util.Vector; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ByteClass { - public static class AccessFlags { - public static int - PUBLIC = 0x01, - PRIVATE = 0x02, - PROTECTED = 0x04, - STATIC = 0x08, - FINAL = 0x10, - VOLATILE = 0x40, - TRANSIENT = 0x80; - } - /*ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. -ACC_PRIVATE 0x0002 Declared private; usable only within the defining class. -ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses. -ACC_STATIC 0x0008 Declared static. -ACC_FINAL 0x0010 Declared final; no further assignment after initialization. -ACC_VOLATILE 0x0040 Declared volatile; cannot be cached. -ACC_TRANSIENT 0x0080 Declared transient; not written or read by a persistent object manager. -*/ - public class Field { - public int accessFlags; - public Field() { } - }; - - private byte[] data; - private boolean isValidClass; - public int minorVersion; - public int majorVersion; - private int cpSize; - private DataInputStream stream; - private ClassConstant[] constants; - private int[] constantTypes; - private String name; - public int accessFlags; - - private Vector> stringIndices; - private Vector methodIndices; - private Vector methods, properties, constructors; - private Vector floatConstants; - private Vector longConstants; - private Vector utfConstants; - - public Field[] fields; - public int methodCount; // (Method count includes constructors) - public int constructorCount; - - public ByteClass(String name, byte[] classData) { - this.name = name; - methods = new Vector(); - properties = new Vector(); - constructors = new Vector(); - floatConstants = new Vector(); - longConstants = new Vector(); - methodIndices = new Vector(); - stringIndices = new Vector>(); - utfConstants = new Vector(); - - try { - data = classData; - stream = new DataInputStream(new ByteArrayInputStream(data)); - isValidClass = stream.readInt() == 0xCAFEBABE; - if (isValidClass) { - minorVersion = stream.readUnsignedShort(); - majorVersion = stream.readUnsignedShort(); - cpSize = stream.readUnsignedShort(); cpSize--; - constants = new ClassConstant[cpSize]; - constantTypes = new int[cpSize]; - long offset = 10; - for (int q = 0; q < cpSize; q++) { - byte tag = stream.readByte(); - offset++; - constantTypes[q] = tag; - switch (tag) { - case 1: //String - int len = stream.readUnsignedShort(); - String strVal = ""; - for (int i = 0; i < len; i++) - strVal += (char)stream.readByte(); - constants[q] = new ClassConstant(tag, offset, strVal); - utfConstants.add(strVal); - offset += 2 + len; - break; - case 3: //Int - constants[q] = new ClassConstant(tag, offset, stream.readInt()); - offset += 4; - break; - case 4: //Float - float cFloat = stream.readFloat(); - constants[q] = new ClassConstant(tag, offset, cFloat); - floatConstants.add(cFloat); - offset += 4; - break; - case 5: //Long - long cLong = stream.readLong(); - constants[q] = new ClassConstant(tag, offset, cLong); - longConstants.add(cLong); - offset += 8; - q++; - break; - case 6: //Double - constants[q] = new ClassConstant(tag, offset, stream.readDouble()); - offset += 8; - q++; - break; - case 7: //Class reference - constants[q] = new ClassConstant(tag, offset, stream.readUnsignedShort()); - offset += 2; - break; - case 8: //String reference - ClassConstant strRef = new ClassConstant(tag, offset, stream.readUnsignedShort()); - constants[q] = strRef; - stringIndices.add(strRef); - offset += 2; - break; - case 9: //Field reference - constants[q] = new ClassConstant(tag, offset, new ReferenceIndex(stream.readUnsignedShort(), stream.readUnsignedShort())); - offset += 4; - break; - case 10: //Method reference - constants[q] = new ClassConstant(tag, offset, new ReferenceIndex(stream.readUnsignedShort(), stream.readUnsignedShort())); - - offset += 4; - break; - case 11: //Interface method reference - constants[q] = new ClassConstant(tag, offset, new ReferenceIndex(stream.readUnsignedShort(), stream.readUnsignedShort())); - offset += 4; - break; - case 12: //Name and type descriptor - constants[q] = new ClassConstant(tag, offset, new ReferenceIndex(stream.readUnsignedShort(), stream.readUnsignedShort())); - offset += 4; - break; - } - } - - //Access Flags - accessFlags = stream.readUnsignedShort(); - - - //This class - stream.skip(2); - - //Super class - stream.skip(2); - - //Interfaces - int iCount = stream.readUnsignedShort(); - stream.skip(iCount*2); - - //Fields - fields = new Field[stream.readUnsignedShort()]; - for (int i = 0; i < fields.length; i++) { - fields[i] = new Field(); - fields[i].accessFlags = stream.readUnsignedShort(); - stream.skip(4); - int attributeInfoCount = stream.readUnsignedShort(); - for (int q = 0; q < attributeInfoCount; q++) { - stream.skip(2); - int attributeCount = stream.readInt(); - for (int z = 0; z < attributeCount; z++) - stream.skip(1); - } - } - - //Methods - methodCount = stream.readUnsignedShort(); - for (int i = 0; i < methodCount; i++) { - stream.skip(2); - int nameIndex = stream.readUnsignedShort(); - methodIndices.add(new ReferenceIndex(nameIndex, stream.readUnsignedShort())); - - if (((String)constants[nameIndex - 1].get()).contains("")) - constructorCount++; - - int attributeInfoCount = stream.readUnsignedShort(); - for (int q = 0; q < attributeInfoCount; q++) { - stream.skip(2); - int attributeCount = stream.readInt(); - for (int z = 0; z < attributeCount; z++) - stream.skip(1); - } - } - - //Attributes - - stream.close(); - - - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(0); - } - } - public boolean searchForString(String str) { - for (ClassConstant i : stringIndices) { - if (((String)constants[i.get() - 1].get()).contains(str)) - return true; - } - return false; - } - - public boolean searchForUtf(String str) { - for (String text : utfConstants) { - if (text.equals(str)) - return true; - } - return false; - } - - public byte[] getData() { - - return data; - } - - @Override - public String toString() { - return "[ByteClass " + name + "]"; - } - public String searchByReturnType(String type) { - for (ReferenceIndex ref : methodIndices) { - String refType = (String)constants[ref.val2-1].get(); - //Log.i("L" + type + " = " + refType); - if (("L" + type + ";").equals(refType.substring(refType.indexOf(')') + 1))) - return (String)constants[ref.val1-1].get(); - } - return null; - } - public void addMethod(String method, String name) { - methods.add(new String[] {method, name}); - } - public String getClassName() { - return name; - } - public Vector getMethods() { - return methods; - } - public void addProperty(String property, String name) { - properties.add(new String[] { property, name}); - } - public Vector getProperties() { - return properties; - } - public void addConstructor(String constructor, String name) { - constructors.add(new String[] {constructor, name}); - } - public Vector getConstructors() { - return constructors; - } - public boolean isInterface() { - return (accessFlags & 0x0200) == 0x0200; - } - public boolean isFinal() { - return (accessFlags & 0x0010) == 0x0010; - } - public String getArguementsForConstructor(int ID) { - int i = 0; - for (ReferenceIndex ref : methodIndices) { - String name = (String)constants[ref.val1-1].get(); - if (name.equals("")) { - if (i==ID) { - String args = (String)constants[ref.val2-1].get(); - return toArguementString(args); - } - i++; - } - } - return ""; - } - public static String toArguementString(String eArgs) { - String[] args = readArguements(eArgs); - String out = "("; - for (int i = 0; i < args.length ;i++) { - out += args[i] + ((i == args.length - 1)?"":","); - } - out += ")"; - return out; - } - public static String[] readArguements(String eArgs) { - //Log.i(eArgs); - String args = eArgs.substring(1); - String[] argSplit = args.split("\\)"); - - args = argSplit[0]; - Stack argStack = new Stack(); - Pattern argRegex = Pattern.compile("([\\[]+)?([BCDFIJSZ]|L[^;]+)"); - Pattern objectRegex = Pattern.compile("^([\\[]+)?[LBCDFIJSZ]"); - Matcher matcher = argRegex.matcher(args); - while (matcher.find()) { - String arg =args.substring(matcher.start(), matcher.end()); - Matcher objectMatcher = objectRegex.matcher(arg); - if (objectMatcher.find()) { - String replaceWith = ""; - switch (arg.charAt(objectMatcher.end()-1)) { - case 'B': - replaceWith = "byte"; - break; - case 'C': - replaceWith = "char"; - break; - case 'D': - replaceWith = "double"; - break; - case 'F': - replaceWith = "float"; - break; - case 'I': - replaceWith = "int"; - break; - case 'J': - replaceWith = "long"; - break; - case 'S': - replaceWith = "short"; - break; - case 'Z': - replaceWith = "boolean"; - break; - } - arg = arg.substring(0, Math.max(0, objectMatcher.end()-1)) + - replaceWith + - arg.substring(Math.min(objectMatcher.end(), arg.length())); - - } - argStack.push(arg); - } - String[] argArray = new String[argStack.size()]; - for (int i = 0; i < argArray.length; i++) { - argArray[argArray.length - 1 - i] = argStack.pop(); - } - return argArray; - } - public boolean searchForFloat(float f) { - for (Float cFloat : floatConstants) { - if (cFloat.floatValue() == f) { - return true; - } - } - return false; - } - public boolean searchForLong(long l) { - for (Long cLong : longConstants) { - if (cLong.longValue() == l) { - return true; - } - } - return false; - } -} diff --git a/src/amidst/bytedata/CCByteMatch.java b/src/amidst/bytedata/CCByteMatch.java deleted file mode 100644 index f666ebcc8..000000000 --- a/src/amidst/bytedata/CCByteMatch.java +++ /dev/null @@ -1,31 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCByteMatch extends ClassChecker { - private byte[] checkData; - private int dataOffset; - public CCByteMatch(String name, byte[] data, int offset) { // FIXME : Dead class - super(name); - checkData = data; - dataOffset = offset; - } - public boolean check(ByteClass bClass) { - byte[] data = bClass.getData(); - - if (data.length < dataOffset + checkData.length) - return false; - - for (int i = 0; i < checkData.length; i++) { - if (checkData[i] != data[i + dataOffset]) - return false; - } - return true; - } - @Override - public void check(Minecraft m, ByteClass bClass) { - // TODO Auto-generated method stub - - } -} diff --git a/src/amidst/bytedata/CCByteSearch.java b/src/amidst/bytedata/CCByteSearch.java deleted file mode 100644 index 72ec5616f..000000000 --- a/src/amidst/bytedata/CCByteSearch.java +++ /dev/null @@ -1,30 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - -public class CCByteSearch extends ClassChecker { - private byte[] checkData; - public CCByteSearch(String publicName, byte[] data) { - super(publicName); - checkData = data; - } - @Override - public void check(Minecraft m, ByteClass bClass) { - byte[] data = bClass.getData(); - - for (int i = 0; i < data.length + 1 - checkData.length; i++) { - boolean searching = true; - int sIndex = 0; - while (searching) { - if (data[i + sIndex] != checkData[sIndex]) - searching = false; - sIndex++; - if (searching && (sIndex == checkData.length)) { - isComplete = true; - m.registerClass(publicName, bClass); - return; - } - } - } - } -} diff --git a/src/amidst/bytedata/CCConstructorPreset.java b/src/amidst/bytedata/CCConstructorPreset.java deleted file mode 100644 index 67a8c790e..000000000 --- a/src/amidst/bytedata/CCConstructorPreset.java +++ /dev/null @@ -1,34 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - -public class CCConstructorPreset extends ClassChecker { - private String[] constructor; - private boolean multiple; - private int ID; - private String name; - public CCConstructorPreset(String name, String... constructor) { - super(name); - multiple = true; - this.constructor = constructor; - } - public CCConstructorPreset(String name, int i, String construct) { - super(name); - multiple = false; - ID = i; - this.name = construct; - } - @Override - public void check(Minecraft mc, ByteClass bClass) { - ByteClass clazz = mc.getByteClass(publicName); - if (multiple) { - for (int i = 0; i < constructor.length; i += 2) { - clazz.addConstructor(constructor[i], constructor[i+1]); - } - } else { - String args = clazz.getArguementsForConstructor(ID); - clazz.addConstructor(args, name); - } - isComplete = true; - } -} diff --git a/src/amidst/bytedata/CCFloatMatch.java b/src/amidst/bytedata/CCFloatMatch.java deleted file mode 100644 index c6335a5fd..000000000 --- a/src/amidst/bytedata/CCFloatMatch.java +++ /dev/null @@ -1,22 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - -public class CCFloatMatch extends ClassChecker { - private float[] checkData; - public CCFloatMatch(String name, float... data) { - super(name); - checkData = data; - } - @Override - public void check(Minecraft m, ByteClass bClass) { - boolean isMatch = true; - for (int i = 0; i < checkData.length; i++) { - isMatch &= bClass.searchForFloat(checkData[i]); - } - if (isMatch) { - m.registerClass(publicName, bClass); - isComplete = true; - } - } -} diff --git a/src/amidst/bytedata/CCLongMatch.java b/src/amidst/bytedata/CCLongMatch.java deleted file mode 100644 index 6effa0329..000000000 --- a/src/amidst/bytedata/CCLongMatch.java +++ /dev/null @@ -1,23 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCLongMatch extends ClassChecker { - private long[] checkData; - public CCLongMatch(String name, long... data) { - super(name); - checkData = data; - } - @Override - public void check(Minecraft m, ByteClass bClass) { - boolean isMatch = true; - for (int i = 0; i < checkData.length; i++) { - isMatch &= bClass.searchForLong(checkData[i]); - } - if (isMatch) { - m.registerClass(publicName, bClass); - isComplete = true; - } - } -} diff --git a/src/amidst/bytedata/CCMethodByReturnType.java b/src/amidst/bytedata/CCMethodByReturnType.java deleted file mode 100644 index 9c4254af1..000000000 --- a/src/amidst/bytedata/CCMethodByReturnType.java +++ /dev/null @@ -1,20 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - -public class CCMethodByReturnType extends ClassChecker { - private String name, className, returnType, param; - public CCMethodByReturnType(String className, String returnType, String param, String name) { - this.name = name; - this.className = className; - this.param = param; - this.returnType = returnType; - } - @Override - public void check(Minecraft mc, ByteClass bClass) { - ByteClass clazz = mc.getByteClass(className); - String internalName = mc.getByteClass(returnType).getClassName(); - clazz.addMethod(clazz.searchByReturnType(internalName) + param, name); - isComplete = true; - } -} diff --git a/src/amidst/bytedata/CCMethodPreset.java b/src/amidst/bytedata/CCMethodPreset.java deleted file mode 100644 index cebc9bdde..000000000 --- a/src/amidst/bytedata/CCMethodPreset.java +++ /dev/null @@ -1,20 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCMethodPreset extends ClassChecker { - private String[] methods; - public CCMethodPreset(String name, String... methods) { - super(name); - this.methods = methods; - } - @Override - public void check(Minecraft mc, ByteClass bClass) { - ByteClass clazz = mc.getByteClass(publicName); - for (int i = 0; i < methods.length; i += 2) { - clazz.addMethod(methods[i], methods[i+1]); - } - isComplete = true; - } -} diff --git a/src/amidst/bytedata/CCMulti.java b/src/amidst/bytedata/CCMulti.java deleted file mode 100644 index 4af19151e..000000000 --- a/src/amidst/bytedata/CCMulti.java +++ /dev/null @@ -1,23 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCMulti extends ClassChecker { - private ClassChecker[] checks; - public CCMulti(ClassChecker... checks) { - super(checks[0].getName()); - this.checks = checks; - } - @Override - public void check(Minecraft mc, ByteClass bClass) { - boolean complete = true; - for (int i = 0; i < checks.length; i++) { - if (!checks[i].isComplete) - checks[i].check(mc, bClass); - complete &= checks[i].isComplete; - } - isComplete = complete; - } - -} diff --git a/src/amidst/bytedata/CCPropertyPreset.java b/src/amidst/bytedata/CCPropertyPreset.java deleted file mode 100644 index f1179763b..000000000 --- a/src/amidst/bytedata/CCPropertyPreset.java +++ /dev/null @@ -1,20 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCPropertyPreset extends ClassChecker { - private String[] properties; - public CCPropertyPreset(String name, String... properties) { - super(name); - this.properties = properties; - } - @Override - public void check(Minecraft mc, ByteClass bClass) { - ByteClass clazz = mc.getByteClass(publicName); - for (int i = 0; i < properties.length; i += 2) { - clazz.addProperty(properties[i], properties[i+1]); - } - isComplete = true; - } -} \ No newline at end of file diff --git a/src/amidst/bytedata/CCRequire.java b/src/amidst/bytedata/CCRequire.java deleted file mode 100644 index 695593572..000000000 --- a/src/amidst/bytedata/CCRequire.java +++ /dev/null @@ -1,26 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCRequire extends ClassChecker { - private ClassChecker checker; - private String[] names; - public CCRequire(ClassChecker cc, String... requiredNames) { - super(cc.getName()); - checker = cc; - names = requiredNames; - } - @Override - public void check(Minecraft mc, ByteClass bClass) { - for (int i = 0; i < names.length; i++) { - if (mc.getByteClass(names[i]) == null) return; - } - checker.check(mc, bClass); - isComplete = checker.isComplete; - } - @Override - public String toString() { - return "[Require " + names[0] + " " + checker + "]"; - } -} diff --git a/src/amidst/bytedata/CCRequireAccessFlags.java b/src/amidst/bytedata/CCRequireAccessFlags.java deleted file mode 100644 index 242b05622..000000000 --- a/src/amidst/bytedata/CCRequireAccessFlags.java +++ /dev/null @@ -1,16 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCRequireAccessFlags extends CCRequireSimple { - private int flags; - public CCRequireAccessFlags(ClassChecker checker, int flags) { - super(checker); - this.flags = flags; - } - @Override - public boolean canPass(Minecraft mc, ByteClass bClass) { - return bClass.accessFlags == flags; - } -} diff --git a/src/amidst/bytedata/CCRequireFinal.java b/src/amidst/bytedata/CCRequireFinal.java deleted file mode 100644 index 3641964ee..000000000 --- a/src/amidst/bytedata/CCRequireFinal.java +++ /dev/null @@ -1,15 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCRequireFinal extends CCRequireSimple { - public CCRequireFinal(ClassChecker checker) { - super(checker); - } - - @Override - public boolean canPass(Minecraft mc, ByteClass bClass) { - return bClass.isFinal(); - } -} diff --git a/src/amidst/bytedata/CCRequireInterface.java b/src/amidst/bytedata/CCRequireInterface.java deleted file mode 100644 index 109cfc4ad..000000000 --- a/src/amidst/bytedata/CCRequireInterface.java +++ /dev/null @@ -1,14 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - -public class CCRequireInterface extends CCRequireSimple { - public CCRequireInterface(ClassChecker checker) { - super(checker); - } - - @Override - public boolean canPass(Minecraft mc, ByteClass bClass) { - return bClass.isInterface(); - } -} diff --git a/src/amidst/bytedata/CCRequireSimple.java b/src/amidst/bytedata/CCRequireSimple.java deleted file mode 100644 index 50e1e997e..000000000 --- a/src/amidst/bytedata/CCRequireSimple.java +++ /dev/null @@ -1,21 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCRequireSimple extends ClassChecker { - private ClassChecker checker; - public CCRequireSimple(ClassChecker checker) { - this.checker = checker; - } - public boolean canPass(Minecraft mc, ByteClass bClass) { - return true; - } - @Override - public void check(Minecraft mc, ByteClass bClass) { - if (canPass(mc, bClass)) { - checker.check(mc, bClass); - } - isComplete = checker.isComplete; - } -} diff --git a/src/amidst/bytedata/CCStringMatch.java b/src/amidst/bytedata/CCStringMatch.java deleted file mode 100644 index 5801ffc86..000000000 --- a/src/amidst/bytedata/CCStringMatch.java +++ /dev/null @@ -1,20 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public class CCStringMatch extends ClassChecker { - private String checkData; - public CCStringMatch(String name, String data) { - super(name); - checkData = data; - } - @Override - public void check(Minecraft m, ByteClass bClass) { - if (bClass.searchForString(checkData)) { - m.registerClass(publicName, bClass); - isComplete = true; - } - } - -} diff --git a/src/amidst/bytedata/CCWildcardByteSearch.java b/src/amidst/bytedata/CCWildcardByteSearch.java deleted file mode 100644 index b478d14c3..000000000 --- a/src/amidst/bytedata/CCWildcardByteSearch.java +++ /dev/null @@ -1,30 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - -public class CCWildcardByteSearch extends ClassChecker { - private int[] checkData; - public CCWildcardByteSearch(String publicName, int[] data) { - super(publicName); - checkData = data; - } - @Override - public void check(Minecraft m, ByteClass bClass) { - byte[] data = bClass.getData(); - - for (int i = 0; i < data.length + 1 - checkData.length; i++) { - boolean searching = true; - int sIndex = 0; - while (searching) { - if ((checkData[sIndex] != -1) && (data[i + sIndex] != (byte)checkData[sIndex])) - searching = false; - sIndex++; - if (searching && (sIndex == checkData.length)) { - isComplete = true; - m.registerClass(publicName, bClass); - return; - } - } - } - } -} diff --git a/src/amidst/bytedata/ClassChecker.java b/src/amidst/bytedata/ClassChecker.java deleted file mode 100644 index 31baaf652..000000000 --- a/src/amidst/bytedata/ClassChecker.java +++ /dev/null @@ -1,25 +0,0 @@ -package amidst.bytedata; - -import amidst.minecraft.Minecraft; - - -public abstract class ClassChecker { - protected String publicName; - public boolean isComplete; - public int passes = 10; - public ClassChecker() { - this.publicName = "unknown"; - } - public ClassChecker(String publicName) { - this.publicName = publicName; - } - public abstract void check(Minecraft m, ByteClass bClass); - - public String getName() { - return publicName; - } - public ClassChecker passes(int p) { - passes = p; - return this; - } -} diff --git a/src/amidst/bytedata/ClassConstant.java b/src/amidst/bytedata/ClassConstant.java deleted file mode 100644 index dc1b0655e..000000000 --- a/src/amidst/bytedata/ClassConstant.java +++ /dev/null @@ -1,16 +0,0 @@ -package amidst.bytedata; - -public class ClassConstant { - private T value; - private byte type; - public ClassConstant(byte type, long offset, T value) { - this.value = value; - this.type = type; - } - public T get() { - return value; - } - public int getTag() { - return type; - } -} diff --git a/src/amidst/bytedata/ReferenceIndex.java b/src/amidst/bytedata/ReferenceIndex.java deleted file mode 100644 index ff0c5847a..000000000 --- a/src/amidst/bytedata/ReferenceIndex.java +++ /dev/null @@ -1,9 +0,0 @@ -package amidst.bytedata; - -public class ReferenceIndex { - public int val1, val2; - public ReferenceIndex(int val1, int val2) { - this.val1 = val1; - this.val2 = val2; - } -} diff --git a/src/amidst/gui/CrashDialog.java b/src/amidst/gui/CrashDialog.java deleted file mode 100644 index 9d4684e67..000000000 --- a/src/amidst/gui/CrashDialog.java +++ /dev/null @@ -1,50 +0,0 @@ -package amidst.gui; - -import java.awt.Color; -import java.awt.Container; -import java.awt.Font; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.ScrollPaneConstants; -import javax.swing.border.LineBorder; - -import amidst.logging.LogRecorder; -import net.miginfocom.swing.MigLayout; - -public class CrashDialog extends JFrame { - public CrashDialog(String message) { - super("AMIDST encountered an unexpected error."); - Container contentPane = getContentPane(); - contentPane.setLayout(new MigLayout()); - - add(new JLabel("AMIDST has crashed with the following message:"), "growx, pushx, wrap"); - add(new JLabel(message), "growx, pushx, wrap"); - - JTextArea logText = new JTextArea(LogRecorder.getContents()); - - JScrollPane scrollPane = new JScrollPane(logText); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - - logText.setFont(new Font("arial", Font.PLAIN, 10)); - scrollPane.setBorder(new LineBorder(Color.darkGray, 1)); - - add(scrollPane,"grow, push"); - - setSize(500, 400); - setVisible(true); - - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - dispose(); - System.exit(4); - } - }); - - } -} diff --git a/src/amidst/gui/License.java b/src/amidst/gui/License.java deleted file mode 100644 index a09ab47c0..000000000 --- a/src/amidst/gui/License.java +++ /dev/null @@ -1,72 +0,0 @@ -package amidst.gui; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import amidst.logging.Log; -import amidst.resources.ResourceLoader; - -public class License { - private InputStream fileStream; - private String name; - private String contents; - private boolean loaded = false; - - public License(String name, String path) { - this.name = name; - try { - fileStream = ResourceLoader.getResourceStream(path); - } catch (NullPointerException e) { - Log.w("Error finding license for: " + name + " at path: " + path); - e.printStackTrace(); - } - } - - public String getName() { - return name; - } - - public void load() { - if (loaded) - return; - BufferedReader fileReader = new BufferedReader(new InputStreamReader(fileStream)); - BufferedReader bufferedReader = new BufferedReader(fileReader); - try { - StringBuilder stringBuilder = new StringBuilder(); - String line = bufferedReader.readLine(); - - while (line != null) { - stringBuilder.append(line); - stringBuilder.append('\n'); - line = bufferedReader.readLine(); - } - contents = stringBuilder.toString(); - loaded = true; - } catch (IOException e) { - Log.w("Unable to read file: " + name + "."); - e.printStackTrace(); - } finally { - try { - bufferedReader.close(); - } catch (IOException e) { - Log.w("Unable to close BufferedReader for: " + name + "."); - e.printStackTrace(); - } - } - - } - - public String getContents() { - return contents; - } - - public boolean isLoaded() { - return loaded; - } - - @Override - public String toString() { - return name; - } -} diff --git a/src/amidst/gui/LicenseWindow.java b/src/amidst/gui/LicenseWindow.java deleted file mode 100644 index f382124d0..000000000 --- a/src/amidst/gui/LicenseWindow.java +++ /dev/null @@ -1,71 +0,0 @@ -package amidst.gui; - -import java.awt.Color; -import java.awt.Container; -import java.util.ArrayList; - -import javax.swing.JFrame; -import javax.swing.JList; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.ListSelectionModel; -import javax.swing.ScrollPaneConstants; -import javax.swing.border.LineBorder; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import amidst.Amidst; -import net.miginfocom.swing.MigLayout; - -public class LicenseWindow extends JFrame { - private static final long serialVersionUID = 3936119740592768287L; - private ArrayList licenses = new ArrayList(); - private JList licenseList; - private JTextArea licenseText = new JTextArea(); - public LicenseWindow() { - super("Licenses"); - setIconImage(Amidst.icon); - licenseText.setEditable(false); - licenseText.setLineWrap(true); - licenseText.setWrapStyleWord(true); - - licenses.add(new License("AMIDST", "licenses/amidst.txt")); - licenses.add(new License("Args4j", "licenses/args4j.txt")); - licenses.add(new License("Gson", "licenses/gson.txt")); - licenses.add(new License("JGoogleAnalytics", "licenses/jgoogleanalytics.txt")); - licenses.add(new License("JNBT", "licenses/jnbt.txt")); - licenses.add(new License("Kryonet", "licenses/kryonet.txt")); - licenses.add(new License("MiG Layout", "licenses/miglayout.txt")); - licenses.add(new License("Rhino", "licenses/rhino.txt")); - licenseList = new JList(licenses.toArray()); - licenseList.setBorder(new LineBorder(Color.darkGray, 1)); - Container contentPane = this.getContentPane(); - MigLayout layout = new MigLayout(); - contentPane.setLayout(layout); - contentPane.add(licenseList, "w 100!, h 0:2400:2400"); - JScrollPane scrollPane = new JScrollPane(licenseText); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - contentPane.add(scrollPane, "w 0:4800:4800, h 0:2400:2400"); - setSize(870, 550); - setVisible(true); - licenseList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - licenseList.addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - License license = (License)licenseList.getSelectedValue(); - license.load(); - - if (license.isLoaded()) { - licenseText.setText(license.getContents()); - licenseText.setCaretPosition(0); - } - } - }); - licenseList.setSelectedIndex(0); - } - - public void addLicense(License license) { - licenses.add(license); - licenseList.setListData(licenses.toArray()); - } -} diff --git a/src/amidst/gui/menu/AmidstMenu.java b/src/amidst/gui/menu/AmidstMenu.java deleted file mode 100644 index 48a6425e7..000000000 --- a/src/amidst/gui/menu/AmidstMenu.java +++ /dev/null @@ -1,644 +0,0 @@ -package amidst.gui.menu; - -import MoF.*; -import amidst.Options; -import amidst.Util; -import amidst.gui.LicenseWindow; -import amidst.logging.Log; -import amidst.map.MapObjectPlayer; -import amidst.map.layers.StrongholdLayer; -import amidst.minecraft.MinecraftUtil; -import amidst.preferences.BiomeColorProfile; -import amidst.preferences.SelectPrefModel.SelectButtonModel; -import amidst.resources.ResourceLoader; - -import javax.swing.*; -import javax.swing.event.AncestorEvent; -import javax.swing.event.AncestorListener; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import java.awt.*; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.ClipboardOwner; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** Structured menubar-creation to alleviate the huge mess that it would be elsewise - */ - -// TODO: This class is a mess-- it should be split into pieces. -public class AmidstMenu extends JMenuBar { - final JMenu fileMenu; - //final JMenu scriptMenu; - public final JMenu mapMenu; //TODO: protected - final JMenu optionsMenu; - final JMenu helpMenu; - - private final FinderWindow window; - - public AmidstMenu(FinderWindow window) { - this.window = window; - - fileMenu = add(new FileMenu()); - mapMenu = add(new MapMenu()); - optionsMenu = add(new OptionsMenu()); - helpMenu = add(new HelpMenu()); - } - - private class FileMenu extends JMenu { - private FileMenu() { - super("File"); - setMnemonic(KeyEvent.VK_F); - - add(new JMenu("New") {{ - setMnemonic(KeyEvent.VK_N); - add(new SeedMenuItem()); - add(new FileMenuItem()); - add(new RandomSeedMenuItem()); - //add(new JMenuItem("From Server")); - }}); - - add(new JMenuItem("Save player locations") {{ - setEnabled(MinecraftUtil.getVersion().saveEnabled()); - setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - if (window.curProject.saveLoaded) { - for (MapObjectPlayer player : window.curProject.save.getPlayers()) { - if (player.needSave) { - window.curProject.save.movePlayer(player.getName(), player.globalX, player.globalY); - player.needSave = false; - } - } - } - } - }); - }}); - - add(new JMenuItem("Exit") {{ - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - System.exit(0); - } - }); - }}); - } - private String showSeedPrompt(String title) { - final String blankText = "A random seed will be generated if left blank."; - final String leadingSpaceText = "Warning: There is a space at the start!"; - final String trailingSpaceText = "Warning: There is a space at the end!"; - - final JTextField inputText = new JTextField(); - - inputText.addAncestorListener( new AncestorListener() { - @Override - public void ancestorAdded(AncestorEvent arg0) { - inputText.requestFocus(); - } - @Override - public void ancestorMoved(AncestorEvent arg0) { - inputText.requestFocus(); - } - @Override - public void ancestorRemoved(AncestorEvent arg0) { - inputText.requestFocus(); - } - }); - - final JLabel inputInformation = new JLabel(blankText); - inputInformation.setForeground(Color.red); - inputInformation.setFont(new Font("arial", Font.BOLD, 10)); - inputText.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void changedUpdate(DocumentEvent e) { - update(); - } - @Override - public void insertUpdate(DocumentEvent e) { - update(); - } - @Override - public void removeUpdate(DocumentEvent e) { - update(); - } - public void update() { - String text = inputText.getText(); - if (text.equals("")) { - inputInformation.setText(blankText); - inputInformation.setForeground(Color.red); - } else if (text.startsWith(" ")) { - inputInformation.setText(leadingSpaceText); - inputInformation.setForeground(Color.red); - } else if (text.endsWith(" ")) { - inputInformation.setText(trailingSpaceText); - inputInformation.setForeground(Color.red); - } else { - try { - Long.parseLong(text); - inputInformation.setText("Seed is valid."); - inputInformation.setForeground(Color.gray); - } catch (NumberFormatException e) { - inputInformation.setText("This seed's value is " + text.hashCode() + "."); - inputInformation.setForeground(Color.black); - } - } - } - }); - - final JComponent[] inputs = new JComponent[] { - new JLabel("Enter your seed: "), - inputInformation, - inputText - }; - int result = JOptionPane.showConfirmDialog(window, inputs, title, JOptionPane.OK_CANCEL_OPTION); - return (result == 0)?inputText.getText():null; - } - - private class SeedMenuItem extends JMenuItem { - private SeedMenuItem() { - super("From seed"); - setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK)); - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - String seed = showSeedPrompt("New Project"); - if (seed != null) { - String worldTypePreference = Options.instance.worldType.get(); - SaveLoader.Type worldType = null; - if (worldTypePreference.equals("Prompt each time")) { - worldType = choose("New Project", "Enter world type\n", SaveLoader.selectableTypes); - } else { - worldType = SaveLoader.Type.fromMixedCase(worldTypePreference); - } - - if (seed.equals("")) - seed = "" + (new Random()).nextLong(); - if (worldType != null) { - window.clearProject(); - window.setProject(new Project(seed, worldType.getValue())); - } - } - } - }); - } - } - - private class RandomSeedMenuItem extends JMenuItem { - private RandomSeedMenuItem() { - super("From random seed"); - setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - //Create the JOptionPane. - Random random = new Random(); - long seed = random.nextLong(); - String worldTypePreference = Options.instance.worldType.get(); - SaveLoader.Type worldType = null; - if (worldTypePreference.equals("Prompt each time")) { - worldType = choose("New Project", "Enter world type\n", SaveLoader.selectableTypes); - } else { - worldType = SaveLoader.Type.fromMixedCase(worldTypePreference); - } - - //If a string was returned, say so. - if (worldType != null) { - window.clearProject(); - window.setProject(new Project(seed, worldType.getValue())); - } - } - - }); - } - } - - private class FileMenuItem extends JMenuItem { - private FileMenuItem() { - super("From file or folder"); - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - JFileChooser fc = new JFileChooser(); - fc.setFileFilter(SaveLoader.getFilter()); - fc.setAcceptAllFileFilterUsed(false); - fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - File savesDir = null; - if (Util.profileDirectory != null) - savesDir = new File(Util.profileDirectory, "saves"); - else - savesDir = new File(Util.minecraftDirectory, "saves"); - //if (!savesDir.mkdirs()) { - // Log.w("Unable to create save directory!"); - // return; - //} - fc.setCurrentDirectory(savesDir); - fc.setFileHidingEnabled(false); - if (fc.showOpenDialog(window) == JFileChooser.APPROVE_OPTION) { - File f = fc.getSelectedFile(); - - SaveLoader s = null; - if (f.isDirectory()) - s = new SaveLoader(new File(f.getAbsoluteFile() + "/level.dat")); - else - s = new SaveLoader(f); - window.clearProject(); - window.setProject(new Project(s)); - } - } - }); - } - } - } - - public class DisplayingCheckbox extends JCheckBoxMenuItem { - private DisplayingCheckbox(String text, BufferedImage icon, int key, JToggleButton.ToggleButtonModel model) { - super(text, (icon != null) ? new ImageIcon(icon) : null); - if (key != -1) - setAccelerator(KeyStroke.getKeyStroke(key, InputEvent.CTRL_DOWN_MASK)); - setModel(model); - } - } - private class MapMenu extends JMenu { - private MapMenu() { - super("Map"); - setEnabled(false); - setMnemonic(KeyEvent.VK_M); - add(new FindMenu()); - add(new GoToMenu()); - add(new LayersMenu()); - add(new CopySeedMenuItem()); - add(new CaptureMenuItem()); - - } - - private class FindMenu extends JMenu { - private FindMenu() { - super("Find"); - //add(new JMenuItem("Biome")); - //add(new JMenuItem("Village")); - add(new JMenuItem("Stronghold") {{ - setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK)); - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - goToChosenPoint(StrongholdLayer.instance.getStrongholds(), "Stronghold"); - } - }); - }}); - } - } - - private class GoToMenu extends JMenu { - private GoToMenu() { - super("Go to"); - add(new JMenuItem("Coordinate") {{ - setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, InputEvent.CTRL_DOWN_MASK));addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - String s = JOptionPane.showInputDialog(null, "Enter coordinates: (Ex. 123,456)", "Go To", JOptionPane.QUESTION_MESSAGE); - if (s != null) { - String[] c = s.replaceAll(" ", "").split(","); - try { - long x = Long.parseLong(c[0]); - long y = Long.parseLong(c[1]); - window.curProject.moveMapTo(x, y); - } catch (NumberFormatException e1) { - Log.w("Invalid location entered, ignoring."); - e1.printStackTrace(); - } - } - } - }); - }}); - - add(new JMenuItem("Player") {{ - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - if (window.curProject.saveLoaded) { - List playerList = window.curProject.save.getPlayers(); - MapObjectPlayer[] players = playerList.toArray(new MapObjectPlayer[playerList.size()]); - goToChosenPoint(players, "Player"); - MapObjectPlayer p = choose("Go to", "Select player:", players); - if (p != null) - window.curProject.moveMapTo(p.globalX, p.globalY); - } - } - }); - }}); - //add(new JMenuItem("Spawn")); - //add(new JMenuItem("Chunk")); - } - } - - private class LayersMenu extends JMenu { - private LayersMenu() { - super("Layers"); - - add(new DisplayingCheckbox("Grid", - ResourceLoader.getImage("grid.png"), - KeyEvent.VK_1, - Options.instance.showGrid)); - - add(new DisplayingCheckbox("Slime chunks", - ResourceLoader.getImage("slime.png"), - KeyEvent.VK_2, - Options.instance.showSlimeChunks)); - - add(new DisplayingCheckbox("Village Icons", - ResourceLoader.getImage("village.png"), - KeyEvent.VK_3, - Options.instance.showVillages)); - - add(new DisplayingCheckbox("Ocean Monument Icons", - ResourceLoader.getImage("ocean_monument.png"), - KeyEvent.VK_4, - Options.instance.showOceanMonuments)); - - add(new DisplayingCheckbox("Temple/Witch Hut Icons", - ResourceLoader.getImage("desert.png"), - KeyEvent.VK_5, - Options.instance.showTemples)); - - add(new DisplayingCheckbox("Stronghold Icons", - ResourceLoader.getImage("stronghold.png"), - KeyEvent.VK_6, - Options.instance.showStrongholds)); - - add(new DisplayingCheckbox("Player Icons", - ResourceLoader.getImage("player.png"), - KeyEvent.VK_7, - Options.instance.showPlayers)); - - add(new DisplayingCheckbox("Nether Fortress Icons", - ResourceLoader.getImage("nether_fortress.png"), - KeyEvent.VK_8, - Options.instance.showNetherFortresses)); - - add(new DisplayingCheckbox("Spawn Location Icon", - ResourceLoader.getImage("spawn.png"), - KeyEvent.VK_9, - Options.instance.showSpawn)); - - } - - - } - private class CaptureMenuItem extends JMenuItem { - private CaptureMenuItem() { - super("Capture"); - - setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.CTRL_DOWN_MASK)); - - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JFileChooser fc = new JFileChooser(); - fc.setFileFilter(new PNGFileFilter()); - fc.setAcceptAllFileFilterUsed(false); - int returnVal = fc.showSaveDialog(window); - - if (returnVal == JFileChooser.APPROVE_OPTION) { - String s = fc.getSelectedFile().toString(); - if (!s.toLowerCase().endsWith(".png")) - s += ".png"; - window.curProject.map.saveToFile(new File(s)); - } - } - }); - } - } - private class CopySeedMenuItem extends JMenuItem { - private CopySeedMenuItem() { - super("Copy Seed to Clipboard"); - - setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); - - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - StringSelection stringSelection = new StringSelection(Options.instance.seed + ""); - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - clipboard.setContents(stringSelection, new ClipboardOwner() { - @Override - public void lostOwnership(Clipboard arg0, Transferable arg1) { - // TODO Auto-generated method stub - - } - }); - } - }); - } - } - } - - private class OptionsMenu extends JMenu { - private OptionsMenu() { - super("Options"); - add(new MapOptionsMenu()); - if (BiomeColorProfile.isEnabled) - add(new BiomeColorMenu()); - add(new WorldTypeMenu()); - setMnemonic(KeyEvent.VK_M); - } - private class BiomeColorMenu extends JMenu { - private ArrayList profileCheckboxes = new ArrayList(); - private JMenuItem reloadMenuItem; - private class BiomeProfileActionListener implements ActionListener { - private BiomeColorProfile profile; - private ArrayList profileCheckboxes; - private JCheckBoxMenuItem checkBox; - public BiomeProfileActionListener(BiomeColorProfile profile, JCheckBoxMenuItem checkBox, ArrayList profileCheckboxes) { - this.profile = profile; - this.checkBox = checkBox; - this.profileCheckboxes = profileCheckboxes; - } - @Override - public void actionPerformed(ActionEvent e) { - for (int i = 0; i < profileCheckboxes.size(); i++) - profileCheckboxes.get(i).setSelected(false); - checkBox.setSelected(true); - profile.activate(); - } - } - private BiomeColorMenu() { - super("Biome profile"); - reloadMenuItem = new JMenuItem("Reload Menu"); - final BiomeColorMenu biomeColorMenu = this; - reloadMenuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg) { - profileCheckboxes.clear(); - Log.i("Reloading additional biome color profiles."); - File colorProfileFolder = new File("./biome"); - biomeColorMenu.removeAll(); - scanAndLoad(colorProfileFolder, biomeColorMenu); - biomeColorMenu.add(reloadMenuItem); - } - }); - reloadMenuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl B")); - Log.i("Checking for additional biome color profiles."); - File colorProfileFolder = new File("./biome"); - scanAndLoad(colorProfileFolder, this); - profileCheckboxes.get(0).setSelected(true); - add(reloadMenuItem); - } - - private boolean scanAndLoad(File folder, JMenu menu) { - File[] files = folder.listFiles(); - BiomeColorProfile profile; - boolean foundProfiles = false; - for (int i = 0; i < files.length; i++) { - if (files[i].isFile()) { - if ((profile = BiomeColorProfile.createFromFile(files[i])) != null) { - JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(profile.name); - menuItem.addActionListener(new BiomeProfileActionListener(profile, menuItem, profileCheckboxes)); - if (profile.shortcut != null) { - KeyStroke accelerator = KeyStroke.getKeyStroke(profile.shortcut); - if (accelerator != null) - menuItem.setAccelerator(accelerator); - else - Log.i("Unable to create keyboard shortcut from: " + profile.shortcut); - } - menu.add(menuItem); - profileCheckboxes.add(menuItem); - foundProfiles = true; - } - } else { - JMenu subMenu = new JMenu(files[i].getName()); - if (scanAndLoad(files[i], subMenu)) { - menu.add(subMenu); - } - } - } - return foundProfiles; - } - - } - private class MapOptionsMenu extends JMenu { - private MapOptionsMenu() { - super("Map"); - - add(new DisplayingCheckbox("Map Flicking (Smooth Scrolling)", - null, - KeyEvent.VK_I, - Options.instance.mapFlicking)); - - add(new DisplayingCheckbox("Restrict Maximum Zoom", - null, - KeyEvent.VK_Z, - Options.instance.maxZoom)); - - add(new DisplayingCheckbox("Show Framerate", - null, - KeyEvent.VK_L, - Options.instance.showFPS)); - - add(new DisplayingCheckbox("Show Scale", - null, - KeyEvent.VK_K, - Options.instance.showScale)); - - add(new DisplayingCheckbox("Use Fragment Fading", - null, - -1, - Options.instance.mapFading)); - - add(new DisplayingCheckbox("Show Debug Info", - null, - -1, - Options.instance.showDebug)); - } - - } - private class WorldTypeMenu extends JMenu { - private WorldTypeMenu() { - super("World type"); - - SelectButtonModel[] buttonModels = Options.instance.worldType.getButtonModels(); - - for (int i = 0; i < buttonModels.length; i++) { - add(new DisplayingCheckbox(buttonModels[i].getName(), - null, - -1, - buttonModels[i])); - } - } - - } - } - - private class HelpMenu extends JMenu { - private HelpMenu() { - super("Help"); - - add(new JMenuItem("Check for updates") {{ - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - new UpdateManager(window).start(); - } - }); - }}); - - add(new JMenuItem("View licenses") {{ - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - new LicenseWindow(); - } - }); - }}); - - add(new JMenuItem("About") {{ - addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JOptionPane.showMessageDialog(window, - "Advanced Minecraft Interfacing and Data/Structure Tracking (AMIDST)\n" + - "By Skidoodle (amidst.project@gmail.com)"); - } - }); - }}); - - } - } - - /** Allows the user to choose one of several things. - * - * Convenience wrapper around JOptionPane.showInputDialog - */ - private T choose(String title, String message, T[] choices) { - return (T) JOptionPane.showInputDialog( - window, - message, - title, - JOptionPane.PLAIN_MESSAGE, - null, - choices, - choices[0]); - } - - /** Lets the user decide one of the given points and go to it - * @param points Given points to choose from - * @param name name displayed in the choice - */ - private void goToChosenPoint(T[] points, String name) { - - T p = choose("Go to", "Select " + name + ":", points); - if (p != null) - window.curProject.moveMapTo(p.x, p.y); - } -} diff --git a/src/amidst/gui/menu/PlayerMenuItem.java b/src/amidst/gui/menu/PlayerMenuItem.java deleted file mode 100644 index bc6e7136b..000000000 --- a/src/amidst/gui/menu/PlayerMenuItem.java +++ /dev/null @@ -1,45 +0,0 @@ -package amidst.gui.menu; - -import java.awt.Point; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.JMenuItem; - -import MoF.MapViewer; -import amidst.map.Fragment; -import amidst.map.Map; -import amidst.map.MapObjectPlayer; -import amidst.map.layers.PlayerLayer; - -public class PlayerMenuItem extends JMenuItem implements ActionListener { - private PlayerLayer playerLayer; - private MapObjectPlayer player; - private MapViewer mapViewer; - - public PlayerMenuItem(MapViewer mapViewer, MapObjectPlayer player, PlayerLayer playerLayer) { - super(player.getName()); - this.playerLayer = playerLayer; - this.player = player; - this.mapViewer = mapViewer; - addActionListener(this); - } - - @Override - public void actionPerformed(ActionEvent event) { - Map map = playerLayer.getMap(); - - if (player.parentFragment != null) { - player.parentFragment.removeObject(player); - } - Point lastRightClick = mapViewer.lastRightClick; - if (lastRightClick != null) { - Point location = map.screenToLocal(lastRightClick); - player.setPosition(location.x, location.y); - Fragment fragment = map.getFragmentAt(location); - fragment.addObject(player); - player.parentFragment = fragment; - } - } - -} diff --git a/src/amidst/gui/version/LocalVersionComponent.java b/src/amidst/gui/version/LocalVersionComponent.java deleted file mode 100644 index 05e093eb9..000000000 --- a/src/amidst/gui/version/LocalVersionComponent.java +++ /dev/null @@ -1,133 +0,0 @@ -package amidst.gui.version; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.net.MalformedURLException; - -import MoF.FinderWindow; -import amidst.Options; -import amidst.Util; -import amidst.logging.Log; -import amidst.minecraft.Minecraft; -import amidst.minecraft.MinecraftUtil; -import amidst.version.IProfileUpdateListener; -import amidst.version.MinecraftProfile; -import amidst.version.ProfileUpdateEvent; -import amidst.version.MinecraftProfile.Status; - -public class LocalVersionComponent extends VersionComponent { - protected MinecraftProfile profile; - protected int oldWidth = 0; - protected String drawName; - private String name; - - - public LocalVersionComponent(MinecraftProfile profile) { - this.profile = profile; - drawName = profile.getProfileName(); - name = "local:" + profile.getProfileName(); - - profile.addUpdateListener(new IProfileUpdateListener() { - @Override - public void onProfileUpdate(ProfileUpdateEvent event) { - repaint(); - } - }); - } - - @Override - public void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D)g; - FontMetrics fontMetrics = null; - - if (isLoading) - g2d.setColor(loadingBgColor); - else if (isSelected()) - g2d.setColor(selectedBgColor); - else - g2d.setColor(Color.white); - g2d.fillRect(0, 0, getWidth(), getHeight()); - - g2d.setColor(Color.black); - g2d.setFont(versionFont); - fontMetrics = g2d.getFontMetrics(); - int versionNameX = getWidth() - 40 - fontMetrics.stringWidth(profile.getVersionName()); - g2d.drawString(profile.getVersionName(), versionNameX, 20); - - g2d.setColor(Color.black); - g2d.setFont(nameFont); - if (oldWidth != getWidth()) { - fontMetrics = g2d.getFontMetrics(); - String name = profile.getProfileName(); - if (fontMetrics.stringWidth(name) > versionNameX - 25) { - int widthSum = 0; - for (int i = 0; i < name.length(); i++) { - widthSum += fontMetrics.charWidth(name.charAt(i)); - if (widthSum > versionNameX - 25) { - name = name.substring(0, i) + "..."; - break; - } - } - } - drawName = name; - oldWidth = getWidth(); - } - g2d.drawString(drawName, 5, 30); - - g2d.setColor(Color.gray); - g2d.setFont(statusFont); - fontMetrics = g2d.getFontMetrics(); - String statusString = profile.getStatus().toString(); - g2d.drawString(statusString, getWidth() - 40 - fontMetrics.stringWidth(statusString), 32); - - BufferedImage image = inactiveIcon; - if (isLoading) - image = loadingIcon; - else if (profile.getStatus() == Status.FOUND) - image = activeIcon; - g2d.drawImage(image, getWidth() - image.getWidth() - 5, 4, null); - } - - - public String getProfileName() { - return profile.getProfileName(); - } - - public MinecraftProfile getProfile() { - return profile; - } - - @Override - public boolean isReadyToLoad() { - return profile.getStatus() == Status.FOUND; - } - - @Override - public void load() { - isLoading = true; - repaint(); - Options.instance.lastProfile.set(name); - (new Thread(new Runnable() { - @Override - public void run() { - try { - Util.setProfileDirectory(profile.getGameDir()); - MinecraftUtil.setBiomeInterface(new Minecraft(profile.getJarFile()).createInterface()); - new FinderWindow(); - VersionSelectWindow.get().dispose(); - } catch (MalformedURLException e) { - Log.crash(e, "MalformedURLException on Minecraft load."); - } - } - })).start(); - } - - @Override - public String getVersionName() { - return name; - } -} diff --git a/src/amidst/gui/version/RemoteVersionComponent.java b/src/amidst/gui/version/RemoteVersionComponent.java deleted file mode 100644 index 0501968d6..000000000 --- a/src/amidst/gui/version/RemoteVersionComponent.java +++ /dev/null @@ -1,43 +0,0 @@ -package amidst.gui.version; - -import MoF.FinderWindow; -import amidst.Options; -import amidst.minecraft.MinecraftUtil; -import amidst.minecraft.remote.RemoteMinecraft; - -public class RemoteVersionComponent extends VersionComponent { - private String remoteAddress; - private String name; - - public RemoteVersionComponent(String address) { - remoteAddress = address; - name = "remote:" + address; - } - public RemoteVersionComponent() { - this("127.0.0.1"); - } - - @Override - public void load() { - isLoading = true; - repaint(); - Options.instance.lastProfile.set(name); - (new Thread(new Runnable() { - @Override - public void run() { - MinecraftUtil.setBiomeInterface(new RemoteMinecraft(remoteAddress)); - new FinderWindow(); - VersionSelectWindow.get().dispose(); - } - })).start(); - } - - @Override - public boolean isReadyToLoad() { - return true; - } - @Override - public String getVersionName() { - return name; - } -} diff --git a/src/amidst/gui/version/VersionComponent.java b/src/amidst/gui/version/VersionComponent.java deleted file mode 100644 index 3efc35db3..000000000 --- a/src/amidst/gui/version/VersionComponent.java +++ /dev/null @@ -1,40 +0,0 @@ -package amidst.gui.version; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.image.BufferedImage; - -import javax.swing.JComponent; - -import amidst.resources.ResourceLoader; - -public abstract class VersionComponent extends JComponent { - protected static Font nameFont = new Font("arial", Font.BOLD, 30); - protected static Font statusFont = new Font("arial", Font.BOLD, 10); - protected static Font versionFont = new Font("arial", Font.BOLD, 16); - protected static BufferedImage activeIcon = ResourceLoader.getImage("active_profile.png"); - protected static BufferedImage inactiveIcon = ResourceLoader.getImage("inactive_profile.png"); - protected static BufferedImage loadingIcon = ResourceLoader.getImage("loading_profile.png"); - protected static Color selectedBgColor = new Color(160, 190, 255); - protected static Color loadingBgColor = new Color(112, 203, 91); - - protected boolean selected = false; - protected boolean isLoading = false; - - public VersionComponent() { - this.setMinimumSize(new Dimension(300, 40)); - this.setPreferredSize(new Dimension(500, 40)); - } - - public boolean isSelected() { - return selected; - } - public void setSelected(boolean value) { - selected = value; - } - - public abstract void load(); - public abstract boolean isReadyToLoad(); - public abstract String getVersionName(); -} diff --git a/src/amidst/gui/version/VersionSelectPanel.java b/src/amidst/gui/version/VersionSelectPanel.java deleted file mode 100644 index 490814c32..000000000 --- a/src/amidst/gui/version/VersionSelectPanel.java +++ /dev/null @@ -1,169 +0,0 @@ -package amidst.gui.version; - -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.util.ArrayList; -import javax.swing.JPanel; - -import net.miginfocom.swing.MigLayout; - -public class VersionSelectPanel extends JPanel implements MouseListener, KeyListener { - private String emptyMessage; - private int emptyMessageWidth; - private FontMetrics emptyMessageMetric; - private Font emptyMessageFont = new Font("arial", Font.BOLD, 30); - private boolean isLoading = false; - - private ArrayList components = new ArrayList(); - private VersionComponent selected = null; - private int selectedIndex = -1; - - public VersionSelectPanel() { - setLayout(new MigLayout("ins 0", "", "[]0[]")); - setEmptyMessage("Empty"); - addMouseListener(this); - - - } - - public void addVersion(VersionComponent version) { - add(version, "growx, pushx, wrap"); - components.add(version); - } - - @Override - public void paintChildren(Graphics g) { - super.paintChildren(g); - Graphics2D g2d = (Graphics2D)g; - g2d.setColor(Color.gray); - for (int i = 1; i <= components.size(); i++) { - g2d.drawLine(0, i * 40, getWidth(), i * 40); - } - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - if (emptyMessageMetric == null) { - emptyMessageMetric = g.getFontMetrics(emptyMessageFont); - emptyMessageWidth = emptyMessageMetric.stringWidth(emptyMessage); - } - g.setColor(Color.white); - g.fillRect(0, 0, getWidth(), getHeight()); - - if (components.size() == 0) { - g.setColor(Color.gray); - g.setFont(emptyMessageFont); - g.drawString(emptyMessage, (getWidth() >> 1) - (emptyMessageWidth >> 1), 30); - } - - } - - public void setEmptyMessage(String message) { - emptyMessage = message; - if (emptyMessageMetric != null) - emptyMessageWidth = emptyMessageMetric.stringWidth(emptyMessage); - } - - public void select(String name) { - for (int i = 0; i < components.size(); i++) { - if (components.get(i).getVersionName().equals(name)) { - select(i); - break; - } - } - } - - public void select(VersionComponent component) { - for (int i = 0; i < components.size(); i++) { - if (components.get(i) == component) { - select(i); - break; - } - } - } - - public void select(int index) { - if (selected != null) { - selected.setSelected(false); - selected.repaint(); - } - - selected = null; - - if (index < components.size()) { - selected = components.get(index); - selected.setSelected(true); - selected.repaint(); - selectedIndex = index; - } - } - - private void loadSelectedProfile() { - if ((selected == null) || !selected.isReadyToLoad()) - return; - isLoading = true; - selected.load(); - } - - @Override - public void mouseClicked(MouseEvent arg0) { - } - @Override - public void mouseEntered(MouseEvent arg0) { - } - @Override - public void mouseExited(MouseEvent arg0) { - } - @Override - public void mousePressed(MouseEvent event) { - if (isLoading) - return; - - int index = event.getPoint().y / 40; - select(index); - - if (event.getPoint().x > getWidth() - 40) - loadSelectedProfile(); - } - @Override - public void mouseReleased(MouseEvent arg0) { - } - - @Override - public void keyPressed(KeyEvent event) { - if (isLoading) - return; - int key = event.getKeyCode(); - switch (key) { - case KeyEvent.VK_DOWN: - if (selectedIndex < components.size() - 1) - select(selectedIndex + 1); - break; - case KeyEvent.VK_UP: - if (selectedIndex > 0) - select(selectedIndex - 1); - else if (selectedIndex == -1) - select(0); - break; - case KeyEvent.VK_ENTER: - loadSelectedProfile(); - break; - } - } - @Override - public void keyReleased(KeyEvent arg0) { - } - @Override - public void keyTyped(KeyEvent event) { - } -} diff --git a/src/amidst/gui/version/VersionSelectWindow.java b/src/amidst/gui/version/VersionSelectWindow.java deleted file mode 100644 index c92b40867..000000000 --- a/src/amidst/gui/version/VersionSelectWindow.java +++ /dev/null @@ -1,95 +0,0 @@ -package amidst.gui.version; - -import java.awt.Container; -import java.awt.Font; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JScrollPane; -import javax.swing.SwingConstants; - -import net.miginfocom.swing.MigLayout; -import amidst.Amidst; -import amidst.Options; -import amidst.Util; -import amidst.logging.Log; -import amidst.version.LatestVersionList; -import amidst.version.MinecraftProfile; -import amidst.version.VersionFactory; - -public class VersionSelectWindow extends JFrame { - private static VersionSelectWindow instance; - private VersionFactory versionFactory = new VersionFactory(); - - public VersionSelectWindow() { - super("Profile Selector"); - instance = this; - setIconImage(Amidst.icon); - Container contentPane = getContentPane(); - contentPane.setLayout(new MigLayout()); - - LatestVersionList.get().load(true); - - if (!Util.minecraftDirectory.exists() || !Util.minecraftDirectory.isDirectory()) { - Log.crash("Unable to find Minecraft directory at: " + Util.minecraftDirectory); - return; - } - - final JLabel titleLabel = new JLabel("Please select a Minecraft version:", SwingConstants.CENTER); - titleLabel.setFont(new Font("arial", Font.BOLD, 16)); - - add(titleLabel, "h 20!,w :400:, growx, pushx, wrap"); - - final VersionSelectPanel versionSelector = new VersionSelectPanel(); - - (new Thread(new Runnable() { - @Override - public void run() { - versionFactory.scanForProfiles(); - MinecraftProfile[] localVersions = versionFactory.getProfiles(); - String selectedProfile = Options.instance.lastProfile.get(); - - if (localVersions == null) { - versionSelector.setEmptyMessage("Empty"); - return; - } - for (int i = 0; i < localVersions.length; i++) - versionSelector.addVersion(new LocalVersionComponent(localVersions[i])); - versionSelector.addVersion(new RemoteVersionComponent()); - - if (selectedProfile != null) - versionSelector.select(selectedProfile); - - pack(); - try { - Thread.sleep(100); - } catch (InterruptedException ignored) { } - pack(); - } - })).start(); - - versionSelector.setEmptyMessage("Scanning..."); - - JScrollPane scrollPane = new JScrollPane(versionSelector); - add(scrollPane, "grow, push, h 80::"); - pack(); - setLocation(200, 200); - setVisible(true); - - addKeyListener(versionSelector); - - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - dispose(); - System.exit(0); - } - }); - } - - public static VersionSelectWindow get() { - return instance; - } -} diff --git a/src/amidst/json/InstallInformation.java b/src/amidst/json/InstallInformation.java deleted file mode 100644 index e0fda989f..000000000 --- a/src/amidst/json/InstallInformation.java +++ /dev/null @@ -1,12 +0,0 @@ -package amidst.json; - -public class InstallInformation { - public String name; - public String lastVersionId = "latest"; - public String gameDir; - public String javaDir; - public String javaArgs; - public String playerUUID; - public Resolution resolution; - public String[] allowedReleaseTypes = new String[] { "release" }; -} \ No newline at end of file diff --git a/src/amidst/json/JarLibrary.java b/src/amidst/json/JarLibrary.java deleted file mode 100644 index 449f9e982..000000000 --- a/src/amidst/json/JarLibrary.java +++ /dev/null @@ -1,59 +0,0 @@ -package amidst.json; - -import java.io.File; -import java.util.ArrayList; - -import amidst.Util; -import amidst.logging.Log; - -public class JarLibrary { - public String name; - public ArrayList rules; - - private File file; - - public JarLibrary() { - rules = new ArrayList(); - } - - public boolean isActive() { - if (rules.size() == 0) - return true; - - boolean isAllowed = false; - for (JarRule rule : rules) - if (rule.isApplicable()) - isAllowed = rule.isAllowed(); - - return isAllowed; - } - - public File getFile() { - if (file == null) { - String searchPath = Util.minecraftLibraries.getAbsolutePath() + "/"; - String[] pathSplit = name.split(":"); - pathSplit[0] = pathSplit[0].replace('.', '/'); - for (int i = 0; i < pathSplit.length; i++) - searchPath += pathSplit[i] + "/"; - File searchPathFile = new File(searchPath); - if (!searchPathFile.exists()) { - Log.w("Failed attempt to load library at: " + searchPathFile); - return null; - } - - File[] libraryFiles = searchPathFile.listFiles(); - for (int i = 0; i < libraryFiles.length; i++) { - String extension = ""; - String fileName = libraryFiles[i].getName(); - int q = fileName.lastIndexOf('.'); - if (q > 0) - extension = fileName.substring(q+1); - if (extension.equals("jar")) - file = libraryFiles[i]; - } - if (file == null) - Log.w("Attempted to search for file at path: " + searchPath + " but found nothing. Skipping."); - } - return file; - } -} diff --git a/src/amidst/json/JarProfile.java b/src/amidst/json/JarProfile.java deleted file mode 100644 index d3d21bbd9..000000000 --- a/src/amidst/json/JarProfile.java +++ /dev/null @@ -1,11 +0,0 @@ -package amidst.json; - -import java.util.ArrayList; - -public class JarProfile { - public String id, time, releaseTime, type, minecraftArguments; - public ArrayList libraries = new ArrayList(); - - public JarProfile() { - } -} diff --git a/src/amidst/json/JarRule.java b/src/amidst/json/JarRule.java deleted file mode 100644 index 0a398b403..000000000 --- a/src/amidst/json/JarRule.java +++ /dev/null @@ -1,15 +0,0 @@ -package amidst.json; - -public class JarRule { - public String action; - public RuleOs os = new RuleOs("any"); - - public JarRule() { - } - public boolean isApplicable() { - return os.check(); - } - public boolean isAllowed() { - return action.equals("allow"); - } -} diff --git a/src/amidst/json/LauncherProfile.java b/src/amidst/json/LauncherProfile.java deleted file mode 100644 index 99c6d3634..000000000 --- a/src/amidst/json/LauncherProfile.java +++ /dev/null @@ -1,9 +0,0 @@ -package amidst.json; - -import java.util.HashMap; - -public class LauncherProfile { - public HashMap profiles; - public String selectedProfile; - public String clientToken; -} diff --git a/src/amidst/json/Resolution.java b/src/amidst/json/Resolution.java deleted file mode 100644 index 05d9e656f..000000000 --- a/src/amidst/json/Resolution.java +++ /dev/null @@ -1,5 +0,0 @@ -package amidst.json; - -public class Resolution { - public String width, height; -} diff --git a/src/amidst/json/RuleOs.java b/src/amidst/json/RuleOs.java deleted file mode 100644 index fa7f36eca..000000000 --- a/src/amidst/json/RuleOs.java +++ /dev/null @@ -1,20 +0,0 @@ -package amidst.json; - -import amidst.Util; - -public class RuleOs { - public String name; - public RuleOs() { - - } - public RuleOs(String name) { - this.name = name; - } - public boolean check() { - if (name.equals("any")) - return true; - if (name.equals(Util.getOs())) - return true; - return false; - } -} diff --git a/src/amidst/logging/FileLogger.java b/src/amidst/logging/FileLogger.java deleted file mode 100644 index efeca1f79..000000000 --- a/src/amidst/logging/FileLogger.java +++ /dev/null @@ -1,107 +0,0 @@ -package amidst.logging; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.sql.Timestamp; -import java.util.Date; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class FileLogger extends Thread implements LogListener { - private File file; - private boolean enabled = true; - private ConcurrentLinkedQueue logQueue = new ConcurrentLinkedQueue(); - - public FileLogger(File file) { - this.file = file; - if (!file.exists()) { - try { - enabled = file.createNewFile(); - if (!enabled) - Log.w("Unable to create new file at: " + file + " disabling logging to file. (No exception thrown)"); - } catch (IOException e) { - Log.w("Unable to create new file at: " + file + " disabling logging to file."); - e.printStackTrace(); - enabled = false; - } - } else if (file.isDirectory()) { - Log.w("Unable to log at path: " + file + " because location is a directory."); - enabled = false; - } - write("log", "New FileLogger started."); - start(); - } - @Override - public void debug(Object... o) { - write("debug", o); - } - - @Override - public void info(Object... o) { - write("info", o); - } - - @Override - public void warning(Object... o) { - write("warning", o); - } - - @Override - public void error(Object... o) { - write("error", o); - } - - - @Override - public void crash(Throwable e, String exceptionText, String message) { - write("crash", message); - if (exceptionText.length() > 0) - write("crash", exceptionText); - } - - - private void write(String tag, Object... msgs) { - StringBuilder stringBuilder = new StringBuilder(new Timestamp(new Date().getTime()).toString()).append(" [").append(tag).append("] "); - for (int i = 0; i < msgs.length; i++) { - stringBuilder.append(msgs[i]); - stringBuilder.append((i < msgs.length - 1) ? " " : "\r\n"); - } - logQueue.add(stringBuilder.toString()); - } - - @Override - public void run() { - while (enabled) { - if (logQueue.size() != 0) { - StringBuilder stringBuilder = new StringBuilder(); - while (logQueue.size() != 0) - stringBuilder.append(logQueue.poll()); - - if (file.exists() && file.isFile()) { - FileWriter writer = null; - try { - writer = new FileWriter(file, true); - writer.append(stringBuilder.toString()); - } catch (IOException e) { - Log.w("Unable to write to log file."); - e.printStackTrace(); - } finally { - try { - if (writer != null) - writer.close(); - } catch (IOException e) { - Log.w("Unable to close writer for log file."); - e.printStackTrace(); - } - } - } - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - } - } -} diff --git a/src/amidst/logging/Log.java b/src/amidst/logging/Log.java deleted file mode 100644 index 4ec79ee91..000000000 --- a/src/amidst/logging/Log.java +++ /dev/null @@ -1,111 +0,0 @@ -package amidst.logging; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.HashMap; -import javax.swing.JOptionPane; - -import MoF.FinderWindow; -import amidst.gui.CrashDialog; - -public class Log { - private static Object logLock = new Object(); - private static HashMap listeners = new HashMap(); - public static boolean isUsingAlerts = true; - public static boolean isShowingDebug = true; - - static { - addListener("master", new LogRecorder()); - } - - public static void addListener(String name, LogListener listener) { - synchronized (logLock) { - listeners.put(name, listener); - } - } - public static void removeListener(String name) { - synchronized (logLock) { - listeners.remove(name); - } - } - public static void printTraceStack(Throwable e) { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - e.printStackTrace(printWriter); - String exceptionText = stringWriter.toString(); - w(exceptionText); - } - - public static void i(Object... s) { - synchronized (logLock) { - printWithTag("info", s); - if (listeners.size() != 0) - for (LogListener listener : listeners.values()) - listener.info(s); - } - } - public static void debug(Object... s) { - if (!isShowingDebug) - return; - synchronized (logLock) { - printWithTag("debug", s); - if (listeners.size() != 0) - for (LogListener listener : listeners.values()) - listener.debug(s); - } - } - public static void w(Object... s) { - synchronized (logLock) { - printWithTag("warning", s); - if (listeners.size() != 0) - for (LogListener listener : listeners.values()) - listener.warning(s); - } - } - - public static void e(Object... s) { - synchronized (logLock) { - printWithTag("error", s); - if (isUsingAlerts) - JOptionPane.showMessageDialog(null, s, "Error", JOptionPane.ERROR_MESSAGE); - if (listeners.size() != 0) - for (LogListener listener : listeners.values()) - listener.error(s); - } - } - - public static void crash(String message) { - crash(null, message); - } - public static void crash(Throwable e, String message) { - synchronized (logLock) { - printWithTag("crash", message); - String exceptionText = ""; - if (e != null) { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - e.printStackTrace(printWriter); - exceptionText = stringWriter.toString(); - printWithTag("crash", exceptionText); - } - - if (listeners.size() != 0) - for (LogListener listener : listeners.values()) - listener.crash(e, exceptionText, message); - - - new CrashDialog(message); - if (FinderWindow.instance != null) - FinderWindow.instance.dispose(); - //System.exit(0); - } - } - - private static void printWithTag(String tag, Object... msgs) { - System.out.print("[" + tag + "] "); - for (int i = 0; i < msgs.length; i++) { - System.out.print(msgs[i]); - System.out.print((i < msgs.length - 1) ? " " : "\n"); - } - } -} diff --git a/src/amidst/logging/LogListener.java b/src/amidst/logging/LogListener.java deleted file mode 100644 index ec74ec84f..000000000 --- a/src/amidst/logging/LogListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package amidst.logging; - -public interface LogListener { - public void debug(Object... o); - public void info(Object... o); - public void warning(Object... o); - public void error(Object... o); - public void crash(Throwable e, String exceptionText, String message); -} diff --git a/src/amidst/logging/LogRecorder.java b/src/amidst/logging/LogRecorder.java deleted file mode 100644 index 6b46edf4f..000000000 --- a/src/amidst/logging/LogRecorder.java +++ /dev/null @@ -1,43 +0,0 @@ -package amidst.logging; - -public class LogRecorder implements LogListener { - private static StringBuffer buffer = new StringBuffer(); - - @Override - public void debug(Object... o) { - write("debug", o); - } - - @Override - public void info(Object... o) { - write("info", o); - } - - @Override - public void warning(Object... o) { - write("warning", o); - } - - @Override - public void error(Object... o) { - write("error", o); - } - - - @Override - public void crash(Throwable e, String exceptionText, String message) { - write("crash", message); - if (exceptionText.length() > 0) - write("crash", exceptionText); - } - - private static void write(String tag, Object... msgs) { - buffer.append("[" + tag + "] "); - for (int i = 0; i < msgs.length; i++) - buffer.append(msgs[i] + ((i < msgs.length - 1) ? " " : "\n")); - } - - public static String getContents() { - return buffer.toString(); - } -} diff --git a/src/amidst/map/ByteArrayCache.java b/src/amidst/map/ByteArrayCache.java deleted file mode 100644 index 094424f7b..000000000 --- a/src/amidst/map/ByteArrayCache.java +++ /dev/null @@ -1,94 +0,0 @@ -package amidst.map; - -import java.util.Vector; - -import amidst.logging.Log; - -public class ByteArrayCache extends CacheManager { - public static final int CACHE_SIZE = 32, CACHE_SHIFT = 5; - public static final int HEADER_SIZE = (CACHE_SIZE*CACHE_SIZE) >> 3; - public static final int CACHE_MAX_SIZE = CACHE_SIZE*CACHE_SIZE; // TODO : Change name to CACHE_LENGTH ? - - private int maxUnits; - private byte unitSize; - - private Vector cacheMap; - - private byte[] byteCache; - - public ByteArrayCache(byte unitSize, int maxUnits) { - cacheMap = new Vector(); - this.unitSize = unitSize; - this.maxUnits = maxUnits; - byteCache = new byte[unitSize*maxUnits]; - } - - @Override - public void save(Fragment frag) { - - } - - @Override - public void load(Fragment frag, int layerID) { - long keyX = frag.getFragmentX() >> CACHE_SHIFT; - long keyY = frag.getFragmentY() >> CACHE_SHIFT; - long key = (keyX << 32) | (keyY & 0xFFFFFFFFL); - - ByteArrayHub hub = getHub(key); - if (hub == null) { - hub = new ByteArrayHub(key, unitSize, maxUnits, cachePath); - Log.i("Loading [X:" + keyX + " Y:" + keyY + " KEY:" + key + "]"); // TODO : Remove - cacheMap.add(hub); - } - - int subKeyX = Math.abs(frag.getFragmentX()) % CACHE_SIZE; - int subKeyY = Math.abs(frag.getFragmentY()) % CACHE_SIZE; - int subKey = (subKeyX << CACHE_SHIFT) + subKeyY; - //Log.i("FragX:" + frag.getFragmentX() + " FragY:" + frag.getFragmentY() + " |keyX:" + keyX + " keyY:" + keyY + " key:" + key + "| X:" + subKeyX + " Y:" + subKeyY + " Key:" + subKey + " TKey:" + hub.getKey()); - byte[] tempData = null; - if (hub.exists(subKey)) { - tempData = hub.get(subKey); - } else { - //tempData = (byte[]) ((NativeJavaArray)PluginManager.call(funcSave, frag)).unwrap(); - hub.put(subKey, tempData); - } - //PluginManager.call(funcLoad, frag, tempData, layerID); - - hub.activeFragments++; - } - - @Override - public void unload(Fragment frag) { - long keyX = frag.getFragmentX() >> CACHE_SHIFT; - long keyY = frag.getFragmentY() >> CACHE_SHIFT; - long key = (keyX << 32) | (keyY & 0xFFFFFFFFL); - ByteArrayHub hub = getHub(key); - - hub.activeFragments--; - //Log.i(masterCount); - if (hub.activeFragments == 0) { - Log.i("Unloading [X:" + keyX + " Y:" + keyY + " KEY:" + key + "]"); // TODO : Remove - hub.unload(); - cacheMap.remove(hub); - } - - } - private ByteArrayHub getHub(long key) { - for (ByteArrayHub hub : cacheMap) { - if (hub.getKey() == key) - return hub; - } - return null; - } - - // JS Shortcut - // TODO : Add more? - - public byte[] intToCachedBytes(int[] data) { - for (int i = 0; i < byteCache.length; i++) { - byteCache[i] = (byte) data[i]; - } - return byteCache; - } - -} diff --git a/src/amidst/map/ByteArrayHub.java b/src/amidst/map/ByteArrayHub.java deleted file mode 100644 index 7c03f21b0..000000000 --- a/src/amidst/map/ByteArrayHub.java +++ /dev/null @@ -1,81 +0,0 @@ -package amidst.map; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -public class ByteArrayHub { - private File path; - private long key; - private byte unitSize; - private int maxUnits; - private int dataHop; - public int activeFragments = 0; - - private byte[] data; - private byte[] returnCache; - - private int unitOffset; - - public ByteArrayHub(long key, byte unitSize, int maxUnits, File basePath) { - this.unitSize = unitSize; - this.maxUnits = maxUnits; - this.key = key; - - unitOffset = maxUnits*unitSize; - - path = new File(basePath, Long.toHexString(key).toUpperCase() + ".acache"); - data = new byte[unitOffset*ByteArrayCache.CACHE_MAX_SIZE + ByteArrayCache.HEADER_SIZE]; - returnCache = new byte[unitSize*maxUnits]; - if (path.exists()) { - GZIPInputStream inStream; - try { - inStream = new GZIPInputStream(new FileInputStream(path)); - byte[] readBuffer = new byte[1024]; - int len; - int offset = 0; - while ((len = inStream.read(readBuffer)) != -1) { - System.arraycopy(readBuffer, 0, data, offset, len); - offset += len; - } - //Log.i("@@@@@ " + inStream.read(data)); - inStream.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - public boolean exists(int id) { - return (((data[id >> 3] >> (id % 8)) & 0x1) == 0x1); - } - public byte[] get(int id) { - System.arraycopy(data, ByteArrayCache.HEADER_SIZE + unitOffset*id, returnCache, 0, unitOffset); - return returnCache; - } - public void put(int id, byte[] indata) { - System.arraycopy(indata, 0, data, ByteArrayCache.HEADER_SIZE + unitOffset*id, unitOffset); - data[id >> 3] |= 0x1 << (id % 8); - } - - public void unload() { - try { - BufferedOutputStream outStream = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(path))); - outStream.write(data, 0, data.length); - outStream.flush(); - outStream.close(); - - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - public long getKey() { - return key; - } - -} diff --git a/src/amidst/map/CacheManager.java b/src/amidst/map/CacheManager.java deleted file mode 100644 index 2094c2c3a..000000000 --- a/src/amidst/map/CacheManager.java +++ /dev/null @@ -1,19 +0,0 @@ -package amidst.map; - -import java.io.File; - -import amidst.Util; - -public abstract class CacheManager { - protected File cachePath; - - public CacheManager() {} - - public void setCachePath(String name) { - cachePath = Util.getTempDir(name); - } - - public abstract void save(Fragment frag); - public abstract void load(Fragment frag, int layerID); - public abstract void unload(Fragment frag); -} diff --git a/src/amidst/map/Fragment.java b/src/amidst/map/Fragment.java deleted file mode 100644 index 618534e35..000000000 --- a/src/amidst/map/Fragment.java +++ /dev/null @@ -1,235 +0,0 @@ -package amidst.map; - -import java.awt.AlphaComposite; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; - -import amidst.Options; -import amidst.logging.Log; -import amidst.minecraft.MinecraftUtil; - -public class Fragment { - public static final int SIZE = 512, SIZE_SHIFT = 9, MAX_OBJECTS_PER_FRAGMENT = 32, BIOME_SIZE = SIZE >> 2; - private static AffineTransform drawMatrix = new AffineTransform(); - public int blockX, blockY; - - public short[] biomeData = new short[BIOME_SIZE * BIOME_SIZE]; - - private ImageLayer[] imageLayers; - private LiveLayer[] liveLayers; - private IconLayer[] iconLayers; - - private Object loadLock = new Object(); - - private BufferedImage[] images; - public MapObject[] objects; - public int objectsLength = 0; - - private float alpha = 0.0f; - - public boolean isActive = false; - public boolean isLoaded = false; - - public Fragment nextFragment = null, prevFragment = null; - public boolean hasNext = false; - - public boolean endOfLine = false; - - private static int[] dataCache = new int[SIZE*SIZE]; - - public Fragment(ImageLayer... layers) { - this(layers, null, null); - } - public Fragment(ImageLayer[] imageLayers, LiveLayer[] liveLayers, IconLayer[] iconLayers) { - this.imageLayers = imageLayers; - this.liveLayers = liveLayers; - images = new BufferedImage[imageLayers.length]; - for (int i = 0; i < imageLayers.length; i++) - images[imageLayers[i].getLayerId()] = new BufferedImage(imageLayers[i].size, imageLayers[i].size, BufferedImage.TYPE_INT_ARGB); - this.iconLayers = iconLayers; - objects = new MapObject[MAX_OBJECTS_PER_FRAGMENT]; - } - - public void load() { - synchronized (loadLock) { - if (isLoaded) - Log.w("This should never happen!"); - int[] data = MinecraftUtil.getBiomeData(blockX >> 2, blockY >> 2, BIOME_SIZE, BIOME_SIZE, true); - for (int i = 0; i < BIOME_SIZE * BIOME_SIZE; i++) - biomeData[i] = (short)data[i]; - for (int i = 0; i < imageLayers.length; i++) - imageLayers[i].load(this); - for (int i = 0; i < iconLayers.length; i++) - iconLayers[i].generateMapObjects(this); - alpha = Options.instance.mapFading.get()?0.0f:1.0f; - isLoaded = true; - } - } - - public void recycle() { - isActive = false; - isLoaded = false; - } - - public void clearData() { - for (IconLayer layer : iconLayers) - layer.clearMapObjects(this); - isLoaded = false; - } - - public void clear() { - for (IconLayer layer : iconLayers) - layer.clearMapObjects(this); - hasNext = false; - endOfLine = false; - isActive = true; - } - - public void drawLiveLayers(float time, Graphics2D g, AffineTransform mat) { - for (int i = 0; i < liveLayers.length; i++) { - if (liveLayers[i].isVisible()) { - liveLayers[i].drawLive(this, g, mat); - } - } - - } - public void drawImageLayers(float time, Graphics2D g, AffineTransform mat) { - if (!isLoaded) - return; - - alpha = Math.min(1.0f, time*3.0f + alpha); - for (int i = 0; i < images.length; i++) { - if (imageLayers[i].isVisible()) { - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha * imageLayers[i].getAlpha())); - - - // TOOD: FIX THIS - g.setTransform(imageLayers[i].getScaledMatrix(mat)); - if (g.getTransform().getScaleX() < 1.0f) - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - else - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); - g.drawImage(images[i], 0, 0, null); - } - } - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); - } - - public void drawObjects(Graphics2D g, AffineTransform inMatrix) { - if (alpha != 1.0f) - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); - - for (int i = 0; i < objectsLength; i++) { - if (objects[i].parentLayer.isVisible()) { - drawMatrix.setTransform(inMatrix); - drawMatrix.translate(objects[i].x, objects[i].y); - double invZoom = 1.0 / objects[i].parentLayer.map.getZoom(); - drawMatrix.scale(invZoom, invZoom); - g.setTransform(drawMatrix); - - g.drawImage(objects[i].getImage(), - -(objects[i].getWidth() >> 1), - -(objects[i].getHeight() >> 1), - objects[i].getWidth(), - objects[i].getHeight(), - null); - } - } - if (alpha != 1.0f) - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); - } - - public void addObject(MapObject object) { - object.rx = object.x + this.blockX; - object.ry = object.y + this.blockY; - if (objectsLength >= objects.length) { - MapObject[] tempObjects = new MapObject[objects.length << 1]; - for (int i = 0; i < objects.length; i++) - tempObjects[i] = objects[i]; - objects = tempObjects; - } - objects[objectsLength] = object; - objectsLength++; - } - - public void setImageData(int layerId, int[] data) { - images[layerId].setRGB(0, 0, imageLayers[layerId].size, imageLayers[layerId].size, data, 0, imageLayers[layerId].size); - } - - - public int getBlockX() { - return blockX; - } - public int getBlockY() { - return blockY; - } - public int getChunkX() { - return blockX >> 4; - } - public int getChunkY() { - return blockY >> 4; - } - public int getFragmentX() { - return blockX >> SIZE_SHIFT; - } - public int getFragmentY() { - return blockY >> SIZE_SHIFT; - } - - public void setNext(Fragment frag) { - nextFragment = frag; - frag.prevFragment = this; - hasNext = true; - } - - public void remove() { - if (hasNext) - prevFragment.setNext(nextFragment); - else - prevFragment.hasNext = false; - } - - - public static int[] getIntArray() { - return dataCache; - } - - public void removeObject(MapObjectPlayer player) { - for (int i = 0; i < objectsLength; i++) { - if (objects[i] == player) { - objects[i] = objects[objectsLength - 1]; - objectsLength--; - } - } - } - public BufferedImage getBufferedImage(int layer) { - return images[layer]; - } - public void reset() { - objectsLength = 0; - isActive = false; - isLoaded = false; - - nextFragment = null; - prevFragment = null; - hasNext = false; - - endOfLine = false; - } - public void repaint() { - synchronized (loadLock) { - if (isLoaded) - for (int i = 0; i < imageLayers.length; i++) - imageLayers[i].load(this); - } - } - - public void repaintImageLayer(int id) { - synchronized (loadLock) { - if (isLoaded) - imageLayers[id].load(this); - } - } -} diff --git a/src/amidst/map/FragmentManager.java b/src/amidst/map/FragmentManager.java deleted file mode 100644 index b45b970ac..000000000 --- a/src/amidst/map/FragmentManager.java +++ /dev/null @@ -1,191 +0,0 @@ -package amidst.map; - -import java.util.Collections; -import java.util.Stack; -import java.util.concurrent.ConcurrentLinkedQueue; - -import amidst.logging.Log; - -public class FragmentManager implements Runnable { - private int cacheSize = 1024; - - private Thread currentThread; - private boolean running = true; - - private Fragment[] fragmentCache; - private ConcurrentLinkedQueue fragmentQueue; - private ConcurrentLinkedQueue requestQueue; - private ConcurrentLinkedQueue recycleQueue; - private int sleepTick = 0; - - private Object queueLock = new Object(); - - private Stack layerList; - - private ImageLayer[] imageLayers; - private IconLayer[] iconLayers; - private LiveLayer[] liveLayers; - - public FragmentManager(ImageLayer[] imageLayers, LiveLayer[] liveLayers, IconLayer[] iconLayers) { - fragmentQueue = new ConcurrentLinkedQueue(); - requestQueue = new ConcurrentLinkedQueue(); - recycleQueue = new ConcurrentLinkedQueue(); - layerList = new Stack(); - Collections.addAll(layerList, imageLayers); - - fragmentCache = new Fragment[cacheSize]; - for (int i = 0; i < imageLayers.length; i++) - imageLayers[i].setLayerId(i); - for (int i = 0; i < cacheSize; i++) { - fragmentCache[i] = new Fragment(imageLayers, liveLayers, iconLayers); - fragmentQueue.offer(fragmentCache[i]); - } - this.imageLayers = imageLayers; - this.iconLayers = iconLayers; - this.liveLayers = liveLayers; - } - public void updateLayers(float time) { - for (ImageLayer layer : imageLayers) - layer.update(time); - for (LiveLayer layer : liveLayers) - layer.update(time); - for (IconLayer layer : iconLayers) - layer.update(time); - } - - public void reset() { - running = false; - try { - currentThread.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - recycleQueue.clear(); - requestQueue.clear(); - fragmentQueue.clear(); - for (int i = 0; i < cacheSize; i++) { - fragmentCache[i].reset(); - fragmentQueue.offer(fragmentCache[i]); - } - } - - private void increaseFragmentCache() { - Fragment[] newFragments = new Fragment[cacheSize << 1]; - for (int i = 0; i < cacheSize; i++) { - newFragments[i] = fragmentCache[i]; - fragmentCache[i] = null; - } - for (int i = cacheSize; i < cacheSize << 1; i++) { - newFragments[i] = new Fragment(imageLayers, liveLayers, iconLayers); - fragmentQueue.offer(newFragments[i]); - } - fragmentCache = newFragments; - Log.i("FragmentManager cache size increased from " + cacheSize + " to " + (cacheSize << 1)); - cacheSize <<= 1; - System.gc(); - } - - public void repaintFragment(Fragment frag) { - synchronized (queueLock) { - frag.repaint(); - } - } - public Fragment requestFragment(int x, int y) { - if (!running) - return null; - Fragment frag = null; - while ((frag = fragmentQueue.poll()) == null) - increaseFragmentCache(); - - frag.clear(); - frag.blockX = x; - frag.blockY = y; - frag.isActive = true; - requestQueue.offer(frag); - return frag; - } - - public void returnFragment(Fragment frag) { - recycleQueue.offer(frag); - } - - @Override - public void run() { - currentThread.setPriority(Thread.MIN_PRIORITY); - - while (running) { - if(!requestQueue.isEmpty() || !recycleQueue.isEmpty()) { - if (!requestQueue.isEmpty()) { - synchronized (queueLock) { - Fragment frag = requestQueue.poll(); - if (frag.isActive && !frag.isLoaded) { - frag.load(); - sleepTick++; - if (sleepTick == 10) { - sleepTick = 0; - try { - Thread.sleep(1L); - } catch (InterruptedException ignored) {} - } - } - } - } - - while (!recycleQueue.isEmpty()) { - synchronized (queueLock) { - Fragment frag = recycleQueue.poll(); - frag.recycle(); - fragmentQueue.offer(frag); - } - } - } else { - sleepTick = 0; - try { - Thread.sleep(2L); - } catch (InterruptedException ignored) {} - } - } - - } - - public void setMap(Map map) { - for (ImageLayer layer : imageLayers) { - layer.setMap(map); - layer.reload(); - } - - for (LiveLayer layer : liveLayers) { - layer.setMap(map); - layer.reload(); - } - - for (IconLayer layer : iconLayers) { - layer.setMap(map); - layer.reload(); - } - - currentThread = new Thread(this); - - running = true; - currentThread.start(); - } - - public int getCacheSize() { - return cacheSize; - } - public int getFreeFragmentQueueSize() { - return fragmentQueue.size(); - } - public int getRecycleQueueSize() { - return recycleQueue.size(); - } - public int getRequestQueueSize() { - return requestQueue.size(); - } - public void repaintFragmentLayer(Fragment frag, int id) { - synchronized (queueLock) { - frag.repaintImageLayer(id); - } - } -} diff --git a/src/amidst/map/IconLayer.java b/src/amidst/map/IconLayer.java deleted file mode 100644 index 7a17a2072..000000000 --- a/src/amidst/map/IconLayer.java +++ /dev/null @@ -1,15 +0,0 @@ -package amidst.map; - - -public class IconLayer extends Layer { - public IconLayer() { - } - - public void generateMapObjects(Fragment frag) { - - } - - public void clearMapObjects(Fragment frag) { - frag.objectsLength = 0; - } -} diff --git a/src/amidst/map/ImageLayer.java b/src/amidst/map/ImageLayer.java deleted file mode 100644 index 189a03e4b..000000000 --- a/src/amidst/map/ImageLayer.java +++ /dev/null @@ -1,53 +0,0 @@ -package amidst.map; - -import java.awt.geom.AffineTransform; - -import amidst.logging.Log; - -public abstract class ImageLayer extends Layer { - protected float alpha = 1.0f; - protected double scale; - protected int size; - private AffineTransform cachedScalingMatrix = new AffineTransform(); - protected int layerId; - - private int[] defaultData; - - public ImageLayer(int size) { - this.size = size; - defaultData = new int[size*size]; - scale = Fragment.SIZE/(double)size; - for (int i = 0; i < defaultData.length; i++) - defaultData[i] = 0x00000000; - } - - public int[] getDefaultData() { - return defaultData; - } - - public void load(Fragment frag) { - drawToCache(frag); - } - - public AffineTransform getMatrix(AffineTransform inMat) { - cachedScalingMatrix.setTransform(inMat); - return cachedScalingMatrix; - } - public AffineTransform getScaledMatrix(AffineTransform inMat) { - cachedScalingMatrix.setTransform(inMat); - cachedScalingMatrix.scale(scale, scale); - return cachedScalingMatrix; - } - - public float getAlpha() { - return alpha; - } - public int getLayerId() { - return layerId; - } - public void setLayerId(int id) { - layerId = id; - } - public abstract void drawToCache(Fragment fragment); -} - diff --git a/src/amidst/map/Layer.java b/src/amidst/map/Layer.java deleted file mode 100644 index d1db95bc8..000000000 --- a/src/amidst/map/Layer.java +++ /dev/null @@ -1,24 +0,0 @@ -package amidst.map; - -public class Layer { - protected Map map; - - public void update(float time) { - - } - - - public void setMap(Map map) { - this.map = map; - } - public Map getMap() { - return map; - } - public boolean isVisible() { - return true; - } - - public void reload() { - - } -} diff --git a/src/amidst/map/LiveLayer.java b/src/amidst/map/LiveLayer.java deleted file mode 100644 index 8356e2e7a..000000000 --- a/src/amidst/map/LiveLayer.java +++ /dev/null @@ -1,11 +0,0 @@ -package amidst.map; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; - -public abstract class LiveLayer extends Layer { - public LiveLayer() { - - } - public abstract void drawLive(Fragment fragment, Graphics2D g, AffineTransform mat); -} diff --git a/src/amidst/map/Map.java b/src/amidst/map/Map.java deleted file mode 100644 index 88985cee0..000000000 --- a/src/amidst/map/Map.java +++ /dev/null @@ -1,410 +0,0 @@ -package amidst.map; - -import java.awt.*; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; - -import amidst.logging.Log; -import amidst.map.layers.BiomeLayer; - -public class Map { - public static Map instance = null; - private static final boolean START = true, END = false; - private FragmentManager fragmentManager; - - private Fragment startNode = new Fragment(); - - private double scale = 0.25; - private Point2D.Double start; - - public int tileWidth, tileHeight; - public int width = 1, height = 1; - - private final Object resizeLock = new Object(), drawLock = new Object(); - private AffineTransform mat; - - private boolean firstDraw = true; - - - - // TODO : This must be changed with the removal of ChunkManager - public Map(FragmentManager fragmentManager) { - this.fragmentManager = fragmentManager; - fragmentManager.setMap(this); - mat = new AffineTransform(); - - start = new Point2D.Double(); - addStart(0, 0); - - instance = this; - } - - public void resetImageLayer(int id) { - Fragment frag = startNode; - while (frag.hasNext) { - frag = frag.nextFragment; - fragmentManager.repaintFragmentLayer(frag, id); - } - } - public void resetFragments() { - Fragment frag = startNode; - while (frag.hasNext) { - frag = frag.nextFragment; - fragmentManager.repaintFragment(frag); - } - } - - public void draw(Graphics2D g, float time) { - AffineTransform originalTransform = g.getTransform(); - if (firstDraw) { - firstDraw = false; - centerOn(0, 0); - } - - synchronized (drawLock) { - int size = (int) (Fragment.SIZE * scale); - int w = width / size + 2; - int h = height / size + 2; - - while (tileWidth < w) addColumn(END); - while (tileWidth > w) removeColumn(END); - while (tileHeight < h) addRow(END); - while (tileHeight > h) removeRow(END); - - while (start.x > 0) { start.x -= size; addColumn(START); removeColumn(END); } - while (start.x < -size) { start.x += size; addColumn(END); removeColumn(START); } - while (start.y > 0) { start.y -= size; addRow(START); removeRow(END); } - while (start.y < -size) { start.y += size; addRow(END); removeRow(START); } - - Fragment frag = startNode; - size = Fragment.SIZE; - if (frag.hasNext) { - mat.setToIdentity(); - mat.concatenate(originalTransform); - mat.translate(start.x, start.y); - mat.scale(scale, scale); - while (frag.hasNext) { - frag = frag.nextFragment; - frag.drawImageLayers(time, g, mat); - mat.translate(size, 0); - if (frag.endOfLine) - mat.translate(-size * w, size); - } - } - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); - fragmentManager.updateLayers(time); - frag = startNode; - if (frag.hasNext) { - mat.setToIdentity(); - mat.concatenate(originalTransform); - mat.translate(start.x, start.y); - mat.scale(scale, scale); - while (frag.hasNext) { - frag = frag.nextFragment; - frag.drawLiveLayers(time, g, mat); - mat.translate(size, 0); - if (frag.endOfLine) - mat.translate(-size * w, size); - } - } - - frag = startNode; - if (frag.hasNext) { - mat.setToIdentity(); - mat.concatenate(originalTransform); - mat.translate(start.x, start.y); - mat.scale(scale, scale); - while (frag.hasNext) { - frag = frag.nextFragment; - frag.drawObjects(g, mat); - mat.translate(size, 0); - if (frag.endOfLine) - mat.translate(-size * w, size); - } - } - - g.setTransform(originalTransform); - } - } - public void addStart(int x, int y) { - synchronized (resizeLock) { - Fragment start = fragmentManager.requestFragment(x, y); - start.endOfLine = true; - startNode.setNext(start); - tileWidth = 1; - tileHeight = 1; - } - } - - public void addColumn(boolean start) { - synchronized (resizeLock) { - int x = 0; - Fragment frag = startNode; - if (start) { - x = frag.nextFragment.blockX - Fragment.SIZE; - Fragment newFrag = fragmentManager.requestFragment(x, frag.nextFragment.blockY); - newFrag.setNext(startNode.nextFragment); - startNode.setNext(newFrag); - } - while (frag.hasNext) { - frag = frag.nextFragment; - if (frag.endOfLine) { - if (start) { - if (frag.hasNext) { - Fragment newFrag = fragmentManager.requestFragment(x, frag.blockY + Fragment.SIZE); - newFrag.setNext(frag.nextFragment); - frag.setNext(newFrag); - frag = newFrag; - } - } else { - Fragment newFrag = fragmentManager.requestFragment(frag.blockX + Fragment.SIZE, frag.blockY); - - if (frag.hasNext) { - newFrag.setNext(frag.nextFragment); - } - newFrag.endOfLine = true; - frag.endOfLine = false; - frag.setNext(newFrag); - frag = newFrag; - - } - } - } - tileWidth++; - } - } - public void removeRow(boolean start) { - synchronized (resizeLock) { - if (start) { - for (int i = 0; i < tileWidth; i++) { - Fragment frag = startNode.nextFragment; - frag.remove(); - fragmentManager.returnFragment(frag); - } - } else { - Fragment frag = startNode; - while (frag.hasNext) - frag = frag.nextFragment; - for (int i = 0; i < tileWidth; i++) { - frag.remove(); - fragmentManager.returnFragment(frag); - frag = frag.prevFragment; - } - } - tileHeight--; - } - } - public void addRow(boolean start) { - synchronized (resizeLock) { - Fragment frag = startNode; - int y; - if (start) { - frag = startNode.nextFragment; - y = frag.blockY - Fragment.SIZE; - } else { - while (frag.hasNext) - frag = frag.nextFragment; - y = frag.blockY + Fragment.SIZE; - } - - tileHeight++; - Fragment newFrag = fragmentManager.requestFragment(startNode.nextFragment.blockX, y); - Fragment chainFrag = newFrag; - for (int i = 1; i < tileWidth; i++) { - Fragment tempFrag = fragmentManager.requestFragment(chainFrag.blockX + Fragment.SIZE, chainFrag.blockY); - chainFrag.setNext(tempFrag); - chainFrag = tempFrag; - if (i == (tileWidth - 1)) - chainFrag.endOfLine = true; - } - if (start) { - chainFrag.setNext(frag); - startNode.setNext(newFrag); - } else { - frag.setNext(newFrag); - } - } - } - public void removeColumn(boolean start) { - synchronized (resizeLock) { - Fragment frag = startNode; - if (start) { - fragmentManager.returnFragment(frag.nextFragment); - startNode.nextFragment.remove(); - } - while (frag.hasNext) { - frag = frag.nextFragment; - if (frag.endOfLine) { - if (start) { - if (frag.hasNext) { - Fragment tempFrag = frag.nextFragment; - tempFrag.remove(); - fragmentManager.returnFragment(tempFrag); - } - } else { - frag.prevFragment.endOfLine = true; - frag.remove(); - fragmentManager.returnFragment(frag); - frag = frag.prevFragment; - } - } - } - tileWidth--; - } - } - - public void moveBy(Point2D.Double speed) { - moveBy(speed.x, speed.y); - } - - public void moveBy(double x, double y) { - start.x += x; - start.y += y; - } - - public void centerOn(long x, long y) { - long fragOffsetX = x % Fragment.SIZE; - long fragOffsetY = y % Fragment.SIZE; - long startX = x - fragOffsetX; - long startY = y - fragOffsetY; - synchronized (drawLock) { - while (tileHeight > 1) removeRow(false); - while (tileWidth > 1) removeColumn(false); - Fragment frag = startNode.nextFragment; - frag.remove(); - fragmentManager.returnFragment(frag); - // TODO: Support longs? - double offsetX = width >> 1; - double offsetY = height >> 1; - - offsetX -= (fragOffsetX)*scale; - offsetY -= (fragOffsetY)*scale; - - start.x = offsetX; - start.y = offsetY; - - addStart((int)startX, (int)startY); - } - } - - public void setZoom(double scale) { - this.scale = scale; - } - - public double getZoom() { - return scale; - } - - public Point2D.Double getScaled(double oldScale, double newScale, Point p) { - double baseX = p.x - start.x; - double scaledX = baseX - (baseX / oldScale) * newScale; - - double baseY = p.y - start.y; - double scaledY = baseY - (baseY / oldScale) * newScale; - - return new Point2D.Double(scaledX, scaledY); - } - - public void dispose() { - synchronized (drawLock) { - fragmentManager.reset(); - } - } - - public Fragment getFragmentAt(Point position) { - Fragment frag = startNode; - Point cornerPosition = new Point(position.x >> Fragment.SIZE_SHIFT, position.y >> Fragment.SIZE_SHIFT); - Point fragmentPosition = new Point(); - while (frag.hasNext) { - frag = frag.nextFragment; - fragmentPosition.x = frag.getFragmentX(); - fragmentPosition.y = frag.getFragmentY(); - if (cornerPosition.equals(fragmentPosition)) - return frag; - } - return null; - } - - public MapObject getObjectAt(Point position, double maxRange) { - double x = start.x; - double y = start.y; - MapObject closestObject = null; - double closestDistance = maxRange; - Fragment frag = startNode; - int size = (int) (Fragment.SIZE * scale); - while (frag.hasNext) { - frag = frag.nextFragment; - for (int i = 0; i < frag.objectsLength; i ++) { - if (frag.objects[i].parentLayer.isVisible()) { - Point objPosition = frag.objects[i].getLocation(); - objPosition.x *= scale; - objPosition.y *= scale; - objPosition.x += x; - objPosition.y += y; - - double distance = objPosition.distance(position); - if (distance < closestDistance) { - closestDistance = distance; - closestObject = frag.objects[i]; - } - } - } - x += size; - if (frag.endOfLine) { - x = start.x; - y += size; - } - } - return closestObject; - } - - public Point screenToLocal(Point inPoint) { - Point point = inPoint.getLocation(); - - point.x -= start.x; - point.y -= start.y; - - // TODO: int -> double -> int = bad? - point.x /= scale; - point.y /= scale; - - point.x += startNode.nextFragment.blockX; - point.y += startNode.nextFragment.blockY; - - return point; - } - public String getBiomeNameAt(Point point) { - Fragment frag = startNode; - while (frag.hasNext) { - frag = frag.nextFragment; - if ((frag.blockX <= point.x) && - (frag.blockY <= point.y) && - (frag.blockX + Fragment.SIZE > point.x) && - (frag.blockY + Fragment.SIZE > point.y)) { - int x = point.x - frag.blockX; - int y = point.y - frag.blockY; - - return BiomeLayer.getBiomeNameForFragment(frag, x, y); - } - } - return "Unknown"; - } - - public String getBiomeAliasAt(Point point) { - Fragment frag = startNode; - while (frag.hasNext) { - frag = frag.nextFragment; - if ((frag.blockX <= point.x) && - (frag.blockY <= point.y) && - (frag.blockX + Fragment.SIZE > point.x) && - (frag.blockY + Fragment.SIZE > point.y)) { - int x = point.x - frag.blockX; - int y = point.y - frag.blockY; - - return BiomeLayer.getBiomeAliasForFragment(frag, x, y); - } - } - return "Unknown"; - } - -} diff --git a/src/amidst/map/MapMarkers.java b/src/amidst/map/MapMarkers.java deleted file mode 100644 index 1a421c0a3..000000000 --- a/src/amidst/map/MapMarkers.java +++ /dev/null @@ -1,29 +0,0 @@ -package amidst.map; - -import amidst.resources.ResourceLoader; - -import java.awt.image.BufferedImage; - -/** Contains information about all possible Map Markers. - * Map objects use either its or their own icon - * TODO: link to test.amidst.map object class - */ -public enum MapMarkers { - NETHER_FORTRESS, - PLAYER, - SLIME, - STRONGHOLD, - JUNGLE, - DESERT, - VILLAGE, - SPAWN, - WITCH, - OCEAN_MONUMENT; - - public final BufferedImage image; - - private MapMarkers() { - String fileName = this.toString().toLowerCase() + ".png"; - image = ResourceLoader.getImage(fileName); - } -} diff --git a/src/amidst/map/MapObject.java b/src/amidst/map/MapObject.java deleted file mode 100644 index 371bd5170..000000000 --- a/src/amidst/map/MapObject.java +++ /dev/null @@ -1,44 +0,0 @@ -package amidst.map; -import amidst.Options; -import amidst.map.MapMarkers; - -import java.awt.Point; -import java.awt.image.BufferedImage; - -public class MapObject extends Point { - public MapMarkers type; - public int rx, ry; - public double localScale = 1.0; - @Deprecated - public double tempDist = 0; - public IconLayer parentLayer; - - public MapObject(MapMarkers eType, int x, int y) { - super(x, y); - type = eType; - } - - public String getName() { - return type.toString(); - } - - public int getWidth() { - return (int)(type.image.getWidth() * localScale); - } - public int getHeight() { - return (int)(type.image.getHeight() * localScale); - } - - public BufferedImage getImage() { - return type.image; - } - - public boolean isSelectable() { - return Options.instance.showVillages.isSelected(); - } - - public MapObject setParent(IconLayer layer) { - parentLayer = layer; - return this; - } -} diff --git a/src/amidst/map/MapObjectDesertTemple.java b/src/amidst/map/MapObjectDesertTemple.java deleted file mode 100644 index 3380ac2ca..000000000 --- a/src/amidst/map/MapObjectDesertTemple.java +++ /dev/null @@ -1,7 +0,0 @@ -package amidst.map; - -public class MapObjectDesertTemple extends MapObject { - public MapObjectDesertTemple(int eX, int eY) { - super(MapMarkers.DESERT, eX, eY); - } -} diff --git a/src/amidst/map/MapObjectJungleTemple.java b/src/amidst/map/MapObjectJungleTemple.java deleted file mode 100644 index 1d53986a3..000000000 --- a/src/amidst/map/MapObjectJungleTemple.java +++ /dev/null @@ -1,7 +0,0 @@ -package amidst.map; - -public class MapObjectJungleTemple extends MapObject { - public MapObjectJungleTemple(int eX, int eY) { - super(MapMarkers.JUNGLE, eX, eY); - } -} diff --git a/src/amidst/map/MapObjectNether.java b/src/amidst/map/MapObjectNether.java deleted file mode 100644 index ef408c7c7..000000000 --- a/src/amidst/map/MapObjectNether.java +++ /dev/null @@ -1,7 +0,0 @@ -package amidst.map; - -public class MapObjectNether extends MapObject { - public MapObjectNether(int eX, int eY) { - super(MapMarkers.NETHER_FORTRESS, eX, eY); - } -} diff --git a/src/amidst/map/MapObjectOceanMonument.java b/src/amidst/map/MapObjectOceanMonument.java deleted file mode 100644 index 61710e0d4..000000000 --- a/src/amidst/map/MapObjectOceanMonument.java +++ /dev/null @@ -1,7 +0,0 @@ -package amidst.map; - -public class MapObjectOceanMonument extends MapObject { - public MapObjectOceanMonument(int eX, int eY) { - super(MapMarkers.OCEAN_MONUMENT, eX, eY); - } -} diff --git a/src/amidst/map/MapObjectPlayer.java b/src/amidst/map/MapObjectPlayer.java deleted file mode 100644 index 6cc44b92a..000000000 --- a/src/amidst/map/MapObjectPlayer.java +++ /dev/null @@ -1,56 +0,0 @@ -package amidst.map; - -import java.awt.image.BufferedImage; - -public class MapObjectPlayer extends MapObject { - public String name; - public boolean needSave; - private BufferedImage marker; - public int globalX, globalY; - public Fragment parentFragment = null; - - public MapObjectPlayer(String name, int x, int y) { - super(MapMarkers.PLAYER, - ((x < 0)?Fragment.SIZE:0) + x % Fragment.SIZE, - ((y < 0)?Fragment.SIZE:0) + y % Fragment.SIZE); - globalX = x; - globalY = y; - marker = type.image; - needSave = false; - this.name = name; - } - - - @Override - public int getWidth() { - return (int)(marker.getWidth()*localScale); - } - @Override - public int getHeight() { - return (int)(marker.getHeight()*localScale); - } - public void setPosition(int x, int y) { - this.globalX = x; - this.globalY = y; - this.x = ((x < 0)?Fragment.SIZE:0) + x % Fragment.SIZE; - this.y = ((y < 0)?Fragment.SIZE:0) + y % Fragment.SIZE; - needSave = true; - } - - @Override - public BufferedImage getImage() { - return marker; - } - - public void setMarker(BufferedImage img) { - this.marker = img; - } - @Override - public String getName() { - return name; - } - @Override - public String toString() { - return "Player \"" + name + "\" at (" + x + ", " + y + ")"; - } -} diff --git a/src/amidst/map/MapObjectSpawn.java b/src/amidst/map/MapObjectSpawn.java deleted file mode 100644 index 4fc9963ea..000000000 --- a/src/amidst/map/MapObjectSpawn.java +++ /dev/null @@ -1,20 +0,0 @@ -package amidst.map; - -/** Used mainly to be override its toString method for use in choices - */ -public class MapObjectSpawn extends MapObject { - public int globalX, globalY; - public MapObjectSpawn(int x, int y) { - super(MapMarkers.SPAWN, - ((x < 0)?Fragment.SIZE:0) + x % Fragment.SIZE, - ((y < 0)?Fragment.SIZE:0) + y % Fragment.SIZE); - - globalX = x; - globalY = y; - } - - @Override - public String toString() { - return "Spawn point at (" + x + ", " + y + ")"; - } -} diff --git a/src/amidst/map/MapObjectStronghold.java b/src/amidst/map/MapObjectStronghold.java deleted file mode 100644 index cd7b8bd37..000000000 --- a/src/amidst/map/MapObjectStronghold.java +++ /dev/null @@ -1,14 +0,0 @@ -package amidst.map; - -/** Used mainly to be override its toString method for use in choices - */ -public class MapObjectStronghold extends MapObject { - public MapObjectStronghold(int x, int y) { - super(MapMarkers.STRONGHOLD, x, y); - } - - @Override - public String toString() { - return "Stronghold at (" + x + ", " + y + ")"; - } -} diff --git a/src/amidst/map/MapObjectVillage.java b/src/amidst/map/MapObjectVillage.java deleted file mode 100644 index f51ecccfc..000000000 --- a/src/amidst/map/MapObjectVillage.java +++ /dev/null @@ -1,7 +0,0 @@ -package amidst.map; - -public class MapObjectVillage extends MapObject { - public MapObjectVillage(int eX, int eY) { - super(MapMarkers.VILLAGE, eX, eY); - } -} \ No newline at end of file diff --git a/src/amidst/map/MapObjectWitchHut.java b/src/amidst/map/MapObjectWitchHut.java deleted file mode 100644 index 126900947..000000000 --- a/src/amidst/map/MapObjectWitchHut.java +++ /dev/null @@ -1,7 +0,0 @@ -package amidst.map; - -public class MapObjectWitchHut extends MapObject { - public MapObjectWitchHut(int eX, int eY) { - super(MapMarkers.WITCH, eX, eY); - } -} diff --git a/src/amidst/map/layers/BiomeLayer.java b/src/amidst/map/layers/BiomeLayer.java deleted file mode 100644 index 572e3ae5c..000000000 --- a/src/amidst/map/layers/BiomeLayer.java +++ /dev/null @@ -1,86 +0,0 @@ -package amidst.map.layers; - -import MoF.MapViewer; -import amidst.Options; -import amidst.Util; -import amidst.logging.Log; -import amidst.map.Fragment; -import amidst.map.ImageLayer; -import amidst.minecraft.Biome; - -public class BiomeLayer extends ImageLayer { - public static BiomeLayer instance; - protected static int size = Fragment.SIZE >> 2; - - protected boolean[] selectedBiomes = new boolean[Biome.biomes.length]; - private boolean inHighlightMode = false; - - public BiomeLayer() { - super(size); - instance = this; - deselectAllBiomes(); - } - - public void selectAllBiomes() { - setSelectedAllBiomes(true); - } - public void deselectAllBiomes() { - setSelectedAllBiomes(false); - } - - public void selectBiome(int id) { - setSelected(id, true); - } - public void deselectBiome(int id) { - setSelected(id, false); - } - - public void setHighlightMode(boolean enabled) { - inHighlightMode = enabled; - } - - public void toggleBiomeSelect(int id) { - setSelected(id, !selectedBiomes[id]); - } - public void setSelected(int id, boolean value) { - selectedBiomes[id] = value; - } - - public void setSelectedAllBiomes(boolean value) { - for (int i = 0; i < selectedBiomes.length; i++) - selectedBiomes[i] = value; - } - - @Override - public void drawToCache(Fragment fragment) { - int[] dataCache = Fragment.getIntArray(); - if (inHighlightMode) { - for (int i = 0; i < size*size; i++) - if (!selectedBiomes[fragment.biomeData[i]]) - dataCache[i] = Util.deselectColor(Biome.biomes[fragment.biomeData[i]].color); - else - dataCache[i] = Biome.biomes[fragment.biomeData[i]].color; - } else { - for (int i = 0; i < size*size; i++) - dataCache[i] = Biome.biomes[fragment.biomeData[i]].color; - } - - - fragment.setImageData(layerId, dataCache); - } - - public static int getBiomeForFragment(Fragment frag, int blockX, int blockY) { - return frag.biomeData[(blockY >> 2) * Fragment.BIOME_SIZE + (blockX >> 2)]; - } - - public static String getBiomeNameForFragment(Fragment frag, int blockX, int blockY) { - return Biome.biomes[getBiomeForFragment(frag, blockX, blockY)].name; - } - public static String getBiomeAliasForFragment(Fragment frag, int blockX, int blockY) { - return Options.instance.biomeColorProfile.getAliasForId(getBiomeForFragment(frag, blockX, blockY)); - } - - public boolean isBiomeSelected(int id) { - return selectedBiomes[id]; - } -} diff --git a/src/amidst/map/layers/GridLayer.java b/src/amidst/map/layers/GridLayer.java deleted file mode 100644 index 45148a5f9..000000000 --- a/src/amidst/map/layers/GridLayer.java +++ /dev/null @@ -1,80 +0,0 @@ -package amidst.map.layers; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; - -import amidst.Options; -import amidst.map.Fragment; -import amidst.map.LiveLayer; - - -public class GridLayer extends LiveLayer { - private static Font drawFont = new Font("arial", Font.BOLD, 16); - private static StringBuffer textBuffer = new StringBuffer(128); - private static char[] textCache = new char[128]; - - public GridLayer() { - } - - @Override - public boolean isVisible() { - return Options.instance.showGrid.get(); - } - @Override - public void drawLive(Fragment fragment, Graphics2D g, AffineTransform inMat) { - g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - AffineTransform mat = new AffineTransform(inMat); - - textBuffer.setLength(0); - textBuffer.append(fragment.getChunkX() << 4); - textBuffer.append(", "); - textBuffer.append(fragment.getChunkY() << 4); - - textBuffer.getChars(0, textBuffer.length(), textCache, 0); - - int stride = (int)(.25/map.getZoom()); - - - - g.setColor(Color.black); - g.setTransform(mat); - int gridX = (fragment.getFragmentX() % (stride + 1)); - int gridY = (fragment.getFragmentY() % (stride + 1)); - if (gridY == 0) - g.drawLine(0, 0, Fragment.SIZE, 0); - if (gridY == stride) - g.drawLine(0, Fragment.SIZE, Fragment.SIZE, Fragment.SIZE); - if (gridX == 0) - g.drawLine(0, 0, 0, Fragment.SIZE); - if (gridX == stride) - g.drawLine(Fragment.SIZE, 0, Fragment.SIZE, Fragment.SIZE); - - if (gridX != 0) - return; - if (gridY != 0) - return; - double invZoom = 1.0 / map.getZoom(); - mat.scale(invZoom, invZoom); - g.setTransform(mat); - g.setFont(drawFont); - g.drawChars(textCache, 0, textBuffer.length(), 12, 17); - g.drawChars(textCache, 0, textBuffer.length(), 8, 17); - g.drawChars(textCache, 0, textBuffer.length(), 10, 19); - g.drawChars(textCache, 0, textBuffer.length(), 10, 15); - - // This makes the text outline a bit thicker, but seems unneeded. - //g.drawChars(textCache, 0, textBuffer.length(), 12, 15); - //g.drawChars(textCache, 0, textBuffer.length(), 12, 19); - //g.drawChars(textCache, 0, textBuffer.length(), 8, 15); - //g.drawChars(textCache, 0, textBuffer.length(), 8, 19); - - g.setColor(Color.white); - g.drawChars(textCache, 0, textBuffer.length(), 10, 17); - - g.setTransform(inMat); - } - -} diff --git a/src/amidst/map/layers/NetherFortressLayer.java b/src/amidst/map/layers/NetherFortressLayer.java deleted file mode 100644 index 24c54443a..000000000 --- a/src/amidst/map/layers/NetherFortressLayer.java +++ /dev/null @@ -1,50 +0,0 @@ -package amidst.map.layers; - -import java.util.Random; -import amidst.Options; -import amidst.map.Fragment; -import amidst.map.IconLayer; -import amidst.map.MapObjectNether; - -public class NetherFortressLayer extends IconLayer { - private Random random = new Random(); - - public NetherFortressLayer() { - } - - @Override - public boolean isVisible() { - return Options.instance.showNetherFortresses.get(); - } - @Override - public void generateMapObjects(Fragment frag) { - int size = Fragment.SIZE >> 4; - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - int chunkX = x + frag.getChunkX(); - int chunkY = y + frag.getChunkY(); - if (checkChunk(chunkX, chunkY)) { - frag.addObject(new MapObjectNether(x << 4, y << 4).setParent(this)); - } - } - } - } - - - public boolean checkChunk(int chunkX, int chunkY) { - int i = chunkX >> 4; - int j = chunkY >> 4; - - random.setSeed(i ^ j << 4 ^ Options.instance.seed); - random.nextInt(); - - if (random.nextInt(3) != 0) { - return false; - } - if (chunkX != (i << 4) + 4 + random.nextInt(8)) { - return false; - } - - return chunkY == (j << 4) + 4 + random.nextInt(8); - } -} diff --git a/src/amidst/map/layers/OceanMonumentLayer.java b/src/amidst/map/layers/OceanMonumentLayer.java deleted file mode 100644 index 8327ca048..000000000 --- a/src/amidst/map/layers/OceanMonumentLayer.java +++ /dev/null @@ -1,106 +0,0 @@ -package amidst.map.layers; - -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import amidst.Options; -import amidst.map.Fragment; -import amidst.map.IconLayer; -import amidst.map.MapObjectOceanMonument; -import amidst.map.MapObjectVillage; -import amidst.minecraft.Biome; -import amidst.minecraft.MinecraftUtil; - -public class OceanMonumentLayer extends IconLayer { - - public static List validBiomes = Arrays.asList( - new Biome[] { - Biome.deepOcean, - Biome.deepOceanM, // Not sure if the extended biomes count - } - ); - public static List validSurroundingBiomes = Arrays.asList( - new Biome[] { - Biome.ocean, - Biome.deepOcean, - Biome.frozenOcean, - Biome.river, - Biome.frozenRiver, - // Not sure if the extended biomes count - Biome.oceanM, - Biome.deepOceanM, - Biome.frozenOceanM, - Biome.riverM, - Biome.frozenRiverM, - } - ); - - private Random random = new Random(); - - public OceanMonumentLayer() { - } - - @Override - public boolean isVisible() { - return Options.instance.showOceanMonuments.get(); - } - - @Override - public void generateMapObjects(Fragment frag) { - int size = Fragment.SIZE >> 4; - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - int chunkX = x + frag.getChunkX(); - int chunkY = y + frag.getChunkY(); - if (checkChunk(chunkX, chunkY)) { - frag.addObject(new MapObjectOceanMonument(x << 4, y << 4).setParent(this)); - } - } - } - } - - /** puts the World Random seed to a specific state dependent on the inputs */ - public void setRandomSeed(int a, int b, int structure_magic_number) - { - long positionSeed = (long)a * 341873128712L + (long)b * 132897987541L + Options.instance.seed + (long)structure_magic_number; - random.setSeed(positionSeed); - } - - - public boolean checkChunk(int chunkX, int chunkY) { - - boolean result = false; - - byte maxDistanceBetweenScatteredFeatures = 32; - byte minDistanceBetweenScatteredFeatures = 5; - int structureSize = 29; - int structureMagicNumber = 10387313; // 10387313 is the magic salt for ocean monuments - - int chunkXadj = chunkX; - int chunkYadj = chunkY; - if (chunkXadj < 0) chunkXadj -= maxDistanceBetweenScatteredFeatures - 1; - if (chunkYadj < 0) chunkYadj -= maxDistanceBetweenScatteredFeatures - 1; - - int a = chunkXadj / maxDistanceBetweenScatteredFeatures; - int b = chunkYadj / maxDistanceBetweenScatteredFeatures; - - setRandomSeed(a, b, structureMagicNumber); - - int distanceRange = maxDistanceBetweenScatteredFeatures - minDistanceBetweenScatteredFeatures; - a *= maxDistanceBetweenScatteredFeatures; - b *= maxDistanceBetweenScatteredFeatures; - a += (random.nextInt(distanceRange) + random.nextInt(distanceRange)) / 2; - b += (random.nextInt(distanceRange) + random.nextInt(distanceRange)) / 2; - - if ((chunkX == a) && (chunkY == b)) { - - // Note that getBiomeAt() is full-resolution biome data, while isValidBiome() is calculated using - // quarter-resolution biome data. This is identical to how Minecraft calculates it. - result = - MinecraftUtil.getBiomeAt( chunkX * 16 + 8, chunkY * 16 + 8) == Biome.deepOcean && - MinecraftUtil.isValidBiome(chunkX * 16 + 8, chunkY * 16 + 8, structureSize, validSurroundingBiomes); - } - return result; - } -} diff --git a/src/amidst/map/layers/PlayerLayer.java b/src/amidst/map/layers/PlayerLayer.java deleted file mode 100644 index d5045db58..000000000 --- a/src/amidst/map/layers/PlayerLayer.java +++ /dev/null @@ -1,59 +0,0 @@ -package amidst.map.layers; - -import java.util.List; -import MoF.SaveLoader; -import MoF.SkinManager; -import amidst.Options; -import amidst.map.Fragment; -import amidst.map.IconLayer; -import amidst.map.MapObjectPlayer; - -public class PlayerLayer extends IconLayer { - public SaveLoader saveLoader; - public static SkinManager skinManager = new SkinManager(); - public boolean isEnabled; - static { - skinManager.start(); - } - public PlayerLayer() { - - } - - @Override - public boolean isVisible() { - return Options.instance.showPlayers.get(); - } - - @Override - public void generateMapObjects(Fragment frag) { - if (!isEnabled) return; - List players = saveLoader.getPlayers(); - for (MapObjectPlayer player : players) { - if ((player.globalX >= frag.blockX) && - (player.globalX < frag.blockX + Fragment.SIZE) && - (player.globalY >= frag.blockY) && - (player.globalY < frag.blockY + Fragment.SIZE)) { - player.parentLayer = this; - player.parentFragment = frag; - frag.addObject(player); - } - } - } - - @Override - public void clearMapObjects(Fragment frag) { - for (int i = 0; i < frag.objectsLength; i++) { - if (frag.objects[i] instanceof MapObjectPlayer) - ((MapObjectPlayer)frag.objects[i]).parentFragment = null; - - } - super.clearMapObjects(frag); - - } - public void setPlayers(SaveLoader save) { - saveLoader = save; - - for (MapObjectPlayer player : saveLoader.getPlayers()) - skinManager.addPlayer(player); - } -} diff --git a/src/amidst/map/layers/SlimeLayer.java b/src/amidst/map/layers/SlimeLayer.java deleted file mode 100644 index 08332db42..000000000 --- a/src/amidst/map/layers/SlimeLayer.java +++ /dev/null @@ -1,41 +0,0 @@ -package amidst.map.layers; - -import java.util.Random; - -import amidst.Options; -import amidst.map.Fragment; -import amidst.map.ImageLayer; - -public class SlimeLayer extends ImageLayer { - private static int size = Fragment.SIZE >> 4; - private Random random = new Random(); - public SlimeLayer() { - super(size); - } - - @Override - public boolean isVisible() { - return Options.instance.showSlimeChunks.get(); - } - - @Override - public void drawToCache(Fragment fragment) { - int[] dataCache = Fragment.getIntArray(); - for (int y = 0; y < size; y++) { - for (int x = 0; x < size; x++) { - int xPosition = fragment.getChunkX() + x; - int yPosition = fragment.getChunkY() + y; - random.setSeed(Options.instance.seed + - xPosition * xPosition * 0x4c1906 + - xPosition * 0x5ac0db + - yPosition * yPosition * 0x4307a7L + - yPosition * 0x5f24f ^ 0x3ad8025f); - - dataCache[y * size + x] = (random.nextInt(10) == 0) ? 0xA0FF00FF : 0x00000000; - } - } - - fragment.setImageData(layerId, dataCache); - } - -} diff --git a/src/amidst/map/layers/SpawnLayer.java b/src/amidst/map/layers/SpawnLayer.java deleted file mode 100644 index 0cd011335..000000000 --- a/src/amidst/map/layers/SpawnLayer.java +++ /dev/null @@ -1,68 +0,0 @@ -package amidst.map.layers; - -import java.awt.Point; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Random; - -import amidst.Options; -import amidst.logging.Log; -import amidst.map.Fragment; -import amidst.map.IconLayer; -import amidst.map.MapObjectSpawn; -import amidst.minecraft.Biome; -import amidst.minecraft.MinecraftUtil; - -public class SpawnLayer extends IconLayer { - private MapObjectSpawn spawnObject; - public static final ArrayList validBiomes = new ArrayList(Arrays.asList( - Biome.forest, - Biome.plains, - Biome.taiga, - Biome.taigaHills, - Biome.forestHills, - Biome.jungle, - Biome.jungleHills - )); - - public SpawnLayer() { - } - - @Override - public boolean isVisible() { - return Options.instance.showSpawn.get(); - } - - @Override - public void generateMapObjects(Fragment frag) { - if ((spawnObject.globalX >= frag.blockX) && - (spawnObject.globalX < frag.blockX + Fragment.SIZE) && - (spawnObject.globalY >= frag.blockY) && - (spawnObject.globalY < frag.blockY + Fragment.SIZE)) { - spawnObject.parentLayer = this; - frag.addObject(spawnObject); - } - } - - private Point getSpawnPosition() { - Random random = new Random(Options.instance.seed); - Point location = MinecraftUtil.findValidLocation(0, 0, 256, validBiomes, random); - int x = 0; - int y = 0; - if (location != null) { - x = location.x; - y = location.y; - } else { - Log.debug("Unable to find spawn biome."); - } - - return new Point(x, y); - } - - @Override - public void reload() { - Point spawnCenter = getSpawnPosition(); - spawnObject = new MapObjectSpawn(spawnCenter.x, spawnCenter.y); - } - -} diff --git a/src/amidst/map/layers/StrongholdLayer.java b/src/amidst/map/layers/StrongholdLayer.java deleted file mode 100644 index fd588d23f..000000000 --- a/src/amidst/map/layers/StrongholdLayer.java +++ /dev/null @@ -1,149 +0,0 @@ -package amidst.map.layers; - -import java.awt.Point; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import amidst.Options; -import amidst.map.Fragment; -import amidst.map.IconLayer; -import amidst.map.MapObjectStronghold; -import amidst.minecraft.Biome; -import amidst.minecraft.MinecraftUtil; -import amidst.version.VersionInfo; - -public class StrongholdLayer extends IconLayer { - public static StrongholdLayer instance; - - private static final Biome[] biomesDefault = { - Biome.desert, - Biome.forest, - Biome.extremeHills, - Biome.swampland - }; - private static final Biome[] biomes1_0 = { - Biome.desert, - Biome.forest, - Biome.extremeHills, - Biome.swampland, - Biome.taiga, - Biome.icePlains, - Biome.iceMountains - }; - private static final Biome[] biomes1_1 = { - Biome.desert, - Biome.forest, - Biome.extremeHills, - Biome.swampland, - Biome.taiga, - Biome.icePlains, - Biome.iceMountains, - Biome.desertHills, - Biome.forestHills, - Biome.extremeHillsEdge - }; - private static final Biome[] biomes12w03a = { - Biome.desert, - Biome.forest, - Biome.extremeHills, - Biome.swampland, - Biome.taiga, - Biome.icePlains, - Biome.iceMountains, - Biome.desertHills, - Biome.forestHills, - Biome.extremeHillsEdge, - Biome.jungle, - Biome.jungleHills - }; - - private MapObjectStronghold[] strongholds = new MapObjectStronghold[3]; - - public StrongholdLayer() { - instance = this; - } - - @Override - public boolean isVisible() { - return Options.instance.showStrongholds.get(); - } - - @Override - public void generateMapObjects(Fragment frag) { - int size = Fragment.SIZE >> 4; - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - int chunkX = x + frag.getChunkX(); - int chunkY = y + frag.getChunkY(); - if (checkChunk(chunkX, chunkY)) { // TODO: This does not need a per-chunk test! - // FIXME: Possible use of checkChunk causing negative icons to be misaligned! - frag.addObject(new MapObjectStronghold(x << 4, y << 4).setParent(this)); - } - } - } - } - - public void findStrongholds() { - Random random = new Random(); - random.setSeed(Options.instance.seed); - - - // TODO: Replace this system! - Biome[] validBiomes = biomesDefault; - if (MinecraftUtil.getVersion() == VersionInfo.V1_9pre6 || MinecraftUtil.getVersion() == VersionInfo.V1_0) - validBiomes = biomes1_0; - if (MinecraftUtil.getVersion() == VersionInfo.V1_1) - validBiomes = biomes1_1; - if (MinecraftUtil.getVersion().isAtLeast(VersionInfo.V12w03a)) - validBiomes = biomes12w03a; - - List biomeArrayList = Arrays.asList(validBiomes); - - if (MinecraftUtil.getVersion().isAtLeast(VersionInfo.V13w36a)) { - biomeArrayList = new ArrayList(); - for (int i = 0; i < Biome.biomes.length; i++) { - if ((Biome.biomes[i] != null) && (Biome.biomes[i].type.value1 > 0f)) { - biomeArrayList.add(Biome.biomes[i]); - } - } - } - - double angle = random.nextDouble() * 3.141592653589793D * 2.0D; - for (int i = 0; i < 3; i++) { - double distance = (1.25D + random.nextDouble()) * 32.0D; - int x = (int)Math.round(Math.cos(angle) * distance); - int y = (int)Math.round(Math.sin(angle) * distance); - - - - Point strongholdLocation = MinecraftUtil.findValidLocation((x << 4) + 8, (y << 4) + 8, 112, biomeArrayList, random); - if (strongholdLocation != null) { - x = strongholdLocation.x >> 4; - y = strongholdLocation.y >> 4; - } - strongholds[i] = new MapObjectStronghold((x << 4), (y << 4)); - angle += 6.283185307179586D / 3.0D; - } - } - - public boolean checkChunk(int chunkX, int chunkY) { - for (int i = 0; i < 3; i++) { - int strongholdChunkX = strongholds[i].x >> 4; - int strongholdChunkY = strongholds[i].y >> 4; - if ((strongholdChunkX == chunkX) && (strongholdChunkY == chunkY)) - return true; - } - return false; - } - - public MapObjectStronghold[] getStrongholds() { - return strongholds; - } - - @Override - public void reload() { - findStrongholds(); - } -} diff --git a/src/amidst/map/layers/TempleLayer.java b/src/amidst/map/layers/TempleLayer.java deleted file mode 100644 index 68d50d4eb..000000000 --- a/src/amidst/map/layers/TempleLayer.java +++ /dev/null @@ -1,119 +0,0 @@ -package amidst.map.layers; - -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import amidst.Options; -import amidst.logging.Log; -import amidst.map.Fragment; -import amidst.map.IconLayer; -import amidst.map.MapObjectDesertTemple; -import amidst.map.MapObjectJungleTemple; -import amidst.map.MapObjectWitchHut; -import amidst.minecraft.Biome; -import amidst.minecraft.MinecraftUtil; -import amidst.version.VersionInfo; - -public class TempleLayer extends IconLayer { - public static List validBiomes; - private Random random = new Random(); - - public TempleLayer() { - validBiomes = getValidBiomes(); - } - - @Override - public boolean isVisible() { - return Options.instance.showTemples.get(); - } - - @Override - public void generateMapObjects(Fragment frag) { - int size = Fragment.SIZE >> 4; - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - int chunkX = x + frag.getChunkX(); - int chunkY = y + frag.getChunkY(); - Biome chunkBiome = checkChunk(chunkX, chunkY); - if (chunkBiome != null) { - - if (chunkBiome == Biome.swampland) { - frag.addObject(new MapObjectWitchHut(x << 4, y << 4).setParent(this)); - } else if (chunkBiome.name.contains("Jungle")) { - frag.addObject(new MapObjectJungleTemple(x << 4, y << 4).setParent(this)); - } else if (chunkBiome.name.contains("Desert")) { - frag.addObject(new MapObjectDesertTemple(x << 4, y << 4).setParent(this)); - } else { - Log.e("No known structure for this biome type. checkChunk() may be faulting."); - } - } - } - } - } - - public List getValidBiomes() { - Biome[] validBiomes; - - if (MinecraftUtil.getVersion().isAtLeast(VersionInfo.V1_4_2)) { - validBiomes = new Biome[] { - Biome.desert, - Biome.desertHills, - Biome.jungle, - Biome.jungleHills, - Biome.swampland - }; - } else if (MinecraftUtil.getVersion().isAtLeast(VersionInfo.V12w22a)) { - validBiomes = new Biome[] { - Biome.desert, - Biome.desertHills, - Biome.jungle - }; - } else { - validBiomes = new Biome[] { - Biome.desert, - Biome.desertHills - }; - } - - return Arrays.asList(validBiomes); - } - - /** - * @return null if there is no structure in the chunk, otherwise - * returns the biome (from validBiomes) that determines the type - * of structure. - */ - public Biome checkChunk(int chunkX, int chunkY) { - - Biome result = null; - - int maxDistanceBetweenScatteredFeatures = 32; - int minDistanceBetweenScatteredFeatures = 8; - - int k = chunkX; - int m = chunkY; - if (chunkX < 0) chunkX -= maxDistanceBetweenScatteredFeatures - 1; - if (chunkY < 0) chunkY -= maxDistanceBetweenScatteredFeatures - 1; - - int n = chunkX / maxDistanceBetweenScatteredFeatures; - int i1 = chunkY / maxDistanceBetweenScatteredFeatures; - long l1 = n * 341873128712L + i1 * 132897987541L + Options.instance.seed + 14357617; - random.setSeed(l1); - n *= maxDistanceBetweenScatteredFeatures; - i1 *= maxDistanceBetweenScatteredFeatures; - n += random.nextInt(maxDistanceBetweenScatteredFeatures - minDistanceBetweenScatteredFeatures); - i1 += random.nextInt(maxDistanceBetweenScatteredFeatures - minDistanceBetweenScatteredFeatures); - - if (k == n && m == i1) { - // This is a potential feature biome - - // Since the structure-size that would be passed to MinecraftUtil.isValidBiome() - // is 0, we can use MinecraftUtil.getBiomeAt() here instead, which tells us what kind of - // structure it is. - Biome chunkBiome = MinecraftUtil.getBiomeAt(k * 16 + 8, m * 16 + 8); - if (validBiomes.contains(chunkBiome)) result = chunkBiome; - } - return result; - } -} diff --git a/src/amidst/map/layers/VillageLayer.java b/src/amidst/map/layers/VillageLayer.java deleted file mode 100644 index 8b7eb245b..000000000 --- a/src/amidst/map/layers/VillageLayer.java +++ /dev/null @@ -1,69 +0,0 @@ -package amidst.map.layers; - -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import amidst.Options; -import amidst.map.Fragment; -import amidst.map.IconLayer; -import amidst.map.MapObjectVillage; -import amidst.minecraft.Biome; -import amidst.minecraft.MinecraftUtil; - -public class VillageLayer extends IconLayer { - public static List validBiomes = Arrays.asList(new Biome[] { Biome.plains, Biome.desert, Biome.savanna}); - private Random random = new Random(); - - public VillageLayer() { - } - - @Override - public boolean isVisible() { - return Options.instance.showVillages.get(); - } - - @Override - public void generateMapObjects(Fragment frag) { - int size = Fragment.SIZE >> 4; - for (int x = 0; x < size; x++) { - for (int y = 0; y < size; y++) { - int chunkX = x + frag.getChunkX(); - int chunkY = y + frag.getChunkY(); - if (checkChunk(chunkX, chunkY)) { - frag.addObject(new MapObjectVillage(x << 4, y << 4).setParent(this)); - } - } - } - } - - - public boolean checkChunk(int chunkX, int chunkY) { - byte villageParam1 = 32; - byte villageParam2 = 8; - - int k = chunkX; - int m = chunkY; - if (chunkX < 0) chunkX -= villageParam1 - 1; - if (chunkY < 0) chunkY -= villageParam1 - 1; - - int n = chunkX / villageParam1; - int i1 = chunkY / villageParam1; - - long positionSeed = n * 341873128712L + i1 * 132897987541L + Options.instance.seed + 10387312L; - random.setSeed(positionSeed); - - - - n *= villageParam1; - i1 *= villageParam1; - n += random.nextInt(villageParam1 - villageParam2); - i1 += random.nextInt(villageParam1 - villageParam2); - chunkX = k; - chunkY = m; - if ((chunkX == n) && (chunkY == i1)) - return MinecraftUtil.isValidBiome(chunkX * 16 + 8, chunkY * 16 + 8, 0, validBiomes); - - return false; - } -} diff --git a/src/amidst/map/widget/BiomeToggleWidget.java b/src/amidst/map/widget/BiomeToggleWidget.java deleted file mode 100644 index 3f85a8dad..000000000 --- a/src/amidst/map/widget/BiomeToggleWidget.java +++ /dev/null @@ -1,36 +0,0 @@ -package amidst.map.widget; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; - -import amidst.map.layers.BiomeLayer; -import amidst.resources.ResourceLoader; -import MoF.MapViewer; - -public class BiomeToggleWidget extends PanelWidget { - private static BufferedImage highlighterIcon = ResourceLoader.getImage("highlighter.png"); - public static boolean isBiomeWidgetVisible = false; - public BiomeToggleWidget(MapViewer mapViewer) { - super(mapViewer); - setDimensions(36, 36); - } - - @Override - public void draw(Graphics2D g2d, float time) { - super.draw(g2d, time); - g2d.drawImage(highlighterIcon, x, y, 36, 36, null); - } - - @Override - public boolean onMousePressed(int x, int y) { - isBiomeWidgetVisible = !isBiomeWidgetVisible; - BiomeLayer.instance.setHighlightMode(isBiomeWidgetVisible); - (new Thread(new Runnable() { - @Override - public void run() { - map.resetImageLayer(BiomeLayer.instance.getLayerId()); - } - })).start(); - return true; - } -} diff --git a/src/amidst/map/widget/BiomeWidget.java b/src/amidst/map/widget/BiomeWidget.java deleted file mode 100644 index 4aa0647d9..000000000 --- a/src/amidst/map/widget/BiomeWidget.java +++ /dev/null @@ -1,215 +0,0 @@ -package amidst.map.widget; - -import java.awt.Color; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.util.ArrayList; - -import amidst.logging.Log; -import amidst.map.layers.BiomeLayer; -import amidst.minecraft.Biome; -import MoF.MapViewer; - -public class BiomeWidget extends PanelWidget { - private static BiomeWidget instance; - private static Color innerBoxBgColor = new Color(0.3f, 0.3f, 0.3f, 0.3f); - private static Color biomeBgColor1 = new Color(0.8f, 0.8f, 0.8f, 0.2f); - private static Color biomeBgColor2 = new Color(0.6f, 0.6f, 0.6f, 0.2f); - private static Color biomeLitBgColor1 = new Color(0.8f, 0.8f, 1.0f, 0.7f); - private static Color biomeLitBgColor2 = new Color(0.6f, 0.6f, 0.8f, 0.7f); - private static Color innerBoxBorderColor = new Color(1.0f, 1.0f, 1.0f, 1.0f); - private static Color scrollbarColor = new Color(0.6f, 0.6f, 0.6f, 0.8f); - private static Color scrollbarLitColor = new Color(0.6f, 0.6f, 0.8f, 0.8f); - private static Color selectButtonColor = new Color(0.6f, 0.6f, 0.8f, 1.0f); - - private ArrayList biomes = new ArrayList(); - private int maxNameWidth = 0; - private Rectangle innerBox = new Rectangle(0, 0, 1, 1); - - private int biomeListHeight; - private int biomeListYOffset = 0; - private boolean scrollbarVisible = false; - private boolean scrollbarGrabbed = false; - private int scrollbarHeight = 0, scrollbarWidth = 10, scrollbarY = 0, mouseYOnGrab = 0, scrollbarYOnGrab; - - public BiomeWidget(MapViewer mapViewer) { - super(mapViewer); - - FontMetrics fontMetrics = mapViewer.getFontMetrics(textFont); - for (int i = 0; i < Biome.biomes.length; i++) { - if (Biome.biomes[i] != null) { - biomes.add(Biome.biomes[i]); - maxNameWidth = Math.max(fontMetrics.stringWidth(Biome.biomes[i].name), maxNameWidth); - } - } - biomeListHeight = biomes.size() * 16; - setDimensions(250, 400); - y = 100; - forceVisibility(false); - } - - @Override - public void draw(Graphics2D g2d, float time) { - x = mapViewer.getWidth() - width; - super.draw(g2d, time); - g2d.setColor(textColor); - g2d.setFont(textFont); - g2d.drawString("Highlight Biomes", x + 10, y + 20); - - innerBox.x = x + 8; - innerBox.y = y + 30; - innerBox.width = width - 16; - innerBox.height = height - 58; - - biomeListYOffset = Math.min(0, Math.max(-biomeListHeight + innerBox.height, biomeListYOffset)); - - if (biomeListHeight > innerBox.height) { - innerBox.width -= scrollbarWidth; - scrollbarVisible = true; - } else { - scrollbarVisible = false; - } - - g2d.setColor(innerBoxBgColor); - g2d.fillRect(innerBox.x, innerBox.y, innerBox.width, innerBox.height); - g2d.setColor(innerBoxBorderColor); - g2d.drawRect(innerBox.x - 1, innerBox.y - 1, innerBox.width + 1 + (scrollbarVisible?scrollbarWidth:0), innerBox.height + 1); - g2d.setClip(innerBox); - - for (int i = 0; i < biomes.size(); i++) { - Biome biome = biomes.get(i); - if (BiomeLayer.instance.isBiomeSelected(biome.index)) - g2d.setColor(((i % 2) == 1)?biomeLitBgColor1:biomeLitBgColor2); - else - g2d.setColor(((i % 2) == 1)?biomeBgColor1:biomeBgColor2); - g2d.fillRect(innerBox.x, innerBox.y + i * 16 + biomeListYOffset,innerBox.width, 16); - g2d.setColor(new Color(biome.color)); - g2d.fillRect(innerBox.x, innerBox.y + i*16 + biomeListYOffset, 20, 16); - g2d.setColor(Color.white); - g2d.drawString(biome.name, innerBox.x + 25, innerBox.y + 13 + i*16 + biomeListYOffset); - } - - - g2d.setClip(null); - - if (scrollbarVisible) { - float boxHeight = innerBox.height; - float listHeight = biomeListHeight; - - if (scrollbarGrabbed) { - Point mouse = mapViewer.getMousePosition(); - if (mouse != null) { - int tempScrollbarY = - scrollbarYOnGrab - (mouse.y - mouseYOnGrab); - biomeListYOffset = (int)((listHeight/boxHeight) * tempScrollbarY); - biomeListYOffset = Math.min(0, Math.max(-biomeListHeight + innerBox.height, biomeListYOffset)); - } else { - scrollbarGrabbed = false; - } - } - - float yOffset = -biomeListYOffset; - - scrollbarY = (int) ((yOffset/listHeight) * boxHeight); - scrollbarHeight = (int) (Math.ceil(boxHeight * (boxHeight/listHeight))); - g2d.setColor(scrollbarGrabbed?scrollbarLitColor:scrollbarColor); - g2d.fillRect(innerBox.x + innerBox.width, innerBox.y + scrollbarY, scrollbarWidth, scrollbarHeight); - } - - - g2d.setColor(Color.white); - g2d.drawString("Select:", x + 8, y + height - 10); - g2d.setColor(selectButtonColor); - g2d.drawString("All Special None", x + 120, y + height - 10); - - } - - @Override - public boolean onMouseWheelMoved(int mouseX, int mouseY, int notches) { - if ((mouseX > innerBox.x - x) && - (mouseX < innerBox.x - x + innerBox.width) && - (mouseY > innerBox.y - y) && - (mouseY < innerBox.y - y + innerBox.height)) { - biomeListYOffset = Math.min(0, Math.max(-biomeListHeight + innerBox.height, biomeListYOffset - notches * 35)); - } - return true; - } - - @Override - public void onMouseReleased() { - scrollbarGrabbed = false; - } - - @Override - public boolean onMousePressed(int mouseX, int mouseY) { - if (scrollbarVisible) { - if ((mouseX > innerBox.x - x + innerBox.width) && - (mouseX < innerBox.x - x + innerBox.width + scrollbarWidth) && - (mouseY > innerBox.y - y + scrollbarY) && - (mouseY < innerBox.y - y + scrollbarY + scrollbarHeight)) { - - mouseYOnGrab = mouseY + y; - scrollbarYOnGrab = scrollbarY; - scrollbarGrabbed = true; - } - } - - boolean needsRedraw = false; - if ((mouseX > innerBox.x - x) && - (mouseX < innerBox.x - x + innerBox.width) && - (mouseY > innerBox.y - y) && - (mouseY < innerBox.y - y + innerBox.height)) { - int id = (mouseY - (innerBox.y - y) - biomeListYOffset) / 16; - if (id < biomes.size()) { - BiomeLayer.instance.toggleBiomeSelect(biomes.get(id).index); - needsRedraw = true; - } - } - - // TODO: These values are temporarly hard coded for the sake of a fast release - if ((mouseY > height - 25) && (mouseY < height - 9)) { - if ((mouseX > 117) && (mouseX < 139)) { - BiomeLayer.instance.selectAllBiomes(); - needsRedraw = true; - } else if ((mouseX > 143) && (mouseX < 197)) { - for (int i = 128; i < Biome.biomes.length; i++) - if (Biome.biomes[i] != null) - BiomeLayer.instance.selectBiome(i); - needsRedraw = true; - } else if ((mouseX > 203) && (mouseX < 242)) { - BiomeLayer.instance.deselectAllBiomes(); - needsRedraw = true; - } - } - if (needsRedraw) { - (new Thread(new Runnable() { - @Override - public void run() { - map.resetImageLayer(BiomeLayer.instance.getLayerId()); - } - })).start(); - } - return true; - } - - @Override - public boolean onVisibilityCheck() { - height = Math.max(200, mapViewer.getHeight() - 200); - return BiomeToggleWidget.isBiomeWidgetVisible & (height > 200); - } - - private void setMapViewer(MapViewer mapViewer) { - this.mapViewer = mapViewer; - this.map = mapViewer.getMap(); - scrollbarGrabbed = false; - } - - public static BiomeWidget get(MapViewer mapViewer) { - if (instance == null) - instance = new BiomeWidget(mapViewer); - else - instance.setMapViewer(mapViewer); - return instance; - } -} diff --git a/src/amidst/map/widget/CursorInformationWidget.java b/src/amidst/map/widget/CursorInformationWidget.java deleted file mode 100644 index 4a198ca0b..000000000 --- a/src/amidst/map/widget/CursorInformationWidget.java +++ /dev/null @@ -1,37 +0,0 @@ -package amidst.map.widget; - -import java.awt.Graphics2D; -import java.awt.Point; - -import MoF.MapViewer; - -public class CursorInformationWidget extends PanelWidget { - private String message = ""; - - public CursorInformationWidget(MapViewer mapViewer) { - super(mapViewer); - setDimensions(20, 30); - forceVisibility(false); - } - - @Override - public void draw(Graphics2D g2d, float time) { - Point mouseLocation = null; - if ((mouseLocation = mapViewer.getMousePosition()) != null) { - mouseLocation = map.screenToLocal(mouseLocation); - String biomeName = map.getBiomeAliasAt(mouseLocation); - message = biomeName + " [ " + mouseLocation.x + ", " + mouseLocation.y + " ]"; - } - int stringWidth = mapViewer.getFontMetrics().stringWidth(message); - setWidth(stringWidth + 20); - super.draw(g2d, time); - - g2d.setColor(textColor); - g2d.drawString(message, x + 10, y + 20); - } - - @Override - protected boolean onVisibilityCheck() { - return (mapViewer.getMousePosition() != null); - } -} diff --git a/src/amidst/map/widget/DebugWidget.java b/src/amidst/map/widget/DebugWidget.java deleted file mode 100644 index 15d8b97a0..000000000 --- a/src/amidst/map/widget/DebugWidget.java +++ /dev/null @@ -1,51 +0,0 @@ -package amidst.map.widget; - -import java.awt.Graphics2D; -import java.util.ArrayList; - -import MoF.MapViewer; -import amidst.Options; -import amidst.map.FragmentManager; - -public class DebugWidget extends PanelWidget { - public DebugWidget(MapViewer mapViewer) { - super(mapViewer); - forceVisibility(onVisibilityCheck()); - } - - @Override - public void draw(Graphics2D g2d, float time) { - FragmentManager fragmentManager = mapViewer.getFragmentManager(); - ArrayList panelText = new ArrayList(); - panelText.add("Fragment Manager:"); - panelText.add("Pool Size: " + fragmentManager.getCacheSize()); - panelText.add("Free Queue Size: " + fragmentManager.getFreeFragmentQueueSize()); - panelText.add("Request Queue Size: " + fragmentManager.getRequestQueueSize()); - panelText.add("Recycle Queue Size: " + fragmentManager.getRecycleQueueSize()); - panelText.add(""); - panelText.add("Map Viewer:"); - panelText.add("Map Size: " + map.tileWidth + "x" + map.tileHeight + " [" + (map.tileWidth * map.tileHeight) + "]"); - - int width = 0, height; - for (int i = 0; i < panelText.size(); i++) { - int textWidth = mapViewer.getFontMetrics().stringWidth(panelText.get(i)); - if (textWidth > width) - width = textWidth; - } - - width += 20; - height = panelText.size() * 20 + 10; - - setDimensions(width, height); - super.draw(g2d, time); - - g2d.setColor(textColor); - for (int i = 0; i < panelText.size(); i++) - g2d.drawString(panelText.get(i), x + 10, y + 20 + i*20); - } - - @Override - protected boolean onVisibilityCheck() { - return Options.instance.showDebug.get(); - } -} diff --git a/src/amidst/map/widget/FpsWidget.java b/src/amidst/map/widget/FpsWidget.java deleted file mode 100644 index 197bac045..000000000 --- a/src/amidst/map/widget/FpsWidget.java +++ /dev/null @@ -1,32 +0,0 @@ -package amidst.map.widget; - -import java.awt.Graphics2D; - -import MoF.MapViewer; -import amidst.Options; -import amidst.utilties.FramerateTimer; - -public class FpsWidget extends PanelWidget { - private FramerateTimer fps = new FramerateTimer(2); - public FpsWidget(MapViewer mapViewer) { - super(mapViewer); - setDimensions(20, 30); - forceVisibility(onVisibilityCheck()); - } - - @Override - public void draw(Graphics2D g2d, float time) { - String framerate = fps.toString(); - setWidth(mapViewer.getFontMetrics().stringWidth(framerate) + 20); - super.draw(g2d, time); - - fps.tick(); - g2d.setColor(textColor); - g2d.drawString(framerate, x + 10, y + 20); - } - - @Override - protected boolean onVisibilityCheck() { - return Options.instance.showFPS.get(); - } -} diff --git a/src/amidst/map/widget/PanelWidget.java b/src/amidst/map/widget/PanelWidget.java deleted file mode 100644 index ac0d26b05..000000000 --- a/src/amidst/map/widget/PanelWidget.java +++ /dev/null @@ -1,142 +0,0 @@ -package amidst.map.widget; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Stroke; -import java.awt.image.BufferedImage; - -import amidst.resources.ResourceLoader; -import MoF.MapViewer; - -public class PanelWidget extends Widget { - private static BufferedImage - dropShadowBottomLeft = ResourceLoader.getImage("dropshadow/outer_bottom_left.png"), - dropShadowBottomRight = ResourceLoader.getImage("dropshadow/outer_bottom_right.png"), - dropShadowTopLeft = ResourceLoader.getImage("dropshadow/outer_top_left.png"), - dropShadowTopRight = ResourceLoader.getImage("dropshadow/outer_top_right.png"), - dropShadowBottom = ResourceLoader.getImage("dropshadow/outer_bottom.png"), - dropShadowTop = ResourceLoader.getImage("dropshadow/outer_top.png"), - dropShadowLeft = ResourceLoader.getImage("dropshadow/outer_left.png"), - dropShadowRight = ResourceLoader.getImage("dropshadow/outer_right.png"); - public enum CornerAnchorPoint { - TOP_LEFT, - TOP_RIGHT, - BOTTOM_LEFT, - BOTTOM_RIGHT, - BOTTOM_CENTER, - CENTER, - NONE - } - protected Color textColor = new Color(1f, 1f, 1f); - protected Color panelColor = new Color(0.15f, 0.15f, 0.15f, 0.8f); - protected Font textFont = new Font("arial", Font.BOLD, 15); - protected Stroke lineStroke1 = new BasicStroke(1); - protected Stroke lineStroke2 = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - protected CornerAnchorPoint anchor = CornerAnchorPoint.NONE; - protected int xPadding = 10, yPadding = 10; - - protected float alpha = 1.0f, targetAlpha = 1.0f; - protected boolean isFading = false; - protected boolean targetVisibility = true; - - public PanelWidget(MapViewer mapViewer) { - super(mapViewer); - } - - @Override - public void draw(Graphics2D g2d, float time) { - targetAlpha = targetVisibility?1.0f:0.0f; - if (alpha < targetAlpha) - alpha = Math.min(targetAlpha, alpha + time*4.0f); - else if (alpha > targetAlpha) - alpha = Math.max(targetAlpha, alpha - time*4.0f); - isFading = (alpha != targetAlpha); - - updatePosition(); - g2d.setColor(panelColor); - g2d.drawImage(dropShadowTopLeft, x - 10, y - 10, null); - g2d.drawImage(dropShadowTopRight, x + width, y - 10, null); - g2d.drawImage(dropShadowBottomLeft, x - 10, y + height, null); - g2d.drawImage(dropShadowBottomRight, x + width, y + height, null); - - g2d.drawImage(dropShadowTop, x, y - 10, width, 10, null); - g2d.drawImage(dropShadowBottom, x, y + height, width, 10, null); - g2d.drawImage(dropShadowLeft, x - 10, y, 10, height, null); - g2d.drawImage(dropShadowRight, x + width, y, 10, height, null); - - g2d.fillRect(x, y, width, height); - } - - public void setWidth(int width) { - this.width = width; - } - public void setHeight(int height) { - this.height = height; - } - - public void setDimensions(int width, int height) { - this.width = width; - this.height = height; - } - - protected void updatePosition() { - switch (anchor) { - case TOP_LEFT: - x = xPadding; - y = yPadding; - break; - case BOTTOM_LEFT: - x = xPadding; - y = mapViewer.getHeight() - (height + yPadding); - break; - case BOTTOM_RIGHT: - x = mapViewer.getWidth() - (width + xPadding); - y = mapViewer.getHeight() - (height + yPadding); - break; - case BOTTOM_CENTER: - x = (mapViewer.getWidth() >> 1) - (width >> 1); - y = mapViewer.getHeight() - (height + yPadding); - break; - case TOP_RIGHT: - x = mapViewer.getWidth() - (width + xPadding); - y = yPadding; - break; - case CENTER: - x = (mapViewer.getWidth() >> 1) - (width >> 1); - y = (mapViewer.getHeight() >> 1) - (height >> 1); - break; - case NONE: - break; - } - } - - @Override - public boolean isVisible() { - boolean value = (visible && targetVisibility) || isFading; - targetVisibility = onVisibilityCheck(); - return value; - } - - protected boolean onVisibilityCheck() { - return visible; - } - - public void forceVisibility(boolean value) { - targetVisibility = value; - isFading = false; - targetAlpha = value?1.0f:0.0f; - alpha = value?1.0f:0.0f; - } - - @Override - public float getAlpha() { - return alpha; - } - - public PanelWidget setAnchorPoint(CornerAnchorPoint anchor) { - this.anchor = anchor; - return this; - } -} diff --git a/src/amidst/map/widget/ScaleWidget.java b/src/amidst/map/widget/ScaleWidget.java deleted file mode 100644 index 0daedbfce..000000000 --- a/src/amidst/map/widget/ScaleWidget.java +++ /dev/null @@ -1,67 +0,0 @@ -package amidst.map.widget; - -import java.awt.Color; -import java.awt.Graphics2D; - -import amidst.Options; -import MoF.MapViewer; - -public class ScaleWidget extends PanelWidget { - - public static int cScaleLengthMax_px = 200; - public static int cMargin = 8; - - public ScaleWidget(MapViewer mapViewer) { - super(mapViewer); - setDimensions(100, 34); - forceVisibility(false); - } - - @Override - public void draw(Graphics2D g2d, float time) { - - int scaleBlocks = scaleLength_blocks(); - int scaleWidth_px = (int)(scaleBlocks * map.getZoom()); - - String message = scaleBlocks + " blocks"; - - int stringWidth = mapViewer.getFontMetrics().stringWidth(message); - setWidth(Math.max(scaleWidth_px, stringWidth) + (cMargin * 2)); - super.draw(g2d, time); - - g2d.setColor(textColor); - g2d.setFont(textFont); - g2d.drawString(message, x + 1 + ((width - stringWidth) >> 1), y + 18); - - g2d.setColor(Color.white); - - g2d.setStroke(lineStroke2); - g2d.drawLine(x + cMargin, y + 26, x + cMargin + scaleWidth_px, y + 26); - g2d.setStroke(lineStroke1); - g2d.drawLine(x + cMargin, y + 23, x + cMargin, y + 28); - g2d.drawLine(x + cMargin + scaleWidth_px, y + 23, x + cMargin + scaleWidth_px, y + 28); - } - - @Override - protected boolean onVisibilityCheck() { - return Options.instance.showScale.get(); - } - - private int scaleLength_blocks() { - - double scale = map.getZoom(); - - int result = 1000; - if(result * scale > cScaleLengthMax_px) { - result = 500; - if(result * scale > cScaleLengthMax_px) { - result = 200; - if(result * scale > cScaleLengthMax_px) { - result = 100; - } - } - } - - return result; - } -} diff --git a/src/amidst/map/widget/SeedWidget.java b/src/amidst/map/widget/SeedWidget.java deleted file mode 100644 index cf6d5099c..000000000 --- a/src/amidst/map/widget/SeedWidget.java +++ /dev/null @@ -1,21 +0,0 @@ -package amidst.map.widget; - -import java.awt.Graphics2D; - -import MoF.MapViewer; -import amidst.Options; - -public class SeedWidget extends PanelWidget { - public SeedWidget(MapViewer mapViewer) { - super(mapViewer); - setDimensions(20, 30); - } - - @Override - public void draw(Graphics2D g2d, float time) { - setWidth(mapViewer.getFontMetrics().stringWidth(Options.instance.getSeedMessage()) + 20); - super.draw(g2d, time); - g2d.setColor(textColor); - g2d.drawString(Options.instance.getSeedMessage(), x + 10, y + 20); - } -} diff --git a/src/amidst/map/widget/SelectedObjectWidget.java b/src/amidst/map/widget/SelectedObjectWidget.java deleted file mode 100644 index 7006cc0ba..000000000 --- a/src/amidst/map/widget/SelectedObjectWidget.java +++ /dev/null @@ -1,44 +0,0 @@ -package amidst.map.widget; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; - -import amidst.map.MapObject; -import MoF.MapViewer; - -public class SelectedObjectWidget extends PanelWidget { - private String message = ""; - private BufferedImage icon; - - public SelectedObjectWidget(MapViewer mapViewer) { - super(mapViewer); - yPadding += 40; - setDimensions(20, 35); - forceVisibility(false); - } - - @Override - public void draw(Graphics2D g2d, float time) { - if (targetVisibility) { - MapObject selectedObject = mapViewer.getSelectedObject(); - message = selectedObject.getName() + " [" + selectedObject.rx + ", " + selectedObject.ry + "]"; - icon = selectedObject.getImage(); - } - - setWidth(45 + mapViewer.getFontMetrics().stringWidth(message)); - super.draw(g2d, time); - - g2d.setColor(textColor); - double imgWidth = icon.getWidth(); - double imgHeight = icon.getHeight(); - double ratio = imgWidth/imgHeight; - - g2d.drawImage(icon, x + 5, y + 5, (int)(25.*ratio), 25, null); - g2d.drawString(message, x + 35, y + 23); - } - - @Override - protected boolean onVisibilityCheck() { - return (mapViewer.getSelectedObject() != null); - } -} diff --git a/src/amidst/map/widget/Widget.java b/src/amidst/map/widget/Widget.java deleted file mode 100644 index 3b259cd72..000000000 --- a/src/amidst/map/widget/Widget.java +++ /dev/null @@ -1,61 +0,0 @@ -package amidst.map.widget; - -import java.awt.Graphics2D; - -import MoF.MapViewer; -import amidst.map.Map; - -public class Widget { - protected MapViewer mapViewer; - protected Map map; - - protected int x, y, width, height; - protected boolean visible = true; - - public Widget(MapViewer mapViewer) { - this.mapViewer = mapViewer; - this.map = mapViewer.getMap(); - } - - public void draw(Graphics2D g2d, float time) { - - } - public boolean onClick(int x, int y) { - return true; - } - - public boolean onMouseWheelMoved(int x, int y, int rotation) { - return false; - } - - public int getX() { - return x; - } - public int getY() { - return y; - } - public int getWidth() { - return width; - } - public int getHeight() { - return height; - } - - public boolean isVisible() { - return visible; - } - public void setVisibility(boolean value) { - visible = value; - } - - public float getAlpha() { - return 1.0f; - } - - public boolean onMousePressed(int x, int y) { - return true; - } - - public void onMouseReleased() { - } -} diff --git a/src/amidst/minecraft/Biome.java b/src/amidst/minecraft/Biome.java deleted file mode 100644 index f718a65c9..000000000 --- a/src/amidst/minecraft/Biome.java +++ /dev/null @@ -1,164 +0,0 @@ -package amidst.minecraft; - -import java.util.HashMap; - -import amidst.Util; - -public class Biome { - public static final HashMap biomeMap = new HashMap(); - public static final BiomeType typeA = new BiomeType(0.1F, 0.2F); - public static final BiomeType typeB = new BiomeType(-0.5F, 0.0F); - public static final BiomeType typeC = new BiomeType(-1.0F, 0.1F); - public static final BiomeType typeD = new BiomeType(-1.8F, 0.1F); - public static final BiomeType typeE = new BiomeType(0.125F, 0.05F); - public static final BiomeType typeF = new BiomeType(0.2F, 0.2F); - public static final BiomeType typeG = new BiomeType(0.45F, 0.3F); - public static final BiomeType typeH = new BiomeType(1.5F, 0.025F); - public static final BiomeType typeI = new BiomeType(1.0F, 0.5F); - public static final BiomeType typeJ = new BiomeType(0.0F, 0.025F); - public static final BiomeType typeK = new BiomeType(0.1F, 0.8F); - public static final BiomeType typeL = new BiomeType(0.2F, 0.3F); - public static final BiomeType typeM = new BiomeType(-0.2F, 0.1F); - - public static final Biome[] biomes = new Biome[256]; - public static final Biome ocean = new Biome("Ocean", 0, Util.makeColor(0, 0, 112), typeC); - public static final Biome plains = new Biome("Plains", 1, Util.makeColor(141, 179, 96), typeA); - public static final Biome desert = new Biome("Desert", 2, Util.makeColor(250, 148, 24), typeE); - public static final Biome extremeHills = new Biome("Extreme Hills", 3, Util.makeColor(96, 96, 96), typeI); - public static final Biome forest = new Biome("Forest", 4, Util.makeColor(5, 102, 33), typeA); - public static final Biome taiga = new Biome("Taiga", 5, Util.makeColor(11, 102, 89), typeF); - public static final Biome swampland = new Biome("Swampland", 6, Util.makeColor(7, 249, 178), typeM); - public static final Biome river = new Biome("River", 7, Util.makeColor(0, 0, 255), typeB); - public static final Biome hell = new Biome("Hell", 8, Util.makeColor(255, 0, 0), typeA); - public static final Biome sky = new Biome("Sky", 9, Util.makeColor(128, 128, 255), typeA); - public static final Biome frozenOcean = new Biome("Frozen Ocean", 10, Util.makeColor(144, 144, 160), typeC); - public static final Biome frozenRiver = new Biome("Frozen River", 11, Util.makeColor(160, 160, 255), typeB); - public static final Biome icePlains = new Biome("Ice Plains", 12, Util.makeColor(255, 255, 255), typeE); - public static final Biome iceMountains = new Biome("Ice Mountains", 13, Util.makeColor(160, 160, 160), typeG); - public static final Biome mushroomIsland = new Biome("Mushroom Island", 14, Util.makeColor(255, 0, 255), typeL); - public static final Biome mushroomIslandShore = new Biome("Mushroom Island Shore", 15, Util.makeColor(160, 0, 255), typeJ); - public static final Biome beach = new Biome("Beach", 16, Util.makeColor(250, 222, 85), typeJ); - public static final Biome desertHills = new Biome("Desert Hills", 17, Util.makeColor(210, 95, 18), typeG); - public static final Biome forestHills = new Biome("Forest Hills", 18, Util.makeColor(34, 85, 28), typeG); - public static final Biome taigaHills = new Biome("Taiga Hills", 19, Util.makeColor(22, 57, 51), typeG); - public static final Biome extremeHillsEdge = new Biome("Extreme Hills Edge", 20, Util.makeColor(114, 120, 154), typeI.getExtreme()); - public static final Biome jungle = new Biome("Jungle", 21, Util.makeColor(83, 123, 9), typeA); - public static final Biome jungleHills = new Biome("Jungle Hills", 22, Util.makeColor(44, 66, 5), typeG); - public static final Biome jungleEdge = new Biome("Jungle Edge", 23, Util.makeColor(98, 139, 23), typeA); - public static final Biome deepOcean = new Biome("Deep Ocean", 24, Util.makeColor(0, 0, 48), typeD); - public static final Biome stoneBeach = new Biome("Stone Beach", 25, Util.makeColor(162, 162, 132), typeK); - public static final Biome coldBeach = new Biome("Cold Beach", 26, Util.makeColor(250, 240, 192), typeJ); - public static final Biome birchForest = new Biome("Birch Forest", 27, Util.makeColor(48, 116, 68), typeA); - public static final Biome birchForestHills = new Biome("Birch Forest Hills", 28, Util.makeColor(31, 95, 50), typeG); - public static final Biome roofedForest = new Biome("Roofed Forest", 29, Util.makeColor(64, 81, 26), typeA); - public static final Biome coldTaiga = new Biome("Cold Taiga", 30, Util.makeColor(49, 85, 74), typeF); - public static final Biome coldTaigaHills = new Biome("Cold Taiga Hills", 31, Util.makeColor(36, 63, 54), typeG); - public static final Biome megaTaiga = new Biome("Mega Taiga", 32, Util.makeColor(89, 102, 81), typeF); - public static final Biome megaTaigaHills = new Biome("Mega Taiga Hills", 33, Util.makeColor(69, 79, 62), typeG); - public static final Biome extremeHillsPlus = new Biome("Extreme Hills+", 34, Util.makeColor(80, 112, 80), typeI); - public static final Biome savanna = new Biome("Savanna", 35, Util.makeColor(189, 178, 95), typeE); - public static final Biome savannaPlateau = new Biome("Savanna Plateau", 36, Util.makeColor(167, 157, 100), typeH); - public static final Biome mesa = new Biome("Mesa", 37, Util.makeColor(217, 69, 21), typeA); - public static final Biome mesaPlateauF = new Biome("Mesa Plateau F", 38, Util.makeColor(176, 151, 101), typeH); - public static final Biome mesaPlateau = new Biome("Mesa Plateau", 39, Util.makeColor(202, 140, 101), typeH); - - - public static final Biome oceanM = new Biome("Ocean M", 128, Util.makeColor(0, 0, 112)); - public static final Biome sunflowerPlains = new Biome("Sunflower Plains", 129, Util.makeColor(141, 179, 96)); - public static final Biome desertM = new Biome("Desert M", 130, Util.makeColor(250, 148, 24)); - public static final Biome extremeHillsM = new Biome("Extreme Hills M", 131, Util.makeColor(96, 96, 96)); - public static final Biome flowerForest = new Biome("Flower Forest", 132, Util.makeColor(5, 102, 33)); - public static final Biome taigaM = new Biome("Taiga M", 133, Util.makeColor(11, 102, 89)); - public static final Biome swamplandM = new Biome("Swampland M", 134, Util.makeColor(7, 249, 178)); - public static final Biome riverM = new Biome("River M", 135, Util.makeColor(0, 0, 255)); - public static final Biome hellM = new Biome("Hell M", 136, Util.makeColor(255, 0, 0)); - public static final Biome skyM = new Biome("Sky M", 137, Util.makeColor(128, 128, 255)); - public static final Biome frozenOceanM = new Biome("Frozen Ocean M", 138, Util.makeColor(144, 144, 160)); - public static final Biome frozenRiverM = new Biome("Frozen River M", 139, Util.makeColor(160, 160, 255)); - public static final Biome icePlainsSpikes = new Biome("Ice Plains Spikes", 140, Util.makeColor(140, 180, 180)); - public static final Biome iceMountainsM = new Biome("Ice Mountains M", 141, Util.makeColor(160, 160, 160)); - public static final Biome mushroomIslandM = new Biome("Mushroom Island M", 142, Util.makeColor(255, 0, 255)); - public static final Biome mushroomIslandShoreM = new Biome("Mushroom Island Shore M", 143, Util.makeColor(160, 0, 255)); - public static final Biome beachM = new Biome("Beach M", 144, Util.makeColor(250, 222, 85)); - public static final Biome desertHillsM = new Biome("Desert Hills M", 145, Util.makeColor(210, 95, 18)); - public static final Biome forestHillsM = new Biome("Forest Hills M", 146, Util.makeColor(34, 85, 28)); - public static final Biome taigaHillsM = new Biome("Taiga Hills M", 147, Util.makeColor(22, 57, 51)); - public static final Biome extremeHillsEdgeM = new Biome("Extreme Hills Edge M", 148, Util.makeColor(114, 120, 154)); - public static final Biome jungleM = new Biome("Jungle M", 149, Util.makeColor(83, 123, 9)); - public static final Biome jungleHillsM = new Biome("Jungle Hills M", 150, Util.makeColor(44, 66, 5)); - public static final Biome jungleEdgeM = new Biome("Jungle Edge M", 151, Util.makeColor(98, 139, 23)); - public static final Biome deepOceanM = new Biome("Deep Ocean M", 152, Util.makeColor(0, 0, 48)); - public static final Biome stoneBeachM = new Biome("Stone Beach M", 153, Util.makeColor(162, 162, 132)); - public static final Biome coldBeachM = new Biome("Cold Beach M", 154, Util.makeColor(250, 240, 192)); - public static final Biome birchForestM = new Biome("Birch Forest M", 155, Util.makeColor(48, 116, 68)); - public static final Biome birchForestHillsM = new Biome("Birch Forest Hills M", 156, Util.makeColor(31, 95, 50)); - public static final Biome roofedForestM = new Biome("Roofed Forest M", 157, Util.makeColor(64, 81, 26)); - public static final Biome coldTaigaM = new Biome("Cold Taiga M", 158, Util.makeColor(49, 85, 74)); - public static final Biome coldTaigaHillsM = new Biome("Cold Taiga Hills M", 159, Util.makeColor(36, 63, 54)); - public static final Biome megaSpruceTaiga = new Biome("Mega Spruce Taiga", 160, Util.makeColor(89, 102, 81)); - public static final Biome megaSpurceTaigaHills = new Biome("Mega Spruce Taiga (Hills)",161, Util.makeColor(69, 79, 62)); - public static final Biome extremeHillsPlusM = new Biome("Extreme Hills+ M", 162, Util.makeColor(80, 112, 80)); - public static final Biome savannaM = new Biome("Savanna M", 163, Util.makeColor(189, 178, 95)); - public static final Biome savannaPlateauM = new Biome("Savanna Plateau M", 164, Util.makeColor(167, 157, 100)); - public static final Biome mesaBryce = new Biome("Mesa (Bryce)", 165, Util.makeColor(217, 69, 21)); - public static final Biome mesaPlateauFM = new Biome("Mesa Plateau F M", 166, Util.makeColor(176, 151, 101)); - public static final Biome mesaPlateauM = new Biome("Mesa Plateau M", 167, Util.makeColor(202, 140, 101)); - - - public String name; - public int index; - public int color; - public BiomeType type; - - public Biome(String name, int index, int color, boolean remote) { - biomes[index] = this; - this.name = name; - this.index = index; - this.color = color; - this.type = typeC; - biomeMap.put(name, this); - } - - public Biome(String name, int index, int color) { - this(name, index, color, biomes[index - 128].type.getRare()); - } - public Biome(String name, int index, int color, BiomeType type) { - biomes[index] = this; - this.name = name; - this.index = index; - this.color = color; - this.type = type; - biomeMap.put(name, this); - - if (index >= 128) - this.color = Util.lightenColor(color, 40); - } - - @Override - public String toString() { - return "[Biome " + name + "]"; - } - - public static int indexFromName(String name) { - Biome biome = biomeMap.get(name); - if (biome != null) - return biome.index; - return -1; - } - - public static final class BiomeType { // TODO: Rename once we figure out what this actually is! - public float value1, value2; - public BiomeType(float value1, float value2) { - this.value1 = value1; - this.value2 = value2; - } - - public BiomeType getExtreme() { - return new BiomeType(value1 * 0.8F, value2 * 0.6F); - } - public BiomeType getRare(){ - return new BiomeType(value1 + 0.1F, value2 + 0.2F); - } - } - -} diff --git a/src/amidst/minecraft/DeobfuscationData.java b/src/amidst/minecraft/DeobfuscationData.java deleted file mode 100644 index 7ab9d8706..000000000 --- a/src/amidst/minecraft/DeobfuscationData.java +++ /dev/null @@ -1,5 +0,0 @@ -package amidst.minecraft; - -public class DeobfuscationData { - public static final int[] intCache = new int[] {0x11, 0x01, 0x00, 0xB3, 0x00, -1, 0xBB, 0x00, -1, 0x59, 0xB7, 0x00, -1, 0xB3, 0x00, -1, 0xBB, 0x00, -1, 0x59, 0xB7, 0x00, -1, 0xB3, 0x00, -1, 0xBB, 0x00, -1, 0x59, 0xB7, 0x00, -1, 0xB3, 0x00, -1, 0xBB, 0x00, -1, 0x59, 0xB7, 0x00, -1, 0xB3, 0x00, -1, 0xB1}; -} diff --git a/src/amidst/minecraft/IMinecraftInterface.java b/src/amidst/minecraft/IMinecraftInterface.java deleted file mode 100644 index 9fe50f651..000000000 --- a/src/amidst/minecraft/IMinecraftInterface.java +++ /dev/null @@ -1,27 +0,0 @@ -package amidst.minecraft; - -import amidst.version.VersionInfo; - -/** - * Acts as an additional layer of abstraction for interfacing with Minecraft.
- * This allows for other sources of data other than direct reflection against a loaded jar of Minecraft. - */ -public interface IMinecraftInterface { - /** - * @param useQuarterResolutionMap - * Minecraft calculates biomes at quarter-resolution, then noisily interpolates the biome-map up to - * 1:1 resolution when needed, set useQuarterResolutionMap to true to read from the quarter-resolution - * map, or false to read values that have been interpolated up to full resolution. - * - * When useQuarterResolutionMap is true, the x, y, width, and height paramaters must all - * correspond to a quarter of the Minecraft block coordinates/sizes you wish to obtain the - * biome data for. - * - * AMIDST displays the quarter-resolution biome map, however full resolution is required to - * determine the position and nature of structures, as the noisy interpolation can change - * which biome a structure is located in (if the structure is located on a biome boundary). - */ - public int[] getBiomeData(int x, int y, int width, int height, boolean useQuarterResolutionMap); - public void createWorld(long seed, String type, String generatorOptions); - public VersionInfo getVersion(); -} diff --git a/src/amidst/minecraft/LocalMinecraftInterface.java b/src/amidst/minecraft/LocalMinecraftInterface.java deleted file mode 100644 index 1a260c009..000000000 --- a/src/amidst/minecraft/LocalMinecraftInterface.java +++ /dev/null @@ -1,73 +0,0 @@ -package amidst.minecraft; - -import java.lang.reflect.Field; - -import amidst.logging.Log; -import amidst.version.VersionInfo; -import MoF.SaveLoader.Type; - -public class LocalMinecraftInterface implements IMinecraftInterface { - private Minecraft minecraft; - /** - * A GenLayer instance, at quarter scale to the final biome layer - * (i.e. both axis are divided by 4). - * Minecraft calculates biomes at quarter-resolution, then noisily interpolates - * the biome-map up to 1:1 resolution when needed, this is the biome GenLayer - * before it is interpolated. - */ - private MinecraftObject biomeGen; - /** - * A GenLayer instance, the biome layer. (1:1 scale) - * Minecraft calculates biomes at quarter-resolution, then noisily interpolates - * the biome-map up to 1:1 resolution when needed, this is the interpolated - * biome GenLayer. - */ - private MinecraftObject biomeGen_fullResolution; - - public LocalMinecraftInterface(Minecraft minecraft) { - this.minecraft = minecraft; - } - - @Override - public int[] getBiomeData(int x, int y, int width, int height, boolean useQuarterResolutionMap) { - minecraft.getClassByName("IntCache").callFunction("resetIntCache"); - return (int[])(useQuarterResolutionMap ? biomeGen : biomeGen_fullResolution).callFunction("getInts", x, y, width, height); - } - - @Override - public void createWorld(long seed, String typeName, String generatorOptions) { - Log.debug("Attempting to create world with seed: " + seed + ", type: " + typeName + ", and the following generator options:"); - Log.debug(generatorOptions); - - // Minecraft 1.8 and higher require block initialization to be called before creating a biome generator. - MinecraftClass blockInit; - if ((blockInit = minecraft.getClassByName("BlockInit")) != null) - blockInit.callFunction("initialize"); - - Type type = Type.fromMixedCase(typeName); - MinecraftClass genLayerClass = minecraft.getClassByName("GenLayer"); - MinecraftClass worldTypeClass = minecraft.getClassByName("WorldType"); - Object[] genLayers = null; - if (worldTypeClass == null) { - genLayers = (Object[])genLayerClass.callFunction("initializeAllBiomeGenerators", seed); - } else { - Object worldType = ((MinecraftObject) worldTypeClass.getValue(type.getValue())).get(); - if (genLayerClass.getMethod("initializeAllBiomeGeneratorsWithParams").exists()) { - genLayers = (Object[])genLayerClass.callFunction("initializeAllBiomeGeneratorsWithParams", seed, worldType, generatorOptions); - } else { - genLayers = (Object[])genLayerClass.callFunction("initializeAllBiomeGenerators", seed, worldType); - } - - } - - biomeGen = new MinecraftObject(genLayerClass, genLayers[0]); - biomeGen_fullResolution = new MinecraftObject(genLayerClass, genLayers[1]); - } - - @Override - public VersionInfo getVersion() { - return minecraft.version; - } - - -} diff --git a/src/amidst/minecraft/Minecraft.java b/src/amidst/minecraft/Minecraft.java deleted file mode 100644 index d0ee786d0..000000000 --- a/src/amidst/minecraft/Minecraft.java +++ /dev/null @@ -1,396 +0,0 @@ -package amidst.minecraft; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Stack; -import java.util.Vector; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import amidst.Options; -import amidst.Util; -import amidst.bytedata.ByteClass; -import amidst.bytedata.ByteClass.AccessFlags; -import amidst.bytedata.CCLongMatch; -import amidst.bytedata.CCMethodPreset; -import amidst.bytedata.CCMulti; -import amidst.bytedata.CCPropertyPreset; -import amidst.bytedata.CCRequire; -import amidst.bytedata.CCStringMatch; -import amidst.bytedata.CCWildcardByteSearch; -import amidst.bytedata.ClassChecker; -import amidst.json.JarLibrary; -import amidst.json.JarProfile; -import amidst.logging.Log; -import amidst.version.VersionInfo; - -public class Minecraft { - private static final int MAX_CLASSES = 128; - private Class mainClass; - private URLClassLoader classLoader; - private String versionID; - private URL urlToJar; - private File jarFile; - - private static ClassChecker[] classChecks = new ClassChecker[] { - new CCWildcardByteSearch("IntCache", DeobfuscationData.intCache), - new CCStringMatch("WorldType", "default_1_1"), - new CCLongMatch("GenLayer", 1000L, 2001L, 2000L), - new CCStringMatch("IntCache", ", tcache: "), - (new ClassChecker() { - @Override - public void check(Minecraft m, ByteClass bClass) { - if (bClass.fields.length != 3) - return; - int privateStatic = AccessFlags.PRIVATE | AccessFlags.STATIC; - for (int i = 0; i < 3; i++) { - if ((bClass.fields[i].accessFlags & privateStatic) != privateStatic) - return; - } - - if ((bClass.constructorCount == 0) && (bClass.methodCount == 6) && (bClass.searchForUtf("isDebugEnabled"))) { - m.registerClass("BlockInit", bClass); - isComplete = true; - } - } - }), - new CCRequire( - new CCPropertyPreset( - "WorldType", - "a", "types", - "b", "default", - "c", "flat", - "d", "largeBiomes", - "e", "amplified", - "g", "default_1_1", - "f", "customized" - ) - , "WorldType"), - new CCRequire( - new CCMethodPreset( - "BlockInit", - "c()", "initialize" - ) - , "BlockInit"), - new CCRequire( - new CCMethodPreset( - "GenLayer", - "a(long, @WorldType)", "initializeAllBiomeGenerators", - "a(long, @WorldType, String)", "initializeAllBiomeGeneratorsWithParams", - "a(int, int, int, int)", "getInts" - ) - , "GenLayer"), - new CCRequire(new CCMulti( - new CCMethodPreset( - "IntCache", - "a(int)", "getIntCache", - "a()", "resetIntCache", - "b()", "getInformation" - ), - new CCPropertyPreset( - "IntCache", - "a", "intCacheSize", - "b","freeSmallArrays", - "c","inUseSmallArrays", - "d","freeLargeArrays", - "e","inUseLargeArrays" - ) - ), "IntCache") - }; - private HashMap byteClassMap; - private HashMap nameMap; - private HashMap classMap; - private Vector byteClassNames; - - public String versionId; - public VersionInfo version = VersionInfo.unknown; - - public Minecraft(File jarFile) throws MalformedURLException { - this.jarFile = jarFile; - byteClassNames = new Vector(); - byteClassMap = new HashMap(MAX_CLASSES); - urlToJar = jarFile.toURI().toURL(); - - Log.i("Reading minecraft.jar..."); - if (!jarFile.exists()) - Log.crash("Attempted to load jar file at: " + jarFile + " but it does not exist."); - Stack byteClassStack = new Stack(); - try { - ZipFile jar = new ZipFile(jarFile); - Enumeration enu = jar.entries(); - - while (enu.hasMoreElements()) { - ZipEntry entry = enu.nextElement(); - String currentEntry = entry.getName(); - String[] nameSplit = currentEntry.split("\\."); - if (!entry.isDirectory() && (nameSplit.length == 2) && (nameSplit[0].indexOf('/') == -1) && nameSplit[1].equals("class")) { - BufferedInputStream is = new BufferedInputStream(jar.getInputStream(entry)); - if (is.available() < 8000) { // TODO: Double check that this filter won't mess anything up. - byte[] classData = new byte[is.available()]; - is.read(classData); - is.close(); - byteClassStack.push(new ByteClass(nameSplit[0], classData)); - } - } - } - jar.close(); - Log.i("Jar load complete."); - } catch (Exception e) { - e.printStackTrace(); - Log.crash(e, "Error extracting jar data."); - } - - Log.i("Searching for classes..."); - int checksRemaining = classChecks.length; - Object[] byteClasses = byteClassStack.toArray(); - boolean[] found = new boolean[byteClasses.length]; - while (checksRemaining != 0) { - for (int q = 0; q < classChecks.length; q++) { - for (int i = 0; i < byteClasses.length; i++) { - if (!found[q]) { - classChecks[q].check(this, (ByteClass)byteClasses[i]); - if (classChecks[q].isComplete) { - Log.debug("Found: " + byteClasses[i] + " as " + classChecks[q].getName() + " | " + classChecks[q].getClass().getSimpleName()); - found[q] = true; - checksRemaining--; - } - // TODO: What is this line, and why is it commented - //byteClassMap.put(classChecks[q].getName(), classFiles[i].getName().split("\\.")[0]); - } - } - if (!found[q]) { - classChecks[q].passes--; - if (classChecks[q].passes == 0) { - found[q] = true; - checksRemaining--; - } - } - - - } - } - Log.i("Class search complete."); - - Log.i("Generating version ID..."); - use(); - try { - use(); - if (classLoader.findResource("net/minecraft/client/Minecraft.class") != null) - mainClass = classLoader.loadClass("net.minecraft.client.Minecraft"); - else if (classLoader.findResource("net/minecraft/server/MinecraftServer.class") != null) - mainClass = classLoader.loadClass("net.minecraft.server.MinecraftServer"); - else - throw new RuntimeException(); - } catch (Exception e) { - e.printStackTrace(); // TODO: Make this exception far less broad. - Log.crash(e, "Attempted to load non-minecraft jar, or unable to locate starting point."); - } - String typeDump = ""; - Field fields[] = null; - try { - fields = mainClass.getDeclaredFields(); - } catch (NoClassDefFoundError e) { - e.printStackTrace(); - Log.crash(e, "Unable to find critical external class while loading.\nPlease ensure you have the correct Minecraft libraries installed."); - } - - for (int i = 0; i < fields.length; i++) { - String typeString = fields[i].getType().toString(); - if (typeString.startsWith("class ") && !typeString.contains(".")) - typeDump += typeString.substring(6); - } - versionId = typeDump; - for (VersionInfo v : VersionInfo.values()) { - if (versionId.equals(v.versionId)) { - version = v; - break; - } - } - - Log.i("Identified Minecraft [" + version.name() + "] with versionID of " + versionId); - Log.i("Loading classes..."); - nameMap = new HashMap(); - classMap = new HashMap(); - - for (String name : byteClassNames) { - ByteClass byteClass = byteClassMap.get(name); - MinecraftClass minecraftClass = new MinecraftClass(name, byteClass.getClassName()); - minecraftClass.load(this); - nameMap.put(minecraftClass.getName(), minecraftClass); - classMap.put(minecraftClass.getClassName(), minecraftClass); - } - - for (MinecraftClass minecraftClass : nameMap.values()) { - ByteClass byteClass = byteClassMap.get(minecraftClass.getName()); - for (String[] property : byteClass.getProperties()) - minecraftClass.addProperty(new MinecraftProperty(minecraftClass, property[1], property[0])); - for (String[] method : byteClass.getMethods()) { - String methodString = obfuscateStringClasses(method[0]); - methodString = methodString.replaceAll(",INVALID", "").replaceAll("INVALID,","").replaceAll("INVALID", ""); - String methodDeobfName = method[1]; - String methodObfName = methodString.substring(0, methodString.indexOf('(')); - String parameterString = methodString.substring(methodString.indexOf('(') + 1, methodString.indexOf(')')); - - if (parameterString.equals("")) { - minecraftClass.addMethod(new MinecraftMethod(minecraftClass, methodDeobfName, methodObfName)); - } else { - String[] parameterClasses = parameterString.split(","); - minecraftClass.addMethod(new MinecraftMethod(minecraftClass, methodDeobfName, methodObfName, parameterClasses)); - } - } - for (String[] constructor : byteClass.getConstructors()) { - String methodString = obfuscateStringClasses(constructor[0]).replaceAll(",INVALID", "").replaceAll("INVALID,","").replaceAll("INVALID", ""); - String methodDeobfName = constructor[1]; - String methodObfName = methodString.substring(0, methodString.indexOf('(')); - String parameterString = methodString.substring(methodString.indexOf('(') + 1, methodString.indexOf(')')); - - if (parameterString.equals("")) { - minecraftClass.addMethod(new MinecraftMethod(minecraftClass, methodDeobfName, methodObfName)); - } else { - String[] parameterClasses = parameterString.split(","); - minecraftClass.addMethod(new MinecraftMethod(minecraftClass, methodDeobfName, methodObfName, parameterClasses)); - } - } - } - Log.i("Classes loaded."); - Log.i("Minecraft load complete."); - } - private String obfuscateStringClasses(String inString) { - inString = inString.replaceAll(" ", ""); - Pattern cPattern = Pattern.compile("@[A-Za-z]+"); - Matcher cMatcher = cPattern.matcher(inString); - String tempOutput = inString; - while (cMatcher.find()) { - String match = inString.substring(cMatcher.start(), cMatcher.end()); - ByteClass byteClass = getByteClass(match.substring(1)); - if (byteClass != null) { - tempOutput = tempOutput.replaceAll(match, byteClass.getClassName()); - } else { - tempOutput = tempOutput.replaceAll(match, "INVALID"); - } - cMatcher = cPattern.matcher(tempOutput); - } - return tempOutput; - } - - - public URL getPath() { - return urlToJar; - } - - private Stack getLibraries(File jsonFile) { - Log.i("Loading libraries."); - Stack libraries = new Stack(); - JarProfile profile = null; - try { - profile = Util.readObject(jsonFile, JarProfile.class); - } catch (IOException e) { - Log.w("Invalid jar profile loaded. Library loading will be skipped. (Path: " + jsonFile + ")"); - return libraries; - } - - for (int i = 0; i < profile.libraries.size(); i++) { - JarLibrary library = profile.libraries.get(i); - if (library.isActive() && library.getFile() != null && library.getFile().exists()) { - try { - libraries.add(library.getFile().toURI().toURL()); - Log.i("Found library: " + library.getFile()); - } catch (MalformedURLException e) { - Log.w("Unable to convert library file to URL with path: " + library.getFile()); - e.printStackTrace(); - } - } else { - Log.i("Skipping library: " + library.name); - } - } - - return libraries; - } - - /* - * This was the old search-and-add-all libraries method. This may still be useful - * if the user doesn't have a json file, or mojang changes the format. - * - private Stack getLibraries(File path, Stack urls) { - File[] files = path.listFiles(); - for (int i = 0; i < files.length; i++) { - if (files[i].isDirectory()) { - getLibraries(files[i], urls); - } else { - try { - Log.i("Found library: " + files[i]); - urls.push(files[i].toURI().toURL()); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - } - } - return urls; - } - */ - - public void use() { - File librariesJson = Options.instance.minecraftJson == null ? - new File(jarFile.getPath().replace(".jar", ".json")) - : new File(Options.instance.minecraftJson); - if (librariesJson.exists()) { - Stack libraries = getLibraries(librariesJson); - URL[] libraryArray = new URL[libraries.size() + 1]; - libraries.toArray(libraryArray); - libraryArray[libraries.size()] = urlToJar; - classLoader = new URLClassLoader(libraryArray); - } else { - Log.i("Unable to find Minecraft library JSON at: " + librariesJson + ". Skipping."); - classLoader = new URLClassLoader(new URL[] { urlToJar }); - } - Thread.currentThread().setContextClassLoader(classLoader); - } - - public String getVersionID() { - return versionID; - } - public MinecraftClass getClassByName(String name) { - return nameMap.get(name); - } - public URLClassLoader getClassLoader() { - return classLoader; - } - public Class loadClass(String name) { - try { - return classLoader.loadClass(name); - } catch (ClassNotFoundException e) { - Log.crash(e, "Error loading a class (" + name + ")"); - e.printStackTrace(); - } - return null; - } - public MinecraftClass getClassByType(String name) { - return classMap.get(name); - - - - } - public void registerClass(String publicName, ByteClass bClass) { - if (byteClassMap.get(publicName)==null) { - byteClassMap.put(publicName, bClass); - byteClassNames.add(publicName); - } - } - public ByteClass getByteClass(String name) { - return byteClassMap.get(name); - } - - public IMinecraftInterface createInterface() { - return new LocalMinecraftInterface(this); - } - -} diff --git a/src/amidst/minecraft/MinecraftClass.java b/src/amidst/minecraft/MinecraftClass.java deleted file mode 100644 index 5e5b3a509..000000000 --- a/src/amidst/minecraft/MinecraftClass.java +++ /dev/null @@ -1,80 +0,0 @@ -package amidst.minecraft; - -import java.lang.reflect.Constructor; -import java.util.HashMap; - - -public class MinecraftClass { - private String name, className; - private HashMap methods; - private Class clazz; - private HashMap propertiesByName; - private HashMap propertiesByObfName; - private HashMap methodsByName; - private HashMap methodsByObfName; - private HashMap constructorByName; - private Constructor[] constructors; - private Minecraft minecraft; - public MinecraftClass(String name, String className) { - this.name = name; - this.className = className; - methods = new HashMap(); - propertiesByName = new HashMap(); - propertiesByObfName = new HashMap(); - methodsByName = new HashMap(); - methodsByObfName = new HashMap(); - constructorByName = new HashMap(); - } - public String getName() { - return name; - } - public void load(Minecraft mc) { - minecraft = mc; - clazz = minecraft.loadClass(className); - } - public String getClassName() { - return className; - } - public Class getClazz() { - return clazz; - } - public void addProperty(MinecraftProperty property) { - property.load(minecraft, this); - propertiesByName.put(property.getName(), property); - propertiesByObfName.put(property.getInternalName(), property); - } - public Object getValue(String name) { - MinecraftProperty prop = propertiesByName.get(name); - return prop.getStaticValue(); - } - public Object callFunction(String name, Object... args) { - return methodsByName.get(name).callStatic(args); - } - public Object callFunction(String name, MinecraftObject obj, Object... args) { - return methodsByName.get(name).call(obj, args); - } - public void addMethod(MinecraftMethod method) { - method.load(minecraft, this); - methodsByName.put(method.getName(), method); - methodsByObfName.put(method.getInternalName(), method); - } - public void addConstructor(MinecraftConstructor constructor) { - constructor.load(minecraft, this); - constructorByName.put(constructor.getName(), constructor); - } - public String toString() { - return className; - } - public MinecraftObject newInstance(String constructor, Object... param) { - return constructorByName.get(constructor).getNew(param); - } - public MinecraftConstructor getConstructor(String name) { - return constructorByName.get(name); - } - public Object getValue(String propertyName, MinecraftObject minecraftObject) { - return propertiesByName.get(propertyName).getValue(minecraftObject); - } - public MinecraftMethod getMethod(String name) { - return methodsByName.get(name); - } -} diff --git a/src/amidst/minecraft/MinecraftConstructor.java b/src/amidst/minecraft/MinecraftConstructor.java deleted file mode 100644 index 1ecb15b6b..000000000 --- a/src/amidst/minecraft/MinecraftConstructor.java +++ /dev/null @@ -1,108 +0,0 @@ -package amidst.minecraft; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; - -import amidst.logging.Log; - - -public class MinecraftConstructor { - private MinecraftClass parent; - private Minecraft minecraft; - private Class[] paramClasses; - private String[] paramNames; - private boolean hasParameters; - private Constructor constructor; - private String name; - private static HashMap> primitives; - static { - primitives = new HashMap>(); - primitives.put("byte", byte.class); - primitives.put("int", int.class); - primitives.put("float", float.class); - primitives.put("short", short.class); - primitives.put("long", long.class); - primitives.put("double", double.class); - primitives.put("boolean", boolean.class); - primitives.put("char", char.class); - primitives.put("String", String.class); - } - public MinecraftConstructor(MinecraftClass parent, String name) { - this.parent = parent; - hasParameters = false; - this.name = name; - paramClasses = new Class[] {}; - } - public MinecraftConstructor(MinecraftClass parent, String name, String... args) { - this.parent = parent; - paramNames = args; - paramClasses = new Class[paramNames.length]; - hasParameters = true; - this.name = name; - } - public void load(Minecraft mc, MinecraftClass mcClass) { - minecraft = mc; - Class clazz = mcClass.getClazz(); - int i = 0; - try { - if (hasParameters) { - for (; i < paramNames.length; i++) { - paramClasses[i] = primitives.get(paramNames[i]); - if (paramClasses[i] == null) { - if (paramNames[i].charAt(0) == '@') { - - } else { - paramClasses[i] = Class.forName(paramNames[i], true, minecraft.getClassLoader()); - } - } - - } - } - - constructor = clazz.getConstructor(paramClasses); - constructor.setAccessible(true); - } catch (ClassNotFoundException e) { - Log.crash(e, "Unabled to find class for constructor. (" + paramNames[i] + ") on (" + mcClass.getName() + " / " + mcClass.getClassName() + ")"); - e.printStackTrace(); - } catch (SecurityException e) { - Log.crash(e, "SecurityException on (" + mcClass.getName() + " / " + mcClass.getClassName() + ") contructor (" + name + ")"); - e.printStackTrace(); - } catch (NoSuchMethodException e) { - Log.crash(e, "Unable to find class constructor (" + mcClass.getName() + " / " + mcClass.getClassName() + ") (" + name + ")"); - e.printStackTrace(); - } - } - public MinecraftObject getNew(Object... param) { - return new MinecraftObject(parent, call(param)); - } - private Object call(Object... param) { - try { - return constructor.newInstance(param); - } catch (IllegalArgumentException e) { // TODO : Add error text - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - - e.printStackTrace(); - - } - return null; - } - public Object getParentName() { - return parent.getName(); - } - public String getName() { - return name; - } - public Class[] getParameters() { - return paramClasses; - } - @Override - public String toString() { - return "[Constructor " + name +" of class " + parent.getName() + "]"; - } -} diff --git a/src/amidst/minecraft/MinecraftMethod.java b/src/amidst/minecraft/MinecraftMethod.java deleted file mode 100644 index 4a3af4c25..000000000 --- a/src/amidst/minecraft/MinecraftMethod.java +++ /dev/null @@ -1,123 +0,0 @@ -package amidst.minecraft; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; - -import amidst.logging.Log; - -public class MinecraftMethod { - private MinecraftClass parent; - private Minecraft minecraft; - private Class[] paramClasses; - private String[] paramNames; - private boolean hasParameters; - private Method method; - private String name, internalName; - private MinecraftClass returnType; - private boolean isMinecraftClass = false; - private boolean loadFailed = false; - - private static HashMap> primitives; - static { - primitives = new HashMap>(); - primitives.put("byte", byte.class); - primitives.put("int", int.class); - primitives.put("float", float.class); - primitives.put("short", short.class); - primitives.put("long", long.class); - primitives.put("double", double.class); - primitives.put("boolean", boolean.class); - primitives.put("char", char.class); - primitives.put("String", String.class); - } - public MinecraftMethod(MinecraftClass parent, String name, String methodName) { - this.parent = parent; - hasParameters = false; - this.name = name; - internalName = methodName; - paramClasses = new Class[] {}; - } - public MinecraftMethod(MinecraftClass parent, String name, String methodName, String... args) { - this.parent = parent; - paramNames = args; - paramClasses = new Class[paramNames.length]; - hasParameters = true; - this.name = name; - internalName = methodName; - } - public void load(Minecraft mc, MinecraftClass mcClass) { - minecraft = mc; - Class clazz = mcClass.getClazz(); - int i = 0; - try { - if (hasParameters) { - for (; i < paramNames.length; i++) { - paramClasses[i] = primitives.get(paramNames[i]); - if (paramClasses[i] == null) - paramClasses[i] = mc.getClassLoader().loadClass(paramNames[i]); // TODO: Does this cause duplicate loads? - } - } - - method = clazz.getDeclaredMethod(internalName, paramClasses); - method.setAccessible(true); - String methodType = method.getReturnType().getName(); - if (methodType.contains(".")) { - String[] typeSplit = methodType.split("\\."); - methodType = typeSplit[typeSplit.length-1]; - } - returnType = minecraft.getClassByType(methodType); - if (returnType == null) - isMinecraftClass = false; - } catch (ClassNotFoundException e) { - loadFailed = true; - Log.w(e, "Unabled to find class for parameter. (" + paramNames[i] + ") on (" + mcClass.getName() + " / " + mcClass.getClassName() + ")"); - e.printStackTrace(); - } catch (SecurityException e) { - loadFailed = true; - Log.w(e, "SecurityException on (" + mcClass.getName() + " / " + mcClass.getClassName() + ") method (" + name + " / " + internalName +")"); - e.printStackTrace(); - } catch (NoSuchMethodException e) { - loadFailed = true; - Log.w(e, "Unable to find class method (" + mcClass.getName() + " / " + mcClass.getClassName() + ") (" + name + " / " + internalName +")"); - e.printStackTrace(); - } - } - public Object callStatic(Object... param) { - Object value = call((Object)null, param); - if (isMinecraftClass) { - return new MinecraftObject(returnType, value); - } - return value; - } - public Object call(MinecraftObject obj, Object... param) { - Object value = call(obj.get(), param); - if (isMinecraftClass) - return new MinecraftObject(returnType, value); - return value; - } - private Object call(Object obj, Object... param) { - try { - return method.invoke(obj, param); - } catch (IllegalArgumentException e) { // TODO : Add error text - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - return null; - } - public Object getParentName() { - return parent.getName(); - } - public String getInternalName() { - return internalName; - } - public String getName() { - return name; - } - public boolean exists() { - return !loadFailed; - } -} diff --git a/src/amidst/minecraft/MinecraftObject.java b/src/amidst/minecraft/MinecraftObject.java deleted file mode 100644 index 6cb8d882b..000000000 --- a/src/amidst/minecraft/MinecraftObject.java +++ /dev/null @@ -1,25 +0,0 @@ -package amidst.minecraft; - -public class MinecraftObject { - private MinecraftClass type; - private Object value; - public MinecraftObject(MinecraftClass type, Object value) { - this.type = type; - this.value = value; - } - public MinecraftObject(Minecraft mc, Object value) { - this.type = mc.getClassByType(value.getClass().getCanonicalName()); - this.value = value; - } - public Object get() { - return value; - } - - public Object callFunction(String funcName, Object... args) { - return type.callFunction(funcName, this, args); - } - - public Object getValue(String propertyName) { - return type.getValue(propertyName, this); - } -} diff --git a/src/amidst/minecraft/MinecraftProperty.java b/src/amidst/minecraft/MinecraftProperty.java deleted file mode 100644 index dcafc8656..000000000 --- a/src/amidst/minecraft/MinecraftProperty.java +++ /dev/null @@ -1,96 +0,0 @@ -package amidst.minecraft; - -import java.lang.reflect.Field; - -import amidst.logging.Log; - -public class MinecraftProperty { - private String name, internalName; - private MinecraftClass parent; - private Field property; - private boolean isMinecraftClass = true; - private MinecraftClass type; - public MinecraftProperty(MinecraftClass parent, String name, String propertyName) { - this.parent = parent; - this.name = name; - this.internalName = propertyName; - } - - - - @Override - public String toString() { - return "[Method " + name +" (" + internalName +") of class " + parent.getName() + "]"; - } - - - - public String getName() { - return name; - } - - public String getInternalName() { - return internalName; - } - public void setValue(Object obj, Object val) { - try { - property.set(obj, val); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - public void load(Minecraft mc, MinecraftClass mcClass) { - Class clazz = mcClass.getClazz(); - try { - property = clazz.getDeclaredField(internalName); - String propType = property.getType().getName(); - if (propType.contains(".")) { - String[] typeSplit = propType.split("\\."); - propType = typeSplit[typeSplit.length-1]; - } - type = mc.getClassByType(propType); - if (type == null) - isMinecraftClass = false; - property.setAccessible(true); - } catch (SecurityException e) { - Log.crash(e, "SecurityException on (" + mcClass.getName() + " / " + mcClass.getClassName() + ") property (" + name + " / " + internalName +")"); - e.printStackTrace(); - } catch (NoSuchFieldException e) { - Log.crash(e, "Unable to find class property (" + mcClass.getName() + " / " + mcClass.getClassName() + ") (" + name + " / " + internalName +")"); - e.printStackTrace(); - } - } - - public Object getValue(MinecraftObject mcObject) { - Object object = mcObject.get(); - Object value = getValue(object); - if (isMinecraftClass) - return new MinecraftObject(type, value); - return value; - } - public Object getStaticValue() { - Object value = getValue((Object)null); - if (isMinecraftClass) { - return new MinecraftObject(type, value); - } - return value; - } - private Object getValue(Object obj) { - try { - return property.get(obj); - } catch (IllegalArgumentException e) { // TODO : Add error text. - e.printStackTrace(); - Log.crash(e, "Error [IllegalArgumentException] loading property (" + toString() + ")"); - } catch (IllegalAccessException e) { - e.printStackTrace(); - Log.crash(e, "Error [IllegalAccessException] loading property (" + toString() + ")"); - } - return null; - } - - public String getParentName() { - return parent.getName(); - } -} diff --git a/src/amidst/minecraft/MinecraftUtil.java b/src/amidst/minecraft/MinecraftUtil.java deleted file mode 100644 index e5893eb69..000000000 --- a/src/amidst/minecraft/MinecraftUtil.java +++ /dev/null @@ -1,94 +0,0 @@ -package amidst.minecraft; - -import java.awt.Point; -import java.util.List; -import java.util.Random; - -import amidst.logging.Log; -import amidst.version.VersionInfo; - -public class MinecraftUtil { - private static IMinecraftInterface minecraftInterface; - - /** Returns a copy of the biome data (threadsafe). */ - public static int[] getBiomeData(int x, int y, int width, int height, boolean useQuarterResolutionMap) { - return minecraftInterface.getBiomeData(x, y, width, height, useQuarterResolutionMap); - } - - public static Point findValidLocation(int searchX, int searchY, int size, List paramList, Random random) { - // TODO: Find out if we should useQuarterResolutionMap or not - // TODO: Clean up this code - int x1 = searchX - size >> 2; - int y1 = searchY - size >> 2; - int x2 = searchX + size >> 2; - int y2 = searchY + size >> 2; - - int width = x2 - x1 + 1; - int height = y2 - y1 + 1; - int[] arrayOfInt = getBiomeData(x1, y1, width, height, true); - Point location = null; - int numberOfValidFound = 0; - for (int i = 0; i < width*height; i++) { - int x = x1 + i % width << 2; - int y = y1 + i / width << 2; - if (arrayOfInt[i] > Biome.biomes.length) - Log.crash("Unsupported biome type detected"); - Biome localBiome = Biome.biomes[arrayOfInt[i]]; - if ((!paramList.contains(localBiome)) || ((location != null) && (random.nextInt(numberOfValidFound + 1) != 0))) - continue; - location = new Point(x, y); - numberOfValidFound++; - } - - return location; - } - - /** - * Gets the biome located at the block-coordinates. - * This is not a fast routine, it was added for rare things like - * accurately testing structures. - * (uses the 1:1 scale biome-map) - * @return Assume this may return null. - */ - public static Biome getBiomeAt(int x, int y) { - - int[] arrayOfInt = getBiomeData(x, y, 1, 1, false); - return Biome.biomes[arrayOfInt[0] & 0xFF]; - } - - public static boolean isValidBiome(int x, int y, int size, List validBiomes) { - int x1 = x - size >> 2; - int y1 = y - size >> 2; - int x2 = x + size >> 2; - int y2 = y + size >> 2; - - int width = x2 - x1 + 1; - int height = y2 - y1 + 1; - - int[] arrayOfInt = getBiomeData(x1, y1, width, height, true); - for (int i = 0; i < width * height; i++) { - Biome localBiome = Biome.biomes[arrayOfInt[i]]; - if (!validBiomes.contains(localBiome)) return false; - } - return true; - } - - public static void createWorld(long seed, String type) { - minecraftInterface.createWorld(seed, type, ""); - } - - public static void createWorld(long seed, String type, String generatorOptions) { - minecraftInterface.createWorld(seed, type, generatorOptions); - } - - public static void setBiomeInterface(IMinecraftInterface biomeInterface) { - MinecraftUtil.minecraftInterface = biomeInterface; - } - public static VersionInfo getVersion() { - return minecraftInterface.getVersion(); - } - - public static boolean hasInterface() { - return minecraftInterface != null; - } -} diff --git a/src/amidst/minecraft/remote/NetBiome.java b/src/amidst/minecraft/remote/NetBiome.java deleted file mode 100644 index c4090a6c8..000000000 --- a/src/amidst/minecraft/remote/NetBiome.java +++ /dev/null @@ -1,14 +0,0 @@ -package amidst.minecraft.remote; - -public class NetBiome { - public int id, color; - public String name; - public NetBiome() { - - } - public NetBiome(int id, String name, int color) { - this.id = id; - this.name = name; - this.color = color; - } -} diff --git a/src/amidst/minecraft/remote/NetCreateWorldRequest.java b/src/amidst/minecraft/remote/NetCreateWorldRequest.java deleted file mode 100644 index 01ff9eaa1..000000000 --- a/src/amidst/minecraft/remote/NetCreateWorldRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package amidst.minecraft.remote; - -public class NetCreateWorldRequest { - public long seed; - public NetCreateWorldRequest() { - - } - public NetCreateWorldRequest(long seed) { - this.seed = seed; - } -} diff --git a/src/amidst/minecraft/remote/NetGetBiomeDataRequest.java b/src/amidst/minecraft/remote/NetGetBiomeDataRequest.java deleted file mode 100644 index a4bb6e8b5..000000000 --- a/src/amidst/minecraft/remote/NetGetBiomeDataRequest.java +++ /dev/null @@ -1,16 +0,0 @@ -package amidst.minecraft.remote; - -public class NetGetBiomeDataRequest { - public int x, y, width, height; - public boolean useQuarterResolutionMap; - public NetGetBiomeDataRequest() { - - } - public NetGetBiomeDataRequest(int x, int y, int width, int height, boolean useQuarterResolutionMap) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.useQuarterResolutionMap = useQuarterResolutionMap; - } -} diff --git a/src/amidst/minecraft/remote/NetGetBiomeDataResult.java b/src/amidst/minecraft/remote/NetGetBiomeDataResult.java deleted file mode 100644 index 05d84748a..000000000 --- a/src/amidst/minecraft/remote/NetGetBiomeDataResult.java +++ /dev/null @@ -1,11 +0,0 @@ -package amidst.minecraft.remote; - -public class NetGetBiomeDataResult { - public int[] data; - public NetGetBiomeDataResult() { - - } - public NetGetBiomeDataResult(int[] data) { - this.data = data; - } -} diff --git a/src/amidst/minecraft/remote/NetInfoRequest.java b/src/amidst/minecraft/remote/NetInfoRequest.java deleted file mode 100644 index 6eedc8a99..000000000 --- a/src/amidst/minecraft/remote/NetInfoRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package amidst.minecraft.remote; - -public class NetInfoRequest { - public NetInfoRequest() { - - } -} diff --git a/src/amidst/minecraft/remote/RemoteMinecraft.java b/src/amidst/minecraft/remote/RemoteMinecraft.java deleted file mode 100644 index 9fac1f0c3..000000000 --- a/src/amidst/minecraft/remote/RemoteMinecraft.java +++ /dev/null @@ -1,84 +0,0 @@ -package amidst.minecraft.remote; - -import java.io.IOException; - -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryonet.Client; -import com.esotericsoftware.kryonet.Connection; -import com.esotericsoftware.kryonet.Listener; - -import amidst.minecraft.Biome; -import amidst.minecraft.IMinecraftInterface; -import amidst.version.VersionInfo; - -public class RemoteMinecraft implements IMinecraftInterface { - Client client; - static NetGetBiomeDataResult currentResults = null; - - public RemoteMinecraft(String address) { - client = new Client(65536, 65536); - Kryo kryo = client.getKryo(); - kryo.register(NetCreateWorldRequest.class); - kryo.register(NetGetBiomeDataRequest.class); - kryo.register(NetGetBiomeDataResult.class); - kryo.register(NetBiome.class); - kryo.register(NetBiome[].class); - kryo.register(NetInfoRequest.class); - kryo.register(int[].class); - - client.addListener(new Listener() { - @Override - public void received(Connection connection, Object object) { - if (object instanceof NetGetBiomeDataResult) { - currentResults = (NetGetBiomeDataResult)object; - //Log.i("Received NetGetBiomeDataResult: " + currentResults); - } else if (object instanceof NetBiome[]) { - NetBiome[] biomes = (NetBiome[])object; - for (int i = 0; i < biomes.length; i++) { - if (biomes[i] != null) { - new Biome(biomes[i].name, biomes[i].id, biomes[i].color | 0xFF000000, true); - } - } - } - } - }); - - client.start(); - try { - client.connect(5000, address, 54580, 54580); - } catch (IOException e) { - e.printStackTrace(); - } - - client.sendTCP(new NetInfoRequest()); - - } - - @Override - public int[] getBiomeData(int x, int y, int width, int height, boolean useQuarterResolutionMap) { - //Log.i("Send NetGetBiomeDataRequest"); - client.sendTCP(new NetGetBiomeDataRequest(x, y, width, height, useQuarterResolutionMap)); - while (currentResults == null) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - //Log.i("Passed to getBiomeData"); - int[] data = currentResults.data; - currentResults = null; - return data; - } - - @Override - public void createWorld(long seed, String type, String generatorOptions) { - client.sendTCP(new NetCreateWorldRequest(seed)); - } - - @Override - public VersionInfo getVersion() { - return VersionInfo.unknown; - } -} diff --git a/src/amidst/preferences/BiomeColorProfile.java b/src/amidst/preferences/BiomeColorProfile.java deleted file mode 100644 index b6c11ce37..000000000 --- a/src/amidst/preferences/BiomeColorProfile.java +++ /dev/null @@ -1,155 +0,0 @@ -package amidst.preferences; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import com.google.gson.JsonSyntaxException; - -import amidst.Options; -import amidst.Util; -import amidst.logging.Log; -import amidst.minecraft.Biome; - -public class BiomeColorProfile { - private class BiomeColor { - public String alias; - public int r = 0; - public int g = 0; - public int b = 0; - public BiomeColor(int rgb) { - r = (rgb >> 16) & 0xFF; - g = (rgb >> 8) & 0xFF; - b = (rgb) & 0xFF; - } - public int toColorInt() { - return Util.makeColor(r, g, b); - } - } - public static boolean isEnabled = false; - - public HashMap colorMap = new HashMap(); - public int colorArray[] = new int[Biome.biomes.length]; - public String[] nameArray = new String[Biome.biomes.length]; - public String name; - public String shortcut; - - public BiomeColorProfile() { - name = "default"; - for (int i = 0; i < Biome.biomes.length; i++) { - if (Biome.biomes[i] != null) { - colorMap.put(Biome.biomes[i].name, new BiomeColor(Biome.biomes[i].color)); - } - } - } - - public void fillColorArray() { - for (Map.Entry pairs : colorMap.entrySet()) { - int index = Biome.indexFromName(pairs.getKey()); - if (index != -1) { - colorArray[index] = pairs.getValue().toColorInt(); - nameArray[index] = (pairs.getValue().alias != null)?pairs.getValue().alias:Biome.biomes[index].name; - } else { - Log.i("Failed to find biome for: " + pairs.getKey() + " in profile: " + name); - } - } - } - - public boolean save(File path) { - String output = ""; - output += "{ \"name\":\"" + name + "\", \"colorMap\":[\r\n"; - - for (Map.Entry pairs : colorMap.entrySet()) { - output += "[ \"" + pairs.getKey() + "\", { "; - output += "\"r\":" + pairs.getValue().r + ", "; - output += "\"g\":" + pairs.getValue().g + ", "; - output += "\"b\":" + pairs.getValue().b + " } ],\r\n"; - } - output = output.substring(0, output.length() - 3); - - output += " ] }\r\n"; - BufferedWriter writer = null; - try { - writer = new BufferedWriter(new FileWriter(path)); - writer.write(output); - writer.close(); - return true; - } catch ( IOException e) { - try { - if (writer != null) - writer.close(); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - } - return false; - } - - public void activate() { - Options.instance.biomeColorProfile = this; - Log.i("Biome color profile activated."); - for (int i = 0; i < Biome.biomes.length; i++) { - if (Biome.biomes[i] != null) { - Biome.biomes[i].color = colorArray[i]; - } - } - if (amidst.map.Map.instance != null) - amidst.map.Map.instance.resetFragments(); - } - - - public static void scan() { - Log.i("Searching for biome color profiles."); - File colorProfileFolder = new File("./biome"); - - if (!colorProfileFolder.exists() || !colorProfileFolder.isDirectory()) { - Log.i("Unable to find biome color profile folder."); - return; - } - - File defaultProfileFile = new File("./biome/default.json"); - if (!defaultProfileFile.exists()) - if (!Options.instance.biomeColorProfile.save(defaultProfileFile)) - Log.i("Attempted to save default biome color profile, but encountered an error."); - - /* - File[] colorProfiles = colorProfileFolder.listFiles(); - for (int i = 0; i < colorProfiles.length; i++) { - if (colorProfiles[i].exists() && colorProfiles[i].isFile()) { - try { - BiomeColorProfile profile = Util.readObject(colorProfiles[i], BiomeColorProfile.class); - profile.fillColorArray(); - profiles.add(profile); - } catch (FileNotFoundException e) { - Log.i("Unable to load file: " + colorProfiles[i]); - } - } - }*/ - isEnabled = true; - } - - public static BiomeColorProfile createFromFile(File file) { - BiomeColorProfile profile = null; - if (file.exists() && file.isFile()) { - try { - profile = Util.readObject(file, BiomeColorProfile.class); - profile.fillColorArray(); - } catch (JsonSyntaxException e) { - Log.w("Unable to load file: " + file); - e.printStackTrace(); - } catch (IOException e) { - Log.i("Unable to load file: " + file); - } - } - return profile; - } - - public String getAliasForId(int id) { - if (nameArray[id] != null) - return nameArray[id]; - return Biome.biomes[id].name; - } -} diff --git a/src/amidst/preferences/BooleanPrefModel.java b/src/amidst/preferences/BooleanPrefModel.java deleted file mode 100644 index babc35a7e..000000000 --- a/src/amidst/preferences/BooleanPrefModel.java +++ /dev/null @@ -1,45 +0,0 @@ -package amidst.preferences; - -import javax.swing.JToggleButton.ToggleButtonModel; -import java.util.prefs.Preferences; - -public class BooleanPrefModel extends ToggleButtonModel implements PrefModel { - private static final long serialVersionUID = -2291122955784916836L; - - private final String key; - private final Preferences pref; - - public BooleanPrefModel(Preferences pref, String key, boolean selected) { - super(); - this.pref = pref; - this.key = key; - set(pref.getBoolean(key, selected)); - } - - @Override - public String getKey() { - return key; - } - - @Override - public Boolean get() { - assert pref.get(key, null) != null && pref.getBoolean(key, false) == super.isSelected(); - return super.isSelected(); - } - - @Override - public boolean isSelected() { - return get(); - } - - @Override - public void set(Boolean value) { - super.setSelected(value); - pref.putBoolean(key, value); - } - - @Override - public void setSelected(boolean value) { - set(value); - } -} diff --git a/src/amidst/preferences/FilePrefModel.java b/src/amidst/preferences/FilePrefModel.java deleted file mode 100644 index c345c2526..000000000 --- a/src/amidst/preferences/FilePrefModel.java +++ /dev/null @@ -1,40 +0,0 @@ -package amidst.preferences; -import java.io.File; -import java.io.IOException; -import java.util.prefs.Preferences; - -/** Autosaving File model - */ -public class FilePrefModel implements PrefModel { - private final String key; - private final Preferences pref; - - public FilePrefModel(Preferences pref, String key, File init) { - super(); - this.pref = pref; - this.key = key; - if (pref.get(key, null) == null) - set(init); - } - - @Override - public String getKey() { - return key; - } - - @Override - public File get() { - String path = pref.get(key, null); - assert path != null; - return new File(path); - } - - @Override - public void set(File value) { - try { - pref.put(key, value.getCanonicalPath()); - } catch (IOException ignored) { - pref.put(key, value.getPath()); - } - } -} \ No newline at end of file diff --git a/src/amidst/preferences/PrefModel.java b/src/amidst/preferences/PrefModel.java deleted file mode 100644 index 608caa4dc..000000000 --- a/src/amidst/preferences/PrefModel.java +++ /dev/null @@ -1,13 +0,0 @@ -package amidst.preferences; - -import java.io.IOException; - -/** Backed by a Preferences instance, saves and loads its value from and to it. - * TODO: test the fuck out of this - */ -public interface PrefModel { - String getKey(); - - public T get(); - public void set(T value) throws IOException; -} diff --git a/src/amidst/preferences/SelectPrefModel.java b/src/amidst/preferences/SelectPrefModel.java deleted file mode 100644 index 334d20803..000000000 --- a/src/amidst/preferences/SelectPrefModel.java +++ /dev/null @@ -1,70 +0,0 @@ -package amidst.preferences; - -import java.util.prefs.Preferences; - -import javax.swing.JToggleButton.ToggleButtonModel; - -public class SelectPrefModel implements PrefModel { - public class SelectButtonModel extends ToggleButtonModel { - private SelectPrefModel model; - public String name; - public SelectButtonModel(SelectPrefModel model, String name) { - this.model = model; - this.name = name; - super.setSelected(false); - } - - @Override - public boolean isSelected() { - return model.get().equals(name); - } - - @Override - public void setSelected(boolean value) { - super.setSelected(value); - if (value) - model.set(name); - } - - public String getName() { - return name; - } - } - private Preferences preferences; - private String key; - private String selected; - private SelectButtonModel buttonModels[]; - public SelectPrefModel(Preferences pref, String key, String selected, String[] names) { - this.key = key; - this.preferences = pref; - this.selected = selected; - buttonModels = new SelectButtonModel[names.length]; - for (int i = 0; i < buttonModels.length; i++) - buttonModels[i] = new SelectButtonModel(this, names[i]); - set(pref.get(key, selected)); - - } - - @Override - public String getKey() { - return key; - } - - @Override - public String get() { - return selected; - } - - public SelectButtonModel[] getButtonModels() { - return buttonModels; - } - - @Override - public void set(String value) { - preferences.put(key, value); - selected = value; - for (int i = 0; i < buttonModels.length; i++) - if (!value.equals(buttonModels[i].name)) - buttonModels[i].setSelected(false); - } -} diff --git a/src/amidst/preferences/StringPreference.java b/src/amidst/preferences/StringPreference.java deleted file mode 100644 index d6bf1f0a2..000000000 --- a/src/amidst/preferences/StringPreference.java +++ /dev/null @@ -1,23 +0,0 @@ -package amidst.preferences; - -import java.util.prefs.Preferences; - -public class StringPreference { - private Preferences preferences; - private String key; - private String value; - - public StringPreference(Preferences preferences, String key, String defaultValue) { - this.preferences = preferences; - this.key = key; - value = preferences.get(key, defaultValue); - } - - public String get() { - return value; - } - - public void set(String value) { - preferences.put(key, value); - } -} diff --git a/src/amidst/resources/ResourceLoader.java b/src/amidst/resources/ResourceLoader.java deleted file mode 100644 index feff0f7ac..000000000 --- a/src/amidst/resources/ResourceLoader.java +++ /dev/null @@ -1,29 +0,0 @@ -package amidst.resources; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -public class ResourceLoader { - private ResourceLoader() {} - - public static URL getResourceURL(String name) { - // This is also a valid way to load resources, although I'm not sure which is better. - //return ClassLoader.getSystemClassLoader().getResource("amidst/resources/" + name); - return ResourceLoader.class.getResource(name); - } - - public static InputStream getResourceStream(String name) { - return ResourceLoader.class.getResourceAsStream(name); - } - - public static BufferedImage getImage(String name) { - try { - return ImageIO.read(getResourceURL(name)); - } catch (IOException e) { //Don't forget to run the tests :) - throw new RuntimeException(e); - } - } -} diff --git a/src/amidst/resources/licenses/jgoogleanalytics.txt b/src/amidst/resources/licenses/jgoogleanalytics.txt deleted file mode 100644 index 3f014a0e7..000000000 --- a/src/amidst/resources/licenses/jgoogleanalytics.txt +++ /dev/null @@ -1,2 +0,0 @@ -JGoogleAnalytics is licensed under "Apache License, Version 2.0" -More information can be found here: http://www.apache.org/licenses/LICENSE-2.0 \ No newline at end of file diff --git a/src/amidst/resources/licenses/kryonet.txt b/src/amidst/resources/licenses/kryonet.txt deleted file mode 100644 index 3f6a160c2..000000000 --- a/src/amidst/resources/licenses/kryonet.txt +++ /dev/null @@ -1,10 +0,0 @@ -Copyright (c) 2008, Nathan Sweet -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/src/amidst/resources/licenses/rhino.txt b/src/amidst/resources/licenses/rhino.txt deleted file mode 100644 index c0e7c21fb..000000000 --- a/src/amidst/resources/licenses/rhino.txt +++ /dev/null @@ -1,375 +0,0 @@ -The majority of Rhino is licensed under the MPL 2.0: - -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/src/amidst/utilties/FramerateTimer.java b/src/amidst/utilties/FramerateTimer.java deleted file mode 100644 index f24f86f00..000000000 --- a/src/amidst/utilties/FramerateTimer.java +++ /dev/null @@ -1,46 +0,0 @@ -package amidst.utilties; - -public class FramerateTimer { - private int tickCounter; - private long lastUpdate; - private long msPerUpdate; - - private float currentFPS = 0.0f; - - public FramerateTimer(int updatesPerSecond) { - msPerUpdate = (long)(1000f * (1f/updatesPerSecond)); - reset(); - } - - public void reset() { - tickCounter = 0; - lastUpdate = System.currentTimeMillis(); - } - - public void tick() { - tickCounter++; - long curTime = System.currentTimeMillis(); - - if (curTime - lastUpdate > msPerUpdate) { - float timeDifference = curTime - lastUpdate; - - timeDifference /= 1000f; - timeDifference = tickCounter/timeDifference; - - currentFPS = timeDifference; - - tickCounter = 0; - lastUpdate = curTime; - - } - } - - public float getFramerate() { - return currentFPS; - } - - @Override - public String toString() { - return "FPS: " + String.format("%.1f", currentFPS); - } -} diff --git a/src/amidst/utilties/ProgressListener.java b/src/amidst/utilties/ProgressListener.java deleted file mode 100644 index 6069ff5b8..000000000 --- a/src/amidst/utilties/ProgressListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package amidst.utilties; - -public abstract class ProgressListener { - public abstract void onUpdate(ProgressMeter meter, double value); - public abstract void onComplete(ProgressMeter meter); -} diff --git a/src/amidst/utilties/ProgressMeter.java b/src/amidst/utilties/ProgressMeter.java deleted file mode 100644 index c822da72f..000000000 --- a/src/amidst/utilties/ProgressMeter.java +++ /dev/null @@ -1,40 +0,0 @@ -package amidst.utilties; - -import java.util.Deque; - -public class ProgressMeter { - public float minimum = 0.0f; - public float maximum = 1.0f; - public float progress = 0.0f; - public boolean isComplete = false; - - public Deque listeners; - - - public ProgressMeter() { - - } - - public void update(float value) { - progress = value; - for (ProgressListener listener : listeners) - listener.onComplete(this); - - if (!isComplete) { - if (progress >= maximum) { - isComplete = true; - for (ProgressListener listener : listeners) - listener.onComplete(this); - } - } - } - - public void reset() { - progress = minimum; - isComplete = false; - } - - public float getPrecentage() { - return (progress - minimum) / (maximum - minimum); - } -} diff --git a/src/amidst/version/ILatestVersionListListener.java b/src/amidst/version/ILatestVersionListListener.java deleted file mode 100644 index 661ceefae..000000000 --- a/src/amidst/version/ILatestVersionListListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package amidst.version; - -public interface ILatestVersionListListener { - public void onLoadStateChange(LatestVersionListEvent event); -} diff --git a/src/amidst/version/IProfileUpdateListener.java b/src/amidst/version/IProfileUpdateListener.java deleted file mode 100644 index faa603d1b..000000000 --- a/src/amidst/version/IProfileUpdateListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package amidst.version; - -public interface IProfileUpdateListener { - public void onProfileUpdate(ProfileUpdateEvent event); -} diff --git a/src/amidst/version/LatestVersionList.java b/src/amidst/version/LatestVersionList.java deleted file mode 100644 index 0120ae54a..000000000 --- a/src/amidst/version/LatestVersionList.java +++ /dev/null @@ -1,175 +0,0 @@ -package amidst.version; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.HashMap; - -import com.google.gson.JsonSyntaxException; - -import amidst.Util; -import amidst.logging.Log; -import amidst.resources.ResourceLoader; - -public class LatestVersionList { - public static LatestVersionList instance = new LatestVersionList(); - public static LatestVersionList get() { - return instance; - } - public enum LoadState { - LOADED, - LOADING, - FAILED, - IDLE - } - - private LoadState loadState = LoadState.IDLE; - - private VersionList profile; - - private ArrayList loadListeners = new ArrayList(); - private Object listenerLock = new Object(); - - public LatestVersionList() { - - } - - public HashMap[] getVersions() { - return profile.versions; - } - - public void load(boolean threaded) { - if (threaded) { - (new Thread(new Runnable() { - @Override - public void run() { - doLoad(); - } - })).start(); - } else { - doLoad(); - } - } - - private void doLoad() { - Log.i("Beginning latest version list load."); - setLoadState(LoadState.LOADING); - if (!attemptRemoteLoad() && !attemptLocalLoad()) { - Log.w("Failed to load both remote and local version list."); - setLoadState(LoadState.FAILED); - } - - setLoadState(LoadState.LOADED); - } - - private boolean attemptLocalLoad() { - Log.i("Attempting to download local version list..."); - URL versionUrl = ResourceLoader.getResourceURL("versions.json"); - return attemptLoad(versionUrl); - } - - private boolean attemptRemoteLoad() { - Log.i("Attempting to download remote version list..."); - URL versionUrl = null; - try { - versionUrl = new URL(Util.REMOTE_VERSION_LIST_URL); - } catch (MalformedURLException e) { - Log.w("MalformedURLException on remote version list. Aborting load. This should never be possible."); - Log.printTraceStack(e); - Log.w("Aborting remote version list load."); - return false; - } - - return attemptLoad(versionUrl); - } - - private boolean attemptLoad(URL versionUrl) { - URLConnection urlConnection = null; - try { - urlConnection = versionUrl.openConnection(); - } catch (IOException e) { - Log.w("IOException when attempting to open connection to version list."); - Log.printTraceStack(e); - Log.w("Aborting version list load. URL: " + versionUrl); - return false; - } - - int contentLength = urlConnection.getContentLength(); - if (contentLength == -1) { - Log.w("Content length of version list returned -1."); - Log.w("Aborting version list load. URL: " + versionUrl); - return false; - } - - InputStream inputStream = null; - try { - inputStream = urlConnection.getInputStream(); - } catch (IOException e) { - Log.w("IOException on opening input stream to version list."); - Log.printTraceStack(e); - Log.w("Aborting version list load. URL: " + versionUrl); - return false; - } - - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - try { - profile = Util.readObject(bufferedReader, VersionList.class); - } catch (JsonSyntaxException e) { - Log.w("Unable to parse version list."); - Log.printTraceStack(e); - Log.w("Aborting version list load. URL: " + versionUrl); - return false; - } finally { - try { - bufferedReader.close(); - } catch (IOException e) { - Log.w("IOException thrown when attempting to close stream for version list."); - Log.printTraceStack(e); - } - } - - Log.i("Successfully loaded version list. URL: " + versionUrl); - return true; - } - - public LoadState getLoadState() { - return loadState; - } - - private void setLoadState(LoadState state) { - synchronized (listenerLock) { - loadState = state; - for (ILatestVersionListListener listener : loadListeners) - listener.onLoadStateChange(new LatestVersionListEvent(this)); - } - } - - public void addLoadListener(ILatestVersionListListener listener) { - synchronized (listenerLock) { - loadListeners.add(listener); - } - } - - public void removeLoadListener(ILatestVersionListListener listener) { - synchronized (listenerLock) { - loadListeners.remove(listener); - } - } - - public void addAndNotifyLoadListener(ILatestVersionListListener listener) { - synchronized (listenerLock) { - loadListeners.add(listener); - listener.onLoadStateChange(new LatestVersionListEvent(this)); - } - } - - private class VersionList { - public HashMap latest; - public HashMap[] versions; - } -} diff --git a/src/amidst/version/LatestVersionListEvent.java b/src/amidst/version/LatestVersionListEvent.java deleted file mode 100644 index 98c608818..000000000 --- a/src/amidst/version/LatestVersionListEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package amidst.version; - -public class LatestVersionListEvent { - private LatestVersionList source; - public LatestVersionListEvent(LatestVersionList source) { - this.source = source; - } - public LatestVersionList getSource() { - return source; - } -} diff --git a/src/amidst/version/MinecraftProfile.java b/src/amidst/version/MinecraftProfile.java deleted file mode 100644 index b7ccdcba1..000000000 --- a/src/amidst/version/MinecraftProfile.java +++ /dev/null @@ -1,106 +0,0 @@ -package amidst.version; - -import java.io.File; -import java.util.ArrayList; - -import amidst.json.InstallInformation; - -public class MinecraftProfile implements ILatestVersionListListener { - public enum Status { - IDLE("scanning"), - MISSING("missing"), - FAILED("failed"), - FOUND("found"); - - private String name; - private Status(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - private ArrayList listeners = new ArrayList(); - - private MinecraftVersion version; - private InstallInformation profile; - - private Status status = Status.IDLE; - private String versionName = "unknown"; - - public MinecraftProfile(InstallInformation profile) { - this.profile = profile; - if (profile.lastVersionId.equals("latest")) { - LatestVersionList.get().addAndNotifyLoadListener(this); - } else { - version = MinecraftVersion.fromVersionId(profile.lastVersionId); - if (version == null) { - status = Status.MISSING; - return; - } - status = Status.FOUND; - versionName = version.getName(); - } - } - - public String getProfileName() { - return profile.name; - } - - public String getGameDir() { - return profile.gameDir; - } - - public String getVersionName() { - return versionName; - } - - public void addUpdateListener(IProfileUpdateListener listener) { - listeners.add(listener); - } - public void removeUpdateListener(IProfileUpdateListener listener) { - listeners.remove(listener); - } - - public Status getStatus() { - return status; - } - - @Override - public void onLoadStateChange(LatestVersionListEvent event) { - switch (event.getSource().getLoadState()) { - case FAILED: - status = Status.FAILED; - break; - case IDLE: - status = Status.IDLE; - break; - case LOADED: - status = Status.FOUND; - boolean usingSnapshots = false; - for (int i = 0; i < profile.allowedReleaseTypes.length; i++) - if (profile.allowedReleaseTypes[i].equals("snapshot")) - usingSnapshots = true; - if (usingSnapshots) - version = MinecraftVersion.fromLatestSnapshot(); - else - version = MinecraftVersion.fromLatestRelease(); - if (version == null) - status = Status.FAILED; - else - versionName = version.getName(); - break; - case LOADING: - status = Status.IDLE; - break; - } - for (IProfileUpdateListener listener: listeners) - listener.onProfileUpdate(new ProfileUpdateEvent(this)); - } - - public File getJarFile() { - return version.getJarFile(); - } -} diff --git a/src/amidst/version/MinecraftVersion.java b/src/amidst/version/MinecraftVersion.java deleted file mode 100644 index b1abee844..000000000 --- a/src/amidst/version/MinecraftVersion.java +++ /dev/null @@ -1,66 +0,0 @@ -package amidst.version; - -import java.io.File; -import java.util.HashMap; - -import amidst.Util; -import amidst.logging.Log; - -public class MinecraftVersion { - private File jarFile, jsonFile; - private String name; - - private MinecraftVersion(String name, File jarFile, File jsonFile) { - this.name = name; - - this.jarFile = jarFile; - this.jsonFile = jsonFile; - } - public static MinecraftVersion fromVersionId(String lastVersionId) { - return fromVersionPath(new File(Util.minecraftDirectory + "/versions/" + lastVersionId)); - } - public static MinecraftVersion fromVersionPath(File path) { - File jarFile = new File(path + "/" + path.getName() + ".jar"); - File jsonFile = new File(path + "/" + path.getName() + ".json"); - - if (!jarFile.exists() || jarFile.isDirectory()) { - Log.w("Unable to load MinecraftVersion at path: " + path + " because jarFile: " + jarFile + " is missing or a directory."); - return null; - } - if (!jsonFile.exists() || jsonFile.isDirectory()) { - Log.w("Unable to load MinecraftVersion at path: " + path + " because jsonFile: " + jsonFile + " is missing or a directory."); - return null; - } - - MinecraftVersion version = new MinecraftVersion(path.getName(), jarFile, jsonFile); - return version; - } - public static MinecraftVersion fromLatestRelease() { - MinecraftVersion version = null; - HashMap[] versions = LatestVersionList.get().getVersions(); - - for (int i = 0; i < versions.length; i++) { - if (versions[i].get("type").equals("release") && (version = fromVersionId(versions[i].get("id"))) != null) - return version; - } - return null; - } - - public static MinecraftVersion fromLatestSnapshot() { - MinecraftVersion version = null; - HashMap[] versions = LatestVersionList.get().getVersions(); - - for (int i = 0; i < versions.length; i++) { - if ((version = fromVersionId(versions[i].get("id"))) != null) - return version; - } - return null; - } - - public String getName() { - return name; - } - public File getJarFile() { - return jarFile; - } -} diff --git a/src/amidst/version/ProfileUpdateEvent.java b/src/amidst/version/ProfileUpdateEvent.java deleted file mode 100644 index 9edc4dc0c..000000000 --- a/src/amidst/version/ProfileUpdateEvent.java +++ /dev/null @@ -1,12 +0,0 @@ -package amidst.version; - -public class ProfileUpdateEvent { - private MinecraftProfile profile; - public ProfileUpdateEvent(MinecraftProfile profile) { - this.profile = profile; - } - - public MinecraftProfile getSource() { - return profile; - } -} diff --git a/src/amidst/version/VersionFactory.java b/src/amidst/version/VersionFactory.java deleted file mode 100644 index a446c9edc..000000000 --- a/src/amidst/version/VersionFactory.java +++ /dev/null @@ -1,73 +0,0 @@ -package amidst.version; - -import java.io.File; -import java.io.IOException; -import java.util.Stack; - -import com.google.gson.JsonSyntaxException; - -import amidst.Util; -import amidst.json.InstallInformation; -import amidst.json.LauncherProfile; -import amidst.logging.Log; - -public class VersionFactory { - private MinecraftVersion[] localVersions; - private MinecraftProfile[] profiles; - public VersionFactory() { - - } - - public void scanForLocalVersions() { - File versionPath = new File(Util.minecraftDirectory + "/versions"); - if (!versionPath.exists()) { - Log.e("Cannot find version directory."); - return; - } else if (!versionPath.isDirectory()) { - Log.e("Attempted to open version directory but found file."); - return; - } - File[] versionDirectories = versionPath.listFiles(); - Stack versionStack = new Stack(); - for (int i = 0; i < versionDirectories.length; i++) { - MinecraftVersion version = MinecraftVersion.fromVersionPath(versionDirectories[i]); - if (version != null) - versionStack.add(version); - } - if (versionStack.size() == 0) - return; - - localVersions = new MinecraftVersion[versionStack.size()]; - versionStack.toArray(localVersions); - } - - public void scanForProfiles() { - Log.i("Scanning for profiles."); - File profileJsonFile = new File(Util.minecraftDirectory + "/launcher_profiles.json"); - LauncherProfile launcherProfile = null; - try { - launcherProfile = Util.readObject(profileJsonFile, LauncherProfile.class); - } catch (JsonSyntaxException e) { - Log.crash(e, "Syntax exception thrown from launch_profiles.json"); - return; - } catch (IOException e) { - Log.crash(e, "Unable to open launch_profiles.json"); - return; - } - Log.i("Successfully loaded profile list."); - - profiles = new MinecraftProfile[launcherProfile.profiles.size()]; - - int i = 0; - for (InstallInformation installInformation : launcherProfile.profiles.values()) - profiles[i++] = new MinecraftProfile(installInformation); - - - } - public MinecraftProfile[] getProfiles() { - return profiles; - } - public MinecraftVersion[] getLocalVersions() { - return localVersions; - } -} diff --git a/src/amidst/version/VersionInfo.java b/src/amidst/version/VersionInfo.java deleted file mode 100644 index b278f9f55..000000000 --- a/src/amidst/version/VersionInfo.java +++ /dev/null @@ -1,78 +0,0 @@ -package amidst.version; - -/** Information about what each supported version is - */ -public enum VersionInfo { - unknown(null), - V1_8_4("orntlljs[Lle;lx[J[[Jlt"), - V1_8_3("osnulmjt[Llf;ly[J[[Jlu"), // 1.8.3 and 1.8.2 have the same typeDump version ID - probably because 1.8.2 -> 1.8.3 was a fix for a server-side bug (https://mojang.com/2015/02/minecraft-1-8-2-is-now-available/) - V1_8_1("wduyrdnq[Lqu;sp[J[[Jsa"), - V1_8("wbuwrcnp[Lqt;sn[J[[Jry"), - V14w21b("tjseoylw[Loq;qd[J[[Jpo"), - V1_7_10("riqinckb[Lmt;oi[J[[Jns"), - V1_7_9("rhqhnbkb[Lms;oh[J[[Jnr"), - V14w02a("qrponkki[Lnb;lv[J[[J"), - V1_7_4("pzozmvjs[Lmm;lg[J[[J"), - V1_7_2("pvovmsjp[Lmj;ld[J[[J"), - V13w39a_or_b("npmp[Lkn;jh[J[J[J[J[J[[J"), - V13w37b_or_38a("ntmt[Lkm;jg[J[J[J[J[J[[J"), - V13w37a("nsms[Lkl;jf[J[J[J[J[J[[J"), - V13w36b("nkmk[Lkd;hw[J[J[J[J[J[[J"), - V13w36a("nkmk[Lkd;hx[J[J[J[J[J[[J"), - V1_6_4("mvlv[Ljs;hn[J[J[J[J[J[[J"), - V1_6_2("mulu[Ljr;hm[J[J[J[J[J[[J"), - V1_6_1("msls[Ljp;hk[J[J[J[J[J[[J"), - V1_5_1("[Bbeabdsbawemabdtbfzbdwngngbevawfbgfawvawvaxrawbbfrausbjhaycawwaraavybkdavwbjvbkila"), - V1_5_0("Invalid"), // TODO: This makes no sense? 1.5.0 is not on the version list! - V1_4_6("[Baywayoaaszleaypbavaysmdazratabbaatqatqaulaswbanarnbdzauwatraohastbevasrbenbezbdmbdjkh"), // Includes 1.4.7 - V1_4_5("[Bayoaygaasrleayhbakaykmdazfassbapatjatjaueasobacarfbdoaupatkanzaslbekasjbecbenbdbbcykh"), - V1_4_2("[Baxgawyaarjkpawzayyaxclnaxxarkazcasbasbaswargaytaqabcbathascamuardbcxarbbcpbdabbobbljy"), - V1_3_2("[Batkatcaaofjbatdavbatgjwaubaogavfaovaovapnaocauwamxaxvapyaowajqanzayqanxayjaytaxkaxhik"), - V1_3_1("adb"), - V1_3pre("acl"), - V12w27a("acs"), - V12w26a("acl"), - V12w25a("acg"), - V12w24a("aca"), - V12w23b("acg"), - V12w22a("ace"), - V12w21b("aby"), - V12w21a("abm"), - V12w19a("aau"), - V1_2_4("[Bkivmaftxdlvqacqcwfcaawnlnlvpjclrckqdaiyxgplhusdakagi[J[Jalfqabv"), // Includes 1.2.5 - V1_2_2("wl"), - V12w08a("wj"), - V12w07b("wd"), - V12w06a("wb"), - V12w05a("vy"), - V12w04a("vu"), - V12w03a("vj"), - V1_1("[Bjsudadrvqluhaarcqevyzmqmqugiokzcepgagqvsonhhrgahqfy[J[Jaitpdbo"), - V1_0("[Baesmmaijryafvdinqfdrzhabeabexexwadtnglkqdfagvkiahmhsadk[J[Jtkgkyu"), - V1_9pre6("uk"), // TODO: Remove these versions? - V1_9pre5("ug"), - V1_9pre4("uh"), //TODO stronghold reset?? - V1_9pre3("to"), - V1_9pre2("sv"), - V1_9pre1("sq"), - Vbeta_1_8_1("[Bhwqpyrrviqswdbzdqurkhqrgviwbomnabjrxmafvoeacfer[J[Jaddmkbb"); // Had to rename from V1_8_1 - should it just be removed? - - public final String versionId; - - VersionInfo(String versionId) { - this.versionId = versionId; - } - - public boolean saveEnabled() { - return this != V12w21a && this != V12w21b && this != V12w22a; - } - - @Override - public String toString() { - return super.toString().replace("_", "."); - } - - public boolean isAtLeast(VersionInfo other) { - return this.ordinal() <= other.ordinal(); - } -} \ No newline at end of file diff --git a/src/devtools/java/amidst/devtools/MinecraftJarDownloadAvailabilityChecker.java b/src/devtools/java/amidst/devtools/MinecraftJarDownloadAvailabilityChecker.java new file mode 100644 index 000000000..48d66273c --- /dev/null +++ b/src/devtools/java/amidst/devtools/MinecraftJarDownloadAvailabilityChecker.java @@ -0,0 +1,33 @@ +package amidst.devtools; + +import java.io.IOException; + +import amidst.devtools.utils.VersionStateRenderer; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.JsonReader; +import amidst.mojangapi.file.json.versionlist.VersionListEntryJson; +import amidst.mojangapi.file.json.versionlist.VersionListJson; + +public class MinecraftJarDownloadAvailabilityChecker { + public static void main(String[] args) throws IOException, + MojangApiParsingException { + new MinecraftJarDownloadAvailabilityChecker( + JsonReader.readRemoteVersionList()) + .displayDownloadAvailability(); + } + + private VersionStateRenderer renderer = new VersionStateRenderer(); + private VersionListJson versionList; + + public MinecraftJarDownloadAvailabilityChecker(VersionListJson versionList) { + this.versionList = versionList; + } + + public void displayDownloadAvailability() { + for (VersionListEntryJson version : versionList.getVersions()) { + boolean hasServer = version.hasServer(); + boolean hasClient = version.hasClient(); + System.out.println(renderer.render(version, hasServer, hasClient)); + } + } +} diff --git a/src/devtools/java/amidst/devtools/MinecraftJarDownloader.java b/src/devtools/java/amidst/devtools/MinecraftJarDownloader.java new file mode 100644 index 000000000..80f1e835d --- /dev/null +++ b/src/devtools/java/amidst/devtools/MinecraftJarDownloader.java @@ -0,0 +1,36 @@ +package amidst.devtools; + +import java.io.IOException; + +import amidst.devtools.settings.DevToolsSettings; +import amidst.devtools.utils.VersionStateRenderer; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.JsonReader; +import amidst.mojangapi.file.json.versionlist.VersionListEntryJson; +import amidst.mojangapi.file.json.versionlist.VersionListJson; + +public class MinecraftJarDownloader { + public static void main(String[] args) throws IOException, + MojangApiParsingException { + new MinecraftJarDownloader( + DevToolsSettings.INSTANCE.getMinecraftVersionsDirectory(), + JsonReader.readRemoteVersionList()).downloadAll(); + } + + private VersionStateRenderer renderer = new VersionStateRenderer(); + private String prefix; + private VersionListJson versionList; + + public MinecraftJarDownloader(String prefix, VersionListJson versionList) { + this.prefix = prefix; + this.versionList = versionList; + } + + public void downloadAll() { + for (VersionListEntryJson version : versionList.getVersions()) { + boolean hasServer = version.tryDownloadServer(prefix); + boolean hasClient = version.tryDownloadClient(prefix); + System.out.println(renderer.render(version, hasServer, hasClient)); + } + } +} diff --git a/src/devtools/java/amidst/devtools/MinecraftVersionCompatibilityChecker.java b/src/devtools/java/amidst/devtools/MinecraftVersionCompatibilityChecker.java new file mode 100644 index 000000000..3da1a83bf --- /dev/null +++ b/src/devtools/java/amidst/devtools/MinecraftVersionCompatibilityChecker.java @@ -0,0 +1,96 @@ +package amidst.devtools; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import amidst.clazz.Classes; +import amidst.clazz.real.JarFileParsingException; +import amidst.clazz.symbolic.declaration.SymbolicClassDeclaration; +import amidst.clazz.translator.ClassTranslator; +import amidst.devtools.settings.DevToolsSettings; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.JsonReader; +import amidst.mojangapi.file.json.versionlist.VersionListEntryJson; +import amidst.mojangapi.file.json.versionlist.VersionListJson; +import amidst.mojangapi.minecraftinterface.local.DefaultClassTranslator; + +/** + * This only checks if there is exactly one class in the jar file for each + * class, defined by the class translator. Optional classes can be missing. + * However, it does not check if the class has all the required constructors, + * methods and fields. It also does not try to use the found classes. + */ +public class MinecraftVersionCompatibilityChecker { + public static void main(String[] args) throws IOException, + MojangApiParsingException { + new MinecraftVersionCompatibilityChecker( + DevToolsSettings.INSTANCE.getMinecraftVersionsDirectory(), + JsonReader.readRemoteVersionList()).checkAll(); + } + + private String prefix; + private VersionListJson versionList; + + public MinecraftVersionCompatibilityChecker(String prefix, + VersionListJson versionList) { + this.prefix = prefix; + this.versionList = versionList; + } + + public void checkAll() { + List supported = new ArrayList(); + List unsupported = new ArrayList(); + for (VersionListEntryJson version : versionList.getVersions()) { + if (checkOne(version)) { + supported.add(version); + } else { + unsupported.add(version); + } + } + displayVersionList(supported, + "================= SUPPORTED VERSIONS ================="); + displayVersionList(unsupported, + "================ UNSUPPORTED VERSIONS ================"); + } + + private boolean checkOne(VersionListEntryJson version) { + if (version.tryDownloadClient(prefix)) { + try { + File jarFile = new File(version.getClientJar(prefix)); + ClassTranslator translator = DefaultClassTranslator.INSTANCE + .get(); + return isSupported(Classes.countMatches(jarFile, translator)); + } catch (FileNotFoundException | JarFileParsingException e) { + e.printStackTrace(); + } + } + return false; + } + + private boolean isSupported( + Map matchesMap) { + for (Entry entry : matchesMap + .entrySet()) { + if (entry.getValue() > 1) { + return false; + } else if (entry.getValue() == 0 && !entry.getKey().isOptional()) { + return false; + } + } + return true; + } + + private void displayVersionList(List supported, + String message) { + System.out.println(); + System.out.println(message); + for (VersionListEntryJson version : supported) { + System.out.println(version.getId()); + } + } +} diff --git a/src/devtools/java/amidst/devtools/settings/DevToolsSettings.java b/src/devtools/java/amidst/devtools/settings/DevToolsSettings.java new file mode 100644 index 000000000..1cddfd304 --- /dev/null +++ b/src/devtools/java/amidst/devtools/settings/DevToolsSettings.java @@ -0,0 +1,11 @@ +package amidst.devtools.settings; + +public enum DevToolsSettings { + INSTANCE; + + private String minecraftVersionsDirectory = "/home/stefan/.minecraft/amidst-all-client-versions/"; + + public String getMinecraftVersionsDirectory() { + return minecraftVersionsDirectory; + } +} diff --git a/src/devtools/java/amidst/devtools/utils/VersionStateRenderer.java b/src/devtools/java/amidst/devtools/utils/VersionStateRenderer.java new file mode 100644 index 000000000..ce48651de --- /dev/null +++ b/src/devtools/java/amidst/devtools/utils/VersionStateRenderer.java @@ -0,0 +1,19 @@ +package amidst.devtools.utils; + +import amidst.mojangapi.file.json.versionlist.VersionListEntryJson; + +public class VersionStateRenderer { + public String render(VersionListEntryJson version, boolean hasServer, + boolean hasClient) { + return toBox(hasServer, 'S') + " " + toBox(hasClient, 'C') + " " + + version.getType().getTypeChar() + " " + version.getId(); + } + + private String toBox(boolean value, char c) { + if (value) { + return "[" + c + "]"; + } else { + return "[ ]"; + } + } +} diff --git a/src/main/java/amidst/Amidst.java b/src/main/java/amidst/Amidst.java new file mode 100644 index 000000000..2c1fccc06 --- /dev/null +++ b/src/main/java/amidst/Amidst.java @@ -0,0 +1,126 @@ +package amidst; + +import java.io.File; + +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.gui.crash.CrashWindow; +import amidst.logging.FileLogger; +import amidst.logging.Log; + +@NotThreadSafe +public class Amidst { + private static final String UNCAUGHT_EXCEPTION_ERROR_MESSAGE = "Amidst has encounted an uncaught exception on thread: "; + private static final String COMMAND_LINE_PARSING_ERROR_MESSAGE = "There was an issue parsing command line parameters."; + private static final CommandLineParameters PARAMETERS = new CommandLineParameters(); + + @CalledOnlyBy(AmidstThread.STARTUP) + public static void main(String args[]) { + initUncaughtExceptionHandler(); + parseCommandLineArguments(args); + initLogger(); + initLookAndFeel(); + setJava2DEnvironmentVariables(); + startApplication(); + } + + private static void initUncaughtExceptionHandler() { + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable e) { + handleCrash(e, UNCAUGHT_EXCEPTION_ERROR_MESSAGE + thread); + } + }); + } + + private static void parseCommandLineArguments(String[] args) { + try { + new CmdLineParser(PARAMETERS).parseArgument(args); + } catch (CmdLineException e) { + Log.w(COMMAND_LINE_PARSING_ERROR_MESSAGE); + e.printStackTrace(); + } + } + + private static void initLogger() { + if (PARAMETERS.logPath != null) { + Log.addListener("file", + new FileLogger(new File(PARAMETERS.logPath))); + } + } + + private static void initLookAndFeel() { + if (!isOSX()) { + try { + UIManager.setLookAndFeel(UIManager + .getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException | InstantiationException + | IllegalAccessException | UnsupportedLookAndFeelException e) { + Log.printTraceStack(e); + } + } + } + + private static boolean isOSX() { + return System.getProperty("os.name").contains("OS X"); + } + + private static void setJava2DEnvironmentVariables() { + System.setProperty("sun.java2d.opengl", "True"); + System.setProperty("sun.java2d.accthreshold", "0"); + } + + private static void startApplication() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + doStartApplication(); + } + }); + } + + @CalledOnlyBy(AmidstThread.EDT) + private static void doStartApplication() { + try { + new Application(PARAMETERS).run(); + } catch (Exception e) { + handleCrash(e, "Amidst crashed!"); + } + } + + @CalledByAny + private static void handleCrash(Throwable e, String message) { + try { + Log.crash(e, message); + displayCrashWindow(message, Log.getAllMessages()); + } catch (Throwable t) { + System.err.println("Amidst crashed!"); + System.err.println(message); + e.printStackTrace(); + } + } + + private static void displayCrashWindow(final String message, + final String allMessages) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + new CrashWindow(message, allMessages, new Runnable() { + @Override + public void run() { + System.exit(4); + } + }); + } + }); + } +} diff --git a/src/main/java/amidst/AmidstMetaData.java b/src/main/java/amidst/AmidstMetaData.java new file mode 100644 index 000000000..9dda247df --- /dev/null +++ b/src/main/java/amidst/AmidstMetaData.java @@ -0,0 +1,48 @@ +package amidst; + +import java.awt.image.BufferedImage; +import java.util.Properties; + +import amidst.documentation.Immutable; + +@Immutable +public class AmidstMetaData { + public static AmidstMetaData from(Properties properties, BufferedImage icon) { + // @formatter:off + return new AmidstMetaData( + icon, + Integer.parseInt(properties.getProperty("amidst.version.major")), + Integer.parseInt(properties.getProperty("amidst.version.minor")), + properties.getProperty("amidst.gui.mainWindow.title")); + // @formatter:on + } + + private final BufferedImage icon; + private final int majorVersion; + private final int minorVersion; + private final String mainWindowTitle; + + private AmidstMetaData(BufferedImage icon, int majorVersion, + int minorVersion, String mainWindowTitle) { + this.icon = icon; + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.mainWindowTitle = mainWindowTitle; + } + + public BufferedImage getIcon() { + return icon; + } + + public int getMajorVersion() { + return majorVersion; + } + + public int getMinorVersion() { + return minorVersion; + } + + public String getMainWindowTitle() { + return mainWindowTitle; + } +} diff --git a/src/main/java/amidst/Application.java b/src/main/java/amidst/Application.java new file mode 100644 index 000000000..36b9b39e5 --- /dev/null +++ b/src/main/java/amidst/Application.java @@ -0,0 +1,146 @@ +package amidst; + +import java.io.FileNotFoundException; +import java.util.prefs.Preferences; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.layer.LayerBuilder; +import amidst.gui.license.LicenseWindow; +import amidst.gui.main.MainWindow; +import amidst.gui.main.worldsurroundings.WorldSurroundingsBuilder; +import amidst.gui.profileselect.ProfileSelectWindow; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.MojangApiBuilder; +import amidst.mojangapi.minecraftinterface.local.LocalMinecraftInterfaceCreationException; +import amidst.mojangapi.world.SeedHistoryLogger; +import amidst.mojangapi.world.WorldBuilder; +import amidst.mojangapi.world.player.PlayerInformationCache; +import amidst.threading.ThreadMaster; + +@NotThreadSafe +public class Application { + private final CommandLineParameters parameters; + private final AmidstMetaData metadata; + private final Settings settings; + private final MojangApi mojangApi; + private final WorldSurroundingsBuilder worldSurroundingsBuilder; + private final ThreadMaster threadMaster; + + private volatile ProfileSelectWindow profileSelectWindow; + private volatile MainWindow mainWindow; + + @CalledOnlyBy(AmidstThread.EDT) + public Application(CommandLineParameters parameters) + throws FileNotFoundException, + LocalMinecraftInterfaceCreationException { + this.parameters = parameters; + this.metadata = createMetadata(); + this.settings = createSettings(); + this.mojangApi = createMojangApi(); + this.worldSurroundingsBuilder = createWorldSurroundingsBuilder(); + this.threadMaster = createThreadMaster(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private AmidstMetaData createMetadata() { + return AmidstMetaData.from( + ResourceLoader.getProperties("/amidst/metadata.properties"), + ResourceLoader.getImage("/amidst/icon.png")); + } + + @CalledOnlyBy(AmidstThread.EDT) + private Settings createSettings() { + return new Settings(Preferences.userNodeForPackage(Amidst.class)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private MojangApi createMojangApi() throws FileNotFoundException, + LocalMinecraftInterfaceCreationException { + return new MojangApiBuilder(new WorldBuilder( + new PlayerInformationCache(), new SeedHistoryLogger( + parameters.historyPath)), parameters.minecraftPath, + parameters.minecraftLibraries, parameters.minecraftJar, + parameters.minecraftJson).construct(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private WorldSurroundingsBuilder createWorldSurroundingsBuilder() { + return new WorldSurroundingsBuilder(settings, + new LayerBuilder(settings)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private ThreadMaster createThreadMaster() { + return new ThreadMaster(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void run() { + if (mojangApi.canCreateWorld()) { + displayMainWindow(); + } else { + displayProfileSelectWindow(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void displayMainWindow() { + setMainWindow(new MainWindow(this, metadata, settings, mojangApi, + worldSurroundingsBuilder, threadMaster)); + setProfileSelectWindow(null); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void displayProfileSelectWindow() { + setProfileSelectWindow(new ProfileSelectWindow(this, metadata, + threadMaster.getWorkerExecutor(), mojangApi, settings)); + setMainWindow(null); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void setProfileSelectWindow(ProfileSelectWindow profileSelectWindow) { + disposeProfileSelectWindow(); + this.profileSelectWindow = profileSelectWindow; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void setMainWindow(MainWindow mainWindow) { + disposeMainWindow(); + this.mainWindow = mainWindow; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void disposeProfileSelectWindow() { + ProfileSelectWindow profileSelectWindow = this.profileSelectWindow; + if (profileSelectWindow != null) { + profileSelectWindow.dispose(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void disposeMainWindow() { + MainWindow mainWindow = this.mainWindow; + if (mainWindow != null) { + mainWindow.dispose(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void displayLicenseWindow() { + new LicenseWindow(metadata); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void exitGracefully() { + dispose(); + System.exit(0); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void dispose() { + setProfileSelectWindow(null); + setMainWindow(null); + } +} diff --git a/src/main/java/amidst/CommandLineParameters.java b/src/main/java/amidst/CommandLineParameters.java new file mode 100644 index 000000000..0f0baf5a2 --- /dev/null +++ b/src/main/java/amidst/CommandLineParameters.java @@ -0,0 +1,30 @@ +package amidst; + +import org.kohsuke.args4j.Option; + +import amidst.documentation.ThreadSafe; + +/** + * An instance of this class will be created to hold the command line + * parameters. Afterwards, the assigned values should not be modified. + */ +@ThreadSafe +public class CommandLineParameters { + @Option(name = "-history", usage = "Sets the path to seed history file.", metaVar = "") + public volatile String historyPath; + + @Option(name = "-log", usage = "Sets the path to logging file.", metaVar = "") + public volatile String logPath; + + @Option(name = "-mcpath", usage = "Sets the path to the .minecraft directory.", metaVar = "") + public volatile String minecraftPath; + + @Option(name = "-mcjar", usage = "Sets the path to the minecraft .jar", metaVar = "") + public volatile String minecraftJar; + + @Option(name = "-mcjson", usage = "Sets the path to the minecraft .json", metaVar = "") + public volatile String minecraftJson; + + @Option(name = "-mclibs", usage = "Sets the path to the libraries/ folder", metaVar = "") + public volatile String minecraftLibraries; +} diff --git a/src/main/java/amidst/ResourceLoader.java b/src/main/java/amidst/ResourceLoader.java new file mode 100644 index 000000000..f72960444 --- /dev/null +++ b/src/main/java/amidst/ResourceLoader.java @@ -0,0 +1,62 @@ +package amidst; + +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +import javax.imageio.ImageIO; + +import amidst.documentation.Immutable; + +@Immutable +public enum ResourceLoader { + ; + + public static URL getResourceURL(String name) { + return ResourceLoader.class.getResource(name); + } + + public static BufferedImage getImage(String name) { + try { + return ImageIO.read(getResourceURL(name)); + } catch (IOException e) { + // This is always a developer error, because a resource was not + // included in the jar file. + throw new IllegalArgumentException(e); + } + } + + public static Properties getProperties(String name) { + Properties properties = new Properties(); + try { + properties.load(getResourceAsStream(name)); + } catch (IOException e) { + // This is always a developer error, because a resource was not + // included in the jar file. + throw new IllegalArgumentException(e); + } + return properties; + } + + public static String getResourceAsString(String name) throws IOException { + InputStreamReader reader = new InputStreamReader( + getResourceAsStream(name), StandardCharsets.UTF_8); + char[] buffer = new char[1024]; + int length; + StringBuilder result = new StringBuilder(); + while ((length = reader.read(buffer)) != -1) { + result.append(buffer, 0, length); + } + return result.toString(); + } + + private static InputStream getResourceAsStream(String filename) { + return new BufferedInputStream( + ResourceLoader.class.getResourceAsStream(filename)); + } +} diff --git a/src/main/java/amidst/Settings.java b/src/main/java/amidst/Settings.java new file mode 100644 index 000000000..d7ab070f7 --- /dev/null +++ b/src/main/java/amidst/Settings.java @@ -0,0 +1,62 @@ +package amidst; + +import java.util.prefs.Preferences; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.ThreadSafe; +import amidst.mojangapi.world.WorldType; +import amidst.settings.BooleanSetting; +import amidst.settings.MultipleStringsSetting; +import amidst.settings.Setting; +import amidst.settings.StringSetting; +import amidst.settings.biomecolorprofile.BiomeColorProfile; +import amidst.settings.biomecolorprofile.BiomeColorProfileSelection; + +@ThreadSafe +public class Settings { + public final BooleanSetting showSlimeChunks; + public final BooleanSetting showGrid; + public final BooleanSetting showNetherFortresses; + public final BooleanSetting showTemples; + public final BooleanSetting showPlayers; + public final BooleanSetting showStrongholds; + public final BooleanSetting showVillages; + public final BooleanSetting showOceanMonuments; + public final BooleanSetting showSpawn; + public final BooleanSetting smoothScrolling; + public final BooleanSetting fragmentFading; + public final BooleanSetting showFPS; + public final BooleanSetting showScale; + public final BooleanSetting showDebug; + public final BooleanSetting updateToUnstable; + public final BooleanSetting maxZoom; + public final Setting lastProfile; + public final MultipleStringsSetting worldType; + public final BiomeColorProfileSelection biomeColorProfileSelection; + + @CalledOnlyBy(AmidstThread.EDT) + public Settings(Preferences preferences) { + // @formatter:off + showSlimeChunks = new BooleanSetting( preferences, "slimeChunks", false); + showGrid = new BooleanSetting( preferences, "grid", false); + showNetherFortresses = new BooleanSetting( preferences, "netherFortressIcons", false); + smoothScrolling = new BooleanSetting( preferences, "mapFlicking", true); + fragmentFading = new BooleanSetting( preferences, "mapFading", true); + maxZoom = new BooleanSetting( preferences, "maxZoom", true); + showStrongholds = new BooleanSetting( preferences, "strongholdIcons", true); + showPlayers = new BooleanSetting( preferences, "playerIcons", true); + showTemples = new BooleanSetting( preferences, "templeIcons", true); + showVillages = new BooleanSetting( preferences, "villageIcons", true); + showOceanMonuments = new BooleanSetting( preferences, "oceanMonumentIcons", true); + showSpawn = new BooleanSetting( preferences, "spawnIcon", true); + showFPS = new BooleanSetting( preferences, "showFPS", true); + showScale = new BooleanSetting( preferences, "showScale", true); + showDebug = new BooleanSetting( preferences, "showDebug", false); + updateToUnstable = new BooleanSetting( preferences, "updateToUnstable", false); + lastProfile = new StringSetting( preferences, "profile", null); + worldType = new MultipleStringsSetting(preferences, "worldType", WorldType.PROMPT_EACH_TIME, WorldType.getWorldTypeSettingAvailableValues()); + biomeColorProfileSelection = new BiomeColorProfileSelection(BiomeColorProfile.getDefaultProfile()); + // @formatter:on + } +} diff --git a/src/main/java/amidst/clazz/Classes.java b/src/main/java/amidst/clazz/Classes.java new file mode 100644 index 000000000..dfeea9d9b --- /dev/null +++ b/src/main/java/amidst/clazz/Classes.java @@ -0,0 +1,70 @@ +package amidst.clazz; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import amidst.clazz.real.JarFileParsingException; +import amidst.clazz.real.RealClass; +import amidst.clazz.real.RealClasses; +import amidst.clazz.symbolic.SymbolicClass; +import amidst.clazz.symbolic.SymbolicClassGraphCreationException; +import amidst.clazz.symbolic.SymbolicClasses; +import amidst.clazz.symbolic.declaration.SymbolicClassDeclaration; +import amidst.clazz.translator.ClassTranslator; +import amidst.documentation.Immutable; +import amidst.logging.Log; + +@Immutable +public enum Classes { + ; + + public static Map createSymbolicClassMap( + File jarFile, URLClassLoader classLoader, ClassTranslator translator) + throws FileNotFoundException, JarFileParsingException, + SymbolicClassGraphCreationException, ClassNotFoundException { + Log.i("Reading " + jarFile.getName()); + List realClasses = RealClasses.fromJarFile(jarFile); + Log.i("Jar load complete."); + Log.i("Searching for classes..."); + Map realClassNamesBySymbolicClassDeclaration = translator + .translate(realClasses); + Log.i("Class search complete."); + Log.i("Loading classes..."); + Map result = SymbolicClasses.from( + realClassNamesBySymbolicClassDeclaration, classLoader); + Log.i("Classes loaded."); + return result; + } + + public static Map countMatches( + File jarFile, ClassTranslator translator) + throws FileNotFoundException, JarFileParsingException { + Log.i("Checking " + jarFile.getName()); + List realClasses = RealClasses.fromJarFile(jarFile); + Map> map = translator + .translateToAllMatching(realClasses); + Map result = new HashMap(); + for (Entry> entry : map + .entrySet()) { + result.put(entry.getKey(), entry.getValue().size()); + if (entry.getValue().isEmpty()) { + Log.w(entry.getKey().getSymbolicClassName() + + " has no matching class"); + } else if (entry.getValue().size() > 1) { + StringBuilder builder = new StringBuilder(); + for (RealClass realClass : entry.getValue()) { + builder.append(", ").append(realClass.getRealClassName()); + } + Log.w(entry.getKey().getSymbolicClassName() + + " has multiple matching classes: " + + builder.toString().substring(2)); + } + } + return result; + } +} diff --git a/src/main/java/amidst/clazz/real/AccessFlags.java b/src/main/java/amidst/clazz/real/AccessFlags.java new file mode 100644 index 000000000..55fc70011 --- /dev/null +++ b/src/main/java/amidst/clazz/real/AccessFlags.java @@ -0,0 +1,31 @@ +package amidst.clazz.real; + +import amidst.documentation.Immutable; + +/*- + * ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. + * ACC_PRIVATE 0x0002 Declared private; usable only within the defining class. + * ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses. + * ACC_STATIC 0x0008 Declared static. + * ACC_FINAL 0x0010 Declared final; no further assignment after initialization. + * ACC_VOLATILE 0x0040 Declared volatile; cannot be cached. + * ACC_TRANSIENT 0x0080 Declared transient; not written or read by a persistent object manager. + * ACC_INTERFACE 0x0200 Is an interface, not a class. + **/ +@Immutable +public enum AccessFlags { + ; + + public static final int PUBLIC = 0x01; + public static final int PRIVATE = 0x02; + public static final int PROTECTED = 0x04; + public static final int STATIC = 0x08; + public static final int FINAL = 0x10; + public static final int VOLATILE = 0x40; + public static final int TRANSIENT = 0x80; + public static final int INTERFACE = 0x0200; + + public static boolean hasFlags(int accessFlags, int flags) { + return (accessFlags & flags) == flags; + } +} diff --git a/src/main/java/amidst/clazz/real/JarFileParsingException.java b/src/main/java/amidst/clazz/real/JarFileParsingException.java new file mode 100644 index 000000000..d306199e4 --- /dev/null +++ b/src/main/java/amidst/clazz/real/JarFileParsingException.java @@ -0,0 +1,11 @@ +package amidst.clazz.real; + +import amidst.documentation.Immutable; + +@SuppressWarnings("serial") +@Immutable +public class JarFileParsingException extends Exception { + public JarFileParsingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/amidst/clazz/real/RealClass.java b/src/main/java/amidst/clazz/real/RealClass.java new file mode 100644 index 000000000..0eacc5ad8 --- /dev/null +++ b/src/main/java/amidst/clazz/real/RealClass.java @@ -0,0 +1,261 @@ +package amidst.clazz.real; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import amidst.documentation.Immutable; + +@Immutable +public class RealClass { + private static Map createPrimitiveTypeConversionMap() { + Map result = new HashMap(); + result.put('B', "byte"); + result.put('C', "char"); + result.put('D', "double"); + result.put('F', "float"); + result.put('I', "int"); + result.put('J', "long"); + result.put('S', "short"); + result.put('Z', "boolean"); + return Collections.unmodifiableMap(result); + } + + private static final Map PRIMITIV_TYPE_CONVERSION_MAP = createPrimitiveTypeConversionMap(); + private static final Pattern ARG_PATTERN = Pattern + .compile("([\\[]+)?([BCDFIJSZ]|L[^;]+)"); + private static final Pattern OBJECT_PATTERN = Pattern + .compile("^([\\[]+)?[LBCDFIJSZ]"); + public static final int CLASS_DATA_WILDCARD = -1; + + private final String realClassName; + private final byte[] classData; + + private final int minorVersion; + private final int majorVersion; + + private final int cpSize; + @SuppressWarnings("unused") + private final int[] constantTypes; + private final RealClassConstant[] constants; + + private final List utf8Constants; + private final List floatConstants; + private final List longConstants; + private final List stringIndices; + private final List methodIndices; + + private final int accessFlags; + private final RealClassField[] fields; + + private final int numberOfConstructors; + private final int numberOfMethods; + + RealClass(String realClassName, byte[] classData, int minorVersion, + int majorVersion, int cpSize, int[] constantTypes, + RealClassConstant[] constants, List utf8Constants, + List floatConstants, List longConstants, + List stringIndices, List methodIndices, + int accessFlags, RealClassField[] fields, int numberOfConstructors, + int numberOfMethods) { + this.realClassName = realClassName; + this.classData = classData; + this.minorVersion = minorVersion; + this.majorVersion = majorVersion; + this.cpSize = cpSize; + this.constantTypes = constantTypes; + this.constants = constants; + this.utf8Constants = utf8Constants; + this.floatConstants = floatConstants; + this.longConstants = longConstants; + this.stringIndices = stringIndices; + this.methodIndices = methodIndices; + this.accessFlags = accessFlags; + this.fields = fields; + this.numberOfConstructors = numberOfConstructors; + this.numberOfMethods = numberOfMethods; + } + + public String getRealClassName() { + return realClassName; + } + + public boolean isClassDataWildcardMatching(int[] bytes) { + int loopLimit = classData.length + 1 - bytes.length; + for (int startIndex = 0; startIndex < loopLimit; startIndex++) { + if (isClassDataWildcardMatchingAt(startIndex, bytes)) { + return true; + } + } + return false; + } + + private boolean isClassDataWildcardMatchingAt(int startIndex, int[] bytes) { + for (int offset = 0; offset < bytes.length; offset++) { + if (bytes[offset] != CLASS_DATA_WILDCARD + && classData[startIndex + offset] != (byte) bytes[offset]) { + return false; + } + } + return true; + } + + public int getMinorVersion() { + return minorVersion; + } + + public int getMajorVersion() { + return majorVersion; + } + + public int getCpSize() { + return cpSize; + } + + public boolean searchForUtf8EqualTo(String required) { + for (String entry : utf8Constants) { + if (entry.equals(required)) { + return true; + } + } + return false; + } + + public boolean searchForFloat(float required) { + for (Float entry : floatConstants) { + if (entry.floatValue() == required) { + return true; + } + } + return false; + } + + public boolean searchForLong(long required) { + for (Long entry : longConstants) { + if (entry.longValue() == required) { + return true; + } + } + return false; + } + + public boolean searchForStringContaining(String requiredValue) { + for (Integer entry : stringIndices) { + String entryValue = getStringValueOfConstant(entry); + if (entryValue.contains(requiredValue)) { + return true; + } + } + return false; + } + + public String searchByReturnType(String required) { + String requiredType = "L" + required + ";"; + for (ReferenceIndex entry : methodIndices) { + String value = getStringValueOfConstant(entry.getValue2()); + String entryType = value.substring(value.indexOf(')') + 1); + if (entryType.equals(requiredType)) { + return getStringValueOfConstant(entry.getValue1()); + } + } + return null; + } + + public RealClassField getField(int index) { + return fields[index]; + } + + public int getAccessFlags() { + return accessFlags; + } + + public int getNumberOfConstructors() { + return numberOfConstructors; + } + + public int getNumberOfMethods() { + return numberOfMethods; + } + + public int getNumberOfFields() { + return fields.length; + } + + public boolean isInterface() { + return AccessFlags.hasFlags(accessFlags, AccessFlags.INTERFACE); + } + + public boolean isFinal() { + return AccessFlags.hasFlags(accessFlags, AccessFlags.FINAL); + } + + public String getArgumentsForConstructor(int constructorId) { + int i = 0; + for (ReferenceIndex entry : methodIndices) { + if (getStringValueOfConstant(entry.getValue1()).equals("")) { + if (i == constructorId) { + String arguments = getStringValueOfConstant(entry + .getValue2()); + return toArgumentString(readArguments(arguments)); + } + i++; + } + } + return ""; + } + + private String getStringValueOfConstant(int value) { + return (String) constants[value - 1].getValue(); + } + + private String[] readArguments(String arguments) { + List result = new ArrayList(); + String args = arguments.substring(1).split("\\)")[0]; + Matcher matcher = ARG_PATTERN.matcher(args); + while (matcher.find()) { + String arg = args.substring(matcher.start(), matcher.end()); + Matcher objectMatcher = OBJECT_PATTERN.matcher(arg); + if (objectMatcher.find()) { + arg = getObjectArg(arg, objectMatcher.end()); + } + result.add(arg); + } + return result.toArray(new String[result.size()]); + } + + private String getObjectArg(String arg, int matcherEnd) { + return arg.substring(0, Math.max(0, matcherEnd - 1)) + + getPrimitiveType(arg.charAt(matcherEnd - 1)) + + arg.substring(Math.min(matcherEnd, arg.length())); + } + + private String getPrimitiveType(char typeChar) { + if (PRIMITIV_TYPE_CONVERSION_MAP.containsKey(typeChar)) { + return PRIMITIV_TYPE_CONVERSION_MAP.get(typeChar); + } else { + return ""; + } + } + + private String toArgumentString(String[] arguments) { + StringBuilder result = new StringBuilder(); + result.append("("); + if (arguments.length > 0) { + result.append(arguments[0]); + for (int i = 1; i < arguments.length; i++) { + result.append(",").append(arguments[i]); + } + } + result.append(")"); + return result.toString(); + } + + @Override + public String toString() { + return "[RealClass " + realClassName + "]"; + } +} diff --git a/src/main/java/amidst/clazz/real/RealClassBuilder.java b/src/main/java/amidst/clazz/real/RealClassBuilder.java new file mode 100644 index 000000000..b9a03b09b --- /dev/null +++ b/src/main/java/amidst/clazz/real/RealClassBuilder.java @@ -0,0 +1,254 @@ +package amidst.clazz.real; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import amidst.clazz.real.RealClassConstant.RealClassConstantType; +import amidst.documentation.Immutable; + +@Immutable +public class RealClassBuilder { + public RealClass construct(String realClassName, byte[] classData) + throws RealClassCreationException { + try (DataInputStream stream = new DataInputStream( + new ByteArrayInputStream(classData))) { + return doConstruct(realClassName, classData, stream); + } catch (IOException e) { + throw new RealClassCreationException( + "unable to create real class for the class: " + + realClassName, e); + } + } + + private RealClass doConstruct(String realClassName, byte[] classData, + DataInputStream stream) throws IOException { + if (isValidClass(stream)) { + int minorVersion = readMinorVersion(stream); + int majorVersion = readMajorVersion(stream); + int cpSize = readCpSize(stream); + int[] constantTypes = new int[cpSize]; + RealClassConstant[] constants = new RealClassConstant[cpSize]; + List utf8Constants = new ArrayList(); + List floatConstants = new ArrayList(); + List longConstants = new ArrayList(); + List stringIndices = new ArrayList(); + List methodIndices = new ArrayList(); + readConstants(stream, cpSize, constantTypes, constants, + utf8Constants, floatConstants, longConstants, stringIndices); + int accessFlags = readAccessFlags(stream); + skipThisClass(stream); + skipSuperClass(stream); + skipInterfaces(stream); + RealClassField[] fields = readFields(stream); + int numberOfMethodsAndConstructors = readNumberOfMethodsAndConstructors(stream); + int numberOfConstructors = readMethodsAndConstructors(stream, + numberOfMethodsAndConstructors, constants, methodIndices); + int numberOfMethods = numberOfMethodsAndConstructors + - numberOfConstructors; + return new RealClass(realClassName, classData, minorVersion, + majorVersion, cpSize, constantTypes, constants, + utf8Constants, floatConstants, longConstants, + stringIndices, methodIndices, accessFlags, fields, + numberOfConstructors, numberOfMethods); + } else { + return null; + } + } + + private boolean isValidClass(DataInputStream stream) throws IOException { + return stream.readInt() == 0xCAFEBABE; + } + + private int readMinorVersion(DataInputStream stream) throws IOException { + return stream.readUnsignedShort(); + } + + private int readMajorVersion(DataInputStream stream) throws IOException { + return stream.readUnsignedShort(); + } + + private int readCpSize(DataInputStream stream) throws IOException { + return stream.readUnsignedShort() - 1; + } + + private void readConstants(DataInputStream stream, int cpSize, + int[] constantTypes, RealClassConstant[] constants, + List utf8Constants, List floatConstants, + List longConstants, List stringIndices) + throws IOException { + for (int q = 0; q < cpSize; q++) { + byte type = stream.readByte(); + constantTypes[q] = type; + constants[q] = readConstant(stream, type, utf8Constants, + floatConstants, longConstants, stringIndices); + if (RealClassConstantType.isQIncreasing(type)) { + q++; + } + } + } + + private RealClassConstant readConstant(DataInputStream stream, + byte type, List utf8Constants, List floatConstants, + List longConstants, List stringIndices) + throws IOException { + switch (type) { + case RealClassConstantType.STRING: + return readString(stream, type, utf8Constants); + case RealClassConstantType.INTEGER: + return readInteger(stream, type); + case RealClassConstantType.FLOAT: + return readFloat(stream, type, floatConstants); + case RealClassConstantType.LONG: + return readLong(stream, type, longConstants); + case RealClassConstantType.DOUBLE: + return readDouble(stream, type); + case RealClassConstantType.CLASS_REFERENCE: + return readClassReference(stream, type); + case RealClassConstantType.STRING_REFERENCE: + return readStringReference(stream, type, stringIndices); + case RealClassConstantType.FIELD_REFERENCE: + return readAnotherReference(stream, type); + case RealClassConstantType.METHOD_REFERENCE: + return readAnotherReference(stream, type); + case RealClassConstantType.INTERFACE_METHOD_REFERENCE: + return readAnotherReference(stream, type); + case RealClassConstantType.NAME_AND_TYPE_DESCRIPTOR: + return readAnotherReference(stream, type); + default: + return null; + } + } + + private RealClassConstant readString(DataInputStream stream, + byte type, List utf8Constants) throws IOException { + String value = readStringValue(stream); + utf8Constants.add(value); + return new RealClassConstant(type, value); + } + + private String readStringValue(DataInputStream stream) throws IOException { + char[] result = new char[stream.readUnsignedShort()]; + for (int i = 0; i < result.length; i++) { + result[i] = (char) stream.readByte(); + } + return new String(result); + } + + private RealClassConstant readInteger(DataInputStream stream, + byte type) throws IOException { + int value = stream.readInt(); + return new RealClassConstant(type, value); + } + + private RealClassConstant readFloat(DataInputStream stream, + byte type, List floatConstants) throws IOException { + float value = stream.readFloat(); + floatConstants.add(value); + return new RealClassConstant(type, value); + } + + private RealClassConstant readLong(DataInputStream stream, byte type, + List longConstants) throws IOException { + long value = stream.readLong(); + longConstants.add(value); + return new RealClassConstant(type, value); + } + + private RealClassConstant readDouble(DataInputStream stream, + byte type) throws IOException { + double value = stream.readDouble(); + return new RealClassConstant(type, value); + } + + private RealClassConstant readClassReference( + DataInputStream stream, byte type) throws IOException { + int value = stream.readUnsignedShort(); + return new RealClassConstant(type, value); + } + + private RealClassConstant readStringReference( + DataInputStream stream, byte type, List stringIndices) + throws IOException { + int value = stream.readUnsignedShort(); + stringIndices.add(value); + return new RealClassConstant(type, value); + } + + private RealClassConstant readAnotherReference( + DataInputStream stream, byte type) throws IOException { + ReferenceIndex value = readReferenceIndex(stream); + return new RealClassConstant(type, value); + } + + private int readAccessFlags(DataInputStream stream) throws IOException { + return stream.readUnsignedShort(); + } + + private void skipThisClass(DataInputStream stream) throws IOException { + stream.skip(2); + } + + private void skipSuperClass(DataInputStream stream) throws IOException { + stream.skip(2); + } + + private void skipInterfaces(DataInputStream stream) throws IOException { + stream.skip(2 * stream.readUnsignedShort()); + } + + private RealClassField[] readFields(DataInputStream stream) + throws IOException { + RealClassField[] fields = new RealClassField[stream.readUnsignedShort()]; + for (int i = 0; i < fields.length; i++) { + fields[i] = new RealClassField(stream.readUnsignedShort()); + stream.skip(4); + skipAttributes(stream); + } + return fields; + } + + private int readNumberOfMethodsAndConstructors(DataInputStream stream) + throws IOException { + return stream.readUnsignedShort(); + } + + private int readMethodsAndConstructors(DataInputStream stream, + int numberOfMethodsAndConstructors, + RealClassConstant[] constants, List methodIndices) + throws IOException { + int numberOfConstructors = 0; + for (int i = 0; i < numberOfMethodsAndConstructors; i++) { + stream.skip(2); + ReferenceIndex referenceIndex = readReferenceIndex(stream); + methodIndices.add(referenceIndex); + String constant = (String) constants[referenceIndex.getValue1() - 1] + .getValue(); + if (constant.contains("")) { + numberOfConstructors++; + } + skipAttributes(stream); + } + return numberOfConstructors; + } + + private ReferenceIndex readReferenceIndex(DataInputStream stream) + throws IOException { + int value1 = stream.readUnsignedShort(); + int value2 = stream.readUnsignedShort(); + return new ReferenceIndex(value1, value2); + } + + private void skipAttributes(DataInputStream stream) throws IOException { + int attributeInfoCount = stream.readUnsignedShort(); + for (int q = 0; q < attributeInfoCount; q++) { + stream.skip(2); + int attributeCount = stream.readInt(); + for (int z = 0; z < attributeCount; z++) { + stream.skip(1); + } + } + } +} diff --git a/src/main/java/amidst/clazz/real/RealClassConstant.java b/src/main/java/amidst/clazz/real/RealClassConstant.java new file mode 100644 index 000000000..0f790d197 --- /dev/null +++ b/src/main/java/amidst/clazz/real/RealClassConstant.java @@ -0,0 +1,43 @@ +package amidst.clazz.real; + +import amidst.documentation.Immutable; + +@Immutable +public class RealClassConstant { + @Immutable + public static enum RealClassConstantType { + ; + + public static final int STRING = 1; + public static final int INTEGER = 3; + public static final int FLOAT = 4; + public static final int LONG = 5; + public static final int DOUBLE = 6; + public static final int CLASS_REFERENCE = 7; + public static final int STRING_REFERENCE = 8; + public static final int FIELD_REFERENCE = 9; + public static final int METHOD_REFERENCE = 10; + public static final int INTERFACE_METHOD_REFERENCE = 11; + public static final int NAME_AND_TYPE_DESCRIPTOR = 12; + + public static boolean isQIncreasing(byte type) { + return type == LONG || type == DOUBLE; + } + } + + private final byte type; + private final T value; + + public RealClassConstant(byte type, T value) { + this.type = type; + this.value = value; + } + + public int getType() { + return type; + } + + public T getValue() { + return value; + } +} diff --git a/src/main/java/amidst/clazz/real/RealClassCreationException.java b/src/main/java/amidst/clazz/real/RealClassCreationException.java new file mode 100644 index 000000000..20d4586c6 --- /dev/null +++ b/src/main/java/amidst/clazz/real/RealClassCreationException.java @@ -0,0 +1,11 @@ +package amidst.clazz.real; + +import amidst.documentation.Immutable; + +@SuppressWarnings("serial") +@Immutable +public class RealClassCreationException extends Exception { + public RealClassCreationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/amidst/clazz/real/RealClassField.java b/src/main/java/amidst/clazz/real/RealClassField.java new file mode 100644 index 000000000..43db0f269 --- /dev/null +++ b/src/main/java/amidst/clazz/real/RealClassField.java @@ -0,0 +1,16 @@ +package amidst.clazz.real; + +import amidst.documentation.Immutable; + +@Immutable +public class RealClassField { + private final int accessFlags; + + public RealClassField(int accessFlags) { + this.accessFlags = accessFlags; + } + + public boolean hasFlags(int flags) { + return AccessFlags.hasFlags(accessFlags, flags); + } +} diff --git a/src/main/java/amidst/clazz/real/RealClasses.java b/src/main/java/amidst/clazz/real/RealClasses.java new file mode 100644 index 000000000..992d8b234 --- /dev/null +++ b/src/main/java/amidst/clazz/real/RealClasses.java @@ -0,0 +1,91 @@ +package amidst.clazz.real; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import amidst.documentation.Immutable; + +@Immutable +public enum RealClasses { + ; + + private static final int MAXIMUM_CLASS_BYTES = 8000; + private static final RealClassBuilder REAL_CLASS_BUILDER = new RealClassBuilder(); + + public static List fromJarFile(File jarFile) + throws FileNotFoundException, JarFileParsingException { + return readRealClassesFromJarFile(jarFile); + } + + private static List readRealClassesFromJarFile(File jarFile) + throws JarFileParsingException, FileNotFoundException { + if (!jarFile.exists()) { + throw new FileNotFoundException("Attempted to load jar file at: " + + jarFile + " but it does not exist."); + } + try (ZipFile zipFile = new ZipFile(jarFile)) { + return readJarFile(zipFile); + } catch (IOException e) { + throw new JarFileParsingException("Error extracting jar data.", e); + } catch (RealClassCreationException e) { + throw new JarFileParsingException("Error extracting jar data.", e); + } + } + + private static List readJarFile(ZipFile zipFile) + throws IOException, RealClassCreationException { + Enumeration enu = zipFile.entries(); + List result = new ArrayList(); + while (enu.hasMoreElements()) { + RealClass entry = readJarFileEntry(zipFile, enu.nextElement()); + if (entry != null) { + result.add(entry); + } + } + return result; + } + + private static RealClass readJarFileEntry(ZipFile zipFile, ZipEntry entry) + throws IOException, RealClassCreationException { + String realClassName = getFileNameWithoutExtension(entry.getName(), + "class"); + if (!entry.isDirectory() && realClassName != null) { + return readRealClass(realClassName, + new BufferedInputStream(zipFile.getInputStream(entry))); + } else { + return null; + } + } + + private static RealClass readRealClass(String realClassName, + BufferedInputStream stream) throws IOException, + RealClassCreationException { + try (BufferedInputStream theStream = stream) { + // TODO: Double check that this filter won't mess anything up. + if (theStream.available() < MAXIMUM_CLASS_BYTES) { + byte[] classData = new byte[theStream.available()]; + theStream.read(classData); + return REAL_CLASS_BUILDER.construct(realClassName, classData); + } + } + return null; + } + + private static String getFileNameWithoutExtension(String fileName, + String extension) { + String[] split = fileName.split("\\."); + if (split.length == 2 && split[0].indexOf('/') == -1 + && split[1].equals(extension)) { + return split[0]; + } else { + return null; + } + } +} diff --git a/src/main/java/amidst/clazz/real/ReferenceIndex.java b/src/main/java/amidst/clazz/real/ReferenceIndex.java new file mode 100644 index 000000000..26bff4dfb --- /dev/null +++ b/src/main/java/amidst/clazz/real/ReferenceIndex.java @@ -0,0 +1,22 @@ +package amidst.clazz.real; + +import amidst.documentation.Immutable; + +@Immutable +public class ReferenceIndex { + private final int value1; + private final int value2; + + public ReferenceIndex(int value1, int value2) { + this.value1 = value1; + this.value2 = value2; + } + + public int getValue1() { + return value1; + } + + public int getValue2() { + return value2; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/AllRCD.java b/src/main/java/amidst/clazz/real/detector/AllRCD.java new file mode 100644 index 000000000..c40035a5f --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/AllRCD.java @@ -0,0 +1,25 @@ +package amidst.clazz.real.detector; + +import java.util.List; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class AllRCD extends RealClassDetector { + private final List detectors; + + public AllRCD(List detectors) { + this.detectors = detectors; + } + + @Override + public boolean detect(RealClass realClass) { + for (RealClassDetector detector : detectors) { + if (!detector.detect(realClass)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/AnyRCD.java b/src/main/java/amidst/clazz/real/detector/AnyRCD.java new file mode 100644 index 000000000..43627cc75 --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/AnyRCD.java @@ -0,0 +1,25 @@ +package amidst.clazz.real.detector; + +import java.util.List; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class AnyRCD extends RealClassDetector { + private final List detectors; + + public AnyRCD(List detectors) { + this.detectors = detectors; + } + + @Override + public boolean detect(RealClass realClass) { + for (RealClassDetector detector : detectors) { + if (detector.detect(realClass)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/FieldFlagsRCD.java b/src/main/java/amidst/clazz/real/detector/FieldFlagsRCD.java new file mode 100644 index 000000000..7453309d8 --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/FieldFlagsRCD.java @@ -0,0 +1,27 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.clazz.real.RealClassField; +import amidst.documentation.Immutable; + +@Immutable +public class FieldFlagsRCD extends RealClassDetector { + private final int flags; + private final int[] fieldIndices; + + public FieldFlagsRCD(int flags, int... fieldIndices) { + this.flags = flags; + this.fieldIndices = fieldIndices; + } + + @Override + public boolean detect(RealClass realClass) { + for (int fieldIndex : fieldIndices) { + RealClassField field = realClass.getField(fieldIndex); + if (!field.hasFlags(flags)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/LongRCD.java b/src/main/java/amidst/clazz/real/detector/LongRCD.java new file mode 100644 index 000000000..45e7918b9 --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/LongRCD.java @@ -0,0 +1,23 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class LongRCD extends RealClassDetector { + private final long[] longs; + + public LongRCD(long... longs) { + this.longs = longs; + } + + @Override + public boolean detect(RealClass realClass) { + for (long element : longs) { + if (!realClass.searchForLong(element)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/NumberOfConstructorsRCD.java b/src/main/java/amidst/clazz/real/detector/NumberOfConstructorsRCD.java new file mode 100644 index 000000000..6e70f603e --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/NumberOfConstructorsRCD.java @@ -0,0 +1,18 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class NumberOfConstructorsRCD extends RealClassDetector { + private final int count; + + public NumberOfConstructorsRCD(int count) { + this.count = count; + } + + @Override + public boolean detect(RealClass realClass) { + return realClass.getNumberOfConstructors() == count; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/NumberOfFieldsRCD.java b/src/main/java/amidst/clazz/real/detector/NumberOfFieldsRCD.java new file mode 100644 index 000000000..57d5c4d05 --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/NumberOfFieldsRCD.java @@ -0,0 +1,18 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class NumberOfFieldsRCD extends RealClassDetector { + private final int count; + + public NumberOfFieldsRCD(int count) { + this.count = count; + } + + @Override + public boolean detect(RealClass realClass) { + return realClass.getNumberOfFields() == count; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/NumberOfMethodsRCD.java b/src/main/java/amidst/clazz/real/detector/NumberOfMethodsRCD.java new file mode 100644 index 000000000..6d9ed801f --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/NumberOfMethodsRCD.java @@ -0,0 +1,18 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class NumberOfMethodsRCD extends RealClassDetector { + private final int count; + + public NumberOfMethodsRCD(int count) { + this.count = count; + } + + @Override + public boolean detect(RealClass realClass) { + return realClass.getNumberOfMethods() == count; + } +} diff --git a/src/main/java/amidst/clazz/real/detector/RealClassDetector.java b/src/main/java/amidst/clazz/real/detector/RealClassDetector.java new file mode 100644 index 000000000..1b6d2036d --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/RealClassDetector.java @@ -0,0 +1,31 @@ +package amidst.clazz.real.detector; + +import java.util.ArrayList; +import java.util.List; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public abstract class RealClassDetector { + public RealClass firstMatching(List realClasses) { + for (RealClass realClass : realClasses) { + if (detect(realClass)) { + return realClass; + } + } + return null; + } + + public List allMatching(List realClasses) { + List result = new ArrayList(); + for (RealClass realClass : realClasses) { + if (detect(realClass)) { + result.add(realClass); + } + } + return result; + } + + public abstract boolean detect(RealClass realClass); +} diff --git a/src/main/java/amidst/clazz/real/detector/StringContainingRCD.java b/src/main/java/amidst/clazz/real/detector/StringContainingRCD.java new file mode 100644 index 000000000..187eceeb6 --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/StringContainingRCD.java @@ -0,0 +1,18 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class StringContainingRCD extends RealClassDetector { + private final String string; + + public StringContainingRCD(String string) { + this.string = string; + } + + @Override + public boolean detect(RealClass realClass) { + return realClass.searchForStringContaining(string); + } +} diff --git a/src/main/java/amidst/clazz/real/detector/Utf8EqualToRCD.java b/src/main/java/amidst/clazz/real/detector/Utf8EqualToRCD.java new file mode 100644 index 000000000..e4be4aaba --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/Utf8EqualToRCD.java @@ -0,0 +1,18 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class Utf8EqualToRCD extends RealClassDetector { + private final String utf8; + + public Utf8EqualToRCD(String utf8) { + this.utf8 = utf8; + } + + @Override + public boolean detect(RealClass realClass) { + return realClass.searchForUtf8EqualTo(utf8); + } +} diff --git a/src/main/java/amidst/clazz/real/detector/WildcardByteRCD.java b/src/main/java/amidst/clazz/real/detector/WildcardByteRCD.java new file mode 100644 index 000000000..6ce82f920 --- /dev/null +++ b/src/main/java/amidst/clazz/real/detector/WildcardByteRCD.java @@ -0,0 +1,18 @@ +package amidst.clazz.real.detector; + +import amidst.clazz.real.RealClass; +import amidst.documentation.Immutable; + +@Immutable +public class WildcardByteRCD extends RealClassDetector { + private final int[] bytes; + + public WildcardByteRCD(int[] bytes) { + this.bytes = bytes; + } + + @Override + public boolean detect(RealClass realClass) { + return realClass.isClassDataWildcardMatching(bytes); + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicClass.java b/src/main/java/amidst/clazz/symbolic/SymbolicClass.java new file mode 100644 index 000000000..5d269d1e1 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicClass.java @@ -0,0 +1,96 @@ +package amidst.clazz.symbolic; + +import java.lang.reflect.InvocationTargetException; +import java.util.Map; + +import amidst.documentation.Immutable; + +/** + * The contents of the maps will be altered by the {@link SymbolicClassBuilder} + * after this instance is constructed. However, these maps will not be altered + * after the method {@link SymbolicClassGraphBuilder#construct()} finished + * execution. + */ +@Immutable +public class SymbolicClass { + private final String symbolicClassName; + private final String realClassName; + private final Class clazz; + private final Map constructorsBySymbolicName; + private final Map methodsBySymbolicName; + private final Map fieldsBySymbolicName; + + public SymbolicClass(String symbolicClassName, String realClassName, + Class clazz, + Map constructorsBySymbolicName, + Map methodsBySymbolicName, + Map fieldsBySymbolicName) { + this.symbolicClassName = symbolicClassName; + this.realClassName = realClassName; + this.clazz = clazz; + this.constructorsBySymbolicName = constructorsBySymbolicName; + this.methodsBySymbolicName = methodsBySymbolicName; + this.fieldsBySymbolicName = fieldsBySymbolicName; + } + + public String getSymbolicName() { + return symbolicClassName; + } + + public String getRealName() { + return realClassName; + } + + public Class getClazz() { + return clazz; + } + + public boolean hasConstructor(String symbolicName) { + return constructorsBySymbolicName.get(symbolicName) != null; + } + + public boolean hasMethod(String symbolicName) { + return methodsBySymbolicName.get(symbolicName) != null; + } + + public boolean hasField(String symbolicName) { + return fieldsBySymbolicName.get(symbolicName) != null; + } + + public SymbolicObject callConstructor(String symbolicName, + Object... parameters) throws InstantiationException, + IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return constructorsBySymbolicName.get(symbolicName).call(parameters); + } + + public Object callMethod(String symbolicName, + SymbolicObject symbolicObject, Object... parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return methodsBySymbolicName.get(symbolicName).call(symbolicObject, + parameters); + } + + public Object callStaticMethod(String symbolicName, Object... parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return methodsBySymbolicName.get(symbolicName).callStatic(parameters); + } + + public Object getFieldValue(String symbolicName, + SymbolicObject symbolicObject) throws IllegalArgumentException, + IllegalAccessException { + return fieldsBySymbolicName.get(symbolicName).getValue(symbolicObject); + } + + public Object getStaticFieldValue(String symbolicName) + throws IllegalArgumentException, IllegalAccessException { + return fieldsBySymbolicName.get(symbolicName).getStaticValue(); + } + + @Override + public String toString() { + return realClassName; + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicClassBuilder.java b/src/main/java/amidst/clazz/symbolic/SymbolicClassBuilder.java new file mode 100644 index 000000000..2e397b7e9 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicClassBuilder.java @@ -0,0 +1,193 @@ +package amidst.clazz.symbolic; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import amidst.clazz.symbolic.declaration.SymbolicConstructorDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicFieldDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicMethodDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicParameterDeclaration; +import amidst.documentation.NotThreadSafe; + +/** + * This class should only be used by the class {@link SymbolicClassGraphBuilder} + */ +@NotThreadSafe +public class SymbolicClassBuilder { + private static Map> createPrimitivesMap() { + Map> result = new HashMap>(); + result.put("byte", byte.class); + result.put("int", int.class); + result.put("float", float.class); + result.put("short", short.class); + result.put("long", long.class); + result.put("double", double.class); + result.put("boolean", boolean.class); + result.put("char", char.class); + result.put("String", String.class); + return result; + } + + private static final Map> PRIMITIVES_MAP = createPrimitivesMap(); + + private final Map constructorsBySymbolicName = new HashMap(); + private final Map methodsBySymbolicName = new HashMap(); + private final Map fieldsBySymbolicName = new HashMap(); + + private final ClassLoader classLoader; + private final Map realClassNamesBySymbolicClassName; + private final Map symbolicClassesByRealClassName; + private final SymbolicClass product; + + public SymbolicClassBuilder(ClassLoader classLoader, + Map realClassNamesBySymbolicClassName, + Map symbolicClassesByRealClassName, + String symbolicClassName, String realClassName) + throws ClassNotFoundException { + this.classLoader = classLoader; + this.realClassNamesBySymbolicClassName = realClassNamesBySymbolicClassName; + this.symbolicClassesByRealClassName = symbolicClassesByRealClassName; + this.product = new SymbolicClass(symbolicClassName, realClassName, + loadClass(realClassName), constructorsBySymbolicName, + methodsBySymbolicName, fieldsBySymbolicName); + } + + public SymbolicClass getProduct() { + return product; + } + + private Class loadClass(String realClassName) + throws ClassNotFoundException { + return classLoader.loadClass(realClassName); + } + + public void addConstructor(SymbolicConstructorDeclaration declaration) + throws SymbolicClassGraphCreationException { + try { + constructorsBySymbolicName.put(declaration.getSymbolicName(), + createConstructor(declaration)); + } catch (NoSuchMethodException | ClassNotFoundException e) { + declaration.handleMissing(e, product.getSymbolicName(), + product.getRealName()); + } + } + + public void addMethod(SymbolicMethodDeclaration declaration) + throws SymbolicClassGraphCreationException { + try { + methodsBySymbolicName.put(declaration.getSymbolicName(), + createMethod(declaration)); + } catch (NoSuchMethodException | ClassNotFoundException e) { + declaration.handleMissing(e, product.getSymbolicName(), + product.getRealName()); + } + } + + public void addField(SymbolicFieldDeclaration declaration) + throws SymbolicClassGraphCreationException { + try { + fieldsBySymbolicName.put(declaration.getSymbolicName(), + createField(declaration)); + } catch (NoSuchFieldException e) { + declaration.handleMissing(e, product.getSymbolicName(), + product.getRealName()); + } + } + + private SymbolicConstructor createConstructor( + SymbolicConstructorDeclaration declaration) + throws ClassNotFoundException, NoSuchMethodException { + String symbolicName = declaration.getSymbolicName(); + Class[] parameterClasses = getParameterClasses(declaration + .getParameters().getDeclarations()); + Constructor constructor = getConstructor(product.getClazz(), + parameterClasses); + return new SymbolicConstructor(product, symbolicName, constructor); + } + + private SymbolicMethod createMethod(SymbolicMethodDeclaration declaration) + throws ClassNotFoundException, NoSuchMethodException { + String symbolicName = declaration.getSymbolicName(); + String realName = declaration.getRealName(); + Class[] parameterClasses = getParameterClasses(declaration + .getParameters().getDeclarations()); + Method method = getMethod(product.getClazz(), realName, + parameterClasses); + SymbolicClass returnType = getType(method.getReturnType()); + return new SymbolicMethod(product, symbolicName, realName, method, + returnType); + } + + private SymbolicField createField(SymbolicFieldDeclaration declaration) + throws NoSuchFieldException { + String symbolicName = declaration.getSymbolicName(); + String realName = declaration.getRealName(); + Field field = getField(product.getClazz(), realName); + SymbolicClass type = getType(field.getType()); + return new SymbolicField(product, symbolicName, realName, field, type); + } + + private Constructor getConstructor(Class clazz, + Class[] parameterClasses) throws NoSuchMethodException { + Constructor result = clazz.getConstructor(parameterClasses); + result.setAccessible(true); + return result; + } + + private Method getMethod(Class clazz, String realName, + Class[] parameterClasses) throws NoSuchMethodException { + Method result = clazz.getDeclaredMethod(realName, parameterClasses); + result.setAccessible(true); + return result; + } + + private Field getField(Class clazz, String realName) + throws NoSuchFieldException { + Field result = clazz.getDeclaredField(realName); + result.setAccessible(true); + return result; + } + + private Class[] getParameterClasses( + List declarations) + throws ClassNotFoundException { + Class[] result = new Class[declarations.size()]; + for (int i = 0; i < declarations.size(); i++) { + result[i] = getParameterClass(declarations.get(i)); + } + return result; + } + + private Class getParameterClass(SymbolicParameterDeclaration declaration) + throws ClassNotFoundException { + Class result = PRIMITIVES_MAP.get(declaration.getType()); + if (result != null) { + return result; + } else if (declaration.isSymbolic()) { + String realClassName = realClassNamesBySymbolicClassName + .get(declaration.getType()); + if (realClassName != null) { + return classLoader.loadClass(realClassName); + } else { + throw new ClassNotFoundException( + "cannot resolve symbolic class name: " + + declaration.getType()); + } + } else { + return classLoader.loadClass(declaration.getType()); + } + } + + private SymbolicClass getType(Class type) { + String result = type.getName(); + if (result.contains(".")) { + String[] typeSplit = result.split("\\."); + result = typeSplit[typeSplit.length - 1]; + } + return symbolicClassesByRealClassName.get(result); + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicClassGraphBuilder.java b/src/main/java/amidst/clazz/symbolic/SymbolicClassGraphBuilder.java new file mode 100644 index 000000000..a108f2673 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicClassGraphBuilder.java @@ -0,0 +1,113 @@ +package amidst.clazz.symbolic; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import amidst.clazz.symbolic.declaration.SymbolicClassDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicConstructorDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicFieldDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicMethodDeclaration; +import amidst.documentation.Immutable; + +@Immutable +public class SymbolicClassGraphBuilder { + private final ClassLoader classLoader; + private final Map realClassNamesBySymbolicClassDeclaration; + + public SymbolicClassGraphBuilder( + ClassLoader classLoader, + Map realClassNamesBySymbolicClassDeclaration) { + this.classLoader = classLoader; + this.realClassNamesBySymbolicClassDeclaration = realClassNamesBySymbolicClassDeclaration; + } + + public Map construct() + throws SymbolicClassGraphCreationException { + Map realClassNamesBySymbolicClassName = new HashMap(); + Map symbolicClassesByRealClassName = new HashMap(); + Map symbolicClassBuildersBySymbolicClassDeclaration = new HashMap(); + createSymbolicClasses(realClassNamesBySymbolicClassName, + symbolicClassesByRealClassName, + symbolicClassBuildersBySymbolicClassDeclaration); + addConstructorsMethodsAndFields(symbolicClassBuildersBySymbolicClassDeclaration); + return createProduct(symbolicClassBuildersBySymbolicClassDeclaration); + } + + private void createSymbolicClasses( + Map realClassNamesBySymbolicClassName, + Map symbolicClassesByRealClassName, + Map symbolicClassBuildersBySymbolicClassDeclaration) + throws SymbolicClassGraphCreationException { + for (Entry entry : realClassNamesBySymbolicClassDeclaration + .entrySet()) { + SymbolicClassDeclaration declaration = entry.getKey(); + String symbolicClassName = declaration.getSymbolicClassName(); + String realClassName = entry.getValue(); + try { + SymbolicClassBuilder builder = new SymbolicClassBuilder( + classLoader, realClassNamesBySymbolicClassName, + symbolicClassesByRealClassName, + declaration.getSymbolicClassName(), realClassName); + SymbolicClass symbolicClass = builder.getProduct(); + realClassNamesBySymbolicClassName.put(symbolicClassName, + realClassName); + symbolicClassesByRealClassName + .put(realClassName, symbolicClass); + symbolicClassBuildersBySymbolicClassDeclaration.put( + declaration, builder); + } catch (ClassNotFoundException e) { + declaration.handleMissing(e, realClassName); + } + } + } + + private void addConstructorsMethodsAndFields( + Map symbolicClassBuildersBySymbolicClassDeclaration) + throws SymbolicClassGraphCreationException { + for (Entry entry : symbolicClassBuildersBySymbolicClassDeclaration + .entrySet()) { + SymbolicClassDeclaration declaration = entry.getKey(); + SymbolicClassBuilder builder = entry.getValue(); + addConstructors(builder, declaration.getConstructors()); + addMethods(builder, declaration.getMethods()); + addFields(builder, declaration.getFields()); + } + } + + private void addConstructors(SymbolicClassBuilder builder, + List constructors) + throws SymbolicClassGraphCreationException { + for (SymbolicConstructorDeclaration constructor : constructors) { + builder.addConstructor(constructor); + } + } + + private void addMethods(SymbolicClassBuilder builder, + List methods) + throws SymbolicClassGraphCreationException { + for (SymbolicMethodDeclaration method : methods) { + builder.addMethod(method); + } + } + + private void addFields(SymbolicClassBuilder builder, + List fields) + throws SymbolicClassGraphCreationException { + for (SymbolicFieldDeclaration field : fields) { + builder.addField(field); + } + } + + private Map createProduct( + Map symbolicClassBuildersBySymbolicClassDeclaration) { + Map result = new HashMap(); + for (Entry entry : symbolicClassBuildersBySymbolicClassDeclaration + .entrySet()) { + result.put(entry.getKey().getSymbolicClassName(), entry.getValue() + .getProduct()); + } + return result; + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicClassGraphCreationException.java b/src/main/java/amidst/clazz/symbolic/SymbolicClassGraphCreationException.java new file mode 100644 index 000000000..b4b721f01 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicClassGraphCreationException.java @@ -0,0 +1,11 @@ +package amidst.clazz.symbolic; + +import amidst.documentation.Immutable; + +@Immutable +@SuppressWarnings("serial") +public class SymbolicClassGraphCreationException extends Exception { + public SymbolicClassGraphCreationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicClasses.java b/src/main/java/amidst/clazz/symbolic/SymbolicClasses.java new file mode 100644 index 000000000..6410ada8e --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicClasses.java @@ -0,0 +1,18 @@ +package amidst.clazz.symbolic; + +import java.util.Map; + +import amidst.clazz.symbolic.declaration.SymbolicClassDeclaration; +import amidst.documentation.Immutable; + +@Immutable +public enum SymbolicClasses { + ; + + public static Map from( + Map realClassNamesBySymbolicClassDeclaration, + ClassLoader classLoader) throws SymbolicClassGraphCreationException { + return new SymbolicClassGraphBuilder(classLoader, + realClassNamesBySymbolicClassDeclaration).construct(); + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicConstructor.java b/src/main/java/amidst/clazz/symbolic/SymbolicConstructor.java new file mode 100644 index 000000000..efc9278fd --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicConstructor.java @@ -0,0 +1,38 @@ +package amidst.clazz.symbolic; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import amidst.documentation.Immutable; + +@Immutable +public class SymbolicConstructor { + private final SymbolicClass parent; + private final String symbolicName; + private final Constructor constructor; + + public SymbolicConstructor(SymbolicClass parent, String symbolicName, + Constructor constructor) { + this.parent = parent; + this.symbolicName = symbolicName; + this.constructor = constructor; + } + + public SymbolicObject call(Object... parameters) + throws InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { + return new SymbolicObject(parent, newInstance(parameters)); + } + + private Object newInstance(Object... parameters) + throws InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { + return constructor.newInstance(parameters); + } + + @Override + public String toString() { + return "[Constructor " + symbolicName + " of class " + + parent.getSymbolicName() + "]"; + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicField.java b/src/main/java/amidst/clazz/symbolic/SymbolicField.java new file mode 100644 index 000000000..1807a2ca8 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicField.java @@ -0,0 +1,58 @@ +package amidst.clazz.symbolic; + +import java.lang.reflect.Field; + +import amidst.documentation.Immutable; + +@Immutable +public class SymbolicField { + private final SymbolicClass parent; + private final String symbolicName; + private final String realName; + private final Field field; + private final SymbolicClass type; + + public SymbolicField(SymbolicClass parent, String symbolicName, + String realName, Field field, SymbolicClass type) { + this.parent = parent; + this.symbolicName = symbolicName; + this.realName = realName; + this.field = field; + this.type = type; + } + + public Object getValue(SymbolicObject symbolicObject) + throws IllegalArgumentException, IllegalAccessException { + return getValueFromObject(symbolicObject.getObject()); + } + + public Object getStaticValue() throws IllegalArgumentException, + IllegalAccessException { + return getValueFromObject(null); + } + + private Object getValueFromObject(Object object) + throws IllegalArgumentException, IllegalAccessException { + Object value = get(object); + if (isTypeSymbolicClass()) { + return new SymbolicObject(type, value); + } else { + return value; + } + } + + private Object get(Object object) throws IllegalArgumentException, + IllegalAccessException { + return field.get(object); + } + + private boolean isTypeSymbolicClass() { + return type != null; + } + + @Override + public String toString() { + return "[Field " + symbolicName + " (" + realName + ") of class " + + parent.getSymbolicName() + "]"; + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicMethod.java b/src/main/java/amidst/clazz/symbolic/SymbolicMethod.java new file mode 100644 index 000000000..6ddf0f8c3 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicMethod.java @@ -0,0 +1,70 @@ +package amidst.clazz.symbolic; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import amidst.documentation.Immutable; + +@Immutable +public class SymbolicMethod { + private final SymbolicClass parent; + private final String symbolicName; + private final String realName; + private final Method method; + private final SymbolicClass returnType; + + public SymbolicMethod(SymbolicClass parent, String symbolicName, + String realName, Method method, SymbolicClass returnType) { + this.parent = parent; + this.symbolicName = symbolicName; + this.realName = realName; + this.method = method; + this.returnType = returnType; + } + + public String getSymbolicName() { + return symbolicName; + } + + public String getRealName() { + return realName; + } + + public Object call(SymbolicObject symbolicObject, Object... parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return callFromObject(symbolicObject.getObject(), parameters); + } + + public Object callStatic(Object... parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return callFromObject(null, parameters); + } + + private Object callFromObject(Object object, Object... parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + Object value = invoke(object, parameters); + if (isReturnTypeSymbolicClass()) { + return new SymbolicObject(returnType, value); + } + return value; + } + + private Object invoke(Object object, Object... parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return method.invoke(object, parameters); + } + + private boolean isReturnTypeSymbolicClass() { + return returnType != null; + } + + @Override + public String toString() { + return "[Method " + symbolicName + " (" + realName + ") of class " + + parent.getSymbolicName() + "]"; + } +} diff --git a/src/main/java/amidst/clazz/symbolic/SymbolicObject.java b/src/main/java/amidst/clazz/symbolic/SymbolicObject.java new file mode 100644 index 000000000..ba56d77af --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/SymbolicObject.java @@ -0,0 +1,47 @@ +package amidst.clazz.symbolic; + +import java.lang.reflect.InvocationTargetException; + +import amidst.documentation.Immutable; + +/** + * While the state that is directly contained in instances of this class cannot + * be altered, it is still possible to alter the object that is contained in it. + */ +@Immutable +public class SymbolicObject { + private final SymbolicClass type; + private final Object object; + + public SymbolicObject(SymbolicClass type, Object object) { + this.type = type; + this.object = object; + } + + public SymbolicClass getType() { + return type; + } + + public Object getObject() { + return object; + } + + public boolean hasMethod(String symbolicName) { + return type.hasMethod(symbolicName); + } + + public boolean hasField(String symbolicName) { + return type.hasField(symbolicName); + } + + public Object callMethod(String symbolicName, Object... parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return type.callMethod(symbolicName, this, parameters); + } + + public Object getFieldValue(String symbolicName) + throws IllegalArgumentException, IllegalAccessException { + return type.getFieldValue(symbolicName, this); + } +} diff --git a/src/main/java/amidst/clazz/symbolic/declaration/SymbolicClassDeclaration.java b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicClassDeclaration.java new file mode 100644 index 000000000..18a3b92f7 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicClassDeclaration.java @@ -0,0 +1,80 @@ +package amidst.clazz.symbolic.declaration; + +import java.util.Collections; +import java.util.List; + +import amidst.clazz.symbolic.SymbolicClassGraphCreationException; +import amidst.documentation.Immutable; +import amidst.logging.Log; + +@Immutable +public class SymbolicClassDeclaration { + private final String symbolicClassName; + private final boolean isOptional; + private final List constructors; + private final List methods; + private final List fields; + + public SymbolicClassDeclaration(String symbolicClassName, + boolean isOptional, + List constructors, + List methods, + List fields) { + this.symbolicClassName = symbolicClassName; + this.isOptional = isOptional; + this.constructors = Collections.unmodifiableList(constructors); + this.methods = Collections.unmodifiableList(methods); + this.fields = Collections.unmodifiableList(fields); + } + + public String getSymbolicClassName() { + return symbolicClassName; + } + + public boolean isOptional() { + return isOptional; + } + + public List getConstructors() { + return constructors; + } + + public List getMethods() { + return methods; + } + + public List getFields() { + return fields; + } + + public void handleMultipleMatches(String firstRealClassName, + String otherRealClassName) { + Log.w("Found class " + symbolicClassName + " again: " + + firstRealClassName + ", " + otherRealClassName); + } + + public void handleMatch(String realClassName) { + Log.i("Found class " + symbolicClassName + ": " + realClassName); + } + + public void handleNoMatch() throws ClassNotFoundException { + if (isOptional) { + Log.i("Missing class " + symbolicClassName); + } else { + throw new ClassNotFoundException( + "cannot find a real class matching the symbolic class " + + symbolicClassName); + } + } + + public void handleMissing(ClassNotFoundException e, String realClassName) + throws SymbolicClassGraphCreationException { + String message = "unable to find the real class " + realClassName + + " -> " + symbolicClassName; + if (isOptional) { + Log.i(message); + } else { + throw new SymbolicClassGraphCreationException(message, e); + } + } +} diff --git a/src/main/java/amidst/clazz/symbolic/declaration/SymbolicConstructorDeclaration.java b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicConstructorDeclaration.java new file mode 100644 index 000000000..7f56b9e68 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicConstructorDeclaration.java @@ -0,0 +1,43 @@ +package amidst.clazz.symbolic.declaration; + +import amidst.clazz.symbolic.SymbolicClassGraphCreationException; +import amidst.documentation.Immutable; +import amidst.logging.Log; + +@Immutable +public class SymbolicConstructorDeclaration { + private final String symbolicName; + private final boolean isOptional; + private final SymbolicParameterDeclarationList parameters; + + public SymbolicConstructorDeclaration(String symbolicName, + boolean isOptional, SymbolicParameterDeclarationList parameters) { + this.symbolicName = symbolicName; + this.isOptional = isOptional; + this.parameters = parameters; + } + + public String getSymbolicName() { + return symbolicName; + } + + public boolean isOptional() { + return isOptional; + } + + public SymbolicParameterDeclarationList getParameters() { + return parameters; + } + + public void handleMissing(Exception e, String symbolicClassName, + String realClassName) throws SymbolicClassGraphCreationException { + String message = "unable to find the real class constructor " + + realClassName + "." + parameters.getParameterString() + + " -> " + symbolicClassName + "." + symbolicName; + if (isOptional) { + Log.i(message); + } else { + throw new SymbolicClassGraphCreationException(message, e); + } + } +} diff --git a/src/main/java/amidst/clazz/symbolic/declaration/SymbolicFieldDeclaration.java b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicFieldDeclaration.java new file mode 100644 index 000000000..31373adb7 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicFieldDeclaration.java @@ -0,0 +1,43 @@ +package amidst.clazz.symbolic.declaration; + +import amidst.clazz.symbolic.SymbolicClassGraphCreationException; +import amidst.documentation.Immutable; +import amidst.logging.Log; + +@Immutable +public class SymbolicFieldDeclaration { + private final String symbolicName; + private final String realName; + private final boolean isOptional; + + public SymbolicFieldDeclaration(String symbolicName, String realName, + boolean isOptional) { + this.symbolicName = symbolicName; + this.realName = realName; + this.isOptional = isOptional; + } + + public String getSymbolicName() { + return symbolicName; + } + + public String getRealName() { + return realName; + } + + public boolean isOptional() { + return isOptional; + } + + public void handleMissing(Exception e, String symbolicClassName, + String realClassName) throws SymbolicClassGraphCreationException { + String message = "unable to find the real class field " + realClassName + + "." + realName + " -> " + symbolicClassName + "." + + symbolicName; + if (isOptional) { + Log.i(message); + } else { + throw new SymbolicClassGraphCreationException(message, e); + } + } +} diff --git a/src/main/java/amidst/clazz/symbolic/declaration/SymbolicMethodDeclaration.java b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicMethodDeclaration.java new file mode 100644 index 000000000..d528bf45c --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicMethodDeclaration.java @@ -0,0 +1,50 @@ +package amidst.clazz.symbolic.declaration; + +import amidst.clazz.symbolic.SymbolicClassGraphCreationException; +import amidst.documentation.Immutable; +import amidst.logging.Log; + +@Immutable +public class SymbolicMethodDeclaration { + private final String symbolicName; + private final String realName; + private final boolean isOptional; + private final SymbolicParameterDeclarationList parameters; + + public SymbolicMethodDeclaration(String symbolicName, String realName, + boolean isOptional, SymbolicParameterDeclarationList parameters) { + this.symbolicName = symbolicName; + this.realName = realName; + this.isOptional = isOptional; + this.parameters = parameters; + } + + public String getSymbolicName() { + return symbolicName; + } + + public String getRealName() { + return realName; + } + + public boolean isOptional() { + return isOptional; + } + + public SymbolicParameterDeclarationList getParameters() { + return parameters; + } + + public void handleMissing(Exception e, String symbolicClassName, + String realClassName) throws SymbolicClassGraphCreationException { + String message = "unable to find the real class method " + + realClassName + "." + realName + + parameters.getParameterString() + " -> " + symbolicClassName + + "." + symbolicName; + if (isOptional) { + Log.i(message); + } else { + throw new SymbolicClassGraphCreationException(message, e); + } + } +} diff --git a/src/main/java/amidst/clazz/symbolic/declaration/SymbolicParameterDeclaration.java b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicParameterDeclaration.java new file mode 100644 index 000000000..2ec57fac2 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicParameterDeclaration.java @@ -0,0 +1,30 @@ +package amidst.clazz.symbolic.declaration; + +import amidst.documentation.Immutable; + +@Immutable +public class SymbolicParameterDeclaration { + private final String type; + private final boolean isSymbolic; + + public SymbolicParameterDeclaration(String type, boolean isSymbolic) { + this.type = type; + this.isSymbolic = isSymbolic; + } + + public String getType() { + return type; + } + + public boolean isSymbolic() { + return isSymbolic; + } + + public String getParameterString() { + if (isSymbolic) { + return "@" + type; + } else { + return type; + } + } +} diff --git a/src/main/java/amidst/clazz/symbolic/declaration/SymbolicParameterDeclarationList.java b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicParameterDeclarationList.java new file mode 100644 index 000000000..954fa51b1 --- /dev/null +++ b/src/main/java/amidst/clazz/symbolic/declaration/SymbolicParameterDeclarationList.java @@ -0,0 +1,68 @@ +package amidst.clazz.symbolic.declaration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import amidst.documentation.Immutable; + +@Immutable +public class SymbolicParameterDeclarationList { + public static interface ExecuteOnEnd { + public void run(SymbolicParameterDeclarationList parameters); + } + + @Immutable + public static class SymbolicParameterDeclarationListBuilder { + private final T nextBuilder; + private final List declarations = new ArrayList(); + private final ExecuteOnEnd executeOnEnd; + + public SymbolicParameterDeclarationListBuilder(T nextBuilder, + ExecuteOnEnd executeOnEnd) { + this.nextBuilder = nextBuilder; + this.executeOnEnd = executeOnEnd; + } + + public SymbolicParameterDeclarationListBuilder real(String realType) { + declarations.add(new SymbolicParameterDeclaration(realType, false)); + return this; + } + + public SymbolicParameterDeclarationListBuilder symbolic( + String symbolicType) { + declarations.add(new SymbolicParameterDeclaration(symbolicType, + true)); + return this; + } + + public T end() { + executeOnEnd + .run(new SymbolicParameterDeclarationList(declarations)); + return nextBuilder; + } + } + + private final List declarations; + + public SymbolicParameterDeclarationList( + List declarations) { + this.declarations = Collections.unmodifiableList(declarations); + } + + public List getDeclarations() { + return declarations; + } + + public String getParameterString() { + String separator = ""; + StringBuilder stringBuilder = new StringBuilder("("); + for (SymbolicParameterDeclaration declaration : declarations) { + stringBuilder.append(separator).append( + declaration.getParameterString()); + separator = ", "; + } + stringBuilder.append(")"); + return stringBuilder.toString(); + } +} diff --git a/src/main/java/amidst/clazz/translator/CTBuilder.java b/src/main/java/amidst/clazz/translator/CTBuilder.java new file mode 100644 index 000000000..0f8d68179 --- /dev/null +++ b/src/main/java/amidst/clazz/translator/CTBuilder.java @@ -0,0 +1,240 @@ +package amidst.clazz.translator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import amidst.clazz.real.detector.AllRCD; +import amidst.clazz.real.detector.AnyRCD; +import amidst.clazz.real.detector.FieldFlagsRCD; +import amidst.clazz.real.detector.LongRCD; +import amidst.clazz.real.detector.NumberOfConstructorsRCD; +import amidst.clazz.real.detector.NumberOfFieldsRCD; +import amidst.clazz.real.detector.NumberOfMethodsRCD; +import amidst.clazz.real.detector.RealClassDetector; +import amidst.clazz.real.detector.StringContainingRCD; +import amidst.clazz.real.detector.Utf8EqualToRCD; +import amidst.clazz.real.detector.WildcardByteRCD; +import amidst.clazz.symbolic.declaration.SymbolicClassDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicConstructorDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicFieldDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicMethodDeclaration; +import amidst.clazz.symbolic.declaration.SymbolicParameterDeclarationList; +import amidst.clazz.symbolic.declaration.SymbolicParameterDeclarationList.ExecuteOnEnd; +import amidst.clazz.symbolic.declaration.SymbolicParameterDeclarationList.SymbolicParameterDeclarationListBuilder; +import amidst.documentation.NotThreadSafe; + +/** + * While this class is not thread-safe by itself, its product is thread-safe. + */ +@NotThreadSafe +public class CTBuilder { + @NotThreadSafe + public class RCDBuilder { + private final List> allDetectors = new ArrayList>(); + private List detectors = new ArrayList(); + + private RealClassDetector constructThis() { + if (allDetectors.size() == 1) { + return new AllRCD(allDetectors.get(0)); + } else { + List result = new ArrayList(); + for (List detectors : allDetectors) { + result.add(new AllRCD(detectors)); + } + return new AnyRCD(result); + } + } + + public RCDBuilder or() { + allDetectors.add(detectors); + detectors = new ArrayList(); + return this; + } + + public SCDBuilder thenDeclareRequired(String symbolicClassName) { + return thenDeclare(symbolicClassName, false); + } + + public SCDBuilder thenDeclareOptional(String symbolicClassName) { + return thenDeclare(symbolicClassName, true); + } + + private SCDBuilder thenDeclare(String symbolicClassName, + boolean isOptional) { + allDetectors.add(detectors); + CTBuilder.this.declarationBuilder.init(symbolicClassName, + isOptional); + return CTBuilder.this.declarationBuilder; + } + + public RCDBuilder fieldFlags(int flags, int... fieldIndices) { + detectors.add(new FieldFlagsRCD(flags, fieldIndices)); + return this; + } + + public RCDBuilder longs(long... longs) { + detectors.add(new LongRCD(longs)); + return this; + } + + public RCDBuilder numberOfConstructors(int count) { + detectors.add(new NumberOfConstructorsRCD(count)); + return this; + } + + public RCDBuilder numberOfFields(int count) { + detectors.add(new NumberOfFieldsRCD(count)); + return this; + } + + public RCDBuilder numberOfMethods(int count) { + detectors.add(new NumberOfMethodsRCD(count)); + return this; + } + + public RCDBuilder stringContaining(String string) { + detectors.add(new StringContainingRCD(string)); + return this; + } + + public RCDBuilder utf8EqualTo(String utf8) { + detectors.add(new Utf8EqualToRCD(utf8)); + return this; + } + + public RCDBuilder wildcardBytes(int[] bytes) { + detectors.add(new WildcardByteRCD(bytes)); + return this; + } + } + + @NotThreadSafe + public class SCDBuilder { + private String symbolicClassName; + private boolean isOptional; + private final List constructors = new ArrayList(); + private final List methods = new ArrayList(); + private final List fields = new ArrayList(); + + private void init(String symbolicClassName, boolean isOptional) { + this.symbolicClassName = symbolicClassName; + this.isOptional = isOptional; + } + + private SymbolicClassDeclaration constructThis() { + return new SymbolicClassDeclaration(symbolicClassName, isOptional, + constructors, methods, fields); + } + + public CTBuilder next() { + return new CTBuilder(CTBuilder.this); + } + + public ClassTranslator construct() { + return CTBuilder.this.construct(); + } + + public SymbolicParameterDeclarationListBuilder requiredConstructor( + final String symbolicName) { + return constructor(symbolicName, false); + } + + public SymbolicParameterDeclarationListBuilder optionalConstructor( + final String symbolicName) { + return constructor(symbolicName, true); + } + + private SymbolicParameterDeclarationListBuilder constructor( + final String symbolicName, final boolean isOptional) { + return new SymbolicParameterDeclarationListBuilder( + this, new ExecuteOnEnd() { + @Override + public void run( + SymbolicParameterDeclarationList parameters) { + constructors + .add(new SymbolicConstructorDeclaration( + symbolicName, isOptional, + parameters)); + } + }); + } + + public SymbolicParameterDeclarationListBuilder requiredMethod( + String symbolicName, String realName) { + return method(symbolicName, realName, false); + } + + public SymbolicParameterDeclarationListBuilder optionalMethod( + String symbolicName, String realName) { + return method(symbolicName, realName, true); + } + + private SymbolicParameterDeclarationListBuilder method( + final String symbolicName, final String realName, + final boolean isOptional) { + return new SymbolicParameterDeclarationListBuilder( + this, new ExecuteOnEnd() { + @Override + public void run( + SymbolicParameterDeclarationList parameters) { + methods.add(new SymbolicMethodDeclaration( + symbolicName, realName, isOptional, + parameters)); + } + }); + } + + public SCDBuilder requiredField(String symbolicName, String realName) { + return field(symbolicName, realName, false); + } + + public SCDBuilder optionalField(String symbolicName, String realName) { + return field(symbolicName, realName, true); + } + + private SCDBuilder field(String symbolicName, String realName, + boolean isOptional) { + fields.add(new SymbolicFieldDeclaration(symbolicName, realName, + isOptional)); + return this; + } + } + + public static CTBuilder newInstance() { + return new CTBuilder(null); + } + + private final CTBuilder previous; + + private final RCDBuilder detectorBuilder = new RCDBuilder(); + private final SCDBuilder declarationBuilder = new SCDBuilder(); + + private CTBuilder(CTBuilder previous) { + this.previous = previous; + } + + public RCDBuilder ifDetect() { + return detectorBuilder; + } + + public ClassTranslator construct() { + return new ClassTranslator(constructResult()); + } + + private Map constructResult() { + Map result = constructPreviousResult(); + result.put(detectorBuilder.constructThis(), + declarationBuilder.constructThis()); + return result; + } + + private Map constructPreviousResult() { + if (previous != null) { + return previous.constructResult(); + } else { + return new HashMap(); + } + } +} diff --git a/src/main/java/amidst/clazz/translator/ClassTranslator.java b/src/main/java/amidst/clazz/translator/ClassTranslator.java new file mode 100644 index 000000000..3eb3eab88 --- /dev/null +++ b/src/main/java/amidst/clazz/translator/ClassTranslator.java @@ -0,0 +1,71 @@ +package amidst.clazz.translator; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import amidst.clazz.real.RealClass; +import amidst.clazz.real.detector.RealClassDetector; +import amidst.clazz.symbolic.declaration.SymbolicClassDeclaration; +import amidst.documentation.Immutable; + +@Immutable +public class ClassTranslator { + public static CTBuilder builder() { + return CTBuilder.newInstance(); + } + + private final Map translations; + + public ClassTranslator( + Map translations) { + this.translations = translations; + } + + public Map> translateToAllMatching( + List realClasses) { + Map> result = new HashMap>(); + for (Entry entry : translations + .entrySet()) { + SymbolicClassDeclaration declaration = entry.getValue(); + List allMatching = entry.getKey().allMatching( + realClasses); + if (result.containsKey(declaration)) { + result.get(declaration).addAll(allMatching); + } else { + result.put(declaration, allMatching); + } + } + return result; + } + + public Map translate( + List realClasses) throws ClassNotFoundException { + Map result = new HashMap(); + for (Entry entry : translations + .entrySet()) { + RealClass firstMatching = entry.getKey().firstMatching(realClasses); + String realClassName = null; + if (firstMatching != null) { + realClassName = firstMatching.getRealClassName(); + } + addResult(result, entry.getValue(), realClassName); + } + return result; + } + + private void addResult(Map result, + SymbolicClassDeclaration declaration, String realClassName) + throws ClassNotFoundException { + if (realClassName == null) { + declaration.handleNoMatch(); + } else if (result.containsKey(declaration)) { + declaration.handleMultipleMatches(result.get(declaration), + realClassName); + } else { + declaration.handleMatch(realClassName); + result.put(declaration, realClassName); + } + } +} diff --git a/src/main/java/amidst/documentation/AmidstThread.java b/src/main/java/amidst/documentation/AmidstThread.java new file mode 100644 index 000000000..809cf2026 --- /dev/null +++ b/src/main/java/amidst/documentation/AmidstThread.java @@ -0,0 +1,54 @@ +package amidst.documentation; + +import amidst.logging.FileLogger; + +/** + * Each entry of this enum describes one type of thread that will ever exist in + * the project. It is only used for documentation purposes. + */ +public enum AmidstThread { + /** + * The thread that calls the main method at application startup. This will + * do some initialization. Afterwards it calls the EDT to create and start + * the application. So this thread dies pretty quickly. + */ + STARTUP, + + /** + * This thread constantly causes the EDT to repaint the map, however it does + * not execute any other code by itself. + */ + REPAINTER, + + /** + * This thread is the Event Dispatch Thread used by Swing and AWT. All GUI + * events are executed in this thread. Also, the drawing of the GUI is + * executed by this thread. This thread should not be used to execute long + * running tasks. + */ + EDT, + + /** + * This thread constantly loads, reloads and recycles fragments, because it + * takes to long to do this in the EDT. Since this thread and the EDT + * constantly read from and write to the fragments and fragment graph, extra + * care must be used in this part of the application. + */ + FRAGMENT_LOADER, + + /** + * This is actually a pool of thread that are created and removed as needed. + * They are used to execute tasks that take to long for the EDT and belong + * to no other thread, e.g. the loading of skins. After a worker has + * finished the background task, it should pass its result to the EDT to + * display the result to the user. + */ + WORKER, + + /** + * The file logger also uses a single threaded scheduled executor service to + * actually write the logging messages to the file. However, this thread + * should never leave the class {@link FileLogger}. + */ + FILE_LOGGER; +} diff --git a/src/main/java/amidst/documentation/CalledByAny.java b/src/main/java/amidst/documentation/CalledByAny.java new file mode 100644 index 000000000..f1d14c329 --- /dev/null +++ b/src/main/java/amidst/documentation/CalledByAny.java @@ -0,0 +1,18 @@ +package amidst.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is similar to the annotation {@link CalledBy}. However, it + * specifies that not assumption was made that only a specific thread calls this + * constructor or method. + */ +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) +public @interface CalledByAny { +} diff --git a/src/main/java/amidst/documentation/CalledOnlyBy.java b/src/main/java/amidst/documentation/CalledOnlyBy.java new file mode 100644 index 000000000..0bda02e05 --- /dev/null +++ b/src/main/java/amidst/documentation/CalledOnlyBy.java @@ -0,0 +1,18 @@ +package amidst.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates that the constructor or method it is attached to + * can ONLY be called by the given thread. + */ +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) +public @interface CalledOnlyBy { + AmidstThread value(); +} diff --git a/src/main/java/amidst/documentation/GsonConstructor.java b/src/main/java/amidst/documentation/GsonConstructor.java new file mode 100644 index 000000000..92de5514e --- /dev/null +++ b/src/main/java/amidst/documentation/GsonConstructor.java @@ -0,0 +1,17 @@ +package amidst.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is added to no argument-constructors that are used by gson to + * deserialize object instances. + */ +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target(ElementType.CONSTRUCTOR) +public @interface GsonConstructor { +} diff --git a/src/main/java/amidst/documentation/Immutable.java b/src/main/java/amidst/documentation/Immutable.java new file mode 100644 index 000000000..1f3cb829d --- /dev/null +++ b/src/main/java/amidst/documentation/Immutable.java @@ -0,0 +1,17 @@ +package amidst.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates that instance of this type cannot be modified after + * instantiation. This also implies that the type is thread safe. + */ +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target(ElementType.TYPE) +public @interface Immutable { +} diff --git a/src/main/java/amidst/documentation/NotNull.java b/src/main/java/amidst/documentation/NotNull.java new file mode 100644 index 000000000..f657198d4 --- /dev/null +++ b/src/main/java/amidst/documentation/NotNull.java @@ -0,0 +1,16 @@ +package amidst.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is added to methods that will never return null. + */ +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target({ ElementType.METHOD, ElementType.PARAMETER }) +public @interface NotNull { +} diff --git a/src/main/java/amidst/documentation/NotThreadSafe.java b/src/main/java/amidst/documentation/NotThreadSafe.java new file mode 100644 index 000000000..4164aeb17 --- /dev/null +++ b/src/main/java/amidst/documentation/NotThreadSafe.java @@ -0,0 +1,19 @@ +package amidst.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates that the type it is attached was not designed with + * thread safety in mind. Thus, it should be used from only a single thread or + * if it is used in a multi-threaded environment, the using class has to ensure + * thread safety. + */ +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target(ElementType.TYPE) +public @interface NotThreadSafe { +} diff --git a/src/main/java/amidst/documentation/ThreadSafe.java b/src/main/java/amidst/documentation/ThreadSafe.java new file mode 100644 index 000000000..fad81e3f0 --- /dev/null +++ b/src/main/java/amidst/documentation/ThreadSafe.java @@ -0,0 +1,19 @@ +package amidst.documentation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates that the type it is attached to is thread-safe. + * However, objects of this type might have a modifiable state. For unmodifiable + * types, use the {@link Immutable} annotation. You can read this annotation as + * if {@link CalledByAny} was used on all constructors and methods of this type. + */ +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target(ElementType.TYPE) +public @interface ThreadSafe { +} diff --git a/src/main/java/amidst/fragment/ClosestWorldIconFinder.java b/src/main/java/amidst/fragment/ClosestWorldIconFinder.java new file mode 100644 index 000000000..e3311f02d --- /dev/null +++ b/src/main/java/amidst/fragment/ClosestWorldIconFinder.java @@ -0,0 +1,71 @@ +package amidst.fragment; + +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.layer.LayerDeclaration; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.icon.WorldIcon; + +@NotThreadSafe +public class ClosestWorldIconFinder { + private final FragmentGraph graph; + private final List layerDeclarations; + private final CoordinatesInWorld positionInWorld; + private WorldIcon closestIcon; + private double closestDistanceSq; + + @CalledOnlyBy(AmidstThread.EDT) + public ClosestWorldIconFinder(FragmentGraph graph, + List layerDeclarations, + CoordinatesInWorld positionInWorld, double maxDistanceInWorld) { + this.graph = graph; + this.layerDeclarations = layerDeclarations; + this.positionInWorld = positionInWorld; + this.closestIcon = null; + this.closestDistanceSq = maxDistanceInWorld * maxDistanceInWorld; + find(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void find() { + for (FragmentGraphItem fragmentGraphItem : graph) { + Fragment fragment = fragmentGraphItem.getFragment(); + for (LayerDeclaration declaration : layerDeclarations) { + if (declaration.isVisible()) { + int layerId = declaration.getLayerId(); + for (WorldIcon icon : fragment.getWorldIcons(layerId)) { + updateClosest(icon); + } + } + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateClosest(WorldIcon icon) { + double distanceSq = icon.getCoordinates() + .getDistanceSq(positionInWorld); + if (closestDistanceSq > distanceSq) { + closestDistanceSq = distanceSq; + closestIcon = icon; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean hasResult() { + return closestIcon != null; + } + + @CalledOnlyBy(AmidstThread.EDT) + public WorldIcon getWorldIcon() { + return closestIcon; + } + + @CalledOnlyBy(AmidstThread.EDT) + public double getDistance() { + return Math.sqrt(closestDistanceSq); + } +} diff --git a/src/main/java/amidst/fragment/Fragment.java b/src/main/java/amidst/fragment/Fragment.java new file mode 100644 index 000000000..c16e7b5f8 --- /dev/null +++ b/src/main/java/amidst/fragment/Fragment.java @@ -0,0 +1,173 @@ +package amidst.fragment; + +import java.awt.image.BufferedImage; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.worldsurroundings.Drawer; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.coordinates.Resolution; +import amidst.mojangapi.world.icon.WorldIcon; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +/** + * This class contains nearly no logic but only simple and atomic getters and + * setters. + * + * The life-cycle of a Fragment is quite complex to prevent the garbage + * collection from running too often. When a fragment is no longer needed it + * will be kept available in a queue, so it can be reused later on. The + * life-cycle consists of the two flags isInitialized and isLoaded. + * setInitialized(true) can be called from any thread, however + * setInitialized(false), setLoaded(true) and setLoaded(false) will always be + * called from the fragment loading thread, to ensure consistent state. Also, + * setInitialized(true) will only be called again after setInitialized(false) + * was called. It is not possible that isLoaded is true while isInitialized is + * false. + * + * It is possible that a thread that uses the data in the fragment continues to + * use them after isLoaded is set to false. However, all write operations are + * only called from the fragment loading thread or during the construction of + * the fragment. While the fragment is constructed it will only be accessible by + * one thread. An exception to that rule is the instance variable alpha. It is + * altered from the drawing thread, however this should not cause any issues. + * + * Immediately after a new instance of this class is created, it is passed to + * all FragmentConstructors. At that point in time, no other thread can access + * the fragment, so the whole construction process is single-threaded. After the + * fragment is constructed it will be available to use in the fragment graph. As + * soon as it is requested, its isInitialized variable will be set to true by + * the requesting thread. Also, it is enqueued to the loading queue. Note, that + * the fragment is still not loaded, but used in the fragment graph and thus + * used by the {@link Drawer}. Sometime after the fragment was requested, it + * will be loaded by the fragment loading thread, because it was enqueued to the + * loading queue. The complete loading process is executed in the fragment + * loading thread. When this is done, the isLoaded variable will be set to true. + * This allows the drawer to actually draw the fragment. The complete drawing + * process is executed in the event dispatch thread. When the fragment is no + * longer visible on the screen it will be removed from the fragment graph. + * However, since it holds a data-structure that is quite heavy to allocate and + * garbage-collect, the fragment will be recycled so it can be reused later. + * This recycling is done by enqueuing the fragment to the recycle queue. The + * recycle queue is processed by the fragment loading thread with a very high + * priority. Even tough the fragment loading thread only calls the method + * {@link Fragment#recycle()} and enqueues the fragment to the available queue, + * it is important that this is done by the fragment loading queue. This is, + * because if any other thread sets the isLoaded variable to false, it might be + * set to true by the fragment loading thread afterwards, because the fragment + * was not yet loaded. This problem is solved by modifying the isLoaded variable + * only in the fragment loading thread. This issue only arises, since the + * fragment is already used in the fragment graph, before it is loaded. As soon + * as it is used in the fragment graph it, can be recycled. This often leads to + * a situation where a not yet loaded fragment gets recycled. The isInitialized + * variable is altered by the thread that requests the new fragment, which is + * different from the fragment loading thread. This is not an issue, since all + * fragments in the available queue have both variables isInitialized and + * isLoaded set to false. They are also not used in the fragment graph or + * enqueued in the recycle queue. Therefore, there cannot be a race condition + * because the isInitialized variable will only be set to false when it is + * recycled. + */ +@NotThreadSafe +public class Fragment { + public static final int SIZE = Resolution.FRAGMENT.getStep(); + + private volatile boolean isInitialized = false; + private volatile boolean isLoaded = false; + private volatile CoordinatesInWorld corner; + + private volatile float alpha; + private volatile short[][] biomeData; + private final AtomicReferenceArray images; + private final AtomicReferenceArray> worldIcons; + + public Fragment(int numberOfLayers) { + this.images = new AtomicReferenceArray(numberOfLayers); + this.worldIcons = new AtomicReferenceArray>( + numberOfLayers); + } + + public void setAlpha(float alpha) { + this.alpha = alpha; + } + + public float getAlpha() { + return alpha; + } + + public void initBiomeData(int width, int height) { + biomeData = new short[width][height]; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void populateBiomeData(BiomeDataOracle biomeDataOracle) { + biomeDataOracle.populateArrayUsingQuarterResolution(corner, biomeData); + } + + public short getBiomeDataAt(int x, int y) { + return biomeData[x][y]; + } + + public BufferedImage getAndSetImage(int layerId, BufferedImage image) { + return images.getAndSet(layerId, image); + } + + public void putImage(int layerId, BufferedImage image) { + images.set(layerId, image); + } + + public BufferedImage getImage(int layerId) { + return images.get(layerId); + } + + public void putWorldIcons(int layerId, List icons) { + worldIcons.set(layerId, icons); + } + + public List getWorldIcons(int layerId) { + if (isLoaded) { + List result = worldIcons.get(layerId); + if (result != null) { + return result; + } + } + return Collections.emptyList(); + } + + @CalledByAny + public void setInitialized() { + this.isInitialized = true; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void setLoaded() { + this.isLoaded = true; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void recycle() { + this.isLoaded = false; + this.isInitialized = false; + } + + public boolean isInitialized() { + return isInitialized; + } + + public boolean isLoaded() { + return isLoaded; + } + + public void setCorner(CoordinatesInWorld corner) { + this.corner = corner; + } + + public CoordinatesInWorld getCorner() { + return corner; + } +} diff --git a/src/main/java/amidst/fragment/FragmentCache.java b/src/main/java/amidst/fragment/FragmentCache.java new file mode 100644 index 000000000..582dbb945 --- /dev/null +++ b/src/main/java/amidst/fragment/FragmentCache.java @@ -0,0 +1,73 @@ +package amidst.fragment; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.ThreadSafe; +import amidst.fragment.constructor.FragmentConstructor; +import amidst.logging.Log; + +@ThreadSafe +public class FragmentCache { + private static final int NEW_FRAGMENTS_PER_REQUEST = 1024; + + private final List cache = new LinkedList(); + private volatile int cacheSize = 0; + + private final ConcurrentLinkedQueue availableQueue; + private final ConcurrentLinkedQueue loadingQueue; + private final Iterable constructors; + private final int numberOfLayers; + + @CalledOnlyBy(AmidstThread.EDT) + public FragmentCache(ConcurrentLinkedQueue availableQueue, + ConcurrentLinkedQueue loadingQueue, + Iterable constructors, int numberOfLayers) { + this.availableQueue = availableQueue; + this.loadingQueue = loadingQueue; + this.constructors = constructors; + this.numberOfLayers = numberOfLayers; + } + + @CalledOnlyBy(AmidstThread.EDT) + public synchronized void increaseSize() { + Log.i("increasing fragment cache size from " + cache.size() + " to " + + (cache.size() + NEW_FRAGMENTS_PER_REQUEST)); + requestNewFragments(); + Log.i("fragment cache size increased to " + cache.size()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void requestNewFragments() { + for (int i = 0; i < NEW_FRAGMENTS_PER_REQUEST; i++) { + Fragment fragment = new Fragment(numberOfLayers); + construct(fragment); + cache.add(fragment); + availableQueue.offer(fragment); + } + cacheSize = cache.size(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void construct(Fragment fragment) { + for (FragmentConstructor constructor : constructors) { + constructor.construct(fragment); + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public synchronized void reloadAll() { + loadingQueue.clear(); + for (Fragment fragment : cache) { + loadingQueue.offer(fragment); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public int size() { + return cacheSize; + } +} diff --git a/src/main/java/amidst/fragment/FragmentGraph.java b/src/main/java/amidst/fragment/FragmentGraph.java new file mode 100644 index 000000000..b0797e14b --- /dev/null +++ b/src/main/java/amidst/fragment/FragmentGraph.java @@ -0,0 +1,105 @@ +package amidst.fragment; + +import java.util.Iterator; +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.layer.LayerDeclaration; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.icon.WorldIcon; + +@NotThreadSafe +public class FragmentGraph implements Iterable { + private final List declarations; + private final FragmentManager fragmentManager; + + private FragmentGraphItem topLeftFragment; + private int fragmentsPerRow; + private int fragmentsPerColumn; + + @CalledOnlyBy(AmidstThread.EDT) + public FragmentGraph(List declarations, + FragmentManager fragmentManager) { + this.declarations = declarations; + this.fragmentManager = fragmentManager; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem getTopLeftFragment() { + if (topLeftFragment == null) { + init(CoordinatesInWorld.origin()); + } + return topLeftFragment; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjust(int newLeft, int newAbove, int newRight, int newBelow) { + topLeftFragment = getTopLeftFragment().adjustRowsAndColumns(newAbove, + newBelow, newLeft, newRight, fragmentManager); + fragmentsPerRow = fragmentsPerRow + newLeft + newRight; + fragmentsPerColumn = fragmentsPerColumn + newAbove + newBelow; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void init(CoordinatesInWorld coordinates) { + recycleAll(); + topLeftFragment = new FragmentGraphItem( + fragmentManager.requestFragment(coordinates.toFragmentCorner())); + fragmentsPerRow = 1; + fragmentsPerColumn = 1; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void dispose() { + recycleAll(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void recycleAll() { + if (topLeftFragment != null) { + topLeftFragment.recycleAll(fragmentManager); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getFragmentsPerRow() { + return fragmentsPerRow; + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getFragmentsPerColumn() { + return fragmentsPerColumn; + } + + @CalledOnlyBy(AmidstThread.EDT) + public CoordinatesInWorld getCorner() { + return topLeftFragment.getFragment().getCorner(); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public Iterator iterator() { + return getTopLeftFragment().iterator(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public WorldIcon getClosestWorldIcon(CoordinatesInWorld coordinates, + double maxDistanceInWorld) { + return new ClosestWorldIconFinder(this, declarations, coordinates, + maxDistanceInWorld).getWorldIcon(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public Fragment getFragmentAt(CoordinatesInWorld coordinates) { + CoordinatesInWorld corner = coordinates.toFragmentCorner(); + for (FragmentGraphItem fragmentGraphItem : getTopLeftFragment()) { + Fragment fragment = fragmentGraphItem.getFragment(); + if (corner.equals(fragment.getCorner())) { + return fragment; + } + } + return null; + } +} diff --git a/src/main/java/amidst/fragment/FragmentGraphItem.java b/src/main/java/amidst/fragment/FragmentGraphItem.java new file mode 100644 index 000000000..c564fd7fb --- /dev/null +++ b/src/main/java/amidst/fragment/FragmentGraphItem.java @@ -0,0 +1,391 @@ +package amidst.fragment; + +import java.util.Iterator; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class FragmentGraphItem implements Iterable { + /** + * This is an Iterator that is fail safe in the sense that it will never + * throw a NullPointerException or ConcurrentModificationException when the + * fragment graph is altered while the iterator is used. However, the + * elements returned by this iterator will fit the old state or the new + * state or something in between. This should be good enough for our use + * cases. + */ + @NotThreadSafe + private static class FragmentGraphItemIterator implements + Iterator { + private FragmentGraphItem rowStart; + private FragmentGraphItem currentNode; + + public FragmentGraphItemIterator(FragmentGraphItem fragment) { + rowStart = fragment.getFirstColumn().getFirstRow(); + currentNode = rowStart; + } + + @Override + public boolean hasNext() { + return currentNode != null; + } + + @Override + public FragmentGraphItem next() { + FragmentGraphItem result = currentNode; + updateCurrentNode(); + return result; + } + + private void updateCurrentNode() { + currentNode = currentNode.rightFragment; + if (currentNode == null) { + rowStart = rowStart.belowFragment; + currentNode = rowStart; + } + } + } + + private final Fragment fragment; + + private volatile FragmentGraphItem leftFragment = null; + private volatile FragmentGraphItem rightFragment = null; + private volatile FragmentGraphItem aboveFragment = null; + private volatile FragmentGraphItem belowFragment = null; + + @CalledOnlyBy(AmidstThread.EDT) + public FragmentGraphItem(Fragment fragment) { + this.fragment = fragment; + } + + @CalledOnlyBy(AmidstThread.EDT) + public Fragment getFragment() { + return fragment; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public Iterator iterator() { + return new FragmentGraphItemIterator(this); + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean isEndOfLine() { + return rightFragment == null; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void recycleAll(FragmentManager manager) { + FragmentGraphItem topLeft = getFirstColumn().getFirstRow(); + while (topLeft != null) { + FragmentGraphItem next = topLeft.belowFragment; + topLeft.deleteFirstRow(manager); + topLeft = next; + } + } + + /** + * Returns the new fragment in the top left corner, but never null. + */ + @CalledOnlyBy(AmidstThread.EDT) + public FragmentGraphItem adjustRowsAndColumns(int newAbove, int newBelow, + int newLeft, int newRight, FragmentManager manager) { + FragmentGraphItem firstColumn = getFirstColumn(); + FragmentGraphItem topLeft = firstColumn.getFirstRow(); + topLeft = topLeft.createOrRemoveRowsAbove(manager, newAbove); + topLeft.getLastRow().createOrRemoveRowsBelow(manager, newBelow); + topLeft = topLeft.createOrRemoveColumnsLeft(manager, newLeft); + topLeft.getLastColumn().createOrRemoveColumnsRight(manager, newRight); + return topLeft; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createOrRemoveRowsAbove(FragmentManager manager, + int newAbove) { + FragmentGraphItem topLeft = this; + for (int i = 0; i < newAbove; i++) { + topLeft.createFirstRow(manager); + topLeft = topLeft.aboveFragment; + } + for (int i = 0; i < -newAbove; i++) { + FragmentGraphItem next = topLeft.belowFragment; + topLeft.deleteFirstRow(manager); + topLeft = next; + } + return topLeft; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createOrRemoveRowsBelow(FragmentManager manager, + int newBelow) { + FragmentGraphItem bottomLeft = this; + for (int i = 0; i < newBelow; i++) { + bottomLeft.createLastRow(manager); + bottomLeft = bottomLeft.belowFragment; + } + for (int i = 0; i < -newBelow; i++) { + FragmentGraphItem next = bottomLeft.aboveFragment; + bottomLeft.deleteLastRow(manager); + bottomLeft = next; + } + return bottomLeft; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createOrRemoveColumnsLeft( + FragmentManager manager, int newLeft) { + FragmentGraphItem topLeft = this; + for (int i = 0; i < newLeft; i++) { + topLeft.createFirstColumn(manager); + topLeft = topLeft.leftFragment; + } + for (int i = 0; i < -newLeft; i++) { + FragmentGraphItem next = topLeft.rightFragment; + topLeft.deleteFirstColumn(manager); + topLeft = next; + } + return topLeft; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createOrRemoveColumnsRight( + FragmentManager manager, int newRight) { + FragmentGraphItem topRight = this; + for (int i = 0; i < newRight; i++) { + topRight.createLastColumn(manager); + topRight = topRight.rightFragment; + } + for (int i = 0; i < -newRight; i++) { + FragmentGraphItem next = topRight.leftFragment; + topRight.deleteLastColumn(manager); + topRight = next; + } + return topRight; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void createFirstRow(FragmentManager manager) { + FragmentGraphItem above = createAbove(manager); + FragmentGraphItem below = rightFragment; + while (below != null) { + above = above.createRight(manager); + above.connectBelow(below); + below = below.rightFragment; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void createLastRow(FragmentManager manager) { + FragmentGraphItem below = createBelow(manager); + FragmentGraphItem above = rightFragment; + while (above != null) { + below = below.createRight(manager); + below.connectAbove(above); + above = above.rightFragment; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void createFirstColumn(FragmentManager manager) { + FragmentGraphItem left = createLeft(manager); + FragmentGraphItem right = belowFragment; + while (right != null) { + left = left.createBelow(manager); + left.connectRight(right); + right = right.belowFragment; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void createLastColumn(FragmentManager manager) { + FragmentGraphItem right = createRight(manager); + FragmentGraphItem left = belowFragment; + while (left != null) { + right = right.createBelow(manager); + right.connectLeft(left); + left = left.belowFragment; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void deleteFirstRow(FragmentManager manager) { + FragmentGraphItem current = this; + while (current != null) { + current.disconnectBelow(); + FragmentGraphItem right = current.disconnectRight(); + current.recycle(manager); + current = right; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void deleteLastRow(FragmentManager manager) { + FragmentGraphItem current = this; + while (current != null) { + current.disconnectAbove(); + FragmentGraphItem right = current.disconnectRight(); + current.recycle(manager); + current = right; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void deleteFirstColumn(FragmentManager manager) { + FragmentGraphItem current = this; + while (current != null) { + current.disconnectRight(); + FragmentGraphItem below = current.disconnectBelow(); + current.recycle(manager); + current = below; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void deleteLastColumn(FragmentManager manager) { + FragmentGraphItem current = this; + while (current != null) { + current.disconnectLeft(); + FragmentGraphItem below = current.disconnectBelow(); + current.recycle(manager); + current = below; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem getFirstRow() { + FragmentGraphItem result = this; + while (result.aboveFragment != null) { + result = result.aboveFragment; + } + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem getLastRow() { + FragmentGraphItem result = this; + while (result.belowFragment != null) { + result = result.belowFragment; + } + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem getFirstColumn() { + FragmentGraphItem result = this; + while (result.leftFragment != null) { + result = result.leftFragment; + } + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem getLastColumn() { + FragmentGraphItem result = this; + while (result.rightFragment != null) { + result = result.rightFragment; + } + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem connectAbove(FragmentGraphItem above) { + above.belowFragment = this; + aboveFragment = above; + return above; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem connectBelow(FragmentGraphItem below) { + below.aboveFragment = this; + belowFragment = below; + return below; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem connectLeft(FragmentGraphItem left) { + left.rightFragment = this; + leftFragment = left; + return left; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem connectRight(FragmentGraphItem right) { + right.leftFragment = this; + rightFragment = right; + return right; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem disconnectAbove() { + if (aboveFragment != null) { + aboveFragment.belowFragment = null; + } + FragmentGraphItem result = aboveFragment; + aboveFragment = null; + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem disconnectBelow() { + if (belowFragment != null) { + belowFragment.aboveFragment = null; + } + FragmentGraphItem result = belowFragment; + belowFragment = null; + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem disconnectLeft() { + if (leftFragment != null) { + leftFragment.rightFragment = null; + } + FragmentGraphItem result = leftFragment; + leftFragment = null; + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem disconnectRight() { + if (rightFragment != null) { + rightFragment.leftFragment = null; + } + FragmentGraphItem result = rightFragment; + rightFragment = null; + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createAbove(FragmentManager manager) { + return connectAbove(createFragmentGraphItem(manager, 0, -Fragment.SIZE)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createBelow(FragmentManager manager) { + return connectBelow(createFragmentGraphItem(manager, 0, Fragment.SIZE)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createLeft(FragmentManager manager) { + return connectLeft(createFragmentGraphItem(manager, -Fragment.SIZE, 0)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createRight(FragmentManager manager) { + return connectRight(createFragmentGraphItem(manager, Fragment.SIZE, 0)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private FragmentGraphItem createFragmentGraphItem(FragmentManager manager, + int xInWorld, int yInWorld) { + return new FragmentGraphItem(manager.requestFragment(fragment + .getCorner().add(xInWorld, yInWorld))); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void recycle(FragmentManager manager) { + manager.recycleFragment(fragment); + } +} diff --git a/src/main/java/amidst/fragment/FragmentManager.java b/src/main/java/amidst/fragment/FragmentManager.java new file mode 100644 index 000000000..35fc456ee --- /dev/null +++ b/src/main/java/amidst/fragment/FragmentManager.java @@ -0,0 +1,68 @@ +package amidst.fragment; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.constructor.FragmentConstructor; +import amidst.fragment.layer.LayerLoader; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; + +@NotThreadSafe +public class FragmentManager { + private final ConcurrentLinkedQueue availableQueue = new ConcurrentLinkedQueue(); + private final ConcurrentLinkedQueue loadingQueue = new ConcurrentLinkedQueue(); + private final ConcurrentLinkedQueue recycleQueue = new ConcurrentLinkedQueue(); + private final FragmentCache cache; + + @CalledOnlyBy(AmidstThread.EDT) + public FragmentManager(Iterable constructors, + int numberOfLayers) { + this.cache = new FragmentCache(availableQueue, loadingQueue, + constructors, numberOfLayers); + } + + @CalledOnlyBy(AmidstThread.EDT) + public Fragment requestFragment(CoordinatesInWorld coordinates) { + Fragment fragment; + while ((fragment = availableQueue.poll()) == null) { + cache.increaseSize(); + } + fragment.setCorner(coordinates); + fragment.setInitialized(); + loadingQueue.offer(fragment); + return fragment; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void recycleFragment(Fragment fragment) { + recycleQueue.offer(fragment); + } + + @CalledOnlyBy(AmidstThread.EDT) + public FragmentQueueProcessor createLayerLoader(LayerLoader layerLoader) { + return new FragmentQueueProcessor(availableQueue, loadingQueue, + recycleQueue, cache, layerLoader); + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getAvailableQueueSize() { + return availableQueue.size(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getLoadingQueueSize() { + return loadingQueue.size(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getRecycleQueueSize() { + return recycleQueue.size(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getCacheSize() { + return cache.size(); + } +} diff --git a/src/main/java/amidst/fragment/FragmentQueueProcessor.java b/src/main/java/amidst/fragment/FragmentQueueProcessor.java new file mode 100644 index 000000000..ce6a4936d --- /dev/null +++ b/src/main/java/amidst/fragment/FragmentQueueProcessor.java @@ -0,0 +1,100 @@ +package amidst.fragment; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.layer.LayerLoader; +import amidst.threading.TaskQueue; + +@NotThreadSafe +public class FragmentQueueProcessor { + private final TaskQueue taskQueue = new TaskQueue(); + + private final ConcurrentLinkedQueue availableQueue; + private final ConcurrentLinkedQueue loadingQueue; + private final ConcurrentLinkedQueue recycleQueue; + private final FragmentCache cache; + private final LayerLoader layerLoader; + + @CalledByAny + public FragmentQueueProcessor( + ConcurrentLinkedQueue availableQueue, + ConcurrentLinkedQueue loadingQueue, + ConcurrentLinkedQueue recycleQueue, FragmentCache cache, + LayerLoader layerLoader) { + this.availableQueue = availableQueue; + this.loadingQueue = loadingQueue; + this.recycleQueue = recycleQueue; + this.cache = cache; + this.layerLoader = layerLoader; + } + + @CalledByAny + public void invalidateLayer(final int layerId) { + taskQueue.invoke(new Runnable() { + @Override + public void run() { + doInvalidateLayer(layerId); + } + }); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void doInvalidateLayer(int layerId) { + layerLoader.invalidateLayer(layerId); + cache.reloadAll(); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void processQueues() { + taskQueue.processTasks(); + processRecycleQueue(); + Fragment fragment; + while ((fragment = loadingQueue.poll()) != null) { + loadFragment(fragment); + taskQueue.processTasks(); + processRecycleQueue(); + } + layerLoader.clearInvalidatedLayers(); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void processRecycleQueue() { + Fragment fragment; + while ((fragment = recycleQueue.poll()) != null) { + recycleFragment(fragment); + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void loadFragment(Fragment fragment) { + if (fragment.isInitialized()) { + if (fragment.isLoaded()) { + layerLoader.reloadInvalidated(fragment); + } else { + layerLoader.loadAll(fragment); + fragment.setLoaded(); + } + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void recycleFragment(Fragment fragment) { + fragment.recycle(); + removeFromLoadingQueue(fragment); + availableQueue.offer(fragment); + } + + // TODO: Check performance with and without this. It is not needed, since + // loadFragment checks for isInitialized(). It helps to keep the + // loadingQueue small, but it costs time to remove fragments from the queue. + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void removeFromLoadingQueue(Object fragment) { + while (loadingQueue.remove(fragment)) { + // noop + } + } +} diff --git a/src/main/java/amidst/fragment/colorprovider/BiomeColorProvider.java b/src/main/java/amidst/fragment/colorprovider/BiomeColorProvider.java new file mode 100644 index 000000000..57f8fe2ad --- /dev/null +++ b/src/main/java/amidst/fragment/colorprovider/BiomeColorProvider.java @@ -0,0 +1,37 @@ +package amidst.fragment.colorprovider; + +import amidst.documentation.ThreadSafe; +import amidst.fragment.Fragment; +import amidst.gui.main.worldsurroundings.BiomeSelection; +import amidst.mojangapi.world.biome.BiomeColor; +import amidst.settings.biomecolorprofile.BiomeColorProfileSelection; + +@ThreadSafe +public class BiomeColorProvider implements ColorProvider { + private final BiomeSelection biomeSelection; + private final BiomeColorProfileSelection biomeColorProfileSelection; + + public BiomeColorProvider(BiomeSelection biomeSelection, + BiomeColorProfileSelection biomeColorProfileSelection) { + this.biomeSelection = biomeSelection; + this.biomeColorProfileSelection = biomeColorProfileSelection; + } + + @Override + public int getColorAt(Fragment fragment, long cornerX, long cornerY, int x, + int y) { + return getColor(fragment.getBiomeDataAt(x, y)); + } + + private int getColor(int biomeIndex) { + if (biomeSelection.isSelected(biomeIndex)) { + return getBiomeColor(biomeIndex).getRGB(); + } else { + return getBiomeColor(biomeIndex).getDeselectRGB(); + } + } + + private BiomeColor getBiomeColor(int biomeIndex) { + return biomeColorProfileSelection.getBiomeColorOrUnknown(biomeIndex); + } +} diff --git a/src/main/java/amidst/fragment/colorprovider/ColorProvider.java b/src/main/java/amidst/fragment/colorprovider/ColorProvider.java new file mode 100644 index 000000000..31bf5621c --- /dev/null +++ b/src/main/java/amidst/fragment/colorprovider/ColorProvider.java @@ -0,0 +1,10 @@ +package amidst.fragment.colorprovider; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.fragment.Fragment; + +public interface ColorProvider { + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + int getColorAt(Fragment fragment, long cornerX, long cornerY, int x, int y); +} diff --git a/src/main/java/amidst/fragment/colorprovider/SlimeColorProvider.java b/src/main/java/amidst/fragment/colorprovider/SlimeColorProvider.java new file mode 100644 index 000000000..0e32de4bc --- /dev/null +++ b/src/main/java/amidst/fragment/colorprovider/SlimeColorProvider.java @@ -0,0 +1,30 @@ +package amidst.fragment.colorprovider; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.mojangapi.world.oracle.SlimeChunkOracle; + +@NotThreadSafe +public class SlimeColorProvider implements ColorProvider { + private static final int SLIME_CHUNK_COLOR = 0xA0FF00FF; + private static final int NOT_SLIME_CHUNK_COLOR = 0x00000000; + + private final SlimeChunkOracle slimeChunkOracle; + + public SlimeColorProvider(SlimeChunkOracle slimeChunkOracle) { + this.slimeChunkOracle = slimeChunkOracle; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public int getColorAt(Fragment fragment, long cornerX, long cornerY, int x, + int y) { + if (slimeChunkOracle.isSlimeChunk(cornerX + x, cornerY + y)) { + return SLIME_CHUNK_COLOR; + } else { + return NOT_SLIME_CHUNK_COLOR; + } + } +} diff --git a/src/main/java/amidst/fragment/constructor/BiomeDataConstructor.java b/src/main/java/amidst/fragment/constructor/BiomeDataConstructor.java new file mode 100644 index 000000000..4ebf497fa --- /dev/null +++ b/src/main/java/amidst/fragment/constructor/BiomeDataConstructor.java @@ -0,0 +1,23 @@ +package amidst.fragment.constructor; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.Immutable; +import amidst.fragment.Fragment; +import amidst.mojangapi.world.coordinates.Resolution; + +@Immutable +public class BiomeDataConstructor implements FragmentConstructor { + private final int size; + + @CalledOnlyBy(AmidstThread.EDT) + public BiomeDataConstructor(Resolution resolution) { + this.size = resolution.getStepsPerFragment(); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void construct(Fragment fragment) { + fragment.initBiomeData(size, size); + } +} diff --git a/src/main/java/amidst/fragment/constructor/FragmentConstructor.java b/src/main/java/amidst/fragment/constructor/FragmentConstructor.java new file mode 100644 index 000000000..3eb4c418a --- /dev/null +++ b/src/main/java/amidst/fragment/constructor/FragmentConstructor.java @@ -0,0 +1,12 @@ +package amidst.fragment.constructor; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.Immutable; +import amidst.fragment.Fragment; + +@Immutable +public interface FragmentConstructor { + @CalledOnlyBy(AmidstThread.EDT) + void construct(Fragment fragment); +} diff --git a/src/main/java/amidst/fragment/constructor/ImageConstructor.java b/src/main/java/amidst/fragment/constructor/ImageConstructor.java new file mode 100644 index 000000000..b3316bf7e --- /dev/null +++ b/src/main/java/amidst/fragment/constructor/ImageConstructor.java @@ -0,0 +1,32 @@ +package amidst.fragment.constructor; + +import java.awt.image.BufferedImage; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.Immutable; +import amidst.fragment.Fragment; +import amidst.mojangapi.world.coordinates.Resolution; + +@Immutable +public class ImageConstructor implements FragmentConstructor { + private final int size; + private final int layerId; + + @CalledOnlyBy(AmidstThread.EDT) + public ImageConstructor(Resolution resolution, int layerId) { + this.size = resolution.getStepsPerFragment(); + this.layerId = layerId; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void construct(Fragment fragment) { + fragment.putImage(layerId, createBufferedImage()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private BufferedImage createBufferedImage() { + return new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + } +} diff --git a/src/main/java/amidst/fragment/drawer/AlphaUpdater.java b/src/main/java/amidst/fragment/drawer/AlphaUpdater.java new file mode 100644 index 000000000..60576b6c1 --- /dev/null +++ b/src/main/java/amidst/fragment/drawer/AlphaUpdater.java @@ -0,0 +1,22 @@ +package amidst.fragment.drawer; + +import java.awt.Graphics2D; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; + +@NotThreadSafe +public class AlphaUpdater extends FragmentDrawer { + public AlphaUpdater(LayerDeclaration declaration) { + super(declaration); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void draw(Fragment fragment, Graphics2D g2d, float time) { + fragment.setAlpha(Math.min(1.0f, time * 3.0f + fragment.getAlpha())); + } +} diff --git a/src/main/java/amidst/fragment/drawer/FragmentDrawer.java b/src/main/java/amidst/fragment/drawer/FragmentDrawer.java new file mode 100644 index 000000000..d6f72db27 --- /dev/null +++ b/src/main/java/amidst/fragment/drawer/FragmentDrawer.java @@ -0,0 +1,26 @@ +package amidst.fragment.drawer; + +import java.awt.Graphics2D; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; + +@NotThreadSafe +public abstract class FragmentDrawer { + protected final LayerDeclaration declaration; + + public FragmentDrawer(LayerDeclaration declaration) { + this.declaration = declaration; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean isEnabled() { + return declaration.isVisible(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public abstract void draw(Fragment fragment, Graphics2D g2d, float time); +} diff --git a/src/main/java/amidst/fragment/drawer/GridDrawer.java b/src/main/java/amidst/fragment/drawer/GridDrawer.java new file mode 100644 index 000000000..dcc99f7a2 --- /dev/null +++ b/src/main/java/amidst/fragment/drawer/GridDrawer.java @@ -0,0 +1,121 @@ +package amidst.fragment.drawer; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; +import amidst.gui.main.worldsurroundings.Zoom; +import amidst.mojangapi.world.coordinates.Resolution; + +@NotThreadSafe +public class GridDrawer extends FragmentDrawer { + private static final Font DRAW_FONT = new Font("arial", Font.BOLD, 16); + + private final StringBuffer textBuffer = new StringBuffer(128); + private final char[] textCache = new char[128]; + private final Zoom zoom; + + public GridDrawer(LayerDeclaration declaration, Zoom zoom) { + super(declaration); + this.zoom = zoom; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void draw(Fragment fragment, Graphics2D g2d, float time) { + int stride = getStride(); + int gridX = getGridX(fragment, stride); + int gridY = getGridY(fragment, stride); + initGraphics(g2d); + drawGridLines(g2d, stride, gridX, gridY); + if (isGrid00(gridX, gridY)) { + double invZoom = 1.0 / zoom.getCurrentValue(); + g2d.scale(invZoom, invZoom); + updateText(fragment); + drawText(g2d); + // drawThickTextOutline(g2d); + drawTextOutline(g2d); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getStride() { + return (int) (.25 / zoom.getCurrentValue()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getGridX(Fragment fragment, int stride) { + return (int) fragment.getCorner().getXAs(Resolution.FRAGMENT) + % (stride + 1); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getGridY(Fragment fragment, int stride) { + return (int) fragment.getCorner().getYAs(Resolution.FRAGMENT) + % (stride + 1); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initGraphics(Graphics2D g2d) { + g2d.setFont(DRAW_FONT); + g2d.setColor(Color.black); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawGridLines(Graphics2D g2d, int stride, int gridX, int gridY) { + if (gridY == 0) { + g2d.drawLine(0, 0, Fragment.SIZE, 0); + } + if (gridY == stride) { + g2d.drawLine(0, Fragment.SIZE, Fragment.SIZE, Fragment.SIZE); + } + if (gridX == 0) { + g2d.drawLine(0, 0, 0, Fragment.SIZE); + } + if (gridX == stride) { + g2d.drawLine(Fragment.SIZE, 0, Fragment.SIZE, Fragment.SIZE); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isGrid00(int gridX, int gridY) { + return gridX == 0 && gridY == 0; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateText(Fragment fragment) { + textBuffer.setLength(0); + textBuffer.append(fragment.getCorner().getX()); + textBuffer.append(", "); + textBuffer.append(fragment.getCorner().getY()); + textBuffer.getChars(0, textBuffer.length(), textCache, 0); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawText(Graphics2D g2d) { + g2d.drawChars(textCache, 0, textBuffer.length(), 12, 17); + g2d.drawChars(textCache, 0, textBuffer.length(), 8, 17); + g2d.drawChars(textCache, 0, textBuffer.length(), 10, 19); + g2d.drawChars(textCache, 0, textBuffer.length(), 10, 15); + } + + // This makes the text outline a bit thicker, but seems unneeded. + @SuppressWarnings("unused") + private void drawThickTextOutline(Graphics2D g2d) { + g2d.drawChars(textCache, 0, textBuffer.length(), 12, 15); + g2d.drawChars(textCache, 0, textBuffer.length(), 12, 19); + g2d.drawChars(textCache, 0, textBuffer.length(), 8, 15); + g2d.drawChars(textCache, 0, textBuffer.length(), 8, 19); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawTextOutline(Graphics2D g2d) { + g2d.setColor(Color.white); + g2d.drawChars(textCache, 0, textBuffer.length(), 10, 17); + } +} diff --git a/src/main/java/amidst/fragment/drawer/ImageDrawer.java b/src/main/java/amidst/fragment/drawer/ImageDrawer.java new file mode 100644 index 000000000..00df0a588 --- /dev/null +++ b/src/main/java/amidst/fragment/drawer/ImageDrawer.java @@ -0,0 +1,42 @@ +package amidst.fragment.drawer; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; +import amidst.mojangapi.world.coordinates.Resolution; + +@NotThreadSafe +public class ImageDrawer extends FragmentDrawer { + private final Resolution resolution; + + public ImageDrawer(LayerDeclaration declaration, Resolution resolution) { + super(declaration); + this.resolution = resolution; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void draw(Fragment fragment, Graphics2D g2d, float time) { + int scale = resolution.getStep(); + g2d.scale(scale, scale); + Object oldHint = g2d.getRenderingHint(RenderingHints.KEY_INTERPOLATION); + Object newHint = getRenderingHint(g2d); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, newHint); + g2d.drawImage(fragment.getImage(declaration.getLayerId()), 0, 0, null); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldHint); + } + + @CalledOnlyBy(AmidstThread.EDT) + private Object getRenderingHint(Graphics2D g2d) { + if (g2d.getTransform().getScaleX() < 1.0f) { + return RenderingHints.VALUE_INTERPOLATION_BILINEAR; + } else { + return RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; + } + } +} diff --git a/src/main/java/amidst/fragment/drawer/WorldIconDrawer.java b/src/main/java/amidst/fragment/drawer/WorldIconDrawer.java new file mode 100644 index 000000000..486368aa4 --- /dev/null +++ b/src/main/java/amidst/fragment/drawer/WorldIconDrawer.java @@ -0,0 +1,55 @@ +package amidst.fragment.drawer; + +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; +import amidst.gui.main.worldsurroundings.WorldIconSelection; +import amidst.gui.main.worldsurroundings.Zoom; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.icon.WorldIcon; + +@NotThreadSafe +public class WorldIconDrawer extends FragmentDrawer { + private final Zoom zoom; + private final WorldIconSelection worldIconSelection; + + public WorldIconDrawer(LayerDeclaration declaration, Zoom zoom, + WorldIconSelection worldIconSelection) { + super(declaration); + this.zoom = zoom; + this.worldIconSelection = worldIconSelection; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void draw(Fragment fragment, Graphics2D g2d, float time) { + double invZoom = 1.0 / zoom.getCurrentValue(); + AffineTransform originalTransform = g2d.getTransform(); + for (WorldIcon icon : fragment.getWorldIcons(declaration.getLayerId())) { + drawIcon(icon, invZoom, g2d); + g2d.setTransform(originalTransform); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawIcon(WorldIcon icon, double invZoom, Graphics2D g2d) { + BufferedImage image = icon.getImage(); + int width = image.getWidth(); + int height = image.getHeight(); + if (worldIconSelection.isSelected(icon)) { + width *= 1.5; + height *= 1.5; + } + CoordinatesInWorld coordinates = icon.getCoordinates(); + g2d.translate(coordinates.getXRelativeToFragment(), + coordinates.getYRelativeToFragment()); + g2d.scale(invZoom, invZoom); + g2d.drawImage(image, -(width >> 1), -(height >> 1), width, height, null); + } +} diff --git a/src/main/java/amidst/fragment/layer/LayerBuilder.java b/src/main/java/amidst/fragment/layer/LayerBuilder.java new file mode 100644 index 000000000..6d254ba08 --- /dev/null +++ b/src/main/java/amidst/fragment/layer/LayerBuilder.java @@ -0,0 +1,139 @@ +package amidst.fragment.layer; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import amidst.Settings; +import amidst.documentation.Immutable; +import amidst.fragment.FragmentQueueProcessor; +import amidst.fragment.colorprovider.BiomeColorProvider; +import amidst.fragment.colorprovider.SlimeColorProvider; +import amidst.fragment.constructor.BiomeDataConstructor; +import amidst.fragment.constructor.FragmentConstructor; +import amidst.fragment.constructor.ImageConstructor; +import amidst.fragment.drawer.AlphaUpdater; +import amidst.fragment.drawer.FragmentDrawer; +import amidst.fragment.drawer.GridDrawer; +import amidst.fragment.drawer.ImageDrawer; +import amidst.fragment.drawer.WorldIconDrawer; +import amidst.fragment.loader.AlphaInitializer; +import amidst.fragment.loader.BiomeDataLoader; +import amidst.fragment.loader.FragmentLoader; +import amidst.fragment.loader.ImageLoader; +import amidst.fragment.loader.WorldIconLoader; +import amidst.gui.main.worldsurroundings.BiomeSelection; +import amidst.gui.main.worldsurroundings.WorldIconSelection; +import amidst.gui.main.worldsurroundings.Zoom; +import amidst.mojangapi.world.World; +import amidst.mojangapi.world.coordinates.Resolution; +import amidst.settings.ImmutableSetting; + +@Immutable +public class LayerBuilder { + private final List declarations; + private final Iterable constructors; + + public LayerBuilder(Settings settings) { + this.declarations = createDeclarations(settings); + this.constructors = createConstructors(); + } + + private List createDeclarations(Settings settings) { + LayerDeclaration[] declarations = new LayerDeclaration[LayerIds.NUMBER_OF_LAYERS]; + // @formatter:off + declarations[LayerIds.ALPHA] = new LayerDeclaration(LayerIds.ALPHA, new ImmutableSetting(true)); + declarations[LayerIds.BIOME] = new LayerDeclaration(LayerIds.BIOME, new ImmutableSetting(true)); + declarations[LayerIds.SLIME] = new LayerDeclaration(LayerIds.SLIME, settings.showSlimeChunks); + declarations[LayerIds.GRID] = new LayerDeclaration(LayerIds.GRID, settings.showGrid); + declarations[LayerIds.VILLAGE] = new LayerDeclaration(LayerIds.VILLAGE, settings.showVillages); + declarations[LayerIds.OCEAN_MONUMENT] = new LayerDeclaration(LayerIds.OCEAN_MONUMENT, settings.showOceanMonuments); + declarations[LayerIds.STRONGHOLD] = new LayerDeclaration(LayerIds.STRONGHOLD, settings.showStrongholds); + declarations[LayerIds.TEMPLE] = new LayerDeclaration(LayerIds.TEMPLE, settings.showTemples); + declarations[LayerIds.SPAWN] = new LayerDeclaration(LayerIds.SPAWN, settings.showSpawn); + declarations[LayerIds.NETHER_FORTRESS] = new LayerDeclaration(LayerIds.NETHER_FORTRESS, settings.showNetherFortresses); + declarations[LayerIds.PLAYER] = new LayerDeclaration(LayerIds.PLAYER, settings.showPlayers); + // @formatter:on + return Collections.unmodifiableList(Arrays.asList(declarations)); + } + + /** + * This also defines the construction order. + */ + private Iterable createConstructors() { + // @formatter:off + return Collections.unmodifiableList(Arrays.asList( + new BiomeDataConstructor(Resolution.QUARTER), + new ImageConstructor( Resolution.QUARTER, LayerIds.BIOME), + new ImageConstructor( Resolution.CHUNK, LayerIds.SLIME) + )); + // @formatter:on + } + + public List getDeclarations() { + return declarations; + } + + public Iterable getConstructors() { + return constructors; + } + + public int getNumberOfLayers() { + return LayerIds.NUMBER_OF_LAYERS; + } + + public LayerLoader createLayerLoader(World world, + BiomeSelection biomeSelection, Settings settings) { + return new LayerLoader(createLoaders(world, biomeSelection, settings), + LayerIds.NUMBER_OF_LAYERS); + } + + /** + * This also defines the loading and reloading order. + */ + private Iterable createLoaders(World world, + BiomeSelection biomeSelection, Settings settings) { + // @formatter:off + return Collections.unmodifiableList(Arrays.asList( + new AlphaInitializer(declarations.get(LayerIds.ALPHA), settings.fragmentFading), + new BiomeDataLoader( declarations.get(LayerIds.BIOME), world.getBiomeDataOracle()), + new ImageLoader( declarations.get(LayerIds.BIOME), Resolution.QUARTER, new BiomeColorProvider(biomeSelection, settings.biomeColorProfileSelection)), + new ImageLoader( declarations.get(LayerIds.SLIME), Resolution.CHUNK, new SlimeColorProvider(world.getSlimeChunkOracle())), + new WorldIconLoader( declarations.get(LayerIds.VILLAGE), world.getVillageProducer()), + new WorldIconLoader( declarations.get(LayerIds.OCEAN_MONUMENT), world.getOceanMonumentProducer()), + new WorldIconLoader( declarations.get(LayerIds.STRONGHOLD), world.getStrongholdProducer()), + new WorldIconLoader( declarations.get(LayerIds.TEMPLE), world.getTempleProducer()), + new WorldIconLoader( declarations.get(LayerIds.SPAWN), world.getSpawnProducer()), + new WorldIconLoader( declarations.get(LayerIds.NETHER_FORTRESS), world.getNetherFortressProducer()), + new WorldIconLoader( declarations.get(LayerIds.PLAYER), world.getPlayerProducer()) + )); + // @formatter:on + } + + /** + * This also defines the rendering order. + */ + public Iterable createDrawers(Zoom zoom, + WorldIconSelection worldIconSelection) { + // @formatter:off + return Collections.unmodifiableList(Arrays.asList( + new AlphaUpdater( declarations.get(LayerIds.ALPHA)), + new ImageDrawer( declarations.get(LayerIds.BIOME), Resolution.QUARTER), + new ImageDrawer( declarations.get(LayerIds.SLIME), Resolution.CHUNK), + new GridDrawer( declarations.get(LayerIds.GRID), zoom), + new WorldIconDrawer(declarations.get(LayerIds.VILLAGE), zoom, worldIconSelection), + new WorldIconDrawer(declarations.get(LayerIds.OCEAN_MONUMENT), zoom, worldIconSelection), + new WorldIconDrawer(declarations.get(LayerIds.STRONGHOLD), zoom, worldIconSelection), + new WorldIconDrawer(declarations.get(LayerIds.TEMPLE), zoom, worldIconSelection), + new WorldIconDrawer(declarations.get(LayerIds.SPAWN), zoom, worldIconSelection), + new WorldIconDrawer(declarations.get(LayerIds.NETHER_FORTRESS), zoom, worldIconSelection), + new WorldIconDrawer(declarations.get(LayerIds.PLAYER), zoom, worldIconSelection) + )); + // @formatter:on + } + + public LayerReloader createLayerReloader(World world, + FragmentQueueProcessor fragmentQueueProcessor) { + return new LayerReloader(world, fragmentQueueProcessor); + } +} diff --git a/src/main/java/amidst/fragment/layer/LayerDeclaration.java b/src/main/java/amidst/fragment/layer/LayerDeclaration.java new file mode 100644 index 000000000..a6bb7f752 --- /dev/null +++ b/src/main/java/amidst/fragment/layer/LayerDeclaration.java @@ -0,0 +1,23 @@ +package amidst.fragment.layer; + +import amidst.documentation.Immutable; +import amidst.settings.Setting; + +@Immutable +public class LayerDeclaration { + private final int layerId; + private final Setting isVisibleSetting; + + public LayerDeclaration(int layerId, Setting isVisibleSetting) { + this.layerId = layerId; + this.isVisibleSetting = isVisibleSetting; + } + + public int getLayerId() { + return layerId; + } + + public boolean isVisible() { + return isVisibleSetting.get(); + } +} diff --git a/src/main/java/amidst/fragment/layer/LayerIds.java b/src/main/java/amidst/fragment/layer/LayerIds.java new file mode 100644 index 000000000..a96eaca55 --- /dev/null +++ b/src/main/java/amidst/fragment/layer/LayerIds.java @@ -0,0 +1,23 @@ +package amidst.fragment.layer; + +import amidst.documentation.Immutable; + +/** + * The Ids are used as array indices but they do not influence the loading or + * drawing order. + */ +@Immutable +public class LayerIds { + public static final int ALPHA = 0; + public static final int BIOME = 1; + public static final int SLIME = 2; + public static final int GRID = 3; + public static final int VILLAGE = 4; + public static final int OCEAN_MONUMENT = 5; + public static final int STRONGHOLD = 6; + public static final int TEMPLE = 7; + public static final int SPAWN = 8; + public static final int NETHER_FORTRESS = 9; + public static final int PLAYER = 10; + public static final int NUMBER_OF_LAYERS = 11; +} diff --git a/src/main/java/amidst/fragment/layer/LayerLoader.java b/src/main/java/amidst/fragment/layer/LayerLoader.java new file mode 100644 index 000000000..2532a7aeb --- /dev/null +++ b/src/main/java/amidst/fragment/layer/LayerLoader.java @@ -0,0 +1,53 @@ +package amidst.fragment.layer; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.loader.FragmentLoader; + +@NotThreadSafe +public class LayerLoader { + private final Iterable loaders; + private final boolean[] invalidatedLayers; + + @CalledByAny + public LayerLoader(Iterable loaders, int numberOfLayers) { + this.loaders = loaders; + this.invalidatedLayers = new boolean[numberOfLayers]; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void clearInvalidatedLayers() { + for (int i = 0; i < invalidatedLayers.length; i++) { + invalidatedLayers[i] = false; + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void invalidateLayer(int layerId) { + invalidatedLayers[layerId] = true; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void loadAll(Fragment fragment) { + for (FragmentLoader loader : loaders) { + loader.load(fragment); + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public void reloadInvalidated(Fragment fragment) { + for (FragmentLoader loader : loaders) { + if (isInvalidated(loader.getLayerDeclaration())) { + loader.reload(fragment); + } + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private boolean isInvalidated(LayerDeclaration layerDeclaration) { + return invalidatedLayers[layerDeclaration.getLayerId()]; + } +} diff --git a/src/main/java/amidst/fragment/layer/LayerReloader.java b/src/main/java/amidst/fragment/layer/LayerReloader.java new file mode 100644 index 000000000..3c08910d9 --- /dev/null +++ b/src/main/java/amidst/fragment/layer/LayerReloader.java @@ -0,0 +1,26 @@ +package amidst.fragment.layer; + +import amidst.documentation.ThreadSafe; +import amidst.fragment.FragmentQueueProcessor; +import amidst.mojangapi.world.World; + +@ThreadSafe +public class LayerReloader { + private final World world; + private final FragmentQueueProcessor fragmentQueueProcessor; + + public LayerReloader(World world, + FragmentQueueProcessor fragmentQueueProcessor) { + this.world = world; + this.fragmentQueueProcessor = fragmentQueueProcessor; + } + + public void reloadBiomeLayer() { + fragmentQueueProcessor.invalidateLayer(LayerIds.BIOME); + } + + public void reloadPlayerLayer() { + world.reloadPlayerWorldIcons(); + fragmentQueueProcessor.invalidateLayer(LayerIds.PLAYER); + } +} diff --git a/src/main/java/amidst/fragment/loader/AlphaInitializer.java b/src/main/java/amidst/fragment/loader/AlphaInitializer.java new file mode 100644 index 000000000..2a769bb0a --- /dev/null +++ b/src/main/java/amidst/fragment/loader/AlphaInitializer.java @@ -0,0 +1,35 @@ +package amidst.fragment.loader; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; +import amidst.settings.Setting; + +@NotThreadSafe +public class AlphaInitializer extends FragmentLoader { + private final Setting fragmentFadingSetting; + + public AlphaInitializer(LayerDeclaration declaration, + Setting fragmentFadingSetting) { + super(declaration); + this.fragmentFadingSetting = fragmentFadingSetting; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void load(Fragment fragment) { + if (fragmentFadingSetting.get()) { + fragment.setAlpha(0.0f); + } else { + fragment.setAlpha(1.0f); + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void reload(Fragment fragment) { + // noop + } +} diff --git a/src/main/java/amidst/fragment/loader/BiomeDataLoader.java b/src/main/java/amidst/fragment/loader/BiomeDataLoader.java new file mode 100644 index 000000000..b1ad9b864 --- /dev/null +++ b/src/main/java/amidst/fragment/loader/BiomeDataLoader.java @@ -0,0 +1,31 @@ +package amidst.fragment.loader; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@NotThreadSafe +public class BiomeDataLoader extends FragmentLoader { + private final BiomeDataOracle biomeDataOracle; + + public BiomeDataLoader(LayerDeclaration declaration, + BiomeDataOracle biomeDataOracle) { + super(declaration); + this.biomeDataOracle = biomeDataOracle; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void load(Fragment fragment) { + fragment.populateBiomeData(biomeDataOracle); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void reload(Fragment fragment) { + // noop + } +} diff --git a/src/main/java/amidst/fragment/loader/FragmentLoader.java b/src/main/java/amidst/fragment/loader/FragmentLoader.java new file mode 100644 index 000000000..5f5cabddc --- /dev/null +++ b/src/main/java/amidst/fragment/loader/FragmentLoader.java @@ -0,0 +1,26 @@ +package amidst.fragment.loader; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; + +@NotThreadSafe +public abstract class FragmentLoader { + protected final LayerDeclaration declaration; + + public FragmentLoader(LayerDeclaration declaration) { + this.declaration = declaration; + } + + public LayerDeclaration getLayerDeclaration() { + return declaration; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public abstract void load(Fragment fragment); + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public abstract void reload(Fragment fragment); +} diff --git a/src/main/java/amidst/fragment/loader/ImageLoader.java b/src/main/java/amidst/fragment/loader/ImageLoader.java new file mode 100644 index 000000000..4d44efab9 --- /dev/null +++ b/src/main/java/amidst/fragment/loader/ImageLoader.java @@ -0,0 +1,77 @@ +package amidst.fragment.loader; + +import java.awt.image.BufferedImage; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.colorprovider.ColorProvider; +import amidst.fragment.layer.LayerDeclaration; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.coordinates.Resolution; + +@NotThreadSafe +public class ImageLoader extends FragmentLoader { + private final Resolution resolution; + private final ColorProvider colorProvider; + private final int size; + private final int[] rgbArray; + private volatile BufferedImage bufferedImage; + + @CalledByAny + public ImageLoader(LayerDeclaration declaration, Resolution resolution, + ColorProvider colorProvider) { + super(declaration); + this.resolution = resolution; + this.colorProvider = colorProvider; + this.size = resolution.getStepsPerFragment(); + this.rgbArray = new int[size * size]; + this.bufferedImage = createBufferedImage(); + } + + @CalledByAny + private BufferedImage createBufferedImage() { + return new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void load(Fragment fragment) { + doLoad(fragment); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void reload(Fragment fragment) { + doLoad(fragment); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void doLoad(Fragment fragment) { + CoordinatesInWorld corner = fragment.getCorner(); + long cornerX = corner.getXAs(resolution); + long cornerY = corner.getYAs(resolution); + drawToCache(fragment, cornerX, cornerY); + bufferedImage.setRGB(0, 0, size, size, rgbArray, 0, size); + bufferedImage = fragment.getAndSetImage(declaration.getLayerId(), + bufferedImage); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void drawToCache(Fragment fragment, long cornerX, long cornerY) { + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + int index = getCacheIndex(x, y); + rgbArray[index] = colorProvider.getColorAt(fragment, cornerX, + cornerY, x, y); + } + } + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private int getCacheIndex(int x, int y) { + return x + y * size; + } +} diff --git a/src/main/java/amidst/fragment/loader/WorldIconLoader.java b/src/main/java/amidst/fragment/loader/WorldIconLoader.java new file mode 100644 index 000000000..73a36f8f7 --- /dev/null +++ b/src/main/java/amidst/fragment/loader/WorldIconLoader.java @@ -0,0 +1,37 @@ +package amidst.fragment.loader; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.layer.LayerDeclaration; +import amidst.mojangapi.world.icon.WorldIconProducer; + +@NotThreadSafe +public class WorldIconLoader extends FragmentLoader { + private final WorldIconProducer producer; + + public WorldIconLoader(LayerDeclaration declaration, + WorldIconProducer producer) { + super(declaration); + this.producer = producer; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void load(Fragment fragment) { + doLoad(fragment); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void reload(Fragment fragment) { + doLoad(fragment); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void doLoad(Fragment fragment) { + fragment.putWorldIcons(declaration.getLayerId(), + producer.getAt(fragment.getCorner())); + } +} diff --git a/src/main/java/amidst/gui/crash/CrashWindow.java b/src/main/java/amidst/gui/crash/CrashWindow.java new file mode 100644 index 000000000..4ec09afef --- /dev/null +++ b/src/main/java/amidst/gui/crash/CrashWindow.java @@ -0,0 +1,54 @@ +package amidst.gui.crash; + +import java.awt.Color; +import java.awt.Font; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.ScrollPaneConstants; +import javax.swing.border.LineBorder; + +import net.miginfocom.swing.MigLayout; +import amidst.documentation.Immutable; + +@Immutable +public class CrashWindow { + private final JFrame frame; + + public CrashWindow(String message, String logMessages, + final Runnable executeOnClose) { + frame = new JFrame("Amidst encountered an unexpected error."); + frame.getContentPane().setLayout(new MigLayout()); + frame.add(new JLabel("Amidst has crashed with the following message:"), + "growx, pushx, wrap"); + frame.add(new JLabel(message), "growx, pushx, wrap"); + frame.add(createLogMessagesScrollPane(logMessages), "grow, push"); + frame.setSize(500, 400); + frame.setVisible(true); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + frame.dispose(); + executeOnClose.run(); + } + }); + } + + private JScrollPane createLogMessagesScrollPane(String logMessages) { + JScrollPane result = new JScrollPane( + createLogMessagesTextArea(logMessages)); + result.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + result.setBorder(new LineBorder(Color.darkGray, 1)); + return result; + } + + private JTextArea createLogMessagesTextArea(String logMessages) { + JTextArea result = new JTextArea(logMessages); + result.setFont(new Font("arial", Font.PLAIN, 10)); + return result; + } +} diff --git a/src/main/java/amidst/gui/license/License.java b/src/main/java/amidst/gui/license/License.java new file mode 100644 index 000000000..3cd9e4df3 --- /dev/null +++ b/src/main/java/amidst/gui/license/License.java @@ -0,0 +1,42 @@ +package amidst.gui.license; + +import java.io.IOException; + +import amidst.ResourceLoader; +import amidst.documentation.Immutable; +import amidst.logging.Log; + +@Immutable +public class License { + private final String name; + private final String licenseText; + + public License(String name, String path) { + this.name = name; + this.licenseText = readLicenseText(path); + } + + public String readLicenseText(String path) { + try { + return ResourceLoader.getResourceAsString(path); + } catch (IOException e) { + Log.w("Unable to read license for '" + name + "' at '" + path + + "'."); + e.printStackTrace(); + return "License text is missing."; + } + } + + public String getName() { + return name; + } + + public String getLicenseText() { + return licenseText; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/amidst/gui/license/LicenseWindow.java b/src/main/java/amidst/gui/license/LicenseWindow.java new file mode 100644 index 000000000..33a999c6f --- /dev/null +++ b/src/main/java/amidst/gui/license/LicenseWindow.java @@ -0,0 +1,98 @@ +package amidst.gui.license; + +import java.awt.Color; +import java.awt.Container; +import java.util.Arrays; +import java.util.List; + +import javax.swing.JFrame; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.border.LineBorder; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import net.miginfocom.swing.MigLayout; +import amidst.AmidstMetaData; +import amidst.documentation.Immutable; + +@Immutable +public class LicenseWindow { + private static final String LICENSES_DIRECTORY = "/amidst/gui/license/"; + + private final AmidstMetaData metadata; + + public LicenseWindow(AmidstMetaData metadata) { + this.metadata = metadata; + License[] licenses = createLicenses(); + JTextArea textArea = createLicenseTextArea(); + JScrollPane scrollPane = createScrollPane(textArea); + JList licenseList = createLicenseList(licenses, textArea); + createFrame(licenseList, scrollPane); + } + + private License[] createLicenses() { + List result = Arrays.asList( + createLicense("Amidst", "amidst.txt"), + createLicense("Args4j", "args4j.txt"), + createLicense("Gson", "gson.txt"), + createLicense("JNBT", "jnbt.txt"), + createLicense("MiG Layout", "miglayout.txt")); + return result.toArray(new License[result.size()]); + } + + private License createLicense(String name, String path) { + return new License(name, LICENSES_DIRECTORY + path); + } + + private JTextArea createLicenseTextArea() { + JTextArea result = new JTextArea(); + result.setEditable(false); + result.setLineWrap(true); + result.setWrapStyleWord(true); + return result; + } + + private JScrollPane createScrollPane(JTextArea textArea) { + JScrollPane result = new JScrollPane(textArea); + result.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + return result; + } + + private JList createLicenseList(License[] licenses, + final JTextArea textArea) { + final JList result = new JList(licenses); + result.setBorder(new LineBorder(Color.darkGray, 1)); + result.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + result.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + textArea.setText(result.getSelectedValue().getLicenseText()); + textArea.setCaretPosition(0); + } + }); + result.setSelectedIndex(0); + return result; + } + + private JFrame createFrame(JList licenseList, + JScrollPane scrollPane) { + JFrame frame = new JFrame("Licenses"); + initContentPane(frame.getContentPane(), licenseList, scrollPane); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.setIconImage(metadata.getIcon()); + frame.setSize(870, 550); + frame.setVisible(true); + return frame; + } + + private void initContentPane(Container contentPane, + JList licenseList, JScrollPane scrollPane) { + contentPane.setLayout(new MigLayout()); + contentPane.add(licenseList, "w 150!, h 0:2400:2400"); + contentPane.add(scrollPane, "w 0:4800:4800, h 0:2400:2400"); + } +} diff --git a/src/main/java/amidst/gui/main/Actions.java b/src/main/java/amidst/gui/main/Actions.java new file mode 100644 index 000000000..285142296 --- /dev/null +++ b/src/main/java/amidst/gui/main/Actions.java @@ -0,0 +1,334 @@ +package amidst.gui.main; + +import java.awt.Component; +import java.awt.Point; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import javax.imageio.ImageIO; + +import amidst.Application; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.menu.MovePlayerPopupMenu; +import amidst.gui.main.worldsurroundings.WorldSurroundings; +import amidst.logging.Log; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.minecraftinterface.MinecraftInterfaceException; +import amidst.mojangapi.world.WorldSeed; +import amidst.mojangapi.world.WorldType; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.icon.WorldIcon; +import amidst.mojangapi.world.player.Player; +import amidst.settings.biomecolorprofile.BiomeColorProfile; +import amidst.settings.biomecolorprofile.BiomeColorProfileSelection; +import amidst.threading.WorkerExecutor; + +@NotThreadSafe +public class Actions { + private final Application application; + private final MojangApi mojangApi; + private final MainWindow mainWindow; + private final AtomicReference worldSurroundings; + private final UpdatePrompt updatePrompt; + private final BiomeColorProfileSelection biomeColorProfileSelection; + private final WorkerExecutor workerExecutor; + + @CalledOnlyBy(AmidstThread.EDT) + public Actions(Application application, MojangApi mojangApi, + MainWindow mainWindow, + AtomicReference worldSurroundings, + UpdatePrompt updatePrompt, + BiomeColorProfileSelection biomeColorProfileSelection, + WorkerExecutor workerExecutor) { + this.application = application; + this.mojangApi = mojangApi; + this.mainWindow = mainWindow; + this.worldSurroundings = worldSurroundings; + this.updatePrompt = updatePrompt; + this.biomeColorProfileSelection = biomeColorProfileSelection; + this.workerExecutor = workerExecutor; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void newFromSeed() { + WorldSeed seed = mainWindow.askForSeed(); + if (seed != null) { + newFromSeed(seed); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void newFromRandom() { + newFromSeed(WorldSeed.random()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void newFromSeed(WorldSeed seed) { + WorldType worldType = mainWindow.askForWorldType(); + if (worldType != null) { + try { + mainWindow.setWorld(mojangApi.createWorldFromSeed(seed, + worldType)); + } catch (IllegalStateException | MinecraftInterfaceException e) { + e.printStackTrace(); + mainWindow.displayException(e); + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void openWorldFile() { + File file = mainWindow.askForMinecraftWorldFile(); + if (file != null) { + try { + mainWindow.setWorld(mojangApi.createWorldFromFile(file)); + } catch (IllegalStateException | MinecraftInterfaceException + | IOException | MojangApiParsingException e) { + e.printStackTrace(); + mainWindow.displayException(e); + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void switchProfile() { + application.displayProfileSelectWindow(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void exit() { + application.exitGracefully(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void goToCoordinate() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + String input = mainWindow.askForCoordinates(); + if (input != null) { + CoordinatesInWorld coordinates = CoordinatesInWorld + .tryParse(input); + if (coordinates != null) { + worldSurroundings.centerOn(coordinates); + } else { + Log.w("Invalid location entered, ignoring."); + mainWindow.displayError("You entered an invalid location."); + } + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void goToSpawn() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + worldSurroundings.centerOn(worldSurroundings.getSpawnWorldIcon()); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void goToStronghold() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + WorldIcon stronghold = mainWindow.askForOptions("Go to", + "Select Stronghold:", + worldSurroundings.getStrongholdWorldIcons()); + if (stronghold != null) { + worldSurroundings.centerOn(stronghold); + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void goToPlayer() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + List playerWorldIcons = worldSurroundings + .getPlayerWorldIcons(); + if (!playerWorldIcons.isEmpty()) { + WorldIcon player = mainWindow.askForOptions("Go to", + "Select player:", playerWorldIcons); + if (player != null) { + worldSurroundings.centerOn(player); + } + } else { + mainWindow.displayError("There are no players in this world."); + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void savePlayerLocations() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + worldSurroundings.savePlayerLocations(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void reloadPlayerLocations() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + worldSurroundings.loadPlayers(workerExecutor); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void howCanIMoveAPlayer() { + mainWindow + .displayMessage( + "How can I move a player?", + "If you load the world from a minecraft save folder, you can change the player locations.\n" + + "1. Scroll the map to and right-click on the new player location.\n" + + "2. Select the player you want to move to the new location.\n" + + "3. Enter the new player height (y-coordinate).\n" + + "4. Save player locations.\n\n" + + "WARNING: This will change the contents of the save folder, so there is a chance that the world gets corrupted.\n" + + "We try to minimize the risk by creating a backup of the changed file, before it is changed.\n" + + "If the backup fails, we will not write the changes.\n" + + "You can find the backup files in a sub folder of the world, named 'amidst_backup'.\n" + + "Especially, make sure to not have the world loaded in minecraft during this process."); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void copySeedToClipboard() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + String seed = "" + worldSurroundings.getWorldSeed().getLong(); + StringSelection selection = new StringSelection(seed); + Toolkit.getDefaultToolkit().getSystemClipboard() + .setContents(selection, selection); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void saveCaptureImage() { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + BufferedImage image = worldSurroundings.createCaptureImage(); + File file = mainWindow.askForCaptureImageSaveFile(); + if (file != null) { + saveImageToFile(image, file); + } + image.flush(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void selectBiomeColorProfile(BiomeColorProfile profile) { + biomeColorProfileSelection.set(profile); + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + worldSurroundings.reloadBiomeLayer(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void checkForUpdates() { + updatePrompt.check(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void viewLicense() { + application.displayLicenseWindow(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void about() { + mainWindow.displayMessage("About", + "Advanced Minecraft Interfacing and Data/Structure Tracking (Amidst)\n" + + "By Skidoodle (amidst.project@gmail.com)"); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjustZoom(int notches) { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + worldSurroundings.adjustZoom(notches); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjustZoom(Point mousePosition, int notches) { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + worldSurroundings.adjustZoom(mousePosition, notches); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void selectWorldIcon(WorldIcon worldIcon) { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + worldSurroundings.selectWorldIcon(worldIcon); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void showPlayerPopupMenu(CoordinatesInWorld targetCoordinates, + Component component, int x, int y) { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + if (worldSurroundings.canSavePlayerLocations()) { + new MovePlayerPopupMenu(this, + worldSurroundings.getMovablePlayerList(), + targetCoordinates).show(component, x, y); + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void movePlayer(Player player, CoordinatesInWorld targetCoordinates) { + WorldSurroundings worldSurroundings = this.worldSurroundings.get(); + if (worldSurroundings != null) { + long currentHeight = player.getPlayerCoordinates().getY(); + String input = mainWindow.askForPlayerHeight(currentHeight); + if (input != null) { + player.moveTo(targetCoordinates, + tryParseLong(input, currentHeight)); + worldSurroundings.reloadPlayerLayer(); + if (mainWindow + .askToConfirm( + "Save Player Location", + "Do you want to save the player locations NOW? Make sure to not have the world opened in minecraft at the same time!")) { + worldSurroundings.savePlayerLocations(); + } + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private long tryParseLong(String text, long defaultValue) { + try { + return Long.parseLong(text); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void saveImageToFile(BufferedImage image, File file) { + try { + ImageIO.write(image, "png", appendPNGFileExtensionIfNecessary(file)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private File appendPNGFileExtensionIfNecessary(File file) { + String filename = file.toString(); + if (!filename.toLowerCase().endsWith(".png")) { + filename += ".png"; + } + return new File(filename); + } +} diff --git a/src/main/java/amidst/gui/main/LevelFileFilter.java b/src/main/java/amidst/gui/main/LevelFileFilter.java new file mode 100644 index 000000000..7eef37a54 --- /dev/null +++ b/src/main/java/amidst/gui/main/LevelFileFilter.java @@ -0,0 +1,26 @@ +package amidst.gui.main; + +import java.io.File; + +import javax.swing.filechooser.FileFilter; + +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class LevelFileFilter extends FileFilter { + private static final String LEVEL_DAT = "level.dat"; + + @Override + public boolean accept(File file) { + if (file.isDirectory()) { + return true; + } else { + return file.getName().equalsIgnoreCase(LEVEL_DAT); + } + } + + @Override + public String getDescription() { + return "Minecraft Data File (level.dat)"; + } +} diff --git a/src/main/java/amidst/gui/main/MainWindow.java b/src/main/java/amidst/gui/main/MainWindow.java new file mode 100644 index 000000000..bbbc49363 --- /dev/null +++ b/src/main/java/amidst/gui/main/MainWindow.java @@ -0,0 +1,349 @@ +package amidst.gui.main; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +import amidst.AmidstMetaData; +import amidst.Application; +import amidst.Settings; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.menu.AmidstMenu; +import amidst.gui.main.menu.AmidstMenuBuilder; +import amidst.gui.main.worldsurroundings.WorldSurroundings; +import amidst.gui.main.worldsurroundings.WorldSurroundingsBuilder; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.world.World; +import amidst.mojangapi.world.WorldSeed; +import amidst.mojangapi.world.WorldType; +import amidst.mojangapi.world.player.MovablePlayerList; +import amidst.mojangapi.world.player.WorldPlayerType; +import amidst.threading.ThreadMaster; + +@NotThreadSafe +public class MainWindow { + private final Application application; + private final AmidstMetaData metadata; + private final Settings settings; + private final MojangApi mojangApi; + private final WorldSurroundingsBuilder worldSurroundingsBuilder; + private final ThreadMaster threadMaster; + private final UpdatePrompt updatePrompt; + + private final JFrame frame; + private final Container contentPane; + private final Actions actions; + private final AmidstMenu menuBar; + + private final AtomicReference worldSurroundings = new AtomicReference(); + + @CalledOnlyBy(AmidstThread.EDT) + public MainWindow(Application application, AmidstMetaData metadata, + Settings settings, MojangApi mojangApi, + WorldSurroundingsBuilder worldSurroundingsBuilder, + ThreadMaster threadMaster) { + this.application = application; + this.metadata = metadata; + this.settings = settings; + this.mojangApi = mojangApi; + this.worldSurroundingsBuilder = worldSurroundingsBuilder; + this.threadMaster = threadMaster; + this.updatePrompt = createUpdatePrompt(); + this.frame = createFrame(); + this.contentPane = createContentPane(); + this.actions = createActions(); + this.menuBar = createMenuBar(); + initKeyListener(); + initCloseListener(); + showFrame(); + checkForUpdates(); + clearWorldSurroundings(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private UpdatePrompt createUpdatePrompt() { + return new UpdatePrompt(metadata, this, + threadMaster.getWorkerExecutor()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private JFrame createFrame() { + JFrame frame = new JFrame(); + frame.setTitle(createVersionString(mojangApi.getVersionId(), + mojangApi.getRecognisedVersionName())); + frame.setSize(1000, 800); + frame.setIconImage(metadata.getIcon()); + return frame; + } + + @CalledOnlyBy(AmidstThread.EDT) + private String createVersionString(String versionId, + String recognisedVersionName) { + return metadata.getMainWindowTitle() + " - Minecraft Version " + + versionId + " (" + recognisedVersionName + ")"; + } + + @CalledOnlyBy(AmidstThread.EDT) + private Container createContentPane() { + Container contentPane = frame.getContentPane(); + contentPane.setLayout(new BorderLayout()); + return contentPane; + } + + @CalledOnlyBy(AmidstThread.EDT) + private Actions createActions() { + return new Actions(application, mojangApi, this, worldSurroundings, + updatePrompt, settings.biomeColorProfileSelection, + threadMaster.getWorkerExecutor()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private AmidstMenu createMenuBar() { + AmidstMenu menuBar = new AmidstMenuBuilder(settings, actions) + .construct(); + frame.setJMenuBar(menuBar.getMenuBar()); + return menuBar; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initKeyListener() { + frame.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_PLUS) { + actions.adjustZoom(-1); + } else if (e.getKeyCode() == KeyEvent.VK_MINUS) { + actions.adjustZoom(1); + } + } + }); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initCloseListener() { + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + actions.exit(); + } + }); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void showFrame() { + frame.setVisible(true); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void checkForUpdates() { + updatePrompt.checkSilently(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void setWorld(World world) { + clearWorldSurroundings(); + if (decideWorldPlayerType(world.getMovablePlayerList())) { + setWorldSurroundings(worldSurroundingsBuilder + .create(world, actions)); + } else { + frame.revalidate(); + frame.repaint(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean decideWorldPlayerType(MovablePlayerList movablePlayerList) { + if (movablePlayerList.getWorldPlayerType().equals(WorldPlayerType.BOTH)) { + WorldPlayerType worldPlayerType = askForWorldPlayerType(); + if (worldPlayerType != null) { + movablePlayerList.setWorldPlayerType(worldPlayerType); + return true; + } else { + return false; + } + } else { + return true; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void setWorldSurroundings(WorldSurroundings worldSurroundings) { + contentPane.add(worldSurroundings.getComponent(), BorderLayout.CENTER); + menuBar.setWorldMenuEnabled(true); + menuBar.setSavePlayerLocationsMenuEnabled(worldSurroundings + .canSavePlayerLocations()); + menuBar.setReloadPlayerLocationsMenuEnabled(worldSurroundings + .canLoadPlayerLocations()); + frame.validate(); + worldSurroundings.loadPlayers(threadMaster.getWorkerExecutor()); + threadMaster.setOnRepaintTick(worldSurroundings.getOnRepainterTick()); + threadMaster.setOnFragmentLoadTick(worldSurroundings + .getOnFragmentLoaderTick()); + this.worldSurroundings.set(worldSurroundings); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void clearWorldSurroundings() { + threadMaster.clearOnRepaintTick(); + threadMaster.clearOnFragmentLoadTick(); + WorldSurroundings worldSurroundings = this.worldSurroundings + .getAndSet(null); + if (worldSurroundings != null) { + contentPane.remove(worldSurroundings.getComponent()); + worldSurroundings.dispose(); + } + clearWorldSurroundingsFromGui(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void clearWorldSurroundingsFromGui() { + menuBar.setWorldMenuEnabled(false); + menuBar.setSavePlayerLocationsMenuEnabled(false); + menuBar.setReloadPlayerLocationsMenuEnabled(false); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void dispose() { + clearWorldSurroundings(); + frame.dispose(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private WorldPlayerType askForWorldPlayerType() { + return askForOptions( + "Loading World", + "This world contains Multiplayer and Singleplayer data. What do you want to load?\n" + + "If you do not know what to do, just choose Singleplayer.", + WorldPlayerType.getSelectable()); + } + + @CalledOnlyBy(AmidstThread.EDT) + public WorldSeed askForSeed() { + return new SeedPrompt(frame).askForSeed(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public File askForMinecraftWorldFile() { + return getSelectedFileOrNull(createMinecraftWorldFileChooser()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private JFileChooser createMinecraftWorldFileChooser() { + JFileChooser result = new JFileChooser(); + result.setFileFilter(new LevelFileFilter()); + result.setAcceptAllFileFilterUsed(false); + result.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + result.setCurrentDirectory(mojangApi.getSaves()); + result.setFileHidingEnabled(false); + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + public File askForCaptureImageSaveFile() { + return getSelectedFileOrNull(createCaptureImageSaveFileChooser()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private JFileChooser createCaptureImageSaveFileChooser() { + JFileChooser result = new JFileChooser(); + result.setFileFilter(new PNGFileFilter()); + result.setAcceptAllFileFilterUsed(false); + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private File getSelectedFileOrNull(JFileChooser fileChooser) { + if (fileChooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) { + return fileChooser.getSelectedFile(); + } else { + return null; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void displayMessage(String title, String message) { + JOptionPane.showMessageDialog(frame, message, title, + JOptionPane.INFORMATION_MESSAGE); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void displayError(String message) { + JOptionPane.showMessageDialog(frame, message, "Error", + JOptionPane.ERROR_MESSAGE); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void displayException(Exception exception) { + JOptionPane.showMessageDialog(frame, getStackTraceAsString(exception), + "Error", JOptionPane.ERROR_MESSAGE); + } + + @CalledOnlyBy(AmidstThread.EDT) + private String getStackTraceAsString(Exception exception) { + StringWriter writer = new StringWriter(); + exception.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean askToConfirm(String title, String message) { + return JOptionPane.showConfirmDialog(frame, message, title, + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION; + } + + @CalledOnlyBy(AmidstThread.EDT) + public WorldType askForWorldType() { + String worldTypeSetting = settings.worldType.get(); + if (worldTypeSetting.equals(WorldType.PROMPT_EACH_TIME)) { + return askForOptions("World Type", "Enter world type\n", + WorldType.getSelectable()); + } else { + return WorldType.from(worldTypeSetting); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @SuppressWarnings("unchecked") + public T askForOptions(String title, String message, List choices) { + Object[] choicesArray = choices.toArray(); + return (T) JOptionPane.showInputDialog(frame, message, title, + JOptionPane.PLAIN_MESSAGE, null, choicesArray, choicesArray[0]); + } + + @CalledOnlyBy(AmidstThread.EDT) + public String askForCoordinates() { + return askForString("Go To", "Enter coordinates: (Ex. 123,456)"); + } + + @CalledOnlyBy(AmidstThread.EDT) + public String askForPlayerHeight(long currentHeight) { + Object input = JOptionPane.showInputDialog(frame, + "Enter new player height:", "Move Player", + JOptionPane.QUESTION_MESSAGE, null, null, currentHeight); + if (input != null) { + return input.toString(); + } else { + return null; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private String askForString(String title, String message) { + return JOptionPane.showInputDialog(frame, message, title, + JOptionPane.QUESTION_MESSAGE); + } +} diff --git a/src/amidst/gui/menu/PNGFileFilter.java b/src/main/java/amidst/gui/main/PNGFileFilter.java similarity index 59% rename from src/amidst/gui/menu/PNGFileFilter.java rename to src/main/java/amidst/gui/main/PNGFileFilter.java index 7a6670ebf..30a88fa06 100644 --- a/src/amidst/gui/menu/PNGFileFilter.java +++ b/src/main/java/amidst/gui/main/PNGFileFilter.java @@ -1,17 +1,22 @@ -package amidst.gui.menu; +package amidst.gui.main; -import javax.swing.filechooser.FileFilter; import java.io.File; +import javax.swing.filechooser.FileFilter; + +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe public class PNGFileFilter extends FileFilter { @Override public boolean accept(File file) { - if (file.isDirectory()) + if (file.isDirectory()) { return true; - String[] st = file.getName().split("/."); - return st[st.length - 1].equalsIgnoreCase("png"); + } else { + return file.getName().toLowerCase().endsWith(".png"); + } } - + @Override public String getDescription() { return "Portable Network Graphic (*.PNG)"; diff --git a/src/main/java/amidst/gui/main/SeedPrompt.java b/src/main/java/amidst/gui/main/SeedPrompt.java new file mode 100644 index 000000000..285d0b66c --- /dev/null +++ b/src/main/java/amidst/gui/main/SeedPrompt.java @@ -0,0 +1,138 @@ +package amidst.gui.main; + +import java.awt.Color; +import java.awt.Font; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.event.AncestorEvent; +import javax.swing.event.AncestorListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import net.miginfocom.swing.MigLayout; +import amidst.documentation.NotThreadSafe; +import amidst.mojangapi.world.WorldSeed; +import amidst.mojangapi.world.WorldSeed.WorldSeedType; + +@NotThreadSafe +public class SeedPrompt { + private static final String TITLE = "Enter Seed"; + private static final String STARTS_WITH_SPACE_TEXT = "WARNING: There is a space at the start!"; + private static final String ENDS_WITH_SPACE_TEXT = "WARNING: There is a space at the end!"; + + private final JFrame frame; + private final JTextField textField; + private final JLabel seedLabel; + private final JLabel warningLabel; + private final JPanel panel; + private WorldSeed seed; + + public SeedPrompt(JFrame frame) { + this.frame = frame; + this.textField = createTextField(); + this.seedLabel = createSeedLabel(); + this.warningLabel = createWarningLabel(); + this.panel = createPanel(); + } + + private JTextField createTextField() { + JTextField result = new JTextField(); + result.addAncestorListener(new AncestorListener() { + @Override + public void ancestorAdded(AncestorEvent e) { + grabFocus(); + } + + @Override + public void ancestorMoved(AncestorEvent e) { + grabFocus(); + } + + @Override + public void ancestorRemoved(AncestorEvent e) { + grabFocus(); + } + }); + result.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + }); + return result; + } + + private JLabel createSeedLabel() { + JLabel result = new JLabel(); + result.setFont(new Font("arial", Font.BOLD, 12)); + return result; + } + + private JLabel createWarningLabel() { + JLabel result = new JLabel(); + result.setFont(new Font("arial", Font.BOLD, 12)); + result.setForeground(Color.RED); + return result; + } + + private JPanel createPanel() { + JPanel result = new JPanel(new MigLayout()); + result.add(new JLabel("Enter your seed:"), "w 400!, wrap"); + result.add(textField, "w 400!, wrap"); + result.add(seedLabel, "w 400!, wrap"); + result.add(warningLabel, "w 400!, h 30!"); + return result; + } + + private void grabFocus() { + // The call with invokeLater seems to help resolve an issue on Linux. + // Without it, the textField often does not get the focus. + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + textField.requestFocus(); + } + }); + } + + private void update() { + seed = WorldSeed.fromUserInput(textField.getText()); + if (WorldSeedType.TEXT == seed.getType() + && seed.getText().startsWith(" ")) { + warningLabel.setText(STARTS_WITH_SPACE_TEXT); + } else if (WorldSeedType.TEXT == seed.getType() + && seed.getText().endsWith(" ")) { + warningLabel.setText(ENDS_WITH_SPACE_TEXT); + } else { + warningLabel.setText(""); + } + seedLabel.setText(seed.getLabel()); + seedLabel.revalidate(); + } + + public WorldSeed askForSeed() { + update(); + grabFocus(); + if (JOptionPane.showConfirmDialog(frame, panel, TITLE, + JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { + return seed; + } else { + return null; + } + } +} diff --git a/src/main/java/amidst/gui/main/UpdateInformationRetriever.java b/src/main/java/amidst/gui/main/UpdateInformationRetriever.java new file mode 100644 index 000000000..425708193 --- /dev/null +++ b/src/main/java/amidst/gui/main/UpdateInformationRetriever.java @@ -0,0 +1,99 @@ +package amidst.gui.main; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import amidst.AmidstMetaData; +import amidst.documentation.Immutable; + +@Immutable +public class UpdateInformationRetriever { + public static final String UPDATE_URL = "https://sites.google.com/site/mothfinder/update.xml"; + public static final String UPDATE_UNSTABLE_URL = "https://sites.google.com/site/mothfinder/update_unstable.xml"; + + private static final int MAJOR_INDEX = 0; + private static final int MINOR_INDEX = 1; + + private final AmidstMetaData metadata; + private final String updateURL; + private final int major; + private final int minor; + + public UpdateInformationRetriever(AmidstMetaData metadata) + throws MalformedURLException, SAXException, IOException, + ParserConfigurationException, RuntimeException { + this.metadata = metadata; + Document document = getDocument(); + updateURL = getUpdateURL(document); + int[] versionNumbers = getVersionNumbers(document); + major = versionNumbers[MAJOR_INDEX]; + minor = versionNumbers[MINOR_INDEX]; + } + + private Document getDocument() throws MalformedURLException, SAXException, + IOException, ParserConfigurationException { + URL url = new URL(UPDATE_URL); + Document document = DocumentBuilderFactory.newInstance() + .newDocumentBuilder().parse(url.openStream()); + document.getDocumentElement().normalize(); + return document; + } + + private String getUpdateURL(Document document) { + return document.getFirstChild().getAttributes().item(0).getNodeValue(); + } + + private int[] getVersionNumbers(Document document) { + NodeList version = document.getDocumentElement() + .getElementsByTagName("version").item(0).getChildNodes(); + int[] result = new int[] { -1, -1 }; + for (int i = 0; i < version.getLength(); i++) { + Node node = version.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + if (node.getNodeName().equalsIgnoreCase("major")) { + result[MAJOR_INDEX] = getVersionNumber(node); + } else if (node.getNodeName().equalsIgnoreCase("minor")) { + result[MINOR_INDEX] = getVersionNumber(node); + } + } + } + if (result[MAJOR_INDEX] == -1 || result[MINOR_INDEX] == -1) { + throw new RuntimeException("Error parsing version numbers."); + } + return result; + } + + private int getVersionNumber(Node node) { + return Integer.parseInt(node.getAttributes().item(0).getNodeValue()); + } + + public boolean isNewMajorVersionAvailable() { + return major > metadata.getMajorVersion(); + } + + public boolean isNewMinorVersionAvailable() { + return major == metadata.getMajorVersion() + && minor > metadata.getMinorVersion(); + } + + public String getUpdateURL() { + return updateURL; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } +} diff --git a/src/main/java/amidst/gui/main/UpdatePrompt.java b/src/main/java/amidst/gui/main/UpdatePrompt.java new file mode 100644 index 000000000..6747a472b --- /dev/null +++ b/src/main/java/amidst/gui/main/UpdatePrompt.java @@ -0,0 +1,124 @@ +package amidst.gui.main; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.SAXException; + +import amidst.AmidstMetaData; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.logging.Log; +import amidst.threading.SimpleWorker; +import amidst.threading.WorkerExecutor; + +@NotThreadSafe +public class UpdatePrompt { + private final AmidstMetaData metadata; + private final MainWindow mainWindow; + private final WorkerExecutor workerExecutor; + + @CalledOnlyBy(AmidstThread.EDT) + public UpdatePrompt(AmidstMetaData metadata, MainWindow mainWindow, + WorkerExecutor workerExecutor) { + this.metadata = metadata; + this.mainWindow = mainWindow; + this.workerExecutor = workerExecutor; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void checkSilently() { + check(true); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void check() { + check(false); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void check(final boolean silent) { + workerExecutor + .invokeLater(new SimpleWorker() { + @Override + protected UpdateInformationRetriever main() + throws MalformedURLException, SAXException, + IOException, ParserConfigurationException, + RuntimeException { + return new UpdateInformationRetriever(metadata); + } + + @Override + protected void onMainFinished( + UpdateInformationRetriever retriever) { + displayResult(silent, retriever); + } + + @Override + protected void onMainFinishedWithException(Exception e) { + Log.w("unable to check for updates"); + displayError(silent, e); + } + }); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void displayError(boolean silent, Exception e) { + e.printStackTrace(); + if (!silent) { + mainWindow.displayException(e); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void displayResult(boolean silent, + UpdateInformationRetriever retriever) { + if (getUserChoice(retriever, silent)) { + try { + openURL(new URI(retriever.getUpdateURL())); + } catch (IOException | UnsupportedOperationException + | URISyntaxException e) { + displayError(silent, e); + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean getUserChoice(UpdateInformationRetriever retriever, + boolean silent) { + if (retriever.isNewMajorVersionAvailable()) { + return mainWindow.askToConfirm("Update Found", + "A new version was found. Would you like to update?"); + } else if (retriever.isNewMinorVersionAvailable()) { + return mainWindow.askToConfirm("Update Found", + "A minor revision was found. Update?"); + } else if (silent) { + return false; + } else { + mainWindow.displayMessage("Updater", "There are no new updates."); + return false; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void openURL(URI uri) throws IOException, + UnsupportedOperationException { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.BROWSE)) { + desktop.browse(uri); + } else { + throw new UnsupportedOperationException( + "Unable to open browser page."); + } + } else { + throw new UnsupportedOperationException("Unable to open browser."); + } + } +} diff --git a/src/main/java/amidst/gui/main/menu/AmidstMenu.java b/src/main/java/amidst/gui/main/menu/AmidstMenu.java new file mode 100644 index 000000000..9cfe917b6 --- /dev/null +++ b/src/main/java/amidst/gui/main/menu/AmidstMenu.java @@ -0,0 +1,47 @@ +package amidst.gui.main.menu; + +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class AmidstMenu { + private final JMenuBar menuBar; + private final JMenu worldMenu; + private final JMenuItem savePlayerLocationsMenu; + private final JMenuItem reloadPlayerLocationsMenu; + + @CalledOnlyBy(AmidstThread.EDT) + public AmidstMenu(JMenuBar menuBar, JMenu worldMenu, + JMenuItem savePlayerLocationsMenu, + JMenuItem reloadPlayerLocationsMenu) { + this.menuBar = menuBar; + this.worldMenu = worldMenu; + this.savePlayerLocationsMenu = savePlayerLocationsMenu; + this.reloadPlayerLocationsMenu = reloadPlayerLocationsMenu; + } + + @CalledOnlyBy(AmidstThread.EDT) + public JMenuBar getMenuBar() { + return menuBar; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void setWorldMenuEnabled(boolean isEnabled) { + worldMenu.setEnabled(isEnabled); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void setSavePlayerLocationsMenuEnabled(boolean isEnabled) { + savePlayerLocationsMenu.setEnabled(isEnabled); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void setReloadPlayerLocationsMenuEnabled(boolean isEnabled) { + reloadPlayerLocationsMenu.setEnabled(isEnabled); + } +} diff --git a/src/main/java/amidst/gui/main/menu/AmidstMenuBuilder.java b/src/main/java/amidst/gui/main/menu/AmidstMenuBuilder.java new file mode 100644 index 000000000..756461c0c --- /dev/null +++ b/src/main/java/amidst/gui/main/menu/AmidstMenuBuilder.java @@ -0,0 +1,401 @@ +package amidst.gui.main.menu; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; + +import javax.swing.ImageIcon; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JToggleButton; +import javax.swing.KeyStroke; + +import amidst.ResourceLoader; +import amidst.Settings; +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.Actions; +import amidst.settings.MultipleStringsSetting.SelectButtonModel; +import amidst.settings.biomecolorprofile.BiomeColorProfile; + +@NotThreadSafe +public class AmidstMenuBuilder { + private final Settings settings; + private final Actions actions; + private final JMenuBar menuBar; + private JMenu worldMenu; + private JMenuItem savePlayerLocationsMenu; + private JMenuItem reloadPlayerLocationsMenu; + + public AmidstMenuBuilder(Settings settings, Actions actions) { + this.settings = settings; + this.actions = actions; + this.menuBar = createMenuBar(); + } + + public AmidstMenu construct() { + return new AmidstMenu(menuBar, worldMenu, savePlayerLocationsMenu, + reloadPlayerLocationsMenu); + } + + private JMenuBar createMenuBar() { + JMenuBar result = new JMenuBar(); + result.add(create_File()); + worldMenu = result.add(create_World()); + result.add(create_Layers()); + result.add(create_Settings()); + result.add(create_Help()); + return result; + } + + private JMenu create_File() { + JMenu result = new JMenu("File"); + result.setMnemonic(KeyEvent.VK_F); + result.add(create_File_NewFromSeed()); + result.add(create_File_NewFromRandom()); + result.add(create_File_OpenWorldFile()); + result.addSeparator(); + result.add(create_File_SwitchProfile()); + result.add(create_File_Exit()); + return result; + } + + private JMenuItem create_File_NewFromSeed() { + JMenuItem result = new JMenuItem("New from seed"); + result.setMnemonic(KeyEvent.VK_S); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.newFromSeed(); + } + }); + return result; + } + + private JMenuItem create_File_NewFromRandom() { + JMenuItem result = new JMenuItem("New from random seed"); + result.setMnemonic(KeyEvent.VK_R); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.newFromRandom(); + } + }); + return result; + } + + private JMenuItem create_File_OpenWorldFile() { + JMenuItem result = new JMenuItem("Open world file ..."); + result.setMnemonic(KeyEvent.VK_F); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.openWorldFile(); + } + }); + return result; + } + + private JMenuItem create_File_SwitchProfile() { + JMenuItem result = new JMenuItem("Switch profile ..."); + result.setMnemonic(KeyEvent.VK_P); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.switchProfile(); + } + }); + return result; + } + + private JMenuItem create_File_Exit() { + JMenuItem result = new JMenuItem("Exit"); + result.setMnemonic(KeyEvent.VK_X); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.exit(); + } + }); + return result; + } + + private JMenu create_World() { + JMenu result = new JMenu("World"); + result.setEnabled(false); + result.setMnemonic(KeyEvent.VK_W); + result.add(create_World_GoToCoordinate()); + result.add(create_World_GoToSpawn()); + result.add(create_World_GoToStronghold()); + result.add(create_World_GoToPlayer()); + result.addSeparator(); + savePlayerLocationsMenu = result + .add(create_World_SavePlayerLocations()); + reloadPlayerLocationsMenu = result + .add(create_Players_ReloadPlayerLocations()); + result.add(create_World_HowCanIMoveAPlayer()); + result.addSeparator(); + result.add(create_World_CopySeed()); + result.add(create_World_SaveCaptureImage()); + return result; + } + + private JMenuItem create_World_GoToCoordinate() { + JMenuItem result = new JMenuItem("Go to Coordinate"); + result.setMnemonic(KeyEvent.VK_C); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, + InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.goToCoordinate(); + } + }); + return result; + } + + private JMenuItem create_World_GoToSpawn() { + JMenuItem result = new JMenuItem("Go to Spawn"); + result.setMnemonic(KeyEvent.VK_S); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, + InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.goToSpawn(); + } + }); + return result; + } + + private JMenuItem create_World_GoToStronghold() { + JMenuItem result = new JMenuItem("Go to Stronghold"); + result.setMnemonic(KeyEvent.VK_H); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, + InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.goToStronghold(); + } + }); + return result; + } + + private JMenuItem create_World_GoToPlayer() { + JMenuItem result = new JMenuItem("Go to Player"); + result.setMnemonic(KeyEvent.VK_P); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, + InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.goToPlayer(); + } + }); + return result; + } + + private JMenuItem create_World_SavePlayerLocations() { + JMenuItem result = new JMenuItem("Save player locations"); + result.setEnabled(false); + result.setMnemonic(KeyEvent.VK_V); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.savePlayerLocations(); + } + }); + return result; + } + + private JMenuItem create_Players_ReloadPlayerLocations() { + JMenuItem result = new JMenuItem("Reload player locations"); + result.setEnabled(false); + result.setMnemonic(KeyEvent.VK_R); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.reloadPlayerLocations(); + } + }); + return result; + } + + private JMenuItem create_World_HowCanIMoveAPlayer() { + JMenuItem result = new JMenuItem("How can I move a player?"); + result.setMnemonic(KeyEvent.VK_M); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.howCanIMoveAPlayer(); + } + }); + return result; + } + + private JMenuItem create_World_CopySeed() { + JMenuItem result = new JMenuItem("Copy Seed to Clipboard"); + result.setMnemonic(KeyEvent.VK_B); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.copySeedToClipboard(); + } + }); + return result; + } + + private JMenuItem create_World_SaveCaptureImage() { + JMenuItem result = new JMenuItem("Save capture image ..."); + result.setMnemonic(KeyEvent.VK_I); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.saveCaptureImage(); + } + }); + return result; + } + + private JMenuItem create_Layers() { + JMenu result = new JMenu("Layers"); + result.setMnemonic(KeyEvent.VK_L); + // @formatter:off + result.add(createJCheckBoxItem("Grid", "grid.png", KeyEvent.VK_1, settings.showGrid.getButtonModel())); + result.add(createJCheckBoxItem("Slime chunks", "slime.png", KeyEvent.VK_2, settings.showSlimeChunks.getButtonModel())); + result.add(createJCheckBoxItem("Village Icons", "village.png", KeyEvent.VK_3, settings.showVillages.getButtonModel())); + result.add(createJCheckBoxItem("Ocean Monument Icons", "ocean_monument.png", KeyEvent.VK_4, settings.showOceanMonuments.getButtonModel())); + result.add(createJCheckBoxItem("Temple/Witch Hut Icons", "desert.png", KeyEvent.VK_5, settings.showTemples.getButtonModel())); + result.add(createJCheckBoxItem("Stronghold Icons", "stronghold.png", KeyEvent.VK_6, settings.showStrongholds.getButtonModel())); + result.add(createJCheckBoxItem("Player Icons", "player.png", KeyEvent.VK_7, settings.showPlayers.getButtonModel())); + result.add(createJCheckBoxItem("Nether Fortress Icons", "nether_fortress.png", KeyEvent.VK_8, settings.showNetherFortresses.getButtonModel())); + result.add(createJCheckBoxItem("Spawn Location Icon", "spawn.png", KeyEvent.VK_9, settings.showSpawn.getButtonModel())); + // @formatter:on + return result; + } + + private JMenu create_Settings() { + JMenu result = new JMenu("Settings"); + result.setMnemonic(KeyEvent.VK_S); + result.add(create_Settings_DefaultWorldType()); + if (BiomeColorProfile.isEnabled()) { + result.add(create_Settings_BiomeColor()); + } + result.addSeparator(); + // @formatter:off + result.add(createJCheckBoxItem("Smooth Scrolling", null, KeyEvent.VK_I, settings.smoothScrolling.getButtonModel())); + result.add(createJCheckBoxItem("Restrict Maximum Zoom", null, KeyEvent.VK_Z, settings.maxZoom.getButtonModel())); + result.add(createJCheckBoxItem("Show Framerate", null, KeyEvent.VK_L, settings.showFPS.getButtonModel())); + result.add(createJCheckBoxItem("Show Scale", null, KeyEvent.VK_K, settings.showScale.getButtonModel())); + result.add(createJCheckBoxItem("Fragment Fading", null, -1, settings.fragmentFading.getButtonModel())); + result.add(createJCheckBoxItem("Show Debug Info", null, -1, settings.showDebug.getButtonModel())); + // @formatter:on + return result; + } + + private JMenu create_Settings_DefaultWorldType() { + JMenu result = new JMenu("Default world type"); + for (SelectButtonModel buttonModel : settings.worldType + .getButtonModels()) { + result.add(createJCheckBoxItem(buttonModel.getName(), null, -1, + buttonModel)); + } + return result; + } + + private JMenu create_Settings_BiomeColor() { + return new BiomeColorMenuFactory(actions).getMenu(); + } + + private JMenu create_Help() { + JMenu result = new JMenu("Help"); + result.setMnemonic(KeyEvent.VK_H); + result.add(create_Help_CheckForUpdates()); + result.add(create_Help_ViewLicenses()); + result.add(create_Help_About()); + return result; + } + + private JMenuItem create_Help_CheckForUpdates() { + JMenuItem result = new JMenuItem("Check for Updates"); + result.setMnemonic(KeyEvent.VK_U); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.checkForUpdates(); + } + }); + return result; + } + + private JMenuItem create_Help_ViewLicenses() { + JMenuItem result = new JMenuItem("View Licenses"); + result.setMnemonic(KeyEvent.VK_L); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.viewLicense(); + } + }); + return result; + } + + private JMenuItem create_Help_About() { + JMenuItem result = new JMenuItem("About"); + result.setMnemonic(KeyEvent.VK_A); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.about(); + } + }); + return result; + } + + private JCheckBoxMenuItem createJCheckBoxItem(String text, String image, + int key, JToggleButton.ToggleButtonModel buttonModel) { + JCheckBoxMenuItem result = new JCheckBoxMenuItem(text); + result.setIcon(getIcon(image)); + if (key != -1) { + result.setAccelerator(KeyStroke.getKeyStroke(key, + InputEvent.CTRL_DOWN_MASK)); + } + result.setModel(buttonModel); + return result; + } + + private ImageIcon getIcon(String image) { + if (image != null) { + BufferedImage icon = ResourceLoader + .getImage("/amidst/gui/main/icon/" + image); + if (icon != null) { + return new ImageIcon(icon); + } else { + return null; + } + } else { + return null; + } + } +} diff --git a/src/main/java/amidst/gui/main/menu/BiomeColorMenuFactory.java b/src/main/java/amidst/gui/main/menu/BiomeColorMenuFactory.java new file mode 100644 index 000000000..4400c14d8 --- /dev/null +++ b/src/main/java/amidst/gui/main/menu/BiomeColorMenuFactory.java @@ -0,0 +1,152 @@ +package amidst.gui.main.menu; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; + +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.Actions; +import amidst.logging.Log; +import amidst.settings.biomecolorprofile.BiomeColorProfile; +import amidst.settings.biomecolorprofile.BiomeColorProfileLoader; +import amidst.settings.biomecolorprofile.BiomeColorProfileVisitor; + +@NotThreadSafe +public class BiomeColorMenuFactory { + @NotThreadSafe + private static class BiomeColorProfileVisitorImpl implements + BiomeColorProfileVisitor { + private final List allCheckBoxes = new ArrayList(); + private final List menuStack = new ArrayList(); + private ActionListener firstListener; + private boolean isFirstContainer = true; + + private final Actions actions; + + private BiomeColorProfileVisitorImpl(JMenu parentMenu, Actions actions) { + this.actions = actions; + menuStack.add(parentMenu); + } + + @Override + public void enterDirectory(String name) { + if (isFirstContainer) { + isFirstContainer = false; + } else { + JMenu newMenu = new JMenu(name); + getLastMenu().add(newMenu); + menuStack.add(newMenu); + } + } + + @Override + public void visitProfile(BiomeColorProfile profile) { + JCheckBoxMenuItem checkBox = createCheckBox(profile); + allCheckBoxes.add(checkBox); + getLastMenu().add(checkBox); + } + + @Override + public void leaveDirectory() { + removeLastMenu(); + } + + private JMenu getLastMenu() { + return menuStack.get(menuStack.size() - 1); + } + + private void removeLastMenu() { + menuStack.remove(menuStack.size() - 1); + } + + private JCheckBoxMenuItem createCheckBox(BiomeColorProfile profile) { + JCheckBoxMenuItem result = new JCheckBoxMenuItem(profile.getName()); + tryCreateKeyboardShortcut(profile.getShortcut(), result); + result.addActionListener(createListener(profile, result)); + return result; + } + + private void tryCreateKeyboardShortcut(String shortcut, + JCheckBoxMenuItem checkBox) { + if (shortcut != null) { + KeyStroke accelerator = KeyStroke.getKeyStroke(shortcut); + if (accelerator != null) { + checkBox.setAccelerator(accelerator); + } else { + Log.i("Unable to create keyboard shortcut from: " + + shortcut); + } + } + } + + private ActionListener createListener(final BiomeColorProfile profile, + final JCheckBoxMenuItem selectedCheckBox) { + ActionListener result = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + for (JCheckBoxMenuItem checkBox : allCheckBoxes) { + checkBox.setSelected(false); + } + selectedCheckBox.setSelected(true); + actions.selectBiomeColorProfile(profile); + } + }; + if (firstListener == null) { + firstListener = result; + } + return result; + } + + public void selectFirstProfile() { + if (firstListener != null) { + firstListener.actionPerformed(null); + } + } + } + + private final JMenu parentMenu = new JMenu("Biome color profile"); + private final Actions actions; + private final BiomeColorProfileLoader biomeColorProfileLoader = new BiomeColorProfileLoader(); + + public BiomeColorMenuFactory(Actions actions) { + this.actions = actions; + Log.i("Checking for additional biome color profiles."); + initParentMenu(); + } + + public JMenu getMenu() { + return parentMenu; + } + + private void initParentMenu() { + parentMenu.removeAll(); + BiomeColorProfile.saveDefaultProfileIfNecessary(); + BiomeColorProfileVisitorImpl visitor = new BiomeColorProfileVisitorImpl( + parentMenu, actions); + biomeColorProfileLoader.visitProfiles(visitor); + parentMenu.add(createReloadMenuItem()); + visitor.selectFirstProfile(); + } + + private JMenuItem createReloadMenuItem() { + final JMenuItem result = new JMenuItem("Reload biome color profiles"); + result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, + InputEvent.CTRL_DOWN_MASK)); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg) { + Log.i("Reloading additional biome color profiles."); + initParentMenu(); + } + }); + return result; + } +} diff --git a/src/main/java/amidst/gui/main/menu/MovePlayerPopupMenu.java b/src/main/java/amidst/gui/main/menu/MovePlayerPopupMenu.java new file mode 100644 index 000000000..bb6539f7e --- /dev/null +++ b/src/main/java/amidst/gui/main/menu/MovePlayerPopupMenu.java @@ -0,0 +1,53 @@ +package amidst.gui.main.menu; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; + +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.Actions; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.player.MovablePlayerList; +import amidst.mojangapi.world.player.Player; + +@NotThreadSafe +public class MovePlayerPopupMenu { + private final Actions actions; + private final MovablePlayerList movablePlayerList; + private final CoordinatesInWorld targetCoordinates; + + public MovePlayerPopupMenu(Actions actions, + MovablePlayerList movablePlayerList, + CoordinatesInWorld targetCoordinates) { + this.actions = actions; + this.movablePlayerList = movablePlayerList; + this.targetCoordinates = targetCoordinates; + } + + public void show(Component component, int x, int y) { + createPlayerMenu().show(component, x, y); + } + + private JPopupMenu createPlayerMenu() { + JPopupMenu result = new JPopupMenu(); + for (Player player : movablePlayerList) { + result.add(createPlayerMenuItem(player, targetCoordinates)); + } + return result; + } + + private JMenuItem createPlayerMenuItem(final Player player, + final CoordinatesInWorld targetCoordinates) { + JMenuItem result = new JMenuItem(player.getPlayerName()); + result.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + actions.movePlayer(player, targetCoordinates); + } + }); + return result; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/BiomeSelection.java b/src/main/java/amidst/gui/main/worldsurroundings/BiomeSelection.java new file mode 100644 index 000000000..638d5105d --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/BiomeSelection.java @@ -0,0 +1,67 @@ +package amidst.gui.main.worldsurroundings; + +import java.util.concurrent.atomic.AtomicBoolean; + +import amidst.documentation.ThreadSafe; +import amidst.mojangapi.world.biome.Biome; + +@ThreadSafe +public class BiomeSelection { + private final AtomicBoolean[] selectedBiomes; + private volatile AtomicBoolean isHighlightMode = new AtomicBoolean(false); + + public BiomeSelection() { + this.selectedBiomes = createSelectedBiomes(); + } + + private AtomicBoolean[] createSelectedBiomes() { + AtomicBoolean[] result = new AtomicBoolean[Biome.getBiomesLength()]; + for (int i = 0; i < result.length; i++) { + result[i] = new AtomicBoolean(false); + } + return result; + } + + public boolean isSelected(int id) { + return !isHighlightMode.get() || selectedBiomes[id].get(); + } + + public void selectAll() { + setAll(true); + } + + public void deselectAll() { + setAll(false); + } + + public void toggle(int id) { + toggle(selectedBiomes[id]); + } + + private void setAll(boolean value) { + for (int i = 0; i < selectedBiomes.length; i++) { + selectedBiomes[i].set(value); + } + } + + public void selectOnlySpecial() { + for (int i = 0; i < selectedBiomes.length; i++) { + selectedBiomes[i].set(Biome.isSpecialBiomeIndex(i)); + } + } + + public void toggleHighlightMode() { + toggle(isHighlightMode); + } + + public boolean isHighlightMode() { + return isHighlightMode.get(); + } + + private void toggle(AtomicBoolean atomicBoolean) { + boolean value; + do { + value = atomicBoolean.get(); + } while (!atomicBoolean.compareAndSet(value, !value)); + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/Drawer.java b/src/main/java/amidst/gui/main/worldsurroundings/Drawer.java new file mode 100644 index 000000000..14c2e1162 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/Drawer.java @@ -0,0 +1,237 @@ +package amidst.gui.main.worldsurroundings; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.util.List; + +import amidst.ResourceLoader; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.FragmentGraph; +import amidst.fragment.FragmentGraphItem; +import amidst.fragment.drawer.FragmentDrawer; +import amidst.gui.main.worldsurroundings.widget.Widget; + +@NotThreadSafe +public class Drawer { + private static final BufferedImage DROP_SHADOW_BOTTOM_LEFT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_bottom_left.png"); + private static final BufferedImage DROP_SHADOW_BOTTOM_RIGHT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_bottom_right.png"); + private static final BufferedImage DROP_SHADOW_TOP_LEFT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_top_left.png"); + private static final BufferedImage DROP_SHADOW_TOP_RIGHT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_top_right.png"); + private static final BufferedImage DROP_SHADOW_BOTTOM = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_bottom.png"); + private static final BufferedImage DROP_SHADOW_TOP = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_top.png"); + private static final BufferedImage DROP_SHADOW_LEFT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_left.png"); + private static final BufferedImage DROP_SHADOW_RIGHT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/inner_right.png"); + + private final AffineTransform originalLayerMatrix = new AffineTransform(); + private final AffineTransform layerMatrix = new AffineTransform(); + + private final FragmentGraph graph; + private final FragmentGraphToScreenTranslator translator; + private final Zoom zoom; + private final Movement movement; + private final List widgets; + private final Iterable drawers; + + private Graphics2D g2d; + private int viewerWidth; + private int viewerHeight; + private Point mousePosition; + private FontMetrics widgetFontMetrics; + + private long lastTime = System.currentTimeMillis(); + private float time; + + @CalledOnlyBy(AmidstThread.EDT) + public Drawer(FragmentGraph graph, + FragmentGraphToScreenTranslator translator, Zoom zoom, + Movement movement, List widgets, + Iterable drawers) { + this.graph = graph; + this.translator = translator; + this.zoom = zoom; + this.movement = movement; + this.widgets = widgets; + this.drawers = drawers; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void drawCaptureImage(Graphics2D g2d, int viewerWidth, + int viewerHeight, Point mousePosition, FontMetrics widgetFontMetrics) { + this.g2d = g2d; + this.viewerWidth = viewerWidth; + this.viewerHeight = viewerHeight; + this.mousePosition = mousePosition; + this.widgetFontMetrics = widgetFontMetrics; + this.time = 0; + updateTranslator(); + clear(); + drawFragments(); + drawWidgets(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void draw(Graphics2D g2d, int viewerWidth, int viewerHeight, + Point mousePosition, FontMetrics widgetFontMetrics) { + this.g2d = g2d; + this.viewerWidth = viewerWidth; + this.viewerHeight = viewerHeight; + this.mousePosition = mousePosition; + this.widgetFontMetrics = widgetFontMetrics; + this.time = calculateTimeSpanSinceLastDrawInSeconds(); + updateZoom(); + updateMovement(); + updateTranslator(); + clear(); + drawFragments(); + drawBorder(); + drawWidgets(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private float calculateTimeSpanSinceLastDrawInSeconds() { + long currentTime = System.currentTimeMillis(); + float result = Math.min(Math.max(0, currentTime - lastTime), 100) / 1000.0f; + lastTime = currentTime; + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateZoom() { + zoom.update(translator); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateMovement() { + movement.update(translator, mousePosition); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateTranslator() { + translator.update(viewerWidth, viewerHeight); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void clear() { + g2d.setColor(Color.black); + g2d.fillRect(0, 0, viewerWidth, viewerHeight); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawFragments() { + // TODO: is this needed? + Graphics2D old = g2d; + g2d = (Graphics2D) g2d.create(); + doDrawFragments(); + g2d = old; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doDrawFragments() { + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + AffineTransform originalGraphicsTransform = g2d.getTransform(); + initOriginalLayerMatrix(originalGraphicsTransform); + drawLayers(); + g2d.setTransform(originalGraphicsTransform); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initOriginalLayerMatrix( + AffineTransform originalGraphicsTransform) { + double scale = zoom.getCurrentValue(); + originalLayerMatrix.setTransform(originalGraphicsTransform); + originalLayerMatrix.translate(translator.getLeftOnScreen(), + translator.getTopOnScreen()); + originalLayerMatrix.scale(scale, scale); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawLayers() { + for (FragmentDrawer drawer : drawers) { + if (drawer.isEnabled()) { + initLayerMatrix(); + for (FragmentGraphItem fragmentGraphItem : graph) { + Fragment fragment = fragmentGraphItem.getFragment(); + if (fragment.isLoaded()) { + setAlphaComposite(fragment.getAlpha()); + g2d.setTransform(layerMatrix); + drawer.draw(fragment, g2d, time); + } + updateLayerMatrix(fragmentGraphItem, + graph.getFragmentsPerRow()); + } + } + } + setAlphaComposite(1.0f); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initLayerMatrix() { + layerMatrix.setTransform(originalLayerMatrix); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateLayerMatrix(FragmentGraphItem fragmentGraphItem, + int fragmentsPerRow) { + layerMatrix.translate(Fragment.SIZE, 0); + if (fragmentGraphItem.isEndOfLine()) { + layerMatrix.translate(-Fragment.SIZE * fragmentsPerRow, + Fragment.SIZE); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBorder() { + int width10 = viewerWidth - 10; + int height10 = viewerHeight - 10; + int width20 = viewerWidth - 20; + int height20 = viewerHeight - 20; + g2d.drawImage(DROP_SHADOW_TOP_LEFT, 0, 0, null); + g2d.drawImage(DROP_SHADOW_TOP_RIGHT, width10, 0, null); + g2d.drawImage(DROP_SHADOW_BOTTOM_LEFT, 0, height10, null); + g2d.drawImage(DROP_SHADOW_BOTTOM_RIGHT, width10, height10, null); + g2d.drawImage(DROP_SHADOW_TOP, 10, 0, width20, 10, null); + g2d.drawImage(DROP_SHADOW_BOTTOM, 10, height10, width20, 10, null); + g2d.drawImage(DROP_SHADOW_LEFT, 0, 10, 10, height20, null); + g2d.drawImage(DROP_SHADOW_RIGHT, width10, 10, 10, height20, null); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawWidgets() { + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + for (Widget widget : widgets) { + widget.update(viewerWidth, viewerHeight, mousePosition, + widgetFontMetrics, time); + if (widget.isVisible()) { + setAlphaComposite(widget.getAlpha()); + widget.draw(g2d); + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void setAlphaComposite(float alpha) { + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + alpha)); + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/FragmentGraphToScreenTranslator.java b/src/main/java/amidst/gui/main/worldsurroundings/FragmentGraphToScreenTranslator.java new file mode 100644 index 000000000..fea750438 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/FragmentGraphToScreenTranslator.java @@ -0,0 +1,137 @@ +package amidst.gui.main.worldsurroundings; + +import java.awt.Point; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.FragmentGraph; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; + +@NotThreadSafe +public class FragmentGraphToScreenTranslator { + private final FragmentGraph graph; + private final Zoom zoom; + + private double leftOnScreen; + private double topOnScreen; + + private int viewerWidth; + private int viewerHeight; + + private boolean isFirstUpdate = true; + + @CalledOnlyBy(AmidstThread.EDT) + public FragmentGraphToScreenTranslator(FragmentGraph graph, Zoom zoom) { + this.graph = graph; + this.zoom = zoom; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void update(int viewerWidth, int viewerHeight) { + this.viewerWidth = viewerWidth; + this.viewerHeight = viewerHeight; + centerOnOriginIfNecessary(); + adjustNumberOfRowsAndColumns(); + } + + private void centerOnOriginIfNecessary() { + if (isFirstUpdate) { + isFirstUpdate = false; + centerOn(CoordinatesInWorld.origin()); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void adjustNumberOfRowsAndColumns() { + double fragmentSizeOnScreen = zoom.worldToScreen(Fragment.SIZE); + int desiredFragmentsPerRow = (int) (viewerWidth / fragmentSizeOnScreen + 2); + int desiredFragmentsPerColumn = (int) (viewerHeight + / fragmentSizeOnScreen + 2); + int newColumns = desiredFragmentsPerRow - graph.getFragmentsPerRow(); + int newRows = desiredFragmentsPerColumn - graph.getFragmentsPerColumn(); + int newLeft = getNewLeft(fragmentSizeOnScreen); + int newAbove = getNewAbove(fragmentSizeOnScreen); + int newRight = newColumns - newLeft; + int newBelow = newRows - newAbove; + graph.adjust(newLeft, newAbove, newRight, newBelow); + adjustTopLeftOnScreen(fragmentSizeOnScreen * -newLeft, + fragmentSizeOnScreen * -newAbove); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getNewLeft(double fragmentSizeOnScreen) { + if (leftOnScreen > 0) { + return (int) (leftOnScreen / fragmentSizeOnScreen) + 1; + } else { + return (int) (leftOnScreen / fragmentSizeOnScreen); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getNewAbove(double fragmentSizeOnScreen) { + if (topOnScreen > 0) { + return (int) (topOnScreen / fragmentSizeOnScreen) + 1; + } else { + return (int) (topOnScreen / fragmentSizeOnScreen); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void centerOn(final CoordinatesInWorld coordinates) { + graph.init(coordinates); + int xCenterOnScreen = viewerWidth >> 1; + int yCenterOnScreen = viewerHeight >> 1; + long xFragmentRelative = coordinates.getXRelativeToFragment(); + long yFragmentRelative = coordinates.getYRelativeToFragment(); + setTopLeftOnScreen( + xCenterOnScreen - zoom.worldToScreen(xFragmentRelative), + yCenterOnScreen - zoom.worldToScreen(yFragmentRelative)); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjustToMovement(int deltaX, int deltaY) { + adjustTopLeftOnScreen(deltaX, deltaY); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjustToZoom(double previous, double current, + Point mousePosition) { + double baseX = mousePosition.x - leftOnScreen; + double baseY = mousePosition.y - topOnScreen; + double deltaX = baseX - (baseX / previous) * current; + double deltaY = baseY - (baseY / previous) * current; + adjustTopLeftOnScreen(deltaX, deltaY); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void setTopLeftOnScreen(double leftOnScreen, double topOnScreen) { + this.leftOnScreen = leftOnScreen; + this.topOnScreen = topOnScreen; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void adjustTopLeftOnScreen(double deltaX, double deltaY) { + this.leftOnScreen += deltaX; + this.topOnScreen += deltaY; + } + + @CalledOnlyBy(AmidstThread.EDT) + public double getLeftOnScreen() { + return leftOnScreen; + } + + @CalledOnlyBy(AmidstThread.EDT) + public double getTopOnScreen() { + return topOnScreen; + } + + @CalledOnlyBy(AmidstThread.EDT) + public CoordinatesInWorld screenToWorld(Point pointOnScreen) { + CoordinatesInWorld corner = graph.getCorner(); + return corner.add( + (long) zoom.screenToWorld(pointOnScreen.x - leftOnScreen), + (long) zoom.screenToWorld(pointOnScreen.y - topOnScreen)); + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/Movement.java b/src/main/java/amidst/gui/main/worldsurroundings/Movement.java new file mode 100644 index 000000000..68592e8bb --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/Movement.java @@ -0,0 +1,65 @@ +package amidst.gui.main.worldsurroundings; + +import java.awt.Point; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.settings.Setting; + +@NotThreadSafe +public class Movement { + private double speedX = 0; + private double speedY = 0; + private Point lastMouse; + + private final Setting smoothScrollingSetting; + + @CalledOnlyBy(AmidstThread.EDT) + public Movement(Setting smoothScrollingSetting) { + this.smoothScrollingSetting = smoothScrollingSetting; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void update(FragmentGraphToScreenTranslator translator, + Point currentMouse) { + updateMovementSpeed(currentMouse); + adjustTranslator(translator); + throttleMovementSpeed(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateMovementSpeed(Point currentMouse) { + if (lastMouse != null) { + if (currentMouse != null) { + double dX = currentMouse.x - lastMouse.x; + double dY = currentMouse.y - lastMouse.y; + // TODO: Scale with time + speedX = dX * 0.2; + speedY = dY * 0.2; + } + lastMouse.translate((int) speedX, (int) speedY); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void adjustTranslator(FragmentGraphToScreenTranslator translator) { + translator.adjustToMovement((int) speedX, (int) speedY); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void throttleMovementSpeed() { + if (smoothScrollingSetting.get()) { + speedX *= 0.95f; + speedY *= 0.95f; + } else { + speedX = 0; + speedY = 0; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void setLastMouse(Point lastMouse) { + this.lastMouse = lastMouse; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/Viewer.java b/src/main/java/amidst/gui/main/worldsurroundings/Viewer.java new file mode 100644 index 000000000..c21a7738e --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/Viewer.java @@ -0,0 +1,94 @@ +package amidst.gui.main.worldsurroundings; + +import java.awt.Component; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.image.BufferedImage; + +import javax.swing.JComponent; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.worldsurroundings.widget.Widget; + +@NotThreadSafe +public class Viewer { + @SuppressWarnings("serial") + private static class ViewerComponent extends JComponent { + private final FontMetrics widgetFontMetrics = getFontMetrics(Widget.TEXT_FONT); + private final Drawer drawer; + + @CalledOnlyBy(AmidstThread.EDT) + public ViewerComponent(Drawer drawer) { + this.drawer = drawer; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void paint(Graphics g) { + Graphics2D g2d = (Graphics2D) g.create(); + drawer.draw(g2d, getWidth(), getHeight(), getMousePosition(), + widgetFontMetrics); + } + + @CalledOnlyBy(AmidstThread.EDT) + public BufferedImage createCaptureImage() { + int width = getWidth(); + int height = getHeight(); + Point mousePosition = getMousePosition(); + BufferedImage result = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = result.createGraphics(); + drawer.drawCaptureImage(g2d, width, height, mousePosition, + widgetFontMetrics); + g2d.dispose(); + return result; + } + } + + private final ViewerMouseListener mouseListener; + private final ViewerComponent component; + + @CalledOnlyBy(AmidstThread.EDT) + public Viewer(ViewerMouseListener mouseListener, Drawer drawer) { + this.mouseListener = mouseListener; + this.component = createComponent(drawer); + } + + @CalledOnlyBy(AmidstThread.EDT) + private ViewerComponent createComponent(Drawer drawer) { + ViewerComponent result = new ViewerComponent(drawer); + result.addMouseListener(mouseListener); + result.addMouseWheelListener(mouseListener); + result.setFocusable(true); + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + public BufferedImage createCaptureImage() { + return component.createCaptureImage(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public Point getMousePositionOrCenter() { + Point result = component.getMousePosition(); + if (result == null) { + result = new Point(component.getWidth() >> 1, + component.getHeight() >> 1); + } + return result; + } + + @CalledOnlyBy(AmidstThread.REPAINTER) + public void repaintComponent() { + component.repaint(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public Component getComponent() { + return component; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/ViewerMouseListener.java b/src/main/java/amidst/gui/main/worldsurroundings/ViewerMouseListener.java new file mode 100644 index 000000000..84d9ebd27 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/ViewerMouseListener.java @@ -0,0 +1,132 @@ +package amidst.gui.main.worldsurroundings; + +import java.awt.Component; +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.FragmentGraph; +import amidst.gui.main.Actions; +import amidst.gui.main.worldsurroundings.widget.WidgetManager; + +@NotThreadSafe +public class ViewerMouseListener implements MouseListener, MouseWheelListener { + private final WidgetManager widgetManager; + private final FragmentGraph graph; + private final FragmentGraphToScreenTranslator translator; + private final Zoom zoom; + private final Movement movement; + private final Actions actions; + + @CalledOnlyBy(AmidstThread.EDT) + public ViewerMouseListener(WidgetManager widgetManager, + FragmentGraph graph, FragmentGraphToScreenTranslator translator, + Zoom zoom, Movement movement, Actions actions) { + this.widgetManager = widgetManager; + this.graph = graph; + this.translator = translator; + this.zoom = zoom; + this.movement = movement; + this.actions = actions; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + Point mousePosition = e.getPoint(); + int notches = e.getWheelRotation(); + if (!widgetManager.mouseWheelMoved(mousePosition, notches)) { + doMouseWheelMoved(mousePosition, notches); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void mouseClicked(MouseEvent e) { + Point mousePosition = e.getPoint(); + if (isRightClick(e)) { + // noop + } else if (!widgetManager.mouseClicked(mousePosition)) { + doMouseClicked(mousePosition); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void mousePressed(MouseEvent e) { + Point mousePosition = e.getPoint(); + if (isPopup(e)) { + showPopupMenu(mousePosition, e.getComponent(), e.getX(), e.getY()); + } else if (isRightClick(e)) { + // noop + } else if (!widgetManager.mousePressed(mousePosition)) { + doMousePressed(mousePosition); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void mouseReleased(MouseEvent e) { + Point mousePosition = e.getPoint(); + if (isPopup(e)) { + showPopupMenu(mousePosition, e.getComponent(), e.getX(), e.getY()); + } else if (!widgetManager.mouseReleased()) { + doMouseReleased(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void mouseEntered(MouseEvent e) { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void mouseExited(MouseEvent e) { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isPopup(MouseEvent e) { + return e.isPopupTrigger(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isRightClick(MouseEvent e) { + return e.isMetaDown(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doMouseWheelMoved(Point mousePosition, int notches) { + actions.adjustZoom(mousePosition, notches); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doMouseClicked(Point mousePosition) { + actions.selectWorldIcon(graph.getClosestWorldIcon( + translator.screenToWorld(mousePosition), zoom.screenToWorld(50))); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doMousePressed(Point mousePosition) { + movement.setLastMouse(mousePosition); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doMouseReleased() { + movement.setLastMouse(null); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void showPopupMenu(Point mousePosition, Component component, int x, + int y) { + actions.showPlayerPopupMenu(translator.screenToWorld(mousePosition), + component, x, y); + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/WorldIconSelection.java b/src/main/java/amidst/gui/main/worldsurroundings/WorldIconSelection.java new file mode 100644 index 000000000..7554dd8ef --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/WorldIconSelection.java @@ -0,0 +1,29 @@ +package amidst.gui.main.worldsurroundings; + +import amidst.documentation.ThreadSafe; +import amidst.mojangapi.world.icon.WorldIcon; + +@ThreadSafe +public class WorldIconSelection { + private volatile WorldIcon selection; + + public WorldIcon get() { + return selection; + } + + public void select(WorldIcon selection) { + this.selection = selection; + } + + public void clear() { + this.selection = null; + } + + public boolean isSelected(WorldIcon worldIcon) { + return selection == worldIcon; + } + + public boolean hasSelection() { + return selection != null; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/WorldSurroundings.java b/src/main/java/amidst/gui/main/worldsurroundings/WorldSurroundings.java new file mode 100644 index 000000000..2b20de075 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/WorldSurroundings.java @@ -0,0 +1,165 @@ +package amidst.gui.main.worldsurroundings; + +import java.awt.Component; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.FragmentGraph; +import amidst.fragment.layer.LayerReloader; +import amidst.mojangapi.world.World; +import amidst.mojangapi.world.WorldSeed; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.icon.WorldIcon; +import amidst.mojangapi.world.player.MovablePlayerList; +import amidst.threading.WorkerExecutor; + +/** + * This class works as wrapper around a world instance. It holds everything that + * is needed to display the world on the screen. This allows us to easily + * exchange the currently displayed world. + */ +@NotThreadSafe +public class WorldSurroundings { + private final World world; + private final FragmentGraph graph; + private final FragmentGraphToScreenTranslator translator; + private final Zoom zoom; + private final Viewer viewer; + private final LayerReloader layerReloader; + private final WorldIconSelection worldIconSelection; + private final Runnable onRepainterTick; + private final Runnable onFragmentLoaderTick; + private final Runnable onPlayerFinishedLoading; + + @CalledOnlyBy(AmidstThread.EDT) + public WorldSurroundings(World world, FragmentGraph graph, + FragmentGraphToScreenTranslator translator, Zoom zoom, + Viewer viewer, LayerReloader layerReloader, + WorldIconSelection worldIconSelection, Runnable onRepainterTick, + Runnable onFragmentLoaderTick, Runnable onPlayerFinishedLoading) { + this.world = world; + this.graph = graph; + this.translator = translator; + this.zoom = zoom; + this.viewer = viewer; + this.layerReloader = layerReloader; + this.worldIconSelection = worldIconSelection; + this.onRepainterTick = onRepainterTick; + this.onFragmentLoaderTick = onFragmentLoaderTick; + this.onPlayerFinishedLoading = onPlayerFinishedLoading; + } + + @CalledOnlyBy(AmidstThread.EDT) + public Component getComponent() { + return viewer.getComponent(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void reloadBiomeLayer() { + layerReloader.reloadBiomeLayer(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void reloadPlayerLayer() { + layerReloader.reloadPlayerLayer(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void dispose() { + graph.dispose(); + zoom.skipFading(); + zoom.reset(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public Runnable getOnRepainterTick() { + return onRepainterTick; + } + + @CalledOnlyBy(AmidstThread.EDT) + public Runnable getOnFragmentLoaderTick() { + return onFragmentLoaderTick; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void centerOn(CoordinatesInWorld coordinates) { + translator.centerOn(coordinates); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void centerOn(WorldIcon worldIcon) { + translator.centerOn(worldIcon.getCoordinates()); + worldIconSelection.select(worldIcon); + } + + @CalledOnlyBy(AmidstThread.EDT) + public BufferedImage createCaptureImage() { + return viewer.createCaptureImage(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjustZoom(int notches) { + zoom.adjustZoom(viewer.getMousePositionOrCenter(), notches); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjustZoom(Point mousePosition, int notches) { + zoom.adjustZoom(mousePosition, notches); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void selectWorldIcon(WorldIcon worldIcon) { + worldIconSelection.select(worldIcon); + } + + @CalledOnlyBy(AmidstThread.EDT) + public WorldSeed getWorldSeed() { + return world.getWorldSeed(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public WorldIcon getSpawnWorldIcon() { + return world.getSpawnWorldIcon(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public List getStrongholdWorldIcons() { + return world.getStrongholdWorldIcons(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public List getPlayerWorldIcons() { + return world.getPlayerWorldIcons(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public MovablePlayerList getMovablePlayerList() { + return world.getMovablePlayerList(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean canLoadPlayerLocations() { + return world.getMovablePlayerList().canLoad(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void loadPlayers(WorkerExecutor workerExecutor) { + worldIconSelection.clear(); + world.getMovablePlayerList().load(workerExecutor, + onPlayerFinishedLoading); + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean canSavePlayerLocations() { + return world.getMovablePlayerList().canSave(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void savePlayerLocations() { + world.getMovablePlayerList().save(); + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/WorldSurroundingsBuilder.java b/src/main/java/amidst/gui/main/worldsurroundings/WorldSurroundingsBuilder.java new file mode 100644 index 000000000..bdf4ecafa --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/WorldSurroundingsBuilder.java @@ -0,0 +1,107 @@ +package amidst.gui.main.worldsurroundings; + +import java.util.List; + +import amidst.Settings; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.FragmentGraph; +import amidst.fragment.FragmentManager; +import amidst.fragment.FragmentQueueProcessor; +import amidst.fragment.drawer.FragmentDrawer; +import amidst.fragment.layer.LayerBuilder; +import amidst.fragment.layer.LayerDeclaration; +import amidst.fragment.layer.LayerLoader; +import amidst.fragment.layer.LayerReloader; +import amidst.gui.main.Actions; +import amidst.gui.main.worldsurroundings.widget.Widget; +import amidst.gui.main.worldsurroundings.widget.WidgetBuilder; +import amidst.gui.main.worldsurroundings.widget.WidgetManager; +import amidst.mojangapi.world.World; + +@NotThreadSafe +public class WorldSurroundingsBuilder { + private final Zoom zoom; + private final BiomeSelection biomeSelection = new BiomeSelection(); + + private final Settings settings; + private final LayerBuilder layerBuilder; + private final FragmentManager fragmentManager; + + @CalledOnlyBy(AmidstThread.EDT) + public WorldSurroundingsBuilder(Settings settings, LayerBuilder layerBuilder) { + this.settings = settings; + this.zoom = new Zoom(settings.maxZoom); + this.layerBuilder = layerBuilder; + this.fragmentManager = new FragmentManager( + layerBuilder.getConstructors(), + layerBuilder.getNumberOfLayers()); + } + + @CalledOnlyBy(AmidstThread.EDT) + public WorldSurroundings create(World world, Actions actions) { + Movement movement = new Movement(settings.smoothScrolling); + WorldIconSelection worldIconSelection = new WorldIconSelection(); + List declarations = layerBuilder.getDeclarations(); + FragmentGraph graph = new FragmentGraph(declarations, fragmentManager); + FragmentGraphToScreenTranslator translator = new FragmentGraphToScreenTranslator( + graph, zoom); + LayerLoader layerLoader = layerBuilder.createLayerLoader(world, + biomeSelection, settings); + FragmentQueueProcessor fragmentQueueProcessor = fragmentManager + .createLayerLoader(layerLoader); + Iterable drawers = layerBuilder.createDrawers(zoom, + worldIconSelection); + LayerReloader layerReloader = layerBuilder.createLayerReloader(world, + fragmentQueueProcessor); + WidgetBuilder widgetBuilder = new WidgetBuilder(world, graph, + translator, zoom, biomeSelection, worldIconSelection, + layerReloader, fragmentManager, settings); + List widgets = widgetBuilder.create(); + Drawer drawer = new Drawer(graph, translator, zoom, movement, widgets, + drawers); + Viewer viewer = new Viewer(new ViewerMouseListener(new WidgetManager( + widgets), graph, translator, zoom, movement, actions), drawer); + return new WorldSurroundings(world, graph, translator, zoom, viewer, + layerReloader, worldIconSelection, + createOnRepainterTick(viewer), + createOnFragmentLoaderTick(fragmentQueueProcessor), + createOnPlayerFinishedLoading(layerReloader)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private Runnable createOnRepainterTick(final Viewer viewer) { + return new Runnable() { + @CalledOnlyBy(AmidstThread.REPAINTER) + @Override + public void run() { + viewer.repaintComponent(); + } + }; + } + + @CalledOnlyBy(AmidstThread.EDT) + private Runnable createOnFragmentLoaderTick( + final FragmentQueueProcessor fragmentQueueProcessor) { + return new Runnable() { + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void run() { + fragmentQueueProcessor.processQueues(); + } + }; + } + + @CalledOnlyBy(AmidstThread.EDT) + private Runnable createOnPlayerFinishedLoading( + final LayerReloader layerReloader) { + return new Runnable() { + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void run() { + layerReloader.reloadPlayerLayer(); + } + }; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/Zoom.java b/src/main/java/amidst/gui/main/worldsurroundings/Zoom.java new file mode 100644 index 000000000..c67258ed5 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/Zoom.java @@ -0,0 +1,92 @@ +package amidst.gui.main.worldsurroundings; + +import java.awt.Point; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.settings.Setting; + +@NotThreadSafe +public class Zoom { + private int remainingTicks = 0; + private int level = 0; + private double target = 0.25f; + private double current = 0.25f; + + private Point mousePosition = new Point(); + + private final Setting maxZoomSetting; + + @CalledOnlyBy(AmidstThread.EDT) + public Zoom(Setting maxZoomSetting) { + this.maxZoomSetting = maxZoomSetting; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void update(FragmentGraphToScreenTranslator translator) { + remainingTicks--; + if (remainingTicks >= 0) { + double previous = updateCurrent(); + translator.adjustToZoom(previous, current, mousePosition); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private double updateCurrent() { + double previous = current; + current = (target + current) * 0.5; + return previous; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void adjustZoom(Point mousePosition, int notches) { + this.mousePosition = mousePosition; + if (notches > 0) { + if (level < getMaxZoomLevel()) { + target /= 1.1; + level++; + remainingTicks = 100; + } + } else if (level > -20) { + target *= 1.1; + level--; + remainingTicks = 100; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getMaxZoomLevel() { + if (maxZoomSetting.get()) { + return 10; + } else { + return 10000; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public double getCurrentValue() { + return current; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void skipFading() { + remainingTicks = 0; + current = target; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void reset() { + mousePosition = new Point(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public double screenToWorld(double coordinate) { + return coordinate / current; + } + + @CalledOnlyBy(AmidstThread.EDT) + public double worldToScreen(double coordinate) { + return coordinate * current; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/BiomeToggleWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/BiomeToggleWidget.java new file mode 100644 index 000000000..2034d425e --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/BiomeToggleWidget.java @@ -0,0 +1,37 @@ +package amidst.gui.main.worldsurroundings.widget; + +import amidst.ResourceLoader; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.layer.LayerReloader; +import amidst.gui.main.worldsurroundings.BiomeSelection; + +@NotThreadSafe +public class BiomeToggleWidget extends ImmutableIconWidget { + private final BiomeSelection biomeSelection; + private final LayerReloader layerReloader; + + @CalledOnlyBy(AmidstThread.EDT) + public BiomeToggleWidget(CornerAnchorPoint anchor, + BiomeSelection biomeSelection, LayerReloader layerReloader) { + super(anchor, ResourceLoader + .getImage("/amidst/gui/main/highlighter.png")); + this.biomeSelection = biomeSelection; + this.layerReloader = layerReloader; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public boolean onMousePressed(int x, int y) { + biomeSelection.toggleHighlightMode(); + layerReloader.reloadBiomeLayer(); + return true; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean onVisibilityCheck() { + return true; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/BiomeWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/BiomeWidget.java new file mode 100644 index 000000000..1dbdbd899 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/BiomeWidget.java @@ -0,0 +1,376 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.layer.LayerReloader; +import amidst.gui.main.worldsurroundings.BiomeSelection; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.biome.BiomeColor; +import amidst.settings.biomecolorprofile.BiomeColorProfileSelection; + +@NotThreadSafe +public class BiomeWidget extends Widget { + // @formatter:off + private static final Color INNER_BOX_BG_COLOR = new Color(0.3f, 0.3f, 0.3f, 0.3f); + private static final Color BIOME_BG_COLOR_1 = new Color(0.8f, 0.8f, 0.8f, 0.2f); + private static final Color BIOME_BG_COLOR_2 = new Color(0.6f, 0.6f, 0.6f, 0.2f); + private static final Color BIOME_LIT_BG_COLOR_1 = new Color(0.8f, 0.8f, 1.0f, 0.7f); + private static final Color BIOME_LIT_BG_COLOR_2 = new Color(0.6f, 0.6f, 0.8f, 0.7f); + private static final Color INNER_BOX_BORDER_COLOR = new Color(1.0f, 1.0f, 1.0f, 1.0f); + private static final Color SCROLLBAR_COLOR = new Color(0.6f, 0.6f, 0.6f, 0.8f); + private static final Color SCROLLBAR_LIT_COLOR = new Color(0.6f, 0.6f, 0.8f, 0.8f); + private static final Color SELECT_BUTTON_COLOR = new Color(0.6f, 0.6f, 0.8f, 1.0f); + // @formatter:on + + private final BiomeSelection biomeSelection; + private final LayerReloader layerReloader; + private final BiomeColorProfileSelection biomeColorProfileSelection; + + private List biomes = new ArrayList(); + private int maxNameWidth = 0; + private int biomeListHeight; + private boolean isInitialized = false; + + private Rectangle innerBox = new Rectangle(0, 0, 1, 1); + + private int biomeListYOffset = 0; + private boolean scrollbarVisible = false; + private boolean scrollbarGrabbed = false; + private int scrollbarHeight = 0; + private int scrollbarWidth = 10; + private int scrollbarY = 0; + private int mouseYOnGrab = 0; + private int scrollbarYOnGrab; + + @CalledOnlyBy(AmidstThread.EDT) + public BiomeWidget(CornerAnchorPoint anchor, BiomeSelection biomeSelection, + LayerReloader layerReloader, + BiomeColorProfileSelection biomeColorProfileSelection) { + super(anchor); + this.biomeSelection = biomeSelection; + this.layerReloader = layerReloader; + this.biomeColorProfileSelection = biomeColorProfileSelection; + setWidth(250); + setHeight(400); + setY(100); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doUpdate(FontMetrics fontMetrics, float time) { + initializeIfNecessary(fontMetrics); + updateX(); + updateHeight(); + updateInnerBoxPositionAndSize(); + updateBiomeListYOffset(); + updateScrollbarVisibility(); + if (scrollbarVisible) { + updateInnerBoxWidth(); + updateScrollbarParameter(getMousePosition()); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initializeIfNecessary(FontMetrics fontMetrics) { + if (!isInitialized) { + isInitialized = true; + for (Biome biome : Biome.allBiomes()) { + biomes.add(biome); + int width = fontMetrics.stringWidth(biome.getName()); + maxNameWidth = Math.max(width, maxNameWidth); + } + biomeListHeight = biomes.size() * 16; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateX() { + setX(getViewerWidth() - getWidth()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateHeight() { + setHeight(Math.max(200, getViewerHeight() - 200)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateInnerBoxPositionAndSize() { + innerBox.x = getX() + 8; + innerBox.y = getY() + 30; + innerBox.width = getWidth() - 16; + innerBox.height = getHeight() - 58; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateBiomeListYOffset() { + biomeListYOffset = Math.min(0, + Math.max(-biomeListHeight + innerBox.height, biomeListYOffset)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateScrollbarVisibility() { + if (biomeListHeight > innerBox.height) { + scrollbarVisible = true; + } else { + scrollbarVisible = false; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateInnerBoxWidth() { + innerBox.width -= scrollbarWidth; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateScrollbarParameter(Point mousePosition) { + float boxHeight = innerBox.height; + float listHeight = biomeListHeight; + if (scrollbarGrabbed) { + if (mousePosition != null) { + biomeListYOffset = (int) ((listHeight / boxHeight) * (-scrollbarYOnGrab - (mousePosition.y - mouseYOnGrab))); + updateBiomeListYOffset(); + } else { + scrollbarGrabbed = false; + } + } + scrollbarY = (int) (((float) -biomeListYOffset / listHeight) * boxHeight); + scrollbarHeight = (int) (Math + .ceil(boxHeight * (boxHeight / listHeight))); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doDraw(Graphics2D g2d) { + drawTextHighlightBiomes(g2d); + drawInnerBoxBackground(g2d); + drawInnerBoxBorder(g2d); + setClipToInnerBox(g2d); + for (int i = 0; i < biomes.size(); i++) { + Biome biome = biomes.get(i); + drawBiomeBackgroundColor(g2d, i, getBiomeBackgroudColor(i, biome)); + drawBiomeColor(g2d, i, getBiomeColorOrUnknown(biome)); + drawBiomeName(g2d, i, biome); + } + clearClip(g2d); + if (scrollbarVisible) { + drawScrollbar(g2d); + } + drawTextSelect(g2d); + drawSpecialButtons(g2d); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawTextHighlightBiomes(Graphics2D g2d) { + g2d.drawString("Highlight Biomes", getX() + 10, getY() + 20); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawInnerBoxBackground(Graphics2D g2d) { + g2d.setColor(INNER_BOX_BG_COLOR); + g2d.fillRect(innerBox.x, innerBox.y, innerBox.width, innerBox.height); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawInnerBoxBorder(Graphics2D g2d) { + g2d.setColor(INNER_BOX_BORDER_COLOR); + g2d.drawRect(innerBox.x - 1, innerBox.y - 1, innerBox.width + 1 + + (scrollbarVisible ? scrollbarWidth : 0), innerBox.height + 1); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void setClipToInnerBox(Graphics2D g2d) { + g2d.setClip(innerBox); + } + + @CalledOnlyBy(AmidstThread.EDT) + private Color getBiomeBackgroudColor(int i, Biome biome) { + if (biomeSelection.isSelected(biome.getIndex())) { + if (i % 2 == 1) { + return BIOME_LIT_BG_COLOR_1; + } else { + return BIOME_LIT_BG_COLOR_2; + } + } else { + if (i % 2 == 1) { + return BIOME_BG_COLOR_1; + } else { + return BIOME_BG_COLOR_2; + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBiomeBackgroundColor(Graphics2D g2d, int i, Color color) { + g2d.setColor(color); + g2d.fillRect(innerBox.x, innerBox.y + i * 16 + biomeListYOffset, + innerBox.width, 16); + } + + @CalledOnlyBy(AmidstThread.EDT) + private BiomeColor getBiomeColorOrUnknown(Biome biome) { + return biomeColorProfileSelection.getBiomeColorOrUnknown(biome + .getIndex()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBiomeColor(Graphics2D g2d, int i, BiomeColor biomeColor) { + g2d.setColor(biomeColor.getColor()); + g2d.fillRect(innerBox.x, innerBox.y + i * 16 + biomeListYOffset, 20, 16); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBiomeName(Graphics2D g2d, int i, Biome biome) { + g2d.setColor(Color.white); + g2d.drawString(biome.getName(), innerBox.x + 25, innerBox.y + 13 + i + * 16 + biomeListYOffset); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void clearClip(Graphics2D g2d) { + g2d.setClip(null); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawScrollbar(Graphics2D g2d) { + g2d.setColor(scrollbarGrabbed ? SCROLLBAR_LIT_COLOR : SCROLLBAR_COLOR); + g2d.fillRect(innerBox.x + innerBox.width, innerBox.y + scrollbarY, + scrollbarWidth, scrollbarHeight); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawTextSelect(Graphics2D g2d) { + g2d.setColor(Color.white); + g2d.drawString("Select:", getX() + 8, getY() + getHeight() - 10); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawSpecialButtons(Graphics2D g2d) { + g2d.setColor(SELECT_BUTTON_COLOR); + g2d.drawString("All Special None", getX() + 120, getY() + getHeight() + - 10); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public boolean onMouseWheelMoved(int mouseX, int mouseY, int notches) { + if (!isInitialized) { + return false; + } + if (isInBoundsOfInnerBox(mouseX, mouseY)) { + biomeListYOffset = Math.min(0, Math.max(-biomeListHeight + + innerBox.height, biomeListYOffset - notches * 35)); + } + return true; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void onMouseReleased() { + scrollbarGrabbed = false; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public boolean onMousePressed(int mouseX, int mouseY) { + if (!isInitialized) { + return false; + } + updateScrollbarParameters(mouseX, mouseY); + if (processClick(mouseX, mouseY)) { + layerReloader.reloadBiomeLayer(); + } + return true; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateScrollbarParameters(int mouseX, int mouseY) { + if (scrollbarVisible) { + if (isInBoundsOfScrollbar(mouseX, mouseY)) { + mouseYOnGrab = mouseY + getY(); + scrollbarYOnGrab = scrollbarY; + scrollbarGrabbed = true; + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean processClick(int mouseX, int mouseY) { + if (isInBoundsOfInnerBox(mouseX, mouseY)) { + int id = (mouseY - (innerBox.y - getY()) - biomeListYOffset) / 16; + if (id < biomes.size()) { + int index = biomes.get(id).getIndex(); + biomeSelection.toggle(index); + return true; + } + } else if (isButton(mouseY)) { + if (isSelectAllButton(mouseX)) { + biomeSelection.selectAll(); + return true; + } else if (isSelectSpecialBiomesButton(mouseX)) { + biomeSelection.selectOnlySpecial(); + return true; + } else if (isDeselectAllButton(mouseX)) { + biomeSelection.deselectAll(); + return true; + } + } + return false; + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isInBoundsOfInnerBox(int mouseX, int mouseY) { + int offsetX = translateXToWidgetCoordinates(innerBox.x); + int offsetY = translateYToWidgetCoordinates(innerBox.y); + int width = innerBox.width; + int height = innerBox.height; + return Widget.isInBounds(mouseX, mouseY, offsetX, offsetY, width, + height); + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isInBoundsOfScrollbar(int mouseX, int mouseY) { + int offsetX = translateXToWidgetCoordinates(innerBox.x + innerBox.width); + int offsetY = translateYToWidgetCoordinates(innerBox.y + scrollbarY); + int width = scrollbarWidth; + int height = scrollbarHeight; + return Widget.isInBounds(mouseX, mouseY, offsetX, offsetY, width, + height); + } + + // TODO: These values are temporarily hard coded for the sake of a fast + // release + @CalledOnlyBy(AmidstThread.EDT) + private boolean isButton(int mouseY) { + return mouseY > getHeight() - 25 && mouseY < getHeight() - 9; + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isSelectAllButton(int mouseX) { + return mouseX > 117 && mouseX < 139; + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isSelectSpecialBiomesButton(int mouseX) { + return mouseX > 143 && mouseX < 197; + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isDeselectAllButton(int mouseX) { + return mouseX > 203 && mouseX < 242; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public boolean onVisibilityCheck() { + return biomeSelection.isHighlightMode() && getHeight() > 200; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/CursorInformationWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/CursorInformationWidget.java new file mode 100644 index 000000000..338b8552c --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/CursorInformationWidget.java @@ -0,0 +1,64 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.Point; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.Fragment; +import amidst.fragment.FragmentGraph; +import amidst.gui.main.worldsurroundings.FragmentGraphToScreenTranslator; +import amidst.logging.Log; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.biome.UnknownBiomeIndexException; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.coordinates.Resolution; + +@NotThreadSafe +public class CursorInformationWidget extends TextWidget { + private static final String UNKNOWN_BIOME_NAME = "Unknown"; + + private final FragmentGraph graph; + private final FragmentGraphToScreenTranslator translator; + + @CalledOnlyBy(AmidstThread.EDT) + public CursorInformationWidget(CornerAnchorPoint anchor, + FragmentGraph graph, FragmentGraphToScreenTranslator translator) { + super(anchor); + this.graph = graph; + this.translator = translator; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected String updateText() { + Point mousePosition = getMousePosition(); + if (mousePosition != null) { + CoordinatesInWorld coordinates = translator + .screenToWorld(mousePosition); + String biomeName = getBiomeNameAt(coordinates); + return biomeName + " " + coordinates.toString(); + } else { + return null; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private String getBiomeNameAt(CoordinatesInWorld coordinates) { + Fragment fragment = graph.getFragmentAt(coordinates); + if (fragment != null && fragment.isLoaded()) { + long x = coordinates.getXRelativeToFragmentAs(Resolution.QUARTER); + long y = coordinates.getYRelativeToFragmentAs(Resolution.QUARTER); + short biome = fragment.getBiomeDataAt((int) x, (int) y); + try { + return Biome.getByIndex(biome).getName(); + } catch (UnknownBiomeIndexException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return UNKNOWN_BIOME_NAME; + } + } else { + return UNKNOWN_BIOME_NAME; + } + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/DebugWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/DebugWidget.java new file mode 100644 index 000000000..820faa2e3 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/DebugWidget.java @@ -0,0 +1,56 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.util.Arrays; +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.FragmentGraph; +import amidst.fragment.FragmentManager; +import amidst.settings.Setting; + +@NotThreadSafe +public class DebugWidget extends MultilineTextWidget { + private final FragmentGraph graph; + private final FragmentManager fragmentManager; + private final Setting isVisibleSetting; + + @CalledOnlyBy(AmidstThread.EDT) + public DebugWidget(CornerAnchorPoint anchor, FragmentGraph graph, + FragmentManager fragmentManager, Setting isVisibleSetting) { + super(anchor); + this.graph = graph; + this.fragmentManager = fragmentManager; + this.isVisibleSetting = isVisibleSetting; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected List getTextLines() { + if (isVisibleSetting.get()) { + int columns = graph.getFragmentsPerRow(); + int rows = graph.getFragmentsPerColumn(); + // @formatter:off + return Arrays.asList( + "Fragment Manager:", + "Cache Size: " + fragmentManager.getCacheSize(), + "Available Queue Size: " + fragmentManager.getAvailableQueueSize(), + "Loading Queue Size: " + fragmentManager.getLoadingQueueSize(), + "Recycle Queue Size: " + fragmentManager.getRecycleQueueSize(), + "", + "Viewer:", + "Size: " + columns + "x" + rows + " [" + (columns * rows) + "]" + ); + // @formatter:on + } else { + return null; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public boolean onMousePressed(int x, int y) { + return false; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/FpsWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/FpsWidget.java new file mode 100644 index 000000000..301a92942 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/FpsWidget.java @@ -0,0 +1,31 @@ +package amidst.gui.main.worldsurroundings.widget; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.settings.Setting; + +@NotThreadSafe +public class FpsWidget extends TextWidget { + private final FramerateTimer fpsTimer; + private final Setting isVisibleSetting; + + @CalledOnlyBy(AmidstThread.EDT) + public FpsWidget(CornerAnchorPoint anchor, FramerateTimer fpsTimer, + Setting isVisibleSetting) { + super(anchor); + this.fpsTimer = fpsTimer; + this.isVisibleSetting = isVisibleSetting; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected String updateText() { + fpsTimer.tick(); + if (isVisibleSetting.get()) { + return "FPS: " + String.format("%.1f", fpsTimer.getCurrentFPS()); + } else { + return null; + } + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/FramerateTimer.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/FramerateTimer.java new file mode 100644 index 000000000..54151e9ff --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/FramerateTimer.java @@ -0,0 +1,50 @@ +package amidst.gui.main.worldsurroundings.widget; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class FramerateTimer { + private int tickCounter; + private long lastTime; + private long msPerUpdate; + + private float currentFPS = 0.0f; + + @CalledOnlyBy(AmidstThread.EDT) + public FramerateTimer(int updatesPerSecond) { + msPerUpdate = (long) (1000f * (1f / updatesPerSecond)); + reset(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void reset() { + tickCounter = 0; + lastTime = System.currentTimeMillis(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void tick() { + tickCounter++; + long currentTime = System.currentTimeMillis(); + if (currentTime - lastTime > msPerUpdate) { + currentFPS = calculateCurrentFPS(currentTime); + tickCounter = 0; + lastTime = currentTime; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private float calculateCurrentFPS(long currentTime) { + float timeDifference = currentTime - lastTime; + timeDifference /= 1000f; + timeDifference = tickCounter / timeDifference; + return timeDifference; + } + + @CalledOnlyBy(AmidstThread.EDT) + public float getCurrentFPS() { + return currentFPS; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/IconTextWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/IconTextWidget.java new file mode 100644 index 000000000..1bb8b9652 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/IconTextWidget.java @@ -0,0 +1,74 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public abstract class IconTextWidget extends Widget { + private static final int ICON_HEIGHT = 25; + + private boolean updated; + private BufferedImage icon = null; + private int iconWidth; + private String text = ""; + private boolean isVisible = false; + + @CalledOnlyBy(AmidstThread.EDT) + protected IconTextWidget(CornerAnchorPoint anchor) { + super(anchor); + setWidth(20); + setHeight(35); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doUpdate(FontMetrics fontMetrics, float time) { + updated = false; + BufferedImage newIcon = updateIcon(); + if (newIcon != null && newIcon != icon) { + icon = newIcon; + iconWidth = (int) (((double) ICON_HEIGHT) * icon.getWidth() / icon + .getHeight()); + updated = true; + } + String newText = updateText(); + if (newText != null && !newText.equals(text)) { + text = newText; + updated = true; + } + if (updated) { + setWidth(getTextOffset() + fontMetrics.stringWidth(text) + 10); + } + isVisible = newIcon != null && newText != null; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doDraw(Graphics2D g2d) { + g2d.drawImage(icon, getX() + 5, getY() + 5, iconWidth, ICON_HEIGHT, + null); + g2d.drawString(text, getX() + getTextOffset(), getY() + 23); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getTextOffset() { + return 5 + iconWidth + 5; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean onVisibilityCheck() { + return isVisible; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract BufferedImage updateIcon(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract String updateText(); +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/ImmutableIconWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/ImmutableIconWidget.java new file mode 100644 index 000000000..04eec4000 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/ImmutableIconWidget.java @@ -0,0 +1,33 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; + +public abstract class ImmutableIconWidget extends Widget { + private final BufferedImage icon; + + @CalledOnlyBy(AmidstThread.EDT) + protected ImmutableIconWidget(CornerAnchorPoint anchor, BufferedImage icon) { + super(anchor); + this.icon = icon; + setWidth(icon.getWidth()); + setHeight(icon.getHeight()); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doUpdate(FontMetrics fontMetrics, float time) { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doDraw(Graphics2D g2d) { + g2d.drawImage(icon, getX(), getY(), icon.getWidth(), icon.getHeight(), + null); + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/ImmutableTextWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/ImmutableTextWidget.java new file mode 100644 index 000000000..5c891d214 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/ImmutableTextWidget.java @@ -0,0 +1,22 @@ +package amidst.gui.main.worldsurroundings.widget; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class ImmutableTextWidget extends TextWidget { + private final String text; + + @CalledOnlyBy(AmidstThread.EDT) + public ImmutableTextWidget(CornerAnchorPoint anchor, String text) { + super(anchor); + this.text = text; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected String updateText() { + return text; + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/MultilineTextWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/MultilineTextWidget.java new file mode 100644 index 000000000..984da0bbd --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/MultilineTextWidget.java @@ -0,0 +1,73 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public abstract class MultilineTextWidget extends Widget { + private static final int LINE_HEIGHT = 20; + private static final int PADDING = 10; + + private List textLines; + private boolean isVisible; + private int ascent; + + @CalledOnlyBy(AmidstThread.EDT) + protected MultilineTextWidget(CornerAnchorPoint anchor) { + super(anchor); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doUpdate(FontMetrics fontMetrics, float time) { + List newTextLines = getTextLines(); + if (newTextLines != null && newTextLines != textLines) { + textLines = newTextLines; + ascent = fontMetrics.getAscent(); + setWidth(getNewWidth(fontMetrics)); + setHeight(getNewHeight()); + } + isVisible = newTextLines != null; + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getNewWidth(FontMetrics fontMetrics) { + int result = 0; + for (String line : textLines) { + int textWidth = fontMetrics.stringWidth(line); + if (result < textWidth) { + result = textWidth; + } + } + return result + 2 * PADDING; + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getNewHeight() { + return textLines.size() * LINE_HEIGHT + 2 * PADDING; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doDraw(Graphics2D g2d) { + for (int i = 0; i < textLines.size(); i++) { + int x = getX() + PADDING; + int y = getY() + PADDING + ascent + i * LINE_HEIGHT; + g2d.drawString(textLines.get(i), x, y); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean onVisibilityCheck() { + return isVisible; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract List getTextLines(); +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/ScaleWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/ScaleWidget.java new file mode 100644 index 000000000..2d8b1931b --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/ScaleWidget.java @@ -0,0 +1,114 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics2D; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.worldsurroundings.Zoom; +import amidst.settings.Setting; + +@NotThreadSafe +public class ScaleWidget extends Widget { + public static final int MAX_SCALE_LENGTH_ON_SCREEN = 200; + public static final int MARGIN = 8; + + private final Zoom zoom; + private final Setting isVisibleSetting; + + private int scaleLengthOnScreen; + private String text; + private int textWidth; + + @CalledOnlyBy(AmidstThread.EDT) + public ScaleWidget(CornerAnchorPoint anchor, Zoom zoom, + Setting isVisibleSetting) { + super(anchor); + this.zoom = zoom; + this.isVisibleSetting = isVisibleSetting; + setWidth(100); + setHeight(34); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doUpdate(FontMetrics fontMetrics, float time) { + int scaleLengthInWorld = getScaleLengthInWorld(); + scaleLengthOnScreen = (int) zoom.worldToScreen(scaleLengthInWorld); + text = scaleLengthInWorld + " blocks"; + textWidth = fontMetrics.stringWidth(text); + setWidth(Math.max(scaleLengthOnScreen, textWidth) + (MARGIN * 2)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getScaleLengthInWorld() { + int first = 1; + int base = 100; + int result = first * base; + int previousResult = result; + while (zoom.worldToScreen(result) < MAX_SCALE_LENGTH_ON_SCREEN) { + first = getNextFirst(first); + base = getNextBase(first, base); + previousResult = result; + result = first * base; + } + return previousResult; + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getNextFirst(int first) { + if (first == 1) { + return 2; + } else if (first == 2) { + return 5; + } else { + return 1; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getNextBase(int first, int base) { + if (first == 1) { + return base * 10; + } else { + return base; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doDraw(Graphics2D g2d) { + drawText(g2d); + drawLines(g2d); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawText(Graphics2D g2d) { + int x = getX() + 1 + ((getWidth() - textWidth) >> 1); + int y = getY() + 18; + g2d.drawString(text, x, y); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawLines(Graphics2D g2d) { + int x1 = getX() + 1 + ((getWidth() - scaleLengthOnScreen) >> 1); + int x2 = x1 + scaleLengthOnScreen; + int y1 = getY() + 23; + int y2 = getY() + 26; + int y3 = getY() + 28; + g2d.setColor(Color.white); + g2d.setStroke(LINE_STROKE_2); + g2d.drawLine(x1, y2, x2, y2); + g2d.setStroke(LINE_STROKE_1); + g2d.drawLine(x1, y1, x1, y3); + g2d.drawLine(x2, y1, x2, y3); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean onVisibilityCheck() { + return isVisibleSetting.get(); + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/SelectedIconWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/SelectedIconWidget.java new file mode 100644 index 000000000..21bc1cec7 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/SelectedIconWidget.java @@ -0,0 +1,44 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.image.BufferedImage; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.gui.main.worldsurroundings.WorldIconSelection; +import amidst.mojangapi.world.icon.WorldIcon; + +@NotThreadSafe +public class SelectedIconWidget extends IconTextWidget { + private final WorldIconSelection worldIconSelection; + + @CalledOnlyBy(AmidstThread.EDT) + public SelectedIconWidget(CornerAnchorPoint anchor, + WorldIconSelection worldIconSelection) { + super(anchor); + this.worldIconSelection = worldIconSelection; + increaseYMargin(40); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected BufferedImage updateIcon() { + WorldIcon selection = worldIconSelection.get(); + if (selection != null) { + return selection.getImage(); + } else { + return null; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected String updateText() { + WorldIcon selection = worldIconSelection.get(); + if (selection != null) { + return selection.toString(); + } else { + return null; + } + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/TextWidget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/TextWidget.java new file mode 100644 index 000000000..cad5ca3b9 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/TextWidget.java @@ -0,0 +1,46 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.FontMetrics; +import java.awt.Graphics2D; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public abstract class TextWidget extends Widget { + private String text = ""; + private boolean isVisible = false; + + @CalledOnlyBy(AmidstThread.EDT) + protected TextWidget(CornerAnchorPoint anchor) { + super(anchor); + setWidth(20); + setHeight(30); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doUpdate(FontMetrics fontMetrics, float time) { + String newText = updateText(); + if (newText != null && !newText.equals(text)) { + text = newText; + setWidth(fontMetrics.stringWidth(text) + 20); + } + isVisible = newText != null; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected void doDraw(Graphics2D g2d) { + g2d.drawString(text, getX() + 10, getY() + 20); + } + + @Override + protected boolean onVisibilityCheck() { + return isVisible; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract String updateText(); +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/Widget.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/Widget.java new file mode 100644 index 000000000..dcdbb81b2 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/Widget.java @@ -0,0 +1,347 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Stroke; +import java.awt.image.BufferedImage; + +import amidst.ResourceLoader; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public abstract class Widget { + public static enum CornerAnchorPoint { + TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, BOTTOM_CENTER, CENTER, NONE + } + + protected static boolean isInBounds(int x, int y, int offsetX, int offsetY, + int width, int height) { + return x >= offsetX && x < offsetX + width && y >= offsetY + && y < offsetY + height; + } + + private static final BufferedImage DROP_SHADOW_BOTTOM_LEFT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_bottom_left.png"); + private static final BufferedImage DROP_SHADOW_BOTTOM_RIGHT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_bottom_right.png"); + private static final BufferedImage DROP_SHADOW_TOP_LEFT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_top_left.png"); + private static final BufferedImage DROP_SHADOW_TOP_RIGHT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_top_right.png"); + private static final BufferedImage DROP_SHADOW_BOTTOM = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_bottom.png"); + private static final BufferedImage DROP_SHADOW_TOP = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_top.png"); + private static final BufferedImage DROP_SHADOW_LEFT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_left.png"); + private static final BufferedImage DROP_SHADOW_RIGHT = ResourceLoader + .getImage("/amidst/gui/main/dropshadow/outer_right.png"); + + public static final Font TEXT_FONT = new Font("arial", Font.BOLD, 15); + private static final Color TEXT_COLOR = new Color(1f, 1f, 1f); + private static final Color PANEL_COLOR = new Color(0.15f, 0.15f, 0.15f, + 0.8f); + + protected static final Stroke LINE_STROKE_1 = new BasicStroke(1); + protected static final Stroke LINE_STROKE_2 = new BasicStroke(2, + BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + + private final CornerAnchorPoint anchor; + + private int x; + private int y; + private int width; + private int height; + + private int xMargin = 10; + private int yMargin = 10; + + private boolean isFirstVisibilityCheck = true; + private float alpha = 1.0f; + private float targetAlpha = 1.0f; + + private int viewerWidth; + private int viewerHeight; + private Point mousePosition; + + @CalledOnlyBy(AmidstThread.EDT) + protected Widget(CornerAnchorPoint anchor) { + this.anchor = anchor; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void update(int viewerWidth, int viewerHeight, Point mousePosition, + FontMetrics fontMetrics, float time) { + this.viewerWidth = viewerWidth; + this.viewerHeight = viewerHeight; + this.mousePosition = mousePosition; + updateAlpha(time); + doUpdate(fontMetrics, time); + updatePosition(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateAlpha(float time) { + if (alpha < targetAlpha) { + alpha = Math.min(targetAlpha, alpha + time * 4.0f); + } else if (alpha > targetAlpha) { + alpha = Math.max(targetAlpha, alpha - time * 4.0f); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updatePosition() { + if (anchor == CornerAnchorPoint.TOP_LEFT) { + setX(getLeftX()); + setY(getTopY()); + } else if (anchor == CornerAnchorPoint.BOTTOM_LEFT) { + setX(getLeftX()); + setY(getBottomY(viewerHeight)); + } else if (anchor == CornerAnchorPoint.BOTTOM_RIGHT) { + setX(getRightX(viewerWidth)); + setY(getBottomY(viewerHeight)); + } else if (anchor == CornerAnchorPoint.BOTTOM_CENTER) { + setX(getCenterX(viewerWidth)); + setY(getBottomY(viewerHeight)); + } else if (anchor == CornerAnchorPoint.TOP_RIGHT) { + setX(getRightX(viewerWidth)); + setY(getTopY()); + } else if (anchor == CornerAnchorPoint.CENTER) { + setX(getCenterX(viewerWidth)); + setY(getCenterY(viewerHeight)); + } else if (anchor == CornerAnchorPoint.NONE) { + // TODO: set x and y? + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getLeftX() { + return xMargin; + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getTopY() { + return yMargin; + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getRightX(int viewerWidth) { + return viewerWidth - (width + xMargin); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getBottomY(int viewerHeight) { + return viewerHeight - (height + yMargin); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getCenterX(int viewerWidth) { + return (viewerWidth >> 1) - (width >> 1); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getCenterY(int viewerHeight) { + return (viewerHeight >> 1) - (height >> 1); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void draw(Graphics2D g2d) { + initGraphics(g2d); + drawBorder(g2d); + drawBackground(g2d); + initGraphicsForContent(g2d); + doDraw(g2d); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initGraphics(Graphics2D g2d) { + g2d.setColor(PANEL_COLOR); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBorder(Graphics2D g2d) { + int x10 = x - 10; + int y10 = y - 10; + int xWidth = x + width; + int yHeight = y + height; + g2d.drawImage(DROP_SHADOW_TOP_LEFT, x10, y10, null); + g2d.drawImage(DROP_SHADOW_TOP_RIGHT, xWidth, y10, null); + g2d.drawImage(DROP_SHADOW_BOTTOM_LEFT, x10, yHeight, null); + g2d.drawImage(DROP_SHADOW_BOTTOM_RIGHT, xWidth, yHeight, null); + g2d.drawImage(DROP_SHADOW_TOP, x, y10, width, 10, null); + g2d.drawImage(DROP_SHADOW_BOTTOM, x, yHeight, width, 10, null); + g2d.drawImage(DROP_SHADOW_LEFT, x10, y, 10, height, null); + g2d.drawImage(DROP_SHADOW_RIGHT, xWidth, y, 10, height, null); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBackground(Graphics2D g2d) { + g2d.fillRect(x, y, width, height); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initGraphicsForContent(Graphics2D g2d) { + g2d.setFont(TEXT_FONT); + g2d.setColor(TEXT_COLOR); + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void increaseYMargin(int delta) { + yMargin += delta; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean isInBounds(Point mouse) { + return isInBounds(mouse.x, mouse.y); + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean isInBounds(int x, int y) { + return isInBounds(x, y, this.x, this.y, this.width, this.height); + } + + @CalledOnlyBy(AmidstThread.EDT) + public int translateXToWidgetCoordinates(Point mouse) { + return translateXToWidgetCoordinates(mouse.x); + } + + @CalledOnlyBy(AmidstThread.EDT) + public int translateYToWidgetCoordinates(Point mouse) { + return translateYToWidgetCoordinates(mouse.y); + } + + @CalledOnlyBy(AmidstThread.EDT) + public int translateXToWidgetCoordinates(int x) { + return x - this.x; + } + + @CalledOnlyBy(AmidstThread.EDT) + public int translateYToWidgetCoordinates(int y) { + return y - this.y; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean isVisible() { + boolean isVisible = onVisibilityCheck(); + targetAlpha = getTargetAlpha(isVisible); + if (isFirstVisibilityCheck) { + isFirstVisibilityCheck = false; + skipFading(); + } + return isVisible || isFading(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private float getTargetAlpha(boolean isVisible) { + if (isVisible) { + return 1.0f; + } else { + return 0.0f; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void skipFading() { + alpha = targetAlpha; + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isFading() { + return targetAlpha != alpha; + } + + @CalledOnlyBy(AmidstThread.EDT) + public float getAlpha() { + return alpha; + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getX() { + return x; + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getY() { + return y; + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getWidth() { + return width; + } + + @CalledOnlyBy(AmidstThread.EDT) + public int getHeight() { + return height; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void setX(int x) { + this.x = x; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void setY(int y) { + this.y = y; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void setWidth(int width) { + this.width = width; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void setHeight(int height) { + this.height = height; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean onClick(int x, int y) { + return true; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean onMouseWheelMoved(int x, int y, int rotation) { + return false; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean onMousePressed(int x, int y) { + return true; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void onMouseReleased() { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + protected int getViewerWidth() { + return viewerWidth; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected int getViewerHeight() { + return viewerHeight; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected Point getMousePosition() { + return mousePosition; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract void doUpdate(FontMetrics fontMetrics, float time); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract void doDraw(Graphics2D g2d); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract boolean onVisibilityCheck(); +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/WidgetBuilder.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/WidgetBuilder.java new file mode 100644 index 000000000..a5c6044d0 --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/WidgetBuilder.java @@ -0,0 +1,64 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.util.Arrays; +import java.util.List; + +import amidst.Settings; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.fragment.FragmentGraph; +import amidst.fragment.FragmentManager; +import amidst.fragment.layer.LayerReloader; +import amidst.gui.main.worldsurroundings.BiomeSelection; +import amidst.gui.main.worldsurroundings.FragmentGraphToScreenTranslator; +import amidst.gui.main.worldsurroundings.WorldIconSelection; +import amidst.gui.main.worldsurroundings.Zoom; +import amidst.gui.main.worldsurroundings.widget.Widget.CornerAnchorPoint; +import amidst.mojangapi.world.World; + +@NotThreadSafe +public class WidgetBuilder { + private final World world; + private final FragmentGraph graph; + private final FragmentGraphToScreenTranslator translator; + private final Zoom zoom; + private final BiomeSelection biomeSelection; + private final WorldIconSelection worldIconSelection; + private final LayerReloader layerReloader; + private final FragmentManager fragmentManager; + private final Settings settings; + + @CalledOnlyBy(AmidstThread.EDT) + public WidgetBuilder(World world, FragmentGraph graph, + FragmentGraphToScreenTranslator translator, Zoom zoom, + BiomeSelection biomeSelection, + WorldIconSelection worldIconSelection, LayerReloader layerReloader, + FragmentManager fragmentManager, Settings settings) { + this.world = world; + this.graph = graph; + this.translator = translator; + this.zoom = zoom; + this.biomeSelection = biomeSelection; + this.worldIconSelection = worldIconSelection; + this.layerReloader = layerReloader; + this.fragmentManager = fragmentManager; + this.settings = settings; + } + + @CalledOnlyBy(AmidstThread.EDT) + public List create() { + // @formatter:off + return Arrays.asList( + new FpsWidget( CornerAnchorPoint.BOTTOM_LEFT, new FramerateTimer(2), settings.showFPS), + new ScaleWidget( CornerAnchorPoint.BOTTOM_CENTER, zoom, settings.showScale), + new ImmutableTextWidget( CornerAnchorPoint.TOP_LEFT, world.getWorldSeed().getLabel()), + new DebugWidget( CornerAnchorPoint.BOTTOM_RIGHT, graph, fragmentManager, settings.showDebug), + new SelectedIconWidget( CornerAnchorPoint.TOP_LEFT, worldIconSelection), + new CursorInformationWidget(CornerAnchorPoint.TOP_RIGHT, graph, translator), + new BiomeToggleWidget( CornerAnchorPoint.BOTTOM_RIGHT, biomeSelection, layerReloader), + new BiomeWidget( CornerAnchorPoint.NONE, biomeSelection, layerReloader, settings.biomeColorProfileSelection) + ); + // @formatter:on + } +} diff --git a/src/main/java/amidst/gui/main/worldsurroundings/widget/WidgetManager.java b/src/main/java/amidst/gui/main/worldsurroundings/widget/WidgetManager.java new file mode 100644 index 000000000..5dbb3224e --- /dev/null +++ b/src/main/java/amidst/gui/main/worldsurroundings/widget/WidgetManager.java @@ -0,0 +1,78 @@ +package amidst.gui.main.worldsurroundings.widget; + +import java.awt.Point; +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class WidgetManager { + private final List widgets; + + private Widget mouseOwner; + + @CalledOnlyBy(AmidstThread.EDT) + public WidgetManager(List widgets) { + this.widgets = widgets; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean mouseWheelMoved(Point mousePosition, int notches) { + for (Widget widget : widgets) { + if (widget.isVisible() + && widget.isInBounds(mousePosition) + && widget + .onMouseWheelMoved( + widget.translateXToWidgetCoordinates(mousePosition), + widget.translateYToWidgetCoordinates(mousePosition), + notches)) { + return true; + } + } + return false; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean mouseClicked(Point mousePosition) { + for (Widget widget : widgets) { + if (widget.isVisible() + && widget.isInBounds(mousePosition) + && widget + .onClick( + widget.translateXToWidgetCoordinates(mousePosition), + widget.translateYToWidgetCoordinates(mousePosition))) { + return true; + } + } + return false; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean mousePressed(Point mousePosition) { + for (Widget widget : widgets) { + if (widget.isVisible() + && widget.isInBounds(mousePosition) + && widget + .onMousePressed( + widget.translateXToWidgetCoordinates(mousePosition), + widget.translateYToWidgetCoordinates(mousePosition))) { + mouseOwner = widget; + return true; + } + } + return false; + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean mouseReleased() { + if (mouseOwner != null) { + mouseOwner.onMouseReleased(); + mouseOwner = null; + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/amidst/gui/profileselect/LocalProfileComponent.java b/src/main/java/amidst/gui/profileselect/LocalProfileComponent.java new file mode 100644 index 000000000..8ea5338ee --- /dev/null +++ b/src/main/java/amidst/gui/profileselect/LocalProfileComponent.java @@ -0,0 +1,148 @@ +package amidst.gui.profileselect; + +import java.io.FileNotFoundException; + +import amidst.Application; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.file.directory.ProfileDirectory; +import amidst.mojangapi.file.directory.VersionDirectory; +import amidst.mojangapi.file.json.launcherprofiles.LauncherProfileJson; +import amidst.mojangapi.minecraftinterface.local.LocalMinecraftInterfaceCreationException; +import amidst.threading.SimpleWorker; +import amidst.threading.SimpleWorkerWithoutResult; +import amidst.threading.WorkerExecutor; + +@NotThreadSafe +public class LocalProfileComponent extends ProfileComponent { + private final Application application; + private final WorkerExecutor workerExecutor; + private final MojangApi mojangApi; + private final LauncherProfileJson profile; + + private volatile boolean isSearching = false; + private volatile boolean failedSearching = false; + private volatile boolean isLoading = false; + private volatile boolean failedLoading = false; + private volatile VersionDirectory versionDirectory; + private volatile ProfileDirectory profileDirectory; + + @CalledOnlyBy(AmidstThread.EDT) + public LocalProfileComponent(Application application, + WorkerExecutor workerExecutor, MojangApi mojangApi, + LauncherProfileJson profile) { + this.application = application; + this.mojangApi = mojangApi; + this.workerExecutor = workerExecutor; + this.profile = profile; + initComponent(); + initDirectoriesLater(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void initDirectoriesLater() { + isSearching = true; + repaintComponent(); + workerExecutor.invokeLater(new SimpleWorker() { + @Override + protected Boolean main() { + try { + profileDirectory = profile + .createValidProfileDirectory(mojangApi); + versionDirectory = profile + .createValidVersionDirectory(mojangApi); + return true; + } catch (FileNotFoundException e) { + Log.w(e.getMessage()); + return false; + } + } + + @Override + protected void onMainFinished(Boolean result) { + isSearching = false; + failedSearching = !result; + repaintComponent(); + } + }); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void load() { + isLoading = true; + repaintComponent(); + workerExecutor.invokeLater(new SimpleWorkerWithoutResult() { + @Override + protected void main() + throws LocalMinecraftInterfaceCreationException { + mojangApi.set(profileDirectory, versionDirectory); + } + + @Override + protected void onMainFinished() { + isLoading = false; + repaintComponent(); + application.displayMainWindow(); + } + + @Override + protected void onMainFinishedWithException(Exception e) { + Log.e(e.getMessage()); + e.printStackTrace(); + isLoading = false; + failedLoading = true; + repaintComponent(); + } + }); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean isSearching() { + return isSearching; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean failedSearching() { + return failedSearching; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean isLoading() { + return isLoading; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean failedLoading() { + return failedLoading; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected boolean isReadyToLoad() { + return !isSearching && !failedSearching; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected String getProfileName() { + return profile.getName(); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + protected String getVersionName() { + if (isReadyToLoad()) { + return versionDirectory.getVersionId(); + } else { + return ""; + } + } +} diff --git a/src/main/java/amidst/gui/profileselect/ProfileComponent.java b/src/main/java/amidst/gui/profileselect/ProfileComponent.java new file mode 100644 index 000000000..94c2a2498 --- /dev/null +++ b/src/main/java/amidst/gui/profileselect/ProfileComponent.java @@ -0,0 +1,236 @@ +package amidst.gui.profileselect; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +import javax.swing.JComponent; + +import amidst.ResourceLoader; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public abstract class ProfileComponent { + @SuppressWarnings("serial") + private class Component extends JComponent { + private final FontMetrics statusFontMetrics = getFontMetrics(STATUS_FONT); + private final FontMetrics versionFontMetrics = getFontMetrics(VERSION_NAME_FONT); + private final FontMetrics profileFontMetrics = getFontMetrics(PROFILE_NAME_FONT); + + private int versionNameX; + private int oldWidth; + private String oldVersionName; + private String oldProfileName; + + @CalledOnlyBy(AmidstThread.EDT) + public Component() { + this.setMinimumSize(new Dimension(300, 40)); + this.setPreferredSize(new Dimension(500, 40)); + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + drawBackground(g2d); + updateIfNecessary(); + drawVersionName(g2d); + drawProfileName(g2d); + drawStatus(g2d); + drawIcon(g2d); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBackground(Graphics2D g2d) { + g2d.setColor(getBackgroundColor()); + g2d.fillRect(0, 0, getWidth(), getHeight()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateIfNecessary() { + int width = getWidth(); + String versionName = getVersionName(); + String profileName = getProfileName(); + if (oldVersionName == null || oldWidth != width + || !oldVersionName.equals(versionName)) { + versionNameX = width - 40 + - versionFontMetrics.stringWidth(versionName); + oldWidth = width; + oldVersionName = versionName; + oldProfileName = createProfileName(profileName, + versionNameX - 25); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private String createProfileName(String profileName, int maxWidth) { + String result = profileName; + if (profileFontMetrics.stringWidth(result) > maxWidth) { + int widthSum = 0; + for (int i = 0; i < result.length(); i++) { + widthSum += profileFontMetrics.charWidth(result.charAt(i)); + if (widthSum > maxWidth) { + return result.substring(0, i) + "..."; + } + } + } + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawVersionName(Graphics2D g2d) { + g2d.setColor(Color.black); + g2d.setFont(VERSION_NAME_FONT); + g2d.drawString(oldVersionName, versionNameX, 20); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawProfileName(Graphics2D g2d) { + g2d.setColor(Color.black); + g2d.setFont(PROFILE_NAME_FONT); + g2d.drawString(oldProfileName, 5, 30); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawStatus(Graphics2D g2d) { + g2d.setColor(Color.gray); + g2d.setFont(STATUS_FONT); + String loadingStatus = getLoadingStatus(); + int stringWidth = statusFontMetrics.stringWidth(loadingStatus); + g2d.drawString(loadingStatus, getWidth() - 40 - stringWidth, 32); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawIcon(Graphics2D g2d) { + BufferedImage icon = getIcon(); + g2d.drawImage(icon, getWidth() - icon.getWidth() - 5, 4, null); + } + } + + private static final Font STATUS_FONT = new Font("arial", Font.BOLD, 10); + private static final Font VERSION_NAME_FONT = new Font("arial", Font.BOLD, + 16); + private static final Font PROFILE_NAME_FONT = new Font("arial", Font.BOLD, + 30); + private static final BufferedImage ACTIVE_ICON = ResourceLoader + .getImage("/amidst/gui/profileselect/active.png"); + private static final BufferedImage INACTIVE_ICON = ResourceLoader + .getImage("/amidst/gui/profileselect/inactive.png"); + private static final BufferedImage LOADING_ICON = ResourceLoader + .getImage("/amidst/gui/profileselect/loading.png"); + private static final Color SELECTED_BG_COLOR = new Color(160, 190, 255); + private static final Color LOADING_BG_COLOR = new Color(112, 203, 91); + private static final Color DEFAULT_BG_COLOR = Color.white; + private static final Color FAILED_BG_COLOR = new Color(250, 160, 160); + + private Component component; + private boolean isSelected = false; + + /** + * This cannot be put in the constructor, because that would cause a call to + * e.g. getProfileName by the drawing function before the derived class is + * constructed. + */ + @CalledOnlyBy(AmidstThread.EDT) + protected void initComponent() { + this.component = new Component(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public boolean isSelected() { + return isSelected; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void setSelected(boolean isSelected) { + if (this.isSelected != isSelected) { + this.isSelected = isSelected; + component.repaint(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public Component getComponent() { + return component; + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void repaintComponent() { + component.repaint(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private String getLoadingStatus() { + if (failedLoading()) { + return "failed loading"; + } else if (isLoading()) { + return "loading"; + } else if (failedSearching()) { + return "not found"; + } else if (isSearching()) { + return "searching"; + } else { + return "found"; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private BufferedImage getIcon() { + if (failedLoading()) { + return INACTIVE_ICON; + } else if (isLoading()) { + return LOADING_ICON; + } else if (failedSearching()) { + return INACTIVE_ICON; + } else if (isSearching()) { + return INACTIVE_ICON; + } else { + return ACTIVE_ICON; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private Color getBackgroundColor() { + if (failedLoading()) { + return FAILED_BG_COLOR; + } else if (isLoading()) { + return LOADING_BG_COLOR; + } else if (failedSearching()) { + return FAILED_BG_COLOR; + } else if (isSelected) { + return SELECTED_BG_COLOR; + } else { + return DEFAULT_BG_COLOR; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract void load(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract boolean isSearching(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract boolean failedSearching(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract boolean isLoading(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract boolean failedLoading(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract boolean isReadyToLoad(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract String getProfileName(); + + @CalledOnlyBy(AmidstThread.EDT) + protected abstract String getVersionName(); +} diff --git a/src/main/java/amidst/gui/profileselect/ProfileSelectPanel.java b/src/main/java/amidst/gui/profileselect/ProfileSelectPanel.java new file mode 100644 index 000000000..df0de62a7 --- /dev/null +++ b/src/main/java/amidst/gui/profileselect/ProfileSelectPanel.java @@ -0,0 +1,264 @@ +package amidst.gui.profileselect; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JPanel; + +import net.miginfocom.swing.MigLayout; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.settings.Setting; + +@NotThreadSafe +public class ProfileSelectPanel { + @NotThreadSafe + @SuppressWarnings("serial") + private class Component extends JPanel { + private static final int INVALID_EMPTY_MESSAGE_WIDTH = -1; + + private int emptyMessageWidth = INVALID_EMPTY_MESSAGE_WIDTH; + private String oldEmptyMessage; + + @CalledOnlyBy(AmidstThread.EDT) + public Component() { + this.oldEmptyMessage = emptyMessage; + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void paintChildren(Graphics g) { + super.paintChildren(g); + Graphics2D g2d = (Graphics2D) g; + drawSeparatorLines(g2d); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawSeparatorLines(Graphics2D g2d) { + g2d.setColor(Color.gray); + for (int i = 1; i <= profileComponents.size(); i++) { + g2d.drawLine(0, i * 40, getWidth(), i * 40); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g; + drawBackground(g2d); + if (profileComponents.isEmpty()) { + drawEmptyMessage(g2d); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawBackground(Graphics2D g2d) { + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setColor(Color.white); + g2d.fillRect(0, 0, getWidth(), getHeight()); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void drawEmptyMessage(Graphics2D g2d) { + g2d.setColor(Color.gray); + g2d.setFont(EMPTY_MESSAGE_FONT); + updateEmptyMessageWidth(g2d); + int x = (getWidth() >> 1) - (emptyMessageWidth >> 1); + g2d.drawString(emptyMessage, x, 30); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void updateEmptyMessageWidth(Graphics2D g2d) { + if (!oldEmptyMessage.equals(emptyMessage) + || emptyMessageWidth == INVALID_EMPTY_MESSAGE_WIDTH) { + emptyMessageWidth = g2d.getFontMetrics().stringWidth( + emptyMessage); + } + } + } + + private static final Font EMPTY_MESSAGE_FONT = new Font("arial", Font.BOLD, + 30); + private static final int INVALID_INDEX = -1; + + private final Setting lastProfileSetting; + private final Component component; + private final List profileComponents = new ArrayList(); + + private ProfileComponent selected = null; + private int selectedIndex = INVALID_INDEX; + private String emptyMessage; + + @CalledOnlyBy(AmidstThread.EDT) + public ProfileSelectPanel(Setting lastProfileSetting, + String emptyMessage) { + this.lastProfileSetting = lastProfileSetting; + this.emptyMessage = emptyMessage; + this.component = createComponent(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private Component createComponent() { + Component component = new Component(); + component.setLayout(new MigLayout("ins 0", "", "[]0[]")); + component.addMouseListener(createMouseListener()); + return component; + } + + @CalledOnlyBy(AmidstThread.EDT) + private MouseListener createMouseListener() { + return new MouseAdapter() { + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void mousePressed(MouseEvent e) { + if (!isLoading()) { + doMousePressed(e.getPoint()); + } + } + }; + } + + @CalledOnlyBy(AmidstThread.EDT) + public KeyListener createKeyListener() { + return new KeyAdapter() { + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void keyPressed(KeyEvent e) { + if (!isLoading()) { + doKeyPressed(e.getKeyCode()); + } + } + }; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doKeyPressed(int key) { + if (key == KeyEvent.VK_DOWN) { + select(selectedIndex + 1); + } else if (key == KeyEvent.VK_UP) { + select(selectedIndex - 1); + } else if (key == KeyEvent.VK_ENTER) { + loadSelectedProfile(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doMousePressed(Point mousePosition) { + select(getSelectedIndexFromYCoordinate(mousePosition)); + if (isLoadButtonClicked(mousePosition)) { + loadSelectedProfile(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getSelectedIndexFromYCoordinate(Point mousePosition) { + return mousePosition.y / 40; + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isLoadButtonClicked(Point mousePosition) { + return mousePosition.x > component.getWidth() - 40; + } + + @CalledOnlyBy(AmidstThread.EDT) + public void select(String profileName) { + select(getIndex(profileName)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getIndex(String profileName) { + for (int i = 0; i < profileComponents.size(); i++) { + if (profileComponents.get(i).getProfileName().equals(profileName)) { + return i; + } + } + return INVALID_INDEX; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void select(int index) { + deselectSelected(); + doSelect(getBoundedIndex(index)); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void deselectSelected() { + if (selected != null) { + selected.setSelected(false); + selected = null; + selectedIndex = INVALID_INDEX; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private int getBoundedIndex(int index) { + if (profileComponents.isEmpty()) { + return INVALID_INDEX; + } else if (index < 0) { + return 0; + } else if (index >= profileComponents.size()) { + return profileComponents.size() - 1; + } else { + return index; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void doSelect(int index) { + if (index != INVALID_INDEX) { + selected = profileComponents.get(index); + selected.setSelected(true); + selectedIndex = index; + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void loadSelectedProfile() { + if (selected != null && selected.isReadyToLoad()) { + lastProfileSetting.set(selected.getProfileName()); + selected.load(); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void addProfile(ProfileComponent profile) { + component.add(profile.getComponent(), "growx, pushx, wrap"); + profileComponents.add(profile); + } + + @CalledOnlyBy(AmidstThread.EDT) + public void setEmptyMessage(String emptyMessage) { + this.emptyMessage = emptyMessage; + component.repaint(); + } + + @CalledOnlyBy(AmidstThread.EDT) + public JPanel getComponent() { + return component; + } + + @CalledOnlyBy(AmidstThread.EDT) + private boolean isLoading() { + for (ProfileComponent profileComponent : profileComponents) { + if (profileComponent.isLoading()) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/amidst/gui/profileselect/ProfileSelectWindow.java b/src/main/java/amidst/gui/profileselect/ProfileSelectWindow.java new file mode 100644 index 000000000..782bb0270 --- /dev/null +++ b/src/main/java/amidst/gui/profileselect/ProfileSelectWindow.java @@ -0,0 +1,143 @@ +package amidst.gui.profileselect; + +import java.awt.Font; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; + +import net.miginfocom.swing.MigLayout; +import amidst.AmidstMetaData; +import amidst.Application; +import amidst.Settings; +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.launcherprofiles.LauncherProfileJson; +import amidst.mojangapi.file.json.launcherprofiles.LauncherProfilesJson; +import amidst.threading.SimpleWorker; +import amidst.threading.WorkerExecutor; + +@NotThreadSafe +public class ProfileSelectWindow { + private final Application application; + private final AmidstMetaData metadata; + private final WorkerExecutor workerExecutor; + private final MojangApi mojangApi; + private final Settings settings; + + private final JFrame frame; + private final ProfileSelectPanel profileSelectPanel; + + @CalledOnlyBy(AmidstThread.EDT) + public ProfileSelectWindow(Application application, + AmidstMetaData metadata, WorkerExecutor workerExecutor, + MojangApi mojangApi, Settings settings) { + this.application = application; + this.metadata = metadata; + this.workerExecutor = workerExecutor; + this.mojangApi = mojangApi; + this.settings = settings; + this.profileSelectPanel = new ProfileSelectPanel(settings.lastProfile, + "Scanning..."); + this.frame = createFrame(); + scanAndLoadProfilesLater(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private JFrame createFrame() { + JFrame frame = new JFrame("Profile Selector"); + frame.setIconImage(metadata.getIcon()); + frame.getContentPane().setLayout(new MigLayout()); + frame.add(createTitleLabel(), "h 20!,w :400:, growx, pushx, wrap"); + frame.add(new JScrollPane(profileSelectPanel.getComponent()), + "grow, push, h 80::"); + frame.pack(); + frame.addKeyListener(profileSelectPanel.createKeyListener()); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + application.exitGracefully(); + } + }); + frame.setLocation(200, 200); + frame.setVisible(true); + return frame; + } + + @CalledOnlyBy(AmidstThread.EDT) + private JLabel createTitleLabel() { + JLabel result = new JLabel("Please select a Minecraft profile:", + SwingConstants.CENTER); + result.setFont(new Font("arial", Font.BOLD, 16)); + return result; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void scanAndLoadProfilesLater() { + workerExecutor.invokeLater(new SimpleWorker() { + @Override + protected LauncherProfilesJson main() + throws MojangApiParsingException, IOException { + return scanAndLoadProfiles(); + } + + @Override + protected void onMainFinished(LauncherProfilesJson launcherProfile) { + displayProfiles(launcherProfile); + } + + @Override + protected void onMainFinishedWithException(Exception e) { + Log.e("Error reading launcher_profiles.json"); + e.printStackTrace(); + profileSelectPanel.setEmptyMessage("Failed loading"); + } + }); + } + + @CalledOnlyBy(AmidstThread.WORKER) + private LauncherProfilesJson scanAndLoadProfiles() + throws MojangApiParsingException, IOException { + Log.i("Scanning for profiles."); + LauncherProfilesJson launcherProfile = mojangApi + .getDotMinecraftDirectory().readLauncherProfilesJson(); + Log.i("Successfully loaded profile list."); + return launcherProfile; + } + + @CalledOnlyBy(AmidstThread.EDT) + private void displayProfiles(LauncherProfilesJson launcherProfile) { + createProfileComponents(launcherProfile); + restoreSelection(); + frame.pack(); + } + + @CalledOnlyBy(AmidstThread.EDT) + private void createProfileComponents(LauncherProfilesJson launcherProfile) { + for (LauncherProfileJson profile : launcherProfile.getProfiles()) { + profileSelectPanel.addProfile(new LocalProfileComponent( + application, workerExecutor, mojangApi, profile)); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private void restoreSelection() { + String profileName = settings.lastProfile.get(); + if (profileName != null) { + profileSelectPanel.select(profileName); + } + } + + @CalledOnlyBy(AmidstThread.EDT) + public void dispose() { + frame.dispose(); + } +} diff --git a/src/main/java/amidst/logging/ConsoleLogger.java b/src/main/java/amidst/logging/ConsoleLogger.java new file mode 100644 index 000000000..f47150456 --- /dev/null +++ b/src/main/java/amidst/logging/ConsoleLogger.java @@ -0,0 +1,50 @@ +package amidst.logging; + +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class ConsoleLogger implements Logger { + @Override + public void debug(Object... messages) { + printWithTag("debug", messages); + } + + @Override + public void info(Object... messages) { + printWithTag("info", messages); + } + + @Override + public void warning(Object... messages) { + printWithTag("warning", messages); + } + + @Override + public void error(Object... messages) { + printWithTag("error", messages); + } + + @Override + public void crash(Throwable e, String exceptionText, String message) { + printWithTag("crash", message); + if (!exceptionText.isEmpty()) { + printWithTag("crash", exceptionText); + } + } + + private void printWithTag(String tag, Object... messages) { + System.out.print("[" + tag + "] "); + for (int i = 0; i < messages.length; i++) { + System.out.print(messages[i]); + System.out.print(getMessageDelimiter(i, messages)); + } + } + + private String getMessageDelimiter(int i, Object... messages) { + if (i < messages.length - 1) { + return " "; + } else { + return "\n"; + } + } +} diff --git a/src/main/java/amidst/logging/FileLogger.java b/src/main/java/amidst/logging/FileLogger.java new file mode 100644 index 000000000..60a49356b --- /dev/null +++ b/src/main/java/amidst/logging/FileLogger.java @@ -0,0 +1,173 @@ +package amidst.logging; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class FileLogger implements Logger { + private final ConcurrentLinkedQueue logMessageQueue = new ConcurrentLinkedQueue(); + private final File file; + private final ScheduledExecutorService executor; + + @CalledOnlyBy(AmidstThread.STARTUP) + public FileLogger(File file) { + this.file = file; + this.executor = createExecutor(); + if (ensureFileExists()) { + writeWelcomeMessageToFile(); + start(); + } + } + + @CalledOnlyBy(AmidstThread.STARTUP) + private ScheduledExecutorService createExecutor() { + return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setPriority(Thread.MIN_PRIORITY); + return thread; + } + }); + } + + @CalledOnlyBy(AmidstThread.STARTUP) + private boolean ensureFileExists() { + if (!file.exists()) { + try { + if (!file.createNewFile()) { + disableBecauseFileCreationFailed(); + return false; + } + } catch (IOException e) { + disableBecauseFileCreationThrowsException(e); + return false; + } + } else if (file.isDirectory()) { + disableBecauseFileIsDirectory(); + return false; + } + return true; + } + + @CalledOnlyBy(AmidstThread.STARTUP) + private void disableBecauseFileCreationFailed() { + Log.w("Unable to create new file at: " + file + + " disabling logging to file. (No exception thrown)"); + } + + @CalledOnlyBy(AmidstThread.STARTUP) + private void disableBecauseFileCreationThrowsException(IOException e) { + Log.w("Unable to create new file at: " + file + + " disabling logging to file."); + e.printStackTrace(); + } + + @CalledOnlyBy(AmidstThread.STARTUP) + private void disableBecauseFileIsDirectory() { + Log.w("Unable to log at path: " + file + + " because location is a directory."); + } + + @CalledOnlyBy(AmidstThread.STARTUP) + private void writeWelcomeMessageToFile() { + write("log", "New FileLogger started."); + } + + @CalledOnlyBy(AmidstThread.STARTUP) + private void start() { + executor.scheduleWithFixedDelay(new Runnable() { + @CalledOnlyBy(AmidstThread.FILE_LOGGER) + @Override + public void run() { + processQueue(); + } + }, 0, 100, TimeUnit.MILLISECONDS); + } + + @CalledOnlyBy(AmidstThread.FILE_LOGGER) + private void processQueue() { + if (!logMessageQueue.isEmpty() && file.isFile()) { + writeLogMessage(getLogMessage()); + } + } + + @CalledOnlyBy(AmidstThread.FILE_LOGGER) + private String getLogMessage() { + StringBuilder builder = new StringBuilder(); + while (!logMessageQueue.isEmpty()) { + builder.append(logMessageQueue.poll()); + } + return builder.toString(); + } + + @CalledOnlyBy(AmidstThread.FILE_LOGGER) + private void writeLogMessage(String logMessage) { + try (FileWriter writer = new FileWriter(file, true)) { + writer.append(logMessage); + } catch (IOException e) { + Log.w("Unable to write to log file."); + e.printStackTrace(); + } + } + + @Override + public void debug(Object... messages) { + write("debug", messages); + } + + @Override + public void info(Object... messages) { + write("info", messages); + } + + @Override + public void warning(Object... messages) { + write("warning", messages); + } + + @Override + public void error(Object... messages) { + write("error", messages); + } + + @Override + public void crash(Throwable e, String exceptionText, String message) { + write("crash", message); + if (!exceptionText.isEmpty()) { + write("crash", exceptionText); + } + } + + private void write(String tag, Object... messages) { + String currentTime = new Timestamp(new Date().getTime()).toString(); + StringBuilder builder = new StringBuilder(currentTime); + builder.append(" [").append(tag).append("] "); + for (int i = 0; i < messages.length; i++) { + builder.append(messages[i]); + builder.append(getMessageDelimiter(i, messages)); + } + logMessageQueue.add(builder.toString()); + } + + private String getMessageDelimiter(int i, Object... messages) { + if (i < messages.length - 1) { + return " "; + } else { + return "\r\n"; + } + } +} diff --git a/src/main/java/amidst/logging/InMemoryLogger.java b/src/main/java/amidst/logging/InMemoryLogger.java new file mode 100644 index 000000000..b5946fb4e --- /dev/null +++ b/src/main/java/amidst/logging/InMemoryLogger.java @@ -0,0 +1,56 @@ +package amidst.logging; + +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class InMemoryLogger implements Logger { + private StringBuffer buffer = new StringBuffer(); + + @Override + public void debug(Object... messages) { + write("debug", messages); + } + + @Override + public void info(Object... messages) { + write("info", messages); + } + + @Override + public void warning(Object... messages) { + write("warning", messages); + } + + @Override + public void error(Object... messages) { + write("error", messages); + } + + @Override + public void crash(Throwable e, String exceptionText, String message) { + write("crash", message); + if (!exceptionText.isEmpty()) { + write("crash", exceptionText); + } + } + + private void write(String tag, Object... messages) { + buffer.append("[").append(tag).append("] "); + for (int i = 0; i < messages.length; i++) { + buffer.append(messages[i]); + buffer.append(getMessageDelimiter(i, messages)); + } + } + + private String getMessageDelimiter(int i, Object... messages) { + if (i < messages.length - 1) { + return " "; + } else { + return "\n"; + } + } + + public String getContents() { + return buffer.toString(); + } +} diff --git a/src/main/java/amidst/logging/Log.java b/src/main/java/amidst/logging/Log.java new file mode 100644 index 000000000..d0ff72b06 --- /dev/null +++ b/src/main/java/amidst/logging/Log.java @@ -0,0 +1,111 @@ +package amidst.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JOptionPane; + +import amidst.documentation.ThreadSafe; + +// TODO: switch to standard logging framework like slf4j + log4j? +@ThreadSafe +public class Log { + private static final ConsoleLogger CONSOLE_LOGGER = new ConsoleLogger(); + private static final InMemoryLogger IN_MEMORY_LOGGER = new InMemoryLogger(); + + private static final Object LOG_LOCK = new Object(); + private static final boolean IS_USING_ALERTS = true; + private static final boolean IS_SHOWING_DEBUG = true; + + private static final Map LOGGER = new HashMap(); + + static { + addListener("console", CONSOLE_LOGGER); + addListener("master", IN_MEMORY_LOGGER); + } + + public static void addListener(String name, Logger l) { + synchronized (LOG_LOCK) { + LOGGER.put(name, l); + } + } + + public static void removeListener(String name) { + synchronized (LOG_LOCK) { + LOGGER.remove(name); + } + } + + public static void i(Object... messages) { + synchronized (LOG_LOCK) { + for (Logger listener : LOGGER.values()) { + listener.info(messages); + } + } + } + + public static void debug(Object... messages) { + if (IS_SHOWING_DEBUG) { + synchronized (LOG_LOCK) { + for (Logger listener : LOGGER.values()) { + listener.debug(messages); + } + } + } + } + + public static void w(Object... messages) { + synchronized (LOG_LOCK) { + for (Logger listener : LOGGER.values()) { + listener.warning(messages); + } + } + } + + public static void e(Object... messages) { + synchronized (LOG_LOCK) { + if (IS_USING_ALERTS) { + JOptionPane.showMessageDialog(null, messages, "Error", + JOptionPane.ERROR_MESSAGE); + } + for (Logger listener : LOGGER.values()) { + listener.error(messages); + } + } + } + + public static void crash(Throwable e, String message) { + synchronized (LOG_LOCK) { + String exceptionText = getExceptionText(e); + for (Logger listener : LOGGER.values()) { + listener.crash(e, exceptionText, message); + } + } + } + + public static String getAllMessages() { + synchronized (LOG_LOCK) { + return IN_MEMORY_LOGGER.getContents(); + } + } + + private static String getExceptionText(Throwable e) { + if (e != null) { + return getStackTraceAsString(e); + } else { + return ""; + } + } + + public static void printTraceStack(Throwable e) { + w(getStackTraceAsString(e)); + } + + private static String getStackTraceAsString(Throwable e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } +} diff --git a/src/main/java/amidst/logging/Logger.java b/src/main/java/amidst/logging/Logger.java new file mode 100644 index 000000000..0cf162729 --- /dev/null +++ b/src/main/java/amidst/logging/Logger.java @@ -0,0 +1,13 @@ +package amidst.logging; + +public interface Logger { + public void debug(Object... messages); + + public void info(Object... messages); + + public void warning(Object... messages); + + public void error(Object... messages); + + public void crash(Throwable e, String exceptionText, String message); +} diff --git a/src/main/java/amidst/mojangapi/MojangApi.java b/src/main/java/amidst/mojangapi/MojangApi.java new file mode 100644 index 000000000..605f5b068 --- /dev/null +++ b/src/main/java/amidst/mojangapi/MojangApi.java @@ -0,0 +1,160 @@ +package amidst.mojangapi; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import amidst.documentation.NotNull; +import amidst.documentation.ThreadSafe; +import amidst.mojangapi.file.FilenameFactory; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.directory.DotMinecraftDirectory; +import amidst.mojangapi.file.directory.ProfileDirectory; +import amidst.mojangapi.file.directory.SaveDirectory; +import amidst.mojangapi.file.directory.VersionDirectory; +import amidst.mojangapi.file.json.versionlist.VersionListJson; +import amidst.mojangapi.minecraftinterface.MinecraftInterface; +import amidst.mojangapi.minecraftinterface.MinecraftInterfaceException; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.minecraftinterface.local.LocalMinecraftInterfaceCreationException; +import amidst.mojangapi.world.World; +import amidst.mojangapi.world.WorldBuilder; +import amidst.mojangapi.world.WorldSeed; +import amidst.mojangapi.world.WorldType; + +@ThreadSafe +public class MojangApi { + private static final String UNKNOWN_VERSION_ID = "unknown"; + + private final WorldBuilder worldBuilder; + private final DotMinecraftDirectory dotMinecraftDirectory; + private final VersionListJson versionList; + private final File preferedJson; + + private volatile ProfileDirectory profileDirectory; + private volatile VersionDirectory versionDirectory; + private volatile MinecraftInterface minecraftInterface; + + public MojangApi(WorldBuilder worldBuilder, + DotMinecraftDirectory dotMinecraftDirectory, + @NotNull VersionListJson versionList, File preferedJson) { + this.worldBuilder = worldBuilder; + this.dotMinecraftDirectory = dotMinecraftDirectory; + this.versionList = versionList; + this.preferedJson = preferedJson; + } + + public DotMinecraftDirectory getDotMinecraftDirectory() { + return dotMinecraftDirectory; + } + + @NotNull + public VersionListJson getVersionList() { + return versionList; + } + + public void set(ProfileDirectory profileDirectory, + VersionDirectory versionDirectory) + throws LocalMinecraftInterfaceCreationException { + this.profileDirectory = profileDirectory; + this.versionDirectory = versionDirectory; + if (versionDirectory != null) { + try { + this.minecraftInterface = versionDirectory + .createLocalMinecraftInterface(); + } catch (LocalMinecraftInterfaceCreationException e) { + this.minecraftInterface = null; + throw e; + } + } else { + this.minecraftInterface = null; + } + } + + public VersionDirectory createVersionDirectory(String versionId) { + File versions = dotMinecraftDirectory.getVersions(); + File jar = FilenameFactory.getClientJarFile(versions, versionId); + File json = FilenameFactory.getClientJsonFile(versions, versionId); + return doCreateVersionDirectory(versionId, jar, json); + } + + public VersionDirectory createVersionDirectory(File jar, File json) { + return doCreateVersionDirectory(UNKNOWN_VERSION_ID, jar, json); + } + + private VersionDirectory doCreateVersionDirectory(String versionId, + File jar, File json) { + if (preferedJson != null) { + return new VersionDirectory(dotMinecraftDirectory, versionId, jar, + preferedJson); + } else { + return new VersionDirectory(dotMinecraftDirectory, versionId, jar, + json); + } + } + + public File getSaves() { + ProfileDirectory profileDirectory = this.profileDirectory; + if (profileDirectory != null) { + return profileDirectory.getSaves(); + } else { + return dotMinecraftDirectory.getSaves(); + } + } + + public boolean canCreateWorld() { + return minecraftInterface != null; + } + + /** + * Due to the limitation of the minecraft interface, you can only work with + * one world at a time. Creating a new world will break all previously + * created world objects. + */ + public World createWorldFromSeed(WorldSeed seed, WorldType worldType) + throws IllegalStateException, MinecraftInterfaceException { + MinecraftInterface minecraftInterface = this.minecraftInterface; + if (minecraftInterface != null) { + return worldBuilder.fromSeed(minecraftInterface, seed, worldType); + } else { + throw new IllegalStateException( + "cannot create a world without a minecraft interface"); + } + } + + /** + * Due to the limitation of the minecraft interface, you can only work with + * one world at a time. Creating a new world will break all previously + * created world objects. + */ + public World createWorldFromFile(File file) throws FileNotFoundException, + IOException, IllegalStateException, MinecraftInterfaceException, + MojangApiParsingException { + MinecraftInterface minecraftInterface = this.minecraftInterface; + if (minecraftInterface != null) { + return worldBuilder.fromFile(minecraftInterface, + SaveDirectory.from(file)); + } else { + throw new IllegalStateException( + "cannot create a world without a minecraft interface"); + } + } + + public String getVersionId() { + VersionDirectory versionDirectory = this.versionDirectory; + if (versionDirectory != null) { + return versionDirectory.getVersionId(); + } else { + return UNKNOWN_VERSION_ID; + } + } + + public String getRecognisedVersionName() { + MinecraftInterface minecraftInterface = this.minecraftInterface; + if (minecraftInterface != null) { + return minecraftInterface.getRecognisedVersion().getName(); + } else { + return RecognisedVersion.UNKNOWN.getName(); + } + } +} diff --git a/src/main/java/amidst/mojangapi/MojangApiBuilder.java b/src/main/java/amidst/mojangapi/MojangApiBuilder.java new file mode 100644 index 000000000..5b89bcb05 --- /dev/null +++ b/src/main/java/amidst/mojangapi/MojangApiBuilder.java @@ -0,0 +1,108 @@ +package amidst.mojangapi; + +import java.io.File; +import java.io.FileNotFoundException; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.file.DotMinecraftDirectoryFinder; +import amidst.mojangapi.file.directory.DotMinecraftDirectory; +import amidst.mojangapi.file.directory.ProfileDirectory; +import amidst.mojangapi.file.directory.VersionDirectory; +import amidst.mojangapi.file.json.JsonReader; +import amidst.mojangapi.file.json.versionlist.VersionListJson; +import amidst.mojangapi.minecraftinterface.local.LocalMinecraftInterfaceCreationException; +import amidst.mojangapi.world.WorldBuilder; + +@Immutable +public class MojangApiBuilder { + private final WorldBuilder worldBuilder; + private final String preferedDotMinecraftDirectory; + private final String preferedLibraries; + private final String preferedVersionJar; + private final String preferedVersionJson; + + public MojangApiBuilder(WorldBuilder worldBuilder, + String preferedDotMinecraftDirectory, String preferedLibraries, + String preferedVersionJar, String preferedVersionJson) { + this.worldBuilder = worldBuilder; + this.preferedDotMinecraftDirectory = preferedDotMinecraftDirectory; + this.preferedLibraries = preferedLibraries; + this.preferedVersionJar = preferedVersionJar; + this.preferedVersionJson = preferedVersionJson; + } + + @NotNull + public MojangApi construct() throws FileNotFoundException, + LocalMinecraftInterfaceCreationException { + DotMinecraftDirectory dotMinecraftDirectory = createDotMinecraftDirectory(); + if (!dotMinecraftDirectory.isValid()) { + throw new FileNotFoundException( + "Unable to find valid minecraft directory at: " + + dotMinecraftDirectory.getRoot()); + } + MojangApi result = new MojangApi(worldBuilder, dotMinecraftDirectory, + readRemoteOrLocalVersionList(), createPreferedJson()); + result.set(createProfileDirectory(), createVersionDirectory(result)); + return result; + } + + @NotNull + private DotMinecraftDirectory createDotMinecraftDirectory() { + if (preferedLibraries != null) { + return new DotMinecraftDirectory( + DotMinecraftDirectoryFinder + .find(preferedDotMinecraftDirectory), + new File(preferedLibraries)); + } else { + return new DotMinecraftDirectory( + DotMinecraftDirectoryFinder + .find(preferedDotMinecraftDirectory)); + } + } + + @NotNull + private VersionListJson readRemoteOrLocalVersionList() + throws FileNotFoundException { + return JsonReader.readRemoteOrLocalVersionList(); + } + + private File createPreferedJson() { + if (preferedVersionJson != null) { + File result = new File(preferedVersionJson); + if (result.isFile()) { + return result; + } + } + return null; + } + + private ProfileDirectory createProfileDirectory() { + if (preferedDotMinecraftDirectory != null) { + ProfileDirectory result = new ProfileDirectory(new File( + preferedDotMinecraftDirectory)); + if (result.isValid()) { + return result; + } + } + return null; + } + + private VersionDirectory createVersionDirectory(MojangApi mojangApi) { + if (preferedVersionJar != null) { + File jar = new File(preferedVersionJar); + File json = new File(getJsonFileName()); + VersionDirectory result = mojangApi.createVersionDirectory(jar, + json); + if (result.isValid()) { + return result; + } + } + return null; + } + + private String getJsonFileName() { + return preferedVersionJar.substring(0, preferedVersionJar.length() - 4) + + ".json"; + } +} diff --git a/src/main/java/amidst/mojangapi/file/DotMinecraftDirectoryFinder.java b/src/main/java/amidst/mojangapi/file/DotMinecraftDirectoryFinder.java new file mode 100644 index 000000000..05fdf8026 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/DotMinecraftDirectoryFinder.java @@ -0,0 +1,42 @@ +package amidst.mojangapi.file; + +import java.io.File; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.logging.Log; + +@Immutable +public enum DotMinecraftDirectoryFinder { + ; + + @NotNull + public static File find(String dotMinecraftCMDParameter) { + if (dotMinecraftCMDParameter != null) { + File result = new File(dotMinecraftCMDParameter); + if (result.isDirectory()) { + return result; + } else { + Log.w("Unable to set Minecraft directory to: " + + result + + " as that location does not exist or is not a folder."); + } + } + return getMinecraftDirectory(); + } + + @NotNull + private static File getMinecraftDirectory() { + File home = new File(System.getProperty("user.home", ".")); + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + File appData = new File(System.getenv("APPDATA")); + if (appData.isDirectory()) { + return new File(appData, ".minecraft"); + } + } else if (os.contains("mac")) { + return new File(home, "Library/Application Support/minecraft"); + } + return new File(home, ".minecraft"); + } +} diff --git a/src/main/java/amidst/mojangapi/file/FilenameFactory.java b/src/main/java/amidst/mojangapi/file/FilenameFactory.java new file mode 100644 index 000000000..23ad918ed --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/FilenameFactory.java @@ -0,0 +1,63 @@ +package amidst.mojangapi.file; + +import java.io.File; + +import amidst.documentation.Immutable; + +@Immutable +public enum FilenameFactory { + ; + + private static final String REMOTE_PREFIX = "https://s3.amazonaws.com/Minecraft.Download/versions/"; + private static final String MIDDLE_SERVER = "/minecraft_server."; + private static final String MIDDLE_CLIENT = "/"; + private static final String JAR_FILE_EXTENSION = ".jar"; + private static final String JSON_FILE_EXTENSION = ".json"; + + public static File getClientJsonFile(File prefix, String versionId) { + return getClientFile(prefix, versionId, JSON_FILE_EXTENSION); + } + + public static File getClientJarFile(File prefix, String versionId) { + return getClientFile(prefix, versionId, JAR_FILE_EXTENSION); + } + + private static File getClientFile(File prefix, String versionId, + String fileExtension) { + return new File(prefix, getClientLocation("", versionId, fileExtension)); + } + + public static String getRemoteClientJson(String versionId) { + return getClientJson(REMOTE_PREFIX, versionId); + } + + public static String getClientJson(String prefix, String versionId) { + return getClientLocation(prefix, versionId, JSON_FILE_EXTENSION); + } + + public static String getRemoteClientJar(String versionId) { + return getClientJar(REMOTE_PREFIX, versionId); + } + + public static String getClientJar(String prefix, String versionId) { + return getClientLocation(prefix, versionId, JAR_FILE_EXTENSION); + } + + private static String getClientLocation(String prefix, String versionId, + String fileExtension) { + return prefix + versionId + MIDDLE_CLIENT + versionId + fileExtension; + } + + public static String getRemoteServerJar(String versionId) { + return getServerJar(REMOTE_PREFIX, versionId); + } + + public static String getServerJar(String prefix, String versionId) { + return getServerLocation(prefix, versionId, JAR_FILE_EXTENSION); + } + + private static String getServerLocation(String prefix, String versionId, + String fileExtension) { + return prefix + versionId + MIDDLE_SERVER + versionId + fileExtension; + } +} diff --git a/src/main/java/amidst/mojangapi/file/LibraryFinder.java b/src/main/java/amidst/mojangapi/file/LibraryFinder.java new file mode 100644 index 000000000..9be5f2ce5 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/LibraryFinder.java @@ -0,0 +1,110 @@ +package amidst.mojangapi.file; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.logging.Log; +import amidst.mojangapi.file.json.version.LibraryJson; + +@Immutable +public enum LibraryFinder { + ; + + @NotNull + public static List getLibraryUrls(File librariesDirectory, + List libraries) { + List result = new ArrayList(); + for (LibraryJson library : libraries) { + File libraryFile = getLibraryFile(librariesDirectory, library); + if (libraryFile != null) { + try { + result.add(libraryFile.toURI().toURL()); + Log.i("Found library: " + libraryFile); + } catch (MalformedURLException e) { + Log.w("Unable to convert library file to URL: " + + libraryFile); + e.printStackTrace(); + } + } else { + Log.i("Skipping library: " + library.getName()); + } + } + return result; + } + + private static File getLibraryFile(File librariesDirectory, + LibraryJson library) { + try { + if (library.isActive(getOs())) { + return getLibraryFile(getLibrarySearchPath(librariesDirectory, + library.getName())); + } else { + return null; + } + } catch (NullPointerException e) { + return null; + } + } + + private static String getOs() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) { + return "windows"; + } else if (osName.contains("mac")) { + return "osx"; + } else { + return "linux"; + } + } + + private static File getLibrarySearchPath(File librariesDirectory, + String libraryName) { + String result = librariesDirectory.getAbsolutePath() + "/"; + String[] split = libraryName.split(":"); + split[0] = split[0].replace('.', '/'); + for (int i = 0; i < split.length; i++) { + result += split[i] + "/"; + } + return new File(result); + } + + private static File getLibraryFile(File librarySearchPath) { + if (librarySearchPath.exists()) { + File result = getFirstFileWithExtension( + librarySearchPath.listFiles(), "jar"); + if (result != null && result.exists()) { + return result; + } else { + Log.w("Attempted to search for file at path: " + + librarySearchPath + " but found nothing. Skipping."); + return null; + } + } else { + Log.w("Failed attempt to load library at: " + librarySearchPath); + return null; + } + } + + private static File getFirstFileWithExtension(File[] files, String extension) { + for (File libraryFile : files) { + if (getFileExtension(libraryFile.getName()).equals(extension)) { + return libraryFile; + } + } + return null; + } + + private static String getFileExtension(String fileName) { + String extension = ""; + int q = fileName.lastIndexOf('.'); + if (q > 0) { + extension = fileName.substring(q + 1); + } + return extension; + } +} diff --git a/src/main/java/amidst/mojangapi/file/MojangApiParsingException.java b/src/main/java/amidst/mojangapi/file/MojangApiParsingException.java new file mode 100644 index 000000000..f5b3df5f4 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/MojangApiParsingException.java @@ -0,0 +1,16 @@ +package amidst.mojangapi.file; + +@SuppressWarnings("serial") +public class MojangApiParsingException extends Exception { + public MojangApiParsingException(String message) { + super(message); + } + + public MojangApiParsingException(Throwable cause) { + super(cause); + } + + public MojangApiParsingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/amidst/mojangapi/file/URIUtils.java b/src/main/java/amidst/mojangapi/file/URIUtils.java new file mode 100644 index 000000000..9912ec7ca --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/URIUtils.java @@ -0,0 +1,88 @@ +package amidst.mojangapi.file; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import amidst.documentation.Immutable; + +@Immutable +public enum URIUtils { + ; + + public static URI newURI(String location) throws IOException { + try { + return URI.create(location); + } catch (IllegalArgumentException e) { + throw new IOException("malformed uri: " + location, e); + } + } + + public static URL newURL(String location) throws IOException { + return newURI(location).toURL(); + } + + public static Reader newReader(String location) throws IOException { + return newReader(newURL(location)); + } + + public static Reader newReader(URL url) throws IOException { + return new InputStreamReader(newInputStream(url)); + } + + public static Reader newReader(File file) throws FileNotFoundException { + return new BufferedReader(new FileReader(file)); + } + + public static boolean exists(String location) { + try { + return exists(newURL(location)); + } catch (IOException e) { + return false; + } + } + + public static boolean exists(URL url) { + try { + HttpURLConnection connection = (HttpURLConnection) url + .openConnection(); + connection.setRequestMethod("HEAD"); + return connection.getResponseCode() == HttpURLConnection.HTTP_OK; + } catch (IOException e) { + return false; + } + } + + public static void download(String from, String to) throws IOException { + download(newURL(from), Paths.get(to)); + } + + private static void download(URL from, Path to) throws IOException { + to.getParent().toFile().mkdirs(); + if (to.toFile().exists()) { + return; + } + Path part = Paths.get(to.toString() + ".part"); + InputStream in = newInputStream(from); + Files.copy(in, part, StandardCopyOption.REPLACE_EXISTING); + Files.move(part, to, StandardCopyOption.REPLACE_EXISTING); + } + + private static BufferedInputStream newInputStream(URL url) + throws IOException { + return new BufferedInputStream(url.openStream()); + } +} diff --git a/src/main/java/amidst/mojangapi/file/directory/DotMinecraftDirectory.java b/src/main/java/amidst/mojangapi/file/directory/DotMinecraftDirectory.java new file mode 100644 index 000000000..0029ef8b5 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/directory/DotMinecraftDirectory.java @@ -0,0 +1,67 @@ +package amidst.mojangapi.file.directory; + +import java.io.File; +import java.io.IOException; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.JsonReader; +import amidst.mojangapi.file.json.launcherprofiles.LauncherProfilesJson; + +@Immutable +public class DotMinecraftDirectory { + private final File root; + private final File libraries; + private final File saves; + private final File versions; + private final File launcherProfilesJson; + + public DotMinecraftDirectory(File root) { + this.root = root; + this.libraries = new File(root, "libraries"); + this.saves = new File(root, "saves"); + this.versions = new File(root, "versions"); + this.launcherProfilesJson = new File(root, "launcher_profiles.json"); + } + + public DotMinecraftDirectory(File root, File libraries) { + this.root = root; + this.libraries = libraries; + this.saves = new File(root, "saves"); + this.versions = new File(root, "versions"); + this.launcherProfilesJson = new File(root, "launcher_profiles.json"); + } + + public boolean isValid() { + return root.isDirectory() && libraries.isDirectory() + && saves.isDirectory() && versions.isDirectory() + && launcherProfilesJson.isFile(); + } + + public File getRoot() { + return root; + } + + public File getLibraries() { + return libraries; + } + + public File getSaves() { + return saves; + } + + public File getVersions() { + return versions; + } + + public File getLauncherProfilesJson() { + return launcherProfilesJson; + } + + @NotNull + public LauncherProfilesJson readLauncherProfilesJson() + throws MojangApiParsingException, IOException { + return JsonReader.readLauncherProfilesFrom(launcherProfilesJson); + } +} diff --git a/src/main/java/amidst/mojangapi/file/directory/ProfileDirectory.java b/src/main/java/amidst/mojangapi/file/directory/ProfileDirectory.java new file mode 100644 index 000000000..7447cb62b --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/directory/ProfileDirectory.java @@ -0,0 +1,28 @@ +package amidst.mojangapi.file.directory; + +import java.io.File; + +import amidst.documentation.Immutable; + +@Immutable +public class ProfileDirectory { + private final File root; + private final File saves; + + public ProfileDirectory(File root) { + this.root = root; + this.saves = new File(root, "saves"); + } + + public boolean isValid() { + return root.isDirectory() && saves.isDirectory(); + } + + public File getRoot() { + return root; + } + + public File getSaves() { + return saves; + } +} diff --git a/src/main/java/amidst/mojangapi/file/directory/SaveDirectory.java b/src/main/java/amidst/mojangapi/file/directory/SaveDirectory.java new file mode 100644 index 000000000..4b084ff05 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/directory/SaveDirectory.java @@ -0,0 +1,264 @@ +package amidst.mojangapi.file.directory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.jnbt.CompoundTag; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.logging.Log; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.nbt.LevelDatNbt; +import amidst.mojangapi.file.nbt.NBTUtils; +import amidst.mojangapi.file.nbt.player.LevelDatPlayerNbt; +import amidst.mojangapi.file.nbt.player.PlayerNbt; +import amidst.mojangapi.file.nbt.player.PlayerdataPlayerNbt; +import amidst.mojangapi.file.nbt.player.PlayersPlayerNbt; + +@Immutable +public class SaveDirectory { + /** + * Returns a new valid instance of the class SaveDirectory. It tries to use + * the given file. If that is not valid it tires to use its parent file. If + * that is also not valid it will throw a FileNotFoundException. + */ + @NotNull + public static SaveDirectory from(File file) throws FileNotFoundException { + File currentFile = file; + SaveDirectory result = null; + if (currentFile == null) { + // error + } else { + result = createValidSaveDirectory(currentFile); + currentFile = currentFile.getParentFile(); + if (result != null) { + return result; + } else if (currentFile == null) { + // error + } else { + result = createValidSaveDirectory(currentFile); + currentFile = currentFile.getParentFile(); + if (result != null) { + return result; + } else { + // error + } + } + } + throw new FileNotFoundException("unable to load save directory: " + + file); + } + + private static SaveDirectory createValidSaveDirectory(File currentFile) { + SaveDirectory result = new SaveDirectory(currentFile); + if (result.isValid()) { + return result; + } else { + return null; + } + } + + private final File root; + private final File players; + private final File playerdata; + private final File levelDat; + private final File backupRoot; + private final File backupPlayers; + private final File backupPlayerdata; + + public SaveDirectory(File root) { + this.root = root; + this.players = new File(root, "players"); + this.playerdata = new File(root, "playerdata"); + this.levelDat = new File(root, "level.dat"); + this.backupRoot = new File(root, "amidst_backup"); + this.backupPlayers = new File(backupRoot, "players"); + this.backupPlayerdata = new File(backupRoot, "playerdata"); + } + + public boolean isValid() { + return root.isDirectory() && levelDat.isFile(); + } + + public boolean hasMultiplayerPlayers() { + return playerdata.isDirectory() || players.isDirectory(); + } + + public File getRoot() { + return root; + } + + public File getPlayers() { + return players; + } + + public File getPlayerdata() { + return playerdata; + } + + public File getLevelDat() { + return levelDat; + } + + public File getPlayersFile(String playerName) { + return new File(players, playerName + ".dat"); + } + + public File getPlayerdataFile(String playerUUID) { + return new File(playerdata, playerUUID + ".dat"); + } + + public File getBackupLevelDat() { + return new File(backupRoot, "level.dat" + "_" + millis()); + } + + public File getBackupPlayersFile(String playerName) { + return new File(backupPlayers, playerName + ".dat" + "_" + millis()); + } + + public File getBackupPlayerdataFile(String playerUUID) { + return new File(backupPlayerdata, playerUUID + ".dat" + "_" + millis()); + } + + private String millis() { + return new Timestamp(System.currentTimeMillis()).toString() + .replace(" ", "_").replace(":", "-").replace(".", "_"); + } + + public File[] getPlayersFiles() { + File[] files = players.listFiles(); + if (files == null) { + return new File[0]; + } else { + return files; + } + } + + public File[] getPlayerdataFiles() { + File[] files = playerdata.listFiles(); + if (files == null) { + return new File[0]; + } else { + return files; + } + } + + public CompoundTag readLevelDat() throws IOException { + return NBTUtils.readTagFromFile(levelDat); + } + + public LevelDatNbt createLevelDat() throws IOException, + MojangApiParsingException { + return new LevelDatNbt(readLevelDat()); + } + + public boolean tryBackupLevelDat() { + File backupFile = getBackupLevelDat(); + return ensureDirectoryExists(backupRoot) + && tryCopy(getLevelDat(), backupFile) && backupFile.isFile(); + } + + public boolean tryBackupPlayersFile(String playerName) { + File backupFile = getBackupPlayersFile(playerName); + return ensureDirectoryExists(backupPlayers) + && tryCopy(getPlayersFile(playerName), backupFile) + && backupFile.isFile(); + } + + public boolean tryBackupPlayerdataFile(String playerUUID) { + File backupFile = getBackupPlayerdataFile(playerUUID); + return ensureDirectoryExists(backupPlayerdata) + && tryCopy(getPlayerdataFile(playerUUID), backupFile) + && backupFile.isFile(); + } + + private boolean ensureDirectoryExists(File directory) { + if (!directory.exists()) { + directory.mkdirs(); + } + return directory.isDirectory(); + } + + private boolean tryCopy(File from, File to) { + try { + Files.copy(from.toPath(), to.toPath()); + return true; + } catch (IOException e) { + return false; + } + } + + /** + * Since version 1.7.6, minecraft stores players in the playerdata directory + * and uses the player uuid as filename. + */ + @NotNull + public List createMultiplayerPlayerNbts() { + List result = new ArrayList(); + for (File playerdataFile : getPlayerdataFiles()) { + if (playerdataFile.isFile()) { + result.add(createPlayerdataPlayerNbt(getPlayerUUIDFromPlayerdataFile(playerdataFile))); + } + } + if (!result.isEmpty()) { + Log.i("using players from the playerdata directory"); + return result; + } + for (File playersFile : getPlayersFiles()) { + if (playersFile.isFile()) { + result.add(createPlayersPlayerNbt(getPlayerNameFromPlayersFile(playersFile))); + } + } + if (!result.isEmpty()) { + Log.i("using players from the players directory"); + return result; + } + Log.i("no multiplayer players found"); + return result; + } + + /** + * We need to let the user decide if he wants to load the singleplayer + * player from the level.dat file or if he wants to load the multiplayer + * players. That is, because a singleplayer map will have the playerdata + * directory. It contains information about each player that ever played on + * the map. However, if the map is loaded as singleplayer map, minecraft + * will use the information that is stored in the level.dat file. It will + * also overwrite the file in the playerdata directory that belongs to the + * player that loaded the map in singleplayer mode. So, if we change the + * player location in the playerdata directory it will just be ignored if + * the map is used as singleplayer map. + */ + @NotNull + public List createSingleplayerPlayerNbts() { + Log.i("using player from level.dat"); + return Arrays.asList(createLevelDatPlayerNbt()); + } + + private PlayerNbt createLevelDatPlayerNbt() { + return new LevelDatPlayerNbt(this); + } + + private PlayerNbt createPlayerdataPlayerNbt(String playerUUID) { + return new PlayerdataPlayerNbt(this, playerUUID); + } + + private PlayerNbt createPlayersPlayerNbt(String playerName) { + return new PlayersPlayerNbt(this, playerName); + } + + private String getPlayerUUIDFromPlayerdataFile(File playerdataFile) { + return playerdataFile.getName().split("\\.")[0]; + } + + private String getPlayerNameFromPlayersFile(File playersFile) { + return playersFile.getName().split("\\.")[0]; + } +} diff --git a/src/main/java/amidst/mojangapi/file/directory/VersionDirectory.java b/src/main/java/amidst/mojangapi/file/directory/VersionDirectory.java new file mode 100644 index 000000000..51dff7ffd --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/directory/VersionDirectory.java @@ -0,0 +1,108 @@ +package amidst.mojangapi.file.directory; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.logging.Log; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.JsonReader; +import amidst.mojangapi.file.json.version.VersionJson; +import amidst.mojangapi.minecraftinterface.MinecraftInterface; +import amidst.mojangapi.minecraftinterface.local.DefaultClassTranslator; +import amidst.mojangapi.minecraftinterface.local.LocalMinecraftInterfaceBuilder; +import amidst.mojangapi.minecraftinterface.local.LocalMinecraftInterfaceCreationException; + +@Immutable +public class VersionDirectory { + private static final LocalMinecraftInterfaceBuilder LOCAL_MINECRAFT_INTERFACE_BUILDER = new LocalMinecraftInterfaceBuilder( + DefaultClassTranslator.INSTANCE.get()); + + private final DotMinecraftDirectory dotMinecraftDirectory; + private final String versionId; + private final File jar; + private final File json; + + public VersionDirectory(DotMinecraftDirectory dotMinecraftDirectory, + String versionId, File jar, File json) { + this.dotMinecraftDirectory = dotMinecraftDirectory; + this.versionId = versionId; + this.jar = jar; + this.json = json; + } + + public boolean isValid() { + return jar.isFile() && json.isFile(); + } + + public String getVersionId() { + return versionId; + } + + public File getJar() { + return jar; + } + + public File getJson() { + return json; + } + + @NotNull + public VersionJson readVersionJson() throws MojangApiParsingException, + IOException { + return JsonReader.readVersionFrom(json); + } + + @NotNull + public URLClassLoader createClassLoader() throws MalformedURLException { + if (json.isFile()) { + Log.i("Loading libraries."); + return doCreateClassLoader(getJarFileUrl(), getAllLibraryUrls()); + } else { + Log.i("Unable to find Minecraft library JSON at: " + json + + ". Skipping."); + return doCreateClassLoader(getJarFileUrl()); + } + } + + @NotNull + private URL getJarFileUrl() throws MalformedURLException { + return jar.toURI().toURL(); + } + + @NotNull + private List getAllLibraryUrls() { + try { + return readVersionJson().getLibraryUrls( + dotMinecraftDirectory.getLibraries()); + } catch (IOException | MojangApiParsingException e) { + Log.w("Invalid jar profile loaded. Library loading will be skipped. (Path: " + + json + ")"); + return new ArrayList(); + } + } + + @NotNull + private URLClassLoader doCreateClassLoader(URL jarFileUrl, + List libraries) { + libraries.add(jarFileUrl); + return new URLClassLoader(libraries.toArray(new URL[libraries.size()])); + } + + @NotNull + private URLClassLoader doCreateClassLoader(URL jarFileUrl) { + return new URLClassLoader(new URL[] { jarFileUrl }); + } + + @NotNull + public MinecraftInterface createLocalMinecraftInterface() + throws LocalMinecraftInterfaceCreationException { + return LOCAL_MINECRAFT_INTERFACE_BUILDER.create(this); + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/JsonReader.java b/src/main/java/amidst/mojangapi/file/json/JsonReader.java new file mode 100644 index 000000000..28e352fc6 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/JsonReader.java @@ -0,0 +1,145 @@ +package amidst.mojangapi.file.json; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; +import java.net.URL; + +import amidst.ResourceLoader; +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.logging.Log; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.URIUtils; +import amidst.mojangapi.file.json.launcherprofiles.LauncherProfilesJson; +import amidst.mojangapi.file.json.player.PlayerJson; +import amidst.mojangapi.file.json.player.SimplePlayerJson; +import amidst.mojangapi.file.json.version.VersionJson; +import amidst.mojangapi.file.json.versionlist.VersionListJson; + +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +@Immutable +public enum JsonReader { + ; + + private static final Gson GSON = new Gson(); + + private static final String REMOTE_VERSION_LIST = "https://s3.amazonaws.com/Minecraft.Download/versions/versions.json"; + private static final URL LOCAL_VERSION_LIST = ResourceLoader + .getResourceURL("/amidst/mojangapi/versions.json"); + + private static final String UUID_TO_PROFILE = "https://sessionserver.mojang.com/session/minecraft/profile/"; + private static final String PLAYERNAME_TO_UUID = "https://api.mojang.com/users/profiles/minecraft/"; + + @NotNull + public static VersionListJson readRemoteOrLocalVersionList() + throws FileNotFoundException { + Log.i("Beginning latest version list load."); + Log.i("Attempting to download remote version list..."); + VersionListJson remote = null; + try { + remote = readRemoteVersionList(); + } catch (IOException | MojangApiParsingException e) { + Log.w("Unable to read remote version list."); + Log.printTraceStack(e); + Log.w("Aborting version list load. URL: " + REMOTE_VERSION_LIST); + } + if (remote != null) { + Log.i("Successfully loaded version list. URL: " + + REMOTE_VERSION_LIST); + return remote; + } + Log.i("Attempting to load local version list..."); + VersionListJson local = null; + try { + local = readLocalVersionListFromResource(); + } catch (IOException | MojangApiParsingException e) { + Log.w("Unable to read local version list."); + Log.printTraceStack(e); + Log.w("Aborting version list load. URL: " + LOCAL_VERSION_LIST); + } + if (local != null) { + Log.i("Successfully loaded version list. URL: " + + LOCAL_VERSION_LIST); + return local; + } + Log.w("Failed to load both remote and local version list."); + throw new FileNotFoundException("unable to read version list"); + } + + @NotNull + public static VersionListJson readRemoteVersionList() throws IOException, + MojangApiParsingException { + return read(URIUtils.newReader(REMOTE_VERSION_LIST), + VersionListJson.class); + } + + @NotNull + public static VersionListJson readLocalVersionListFromResource() + throws IOException, MojangApiParsingException { + return read(URIUtils.newReader(LOCAL_VERSION_LIST), + VersionListJson.class); + } + + @NotNull + public static VersionJson readVersionFrom(File file) + throws MojangApiParsingException, IOException { + return read(URIUtils.newReader(file), VersionJson.class); + } + + @NotNull + public static LauncherProfilesJson readLauncherProfilesFrom(File file) + throws MojangApiParsingException, IOException { + return read(URIUtils.newReader(file), LauncherProfilesJson.class); + } + + @NotNull + public static T read(Reader reader, Class clazz) + throws MojangApiParsingException, IOException { + try (Reader theReader = reader) { + T result = GSON.fromJson(theReader, clazz); + if (result != null) { + return result; + } else { + throw new MojangApiParsingException("result was null"); + } + } catch (JsonSyntaxException e) { + throw new MojangApiParsingException(e); + } catch (JsonIOException e) { + throw new IOException(e); + } + } + + @NotNull + public static PlayerJson readPlayerFromUUID(String uuid) + throws IOException, MojangApiParsingException { + return read(URIUtils.newReader(UUID_TO_PROFILE + uuid), + PlayerJson.class); + } + + @NotNull + public static SimplePlayerJson readSimplePlayerFromPlayerName( + String playerName) throws IOException, MojangApiParsingException { + return read(URIUtils.newReader(PLAYERNAME_TO_UUID + playerName), + SimplePlayerJson.class); + } + + @NotNull + public static T read(String string, Class clazz) + throws MojangApiParsingException { + try { + T result = GSON.fromJson(string, clazz); + if (result != null) { + return result; + } else { + throw new MojangApiParsingException("result was null"); + } + } catch (JsonSyntaxException e) { + throw new MojangApiParsingException(e); + } + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/PlayerInformationRetriever.java b/src/main/java/amidst/mojangapi/file/json/PlayerInformationRetriever.java new file mode 100644 index 000000000..22c400be5 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/PlayerInformationRetriever.java @@ -0,0 +1,94 @@ +package amidst.mojangapi.file.json; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; + +import javax.imageio.ImageIO; + +import amidst.documentation.Immutable; +import amidst.logging.Log; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.player.PlayerJson; + +@Immutable +public enum PlayerInformationRetriever { + ; + + private static final String SIMPLE_PLAYER_SKIN_URL = "http://s3.amazonaws.com/MinecraftSkins/"; + + public static PlayerJson tryGetPlayerJsonByName(String name) { + try { + return getPlayerJsonByName(name); + } catch (IOException | MojangApiParsingException | NullPointerException e) { + Log.w("unable to load player information by name: " + name); + return null; + } + } + + public static PlayerJson tryGetPlayerJsonByUUID(String uuid) { + try { + return getPlayerJsonByUUID(uuid); + } catch (IOException | MojangApiParsingException | NullPointerException e) { + Log.w("unable to load player information by uuid: " + uuid); + return null; + } + } + + public static BufferedImage tryGetPlayerHeadByName(String name) { + try { + return getPlayerHeadByName(name); + } catch (IOException | NullPointerException e) { + Log.w("unable to load player head by name: " + name); + return null; + } + } + + public static BufferedImage tryGetPlayerHeadBySkinUrl(String skinUrl) { + try { + return getPlayerHeadBySkinUrl(skinUrl); + } catch (IOException | NullPointerException e) { + Log.w("unable to load player head by skin url: " + skinUrl); + return null; + } + } + + private static PlayerJson getPlayerJsonByName(String name) + throws IOException, MojangApiParsingException { + return JsonReader.readPlayerFromUUID(JsonReader + .readSimplePlayerFromPlayerName(name).getId()); + } + + private static PlayerJson getPlayerJsonByUUID(String uuid) + throws IOException, MojangApiParsingException { + return JsonReader.readPlayerFromUUID(uuid); + } + + private static BufferedImage getPlayerHeadByName(String name) + throws IOException { + return extractPlayerHead(new URL(SIMPLE_PLAYER_SKIN_URL + name + ".png")); + } + + private static BufferedImage getPlayerHeadBySkinUrl(String skinUrl) + throws IOException { + return extractPlayerHead(new URL(skinUrl)); + } + + private static BufferedImage extractPlayerHead(URL url) throws IOException { + return extractPlayerHead(ImageIO.read(url)); + } + + private static BufferedImage extractPlayerHead(BufferedImage skin) { + BufferedImage head = new BufferedImage(20, 20, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = head.createGraphics(); + g2d.setColor(Color.black); + g2d.fillRect(0, 0, 20, 20); + g2d.drawImage(skin, 2, 2, 18, 18, 8, 8, 16, 16, null); + g2d.dispose(); + skin.flush(); + return head; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/ReleaseType.java b/src/main/java/amidst/mojangapi/file/json/ReleaseType.java new file mode 100644 index 000000000..55c4369ff --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/ReleaseType.java @@ -0,0 +1,35 @@ +package amidst.mojangapi.file.json; + +import amidst.documentation.Immutable; + +import com.google.gson.annotations.SerializedName; + +@Immutable +public enum ReleaseType { + // @formatter:off + @SerializedName("snapshot") + SNAPSHOT("snapshot", "S"), + @SerializedName("release") + RELEASE("release", "R"), + @SerializedName("old_beta") + OLD_BETA("old_beta", "B"), + @SerializedName("old_alpha") + OLD_ALPHA("old_alpha", "A"); + // @formatter:on + + private final String name; + private final String typeChar; + + private ReleaseType(String name, String typeChar) { + this.name = name; + this.typeChar = typeChar; + } + + public String getName() { + return name; + } + + public String getTypeChar() { + return typeChar; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/launcherprofiles/LauncherProfileJson.java b/src/main/java/amidst/mojangapi/file/json/launcherprofiles/LauncherProfileJson.java new file mode 100644 index 000000000..775abadad --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/launcherprofiles/LauncherProfileJson.java @@ -0,0 +1,81 @@ +package amidst.mojangapi.file.json.launcherprofiles; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.List; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.file.directory.ProfileDirectory; +import amidst.mojangapi.file.directory.VersionDirectory; +import amidst.mojangapi.file.json.ReleaseType; +import amidst.mojangapi.file.json.versionlist.VersionListJson; + +@Immutable +public class LauncherProfileJson { + private volatile String name; + private volatile String lastVersionId; + private volatile String gameDir; + private volatile List allowedReleaseTypes = Arrays + .asList(ReleaseType.RELEASE); + + @GsonConstructor + public LauncherProfileJson() { + } + + public String getName() { + return name; + } + + public String getLastVersionId() { + return lastVersionId; + } + + public String getGameDir() { + return gameDir; + } + + @NotNull + public ProfileDirectory createValidProfileDirectory(MojangApi mojangApi) + throws FileNotFoundException { + if (gameDir != null) { + ProfileDirectory result = new ProfileDirectory(new File(gameDir)); + if (result.isValid()) { + return result; + } else { + throw new FileNotFoundException( + "cannot find valid profile directory for launcher profile '" + + name + "': " + gameDir); + } + } else { + return new ProfileDirectory(mojangApi.getDotMinecraftDirectory() + .getRoot()); + } + } + + @NotNull + public VersionDirectory createValidVersionDirectory(MojangApi mojangApi) + throws FileNotFoundException { + VersionListJson versionList = mojangApi.getVersionList(); + if (lastVersionId != null) { + VersionDirectory result = mojangApi + .createVersionDirectory(lastVersionId); + if (result.isValid()) { + return result; + } + } else { + VersionDirectory result = versionList + .tryFindFirstValidVersionDirectory(allowedReleaseTypes, + mojangApi); + if (result != null) { + return result; + } + } + throw new FileNotFoundException( + "cannot find valid version directory for launcher profile '" + + name + "'"); + } +} \ No newline at end of file diff --git a/src/main/java/amidst/mojangapi/file/json/launcherprofiles/LauncherProfilesJson.java b/src/main/java/amidst/mojangapi/file/json/launcherprofiles/LauncherProfilesJson.java new file mode 100644 index 000000000..c5213f6d8 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/launcherprofiles/LauncherProfilesJson.java @@ -0,0 +1,22 @@ +package amidst.mojangapi.file.json.launcherprofiles; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class LauncherProfilesJson { + private volatile Map profiles = Collections + .emptyMap(); + + @GsonConstructor + public LauncherProfilesJson() { + } + + public Collection getProfiles() { + return profiles.values(); + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/player/MetadataJson.java b/src/main/java/amidst/mojangapi/file/json/player/MetadataJson.java new file mode 100644 index 000000000..250422909 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/player/MetadataJson.java @@ -0,0 +1,17 @@ +package amidst.mojangapi.file.json.player; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class MetadataJson { + private volatile String model; + + @GsonConstructor + public MetadataJson() { + } + + public String getModel() { + return model; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/player/PlayerJson.java b/src/main/java/amidst/mojangapi/file/json/player/PlayerJson.java new file mode 100644 index 000000000..a92148072 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/player/PlayerJson.java @@ -0,0 +1,61 @@ +package amidst.mojangapi.file.json.player; + +import java.util.Collections; +import java.util.List; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.JsonReader; + +@Immutable +public class PlayerJson { + private volatile String id; + private volatile String name; + private volatile List properties = Collections.emptyList(); + + @GsonConstructor + public PlayerJson() { + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public List getProperties() { + return properties; + } + + @NotNull + public TexturesPropertyJson readTexturesProperty() + throws MojangApiParsingException { + for (PropertyJson property : properties) { + if (property.isTexturesProperty()) { + return JsonReader.read(property.getDecodedValue(), + TexturesPropertyJson.class); + } + } + throw new MojangApiParsingException( + "player json does not contain the textures property"); + } + + @NotNull + public String getSkinUrl() throws MojangApiParsingException { + try { + String result = readTexturesProperty().getTextures().getSKIN() + .getUrl(); + if (result != null) { + return result; + } else { + throw new MojangApiParsingException("unable to get skin url"); + } + } catch (NullPointerException e) { + throw new MojangApiParsingException("unable to get skin url", e); + } + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/player/PropertyJson.java b/src/main/java/amidst/mojangapi/file/json/player/PropertyJson.java new file mode 100644 index 000000000..91a9d22aa --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/player/PropertyJson.java @@ -0,0 +1,47 @@ +package amidst.mojangapi.file.json.player; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.file.MojangApiParsingException; + +@Immutable +public class PropertyJson { + private volatile String name; + private volatile String value; + + @GsonConstructor + public PropertyJson() { + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + @NotNull + public String getDecodedValue() throws MojangApiParsingException { + if (value == null) { + throw new MojangApiParsingException( + "unable to decode property value"); + } else { + return new String(Base64.getDecoder().decode( + value.getBytes(StandardCharsets.UTF_8)), + StandardCharsets.UTF_8); + } + } + + public boolean isTexturesProperty() throws MojangApiParsingException { + if (name == null) { + throw new MojangApiParsingException("property has no name"); + } else { + return name.equals("textures"); + } + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/player/SKINJson.java b/src/main/java/amidst/mojangapi/file/json/player/SKINJson.java new file mode 100644 index 000000000..af4332e2b --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/player/SKINJson.java @@ -0,0 +1,26 @@ +package amidst.mojangapi.file.json.player; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class SKINJson { + private volatile String url; + private volatile MetadataJson metadata; + + @GsonConstructor + public SKINJson() { + } + + public String getUrl() { + return url; + } + + public MetadataJson getMetadata() { + return metadata; + } + + public boolean isSlimModel() { + return metadata != null && metadata.getModel().equals("slim"); + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/player/SimplePlayerJson.java b/src/main/java/amidst/mojangapi/file/json/player/SimplePlayerJson.java new file mode 100644 index 000000000..887b0ba82 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/player/SimplePlayerJson.java @@ -0,0 +1,20 @@ +package amidst.mojangapi.file.json.player; + +import amidst.documentation.GsonConstructor; + +public class SimplePlayerJson { + private String id; + private String name; + + @GsonConstructor + public SimplePlayerJson() { + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/player/TexturesJson.java b/src/main/java/amidst/mojangapi/file/json/player/TexturesJson.java new file mode 100644 index 000000000..443b3d6a4 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/player/TexturesJson.java @@ -0,0 +1,17 @@ +package amidst.mojangapi.file.json.player; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class TexturesJson { + private volatile SKINJson SKIN; + + @GsonConstructor + public TexturesJson() { + } + + public SKINJson getSKIN() { + return SKIN; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/player/TexturesPropertyJson.java b/src/main/java/amidst/mojangapi/file/json/player/TexturesPropertyJson.java new file mode 100644 index 000000000..3b2a4d302 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/player/TexturesPropertyJson.java @@ -0,0 +1,17 @@ +package amidst.mojangapi.file.json.player; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class TexturesPropertyJson { + private volatile TexturesJson textures; + + @GsonConstructor + public TexturesPropertyJson() { + } + + public TexturesJson getTextures() { + return textures; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/version/LibraryJson.java b/src/main/java/amidst/mojangapi/file/json/version/LibraryJson.java new file mode 100644 index 000000000..1b1847136 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/version/LibraryJson.java @@ -0,0 +1,39 @@ +package amidst.mojangapi.file.json.version; + +import java.util.Collections; +import java.util.List; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class LibraryJson { + private volatile String name; + private volatile List rules = Collections.emptyList(); + + @GsonConstructor + public LibraryJson() { + } + + public String getName() { + return name; + } + + /** + * Note, that multiple rules might be applicable. We take the last + * applicable rule. However, this might be wrong so we need to take the most + * specific rule? For now this works fine. + */ + public boolean isActive(String os) { + if (rules.isEmpty()) { + return true; + } + boolean result = false; + for (LibraryRuleJson rule : rules) { + if (rule.isApplicable(os)) { + result = rule.isAllowed(); + } + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/amidst/mojangapi/file/json/version/LibraryRuleJson.java b/src/main/java/amidst/mojangapi/file/json/version/LibraryRuleJson.java new file mode 100644 index 000000000..12633d286 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/version/LibraryRuleJson.java @@ -0,0 +1,24 @@ +package amidst.mojangapi.file.json.version; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class LibraryRuleJson { + private static final String ACTION_ALLOW = "allow"; + + private volatile String action; + private volatile LibraryRuleOsJson os; + + @GsonConstructor + public LibraryRuleJson() { + } + + public boolean isApplicable(String os) { + return this.os == null || this.os.getName().equals(os); + } + + public boolean isAllowed() { + return action.equals(ACTION_ALLOW); + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/version/LibraryRuleOsJson.java b/src/main/java/amidst/mojangapi/file/json/version/LibraryRuleOsJson.java new file mode 100644 index 000000000..4c6616541 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/version/LibraryRuleOsJson.java @@ -0,0 +1,17 @@ +package amidst.mojangapi.file.json.version; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; + +@Immutable +public class LibraryRuleOsJson { + private volatile String name; + + @GsonConstructor + public LibraryRuleOsJson() { + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/version/VersionJson.java b/src/main/java/amidst/mojangapi/file/json/version/VersionJson.java new file mode 100644 index 000000000..43f793ef3 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/version/VersionJson.java @@ -0,0 +1,29 @@ +package amidst.mojangapi.file.json.version; + +import java.io.File; +import java.net.URL; +import java.util.Collections; +import java.util.List; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.file.LibraryFinder; + +@Immutable +public class VersionJson { + private volatile List libraries = Collections.emptyList(); + + @GsonConstructor + public VersionJson() { + } + + public List getLibraries() { + return libraries; + } + + @NotNull + public List getLibraryUrls(File librariesDirectory) { + return LibraryFinder.getLibraryUrls(librariesDirectory, libraries); + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/versionlist/VersionListEntryJson.java b/src/main/java/amidst/mojangapi/file/json/versionlist/VersionListEntryJson.java new file mode 100644 index 000000000..c847d1c76 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/versionlist/VersionListEntryJson.java @@ -0,0 +1,97 @@ +package amidst.mojangapi.file.json.versionlist; + +import java.io.IOException; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.logging.Log; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.file.FilenameFactory; +import amidst.mojangapi.file.URIUtils; +import amidst.mojangapi.file.directory.VersionDirectory; +import amidst.mojangapi.file.json.ReleaseType; + +@Immutable +public class VersionListEntryJson { + private volatile String id; + private volatile ReleaseType type; + + @GsonConstructor + public VersionListEntryJson() { + } + + public String getId() { + return id; + } + + public ReleaseType getType() { + return type; + } + + public VersionDirectory createVersionDirectory(MojangApi mojangApi) { + return mojangApi.createVersionDirectory(id); + } + + public String getClientJar(String prefix) { + return FilenameFactory.getClientJar(prefix, id); + } + + public String getClientJson(String prefix) { + return FilenameFactory.getClientJson(prefix, id); + } + + public String getServerJar(String prefix) { + return FilenameFactory.getServerJar(prefix, id); + } + + public String getRemoteClientJar() { + return FilenameFactory.getRemoteClientJar(id); + } + + public String getRemoteClientJson() { + return FilenameFactory.getRemoteClientJson(id); + } + + public String getRemoteServerJar() { + return FilenameFactory.getRemoteServerJar(id); + } + + public boolean hasServer() { + return URIUtils.exists(getRemoteServerJar()); + } + + public boolean hasClient() { + return URIUtils.exists(getRemoteClientJar()); + } + + public void downloadServer(String prefix) throws IOException { + URIUtils.download(getRemoteServerJar(), getServerJar(prefix)); + } + + public void downloadClient(String prefix) throws IOException { + URIUtils.download(getRemoteClientJar(), getClientJar(prefix)); + URIUtils.download(getRemoteClientJson(), getClientJson(prefix)); + } + + public boolean tryDownloadServer(String prefix) { + try { + downloadServer(prefix); + return true; + } catch (IOException e) { + Log.w("unable to download server: " + id); + e.printStackTrace(); + } + return false; + } + + public boolean tryDownloadClient(String prefix) { + try { + downloadClient(prefix); + return true; + } catch (IOException e) { + Log.w("unable to download client: " + id); + e.printStackTrace(); + } + return false; + } +} diff --git a/src/main/java/amidst/mojangapi/file/json/versionlist/VersionListJson.java b/src/main/java/amidst/mojangapi/file/json/versionlist/VersionListJson.java new file mode 100644 index 000000000..cbf39e4a3 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/json/versionlist/VersionListJson.java @@ -0,0 +1,38 @@ +package amidst.mojangapi.file.json.versionlist; + +import java.util.Collections; +import java.util.List; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.mojangapi.MojangApi; +import amidst.mojangapi.file.directory.VersionDirectory; +import amidst.mojangapi.file.json.ReleaseType; + +@Immutable +public class VersionListJson { + private volatile List versions = Collections + .emptyList(); + + @GsonConstructor + public VersionListJson() { + } + + public List getVersions() { + return versions; + } + + public VersionDirectory tryFindFirstValidVersionDirectory( + List allowedReleaseTypes, MojangApi mojangApi) { + for (VersionListEntryJson version : versions) { + if (allowedReleaseTypes.contains(version.getType())) { + VersionDirectory versionDirectory = version + .createVersionDirectory(mojangApi); + if (versionDirectory.isValid()) { + return versionDirectory; + } + } + } + return null; + } +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/LevelDatNbt.java b/src/main/java/amidst/mojangapi/file/nbt/LevelDatNbt.java new file mode 100644 index 000000000..07a7dd4b7 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/LevelDatNbt.java @@ -0,0 +1,79 @@ +package amidst.mojangapi.file.nbt; + +import org.jnbt.CompoundTag; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.world.WorldType; + +@Immutable +public class LevelDatNbt { + private final long seed; + private final WorldType worldType; + private final String generatorOptions; + private final boolean hasPlayer; + + public LevelDatNbt(CompoundTag root) throws MojangApiParsingException { + try { + CompoundTag dataTag = readDataTag(root); + this.seed = readRandomSeed(dataTag); + if (hasGeneratorName(dataTag)) { + this.worldType = WorldType.from(readGeneratorName(dataTag)); + if (worldType == WorldType.CUSTOMIZED) { + this.generatorOptions = readGeneratorOptions(dataTag); + } else { + this.generatorOptions = ""; + } + } else { + this.worldType = WorldType.DEFAULT; + this.generatorOptions = ""; + } + this.hasPlayer = hasPlayerTag(dataTag); + } catch (NullPointerException e) { + throw new MojangApiParsingException("cannot read leve.dat", e); + } + } + + private CompoundTag readDataTag(CompoundTag root) { + return (CompoundTag) root.getValue().get(NBTTagKeys.TAG_KEY_DATA); + } + + private long readRandomSeed(CompoundTag dataTag) { + return (Long) dataTag.getValue().get(NBTTagKeys.TAG_KEY_RANDOM_SEED) + .getValue(); + } + + private boolean hasGeneratorName(CompoundTag dataTag) { + return dataTag.getValue().get(NBTTagKeys.TAG_KEY_GENERATOR_NAME) != null; + } + + private String readGeneratorName(CompoundTag dataTag) { + return (String) dataTag.getValue() + .get(NBTTagKeys.TAG_KEY_GENERATOR_NAME).getValue(); + } + + private String readGeneratorOptions(CompoundTag dataTag) { + return (String) dataTag.getValue() + .get(NBTTagKeys.TAG_KEY_GENERATOR_OPTIONS).getValue(); + } + + private boolean hasPlayerTag(CompoundTag dataTag) { + return dataTag.getValue().containsKey(NBTTagKeys.TAG_KEY_PLAYER); + } + + public long getSeed() { + return seed; + } + + public WorldType getWorldType() { + return worldType; + } + + public String getGeneratorOptions() { + return generatorOptions; + } + + public boolean hasPlayer() { + return hasPlayer; + } +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/NBTTagKeys.java b/src/main/java/amidst/mojangapi/file/nbt/NBTTagKeys.java new file mode 100644 index 000000000..f7bdf1cd0 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/NBTTagKeys.java @@ -0,0 +1,14 @@ +package amidst.mojangapi.file.nbt; + +import amidst.documentation.Immutable; + +@Immutable +public class NBTTagKeys { + public static final String TAG_KEY_BASE = "Base"; + public static final String TAG_KEY_DATA = "Data"; + public static final String TAG_KEY_POS = "Pos"; + public static final String TAG_KEY_PLAYER = "Player"; + public static final String TAG_KEY_RANDOM_SEED = "RandomSeed"; + public static final String TAG_KEY_GENERATOR_NAME = "generatorName"; + public static final String TAG_KEY_GENERATOR_OPTIONS = "generatorOptions"; +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/NBTUtils.java b/src/main/java/amidst/mojangapi/file/nbt/NBTUtils.java new file mode 100644 index 000000000..2d5da8bfa --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/NBTUtils.java @@ -0,0 +1,44 @@ +package amidst.mojangapi.file.nbt; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.jnbt.CompoundTag; +import org.jnbt.NBTInputStream; +import org.jnbt.NBTOutputStream; + +import amidst.documentation.Immutable; + +@Immutable +public enum NBTUtils { + ; + + public static CompoundTag readTagFromFile(File file) throws IOException { + try (NBTInputStream stream = createNBTInputStream(file)) { + return (CompoundTag) stream.readTag(); + } + } + + public static NBTInputStream createNBTInputStream(File file) + throws IOException { + return new NBTInputStream(new BufferedInputStream(new FileInputStream( + file))); + } + + public static void writeTagToFile(File out, CompoundTag root) + throws IOException { + try (NBTOutputStream outStream = createNBTOutputStream(out)) { + outStream.writeTag(root); + } + } + + public static NBTOutputStream createNBTOutputStream(File file) + throws IOException { + return new NBTOutputStream(new BufferedOutputStream( + new FileOutputStream(file))); + } +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/player/LevelDatPlayerNbt.java b/src/main/java/amidst/mojangapi/file/nbt/player/LevelDatPlayerNbt.java new file mode 100644 index 000000000..3e4854871 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/player/LevelDatPlayerNbt.java @@ -0,0 +1,44 @@ +package amidst.mojangapi.file.nbt.player; + +import java.io.IOException; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.directory.SaveDirectory; +import amidst.mojangapi.world.player.Player; +import amidst.mojangapi.world.player.PlayerCoordinates; +import amidst.mojangapi.world.player.PlayerInformation; +import amidst.mojangapi.world.player.PlayerInformationCache; + +@Immutable +public class LevelDatPlayerNbt extends PlayerNbt { + private final SaveDirectory saveDirectory; + + public LevelDatPlayerNbt(SaveDirectory saveDirectory) { + this.saveDirectory = saveDirectory; + } + + @Override + protected boolean tryBackup() { + return saveDirectory.tryBackupLevelDat(); + } + + @Override + protected void doWriteCoordinates(PlayerCoordinates coordinates) + throws MojangApiParsingException { + PlayerLocationSaver.writeToLevelDat(coordinates, + saveDirectory.getLevelDat()); + } + + @Override + public PlayerCoordinates readCoordinates() throws IOException, + MojangApiParsingException { + return PlayerLocationLoader.readFromLevelDat(saveDirectory + .readLevelDat()); + } + + @Override + public Player createPlayer(PlayerInformationCache cache) { + return new Player(PlayerInformation.theSingleplayerPlayer(), this); + } +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/player/PlayerLocationLoader.java b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerLocationLoader.java new file mode 100644 index 000000000..1b6667541 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerLocationLoader.java @@ -0,0 +1,62 @@ +package amidst.mojangapi.file.nbt.player; + +import java.util.List; + +import org.jnbt.CompoundTag; +import org.jnbt.ListTag; +import org.jnbt.Tag; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.nbt.NBTTagKeys; +import amidst.mojangapi.world.player.PlayerCoordinates; + +@Immutable +public enum PlayerLocationLoader { + ; + + public static PlayerCoordinates readFromPlayerFile(CompoundTag file) + throws MojangApiParsingException { + try { + return readPlayerCoordinates(file); + } catch (NullPointerException e) { + throw new MojangApiParsingException( + "cannot read player coordinates", e); + } + } + + public static PlayerCoordinates readFromLevelDat(CompoundTag file) + throws MojangApiParsingException { + try { + return readPlayerCoordinates(getSinglePlayerPlayerTag(getTagRootTag(file))); + } catch (NullPointerException e) { + throw new MojangApiParsingException( + "cannot read player coordinates", e); + } + } + + private static CompoundTag getTagRootTag(CompoundTag rootTag) { + return (CompoundTag) rootTag.getValue().get(NBTTagKeys.TAG_KEY_DATA); + } + + private static CompoundTag getSinglePlayerPlayerTag(CompoundTag rootDataTag) { + return (CompoundTag) rootDataTag.getValue().get( + NBTTagKeys.TAG_KEY_PLAYER); + + } + + private static PlayerCoordinates readPlayerCoordinates(CompoundTag tag) { + ListTag posTag = (ListTag) getTagPos(tag); + List posList = posTag.getValue(); + // @formatter:off + return new PlayerCoordinates( + (long) (double) (Double) posList.get(0).getValue(), + (long) (double) (Double) posList.get(1).getValue(), + (long) (double) (Double) posList.get(2).getValue()); + // @formatter:on + } + + private static Tag getTagPos(CompoundTag tag) { + return tag.getValue().get(NBTTagKeys.TAG_KEY_POS); + } +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/player/PlayerLocationSaver.java b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerLocationSaver.java new file mode 100644 index 000000000..200a09ff8 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerLocationSaver.java @@ -0,0 +1,131 @@ +package amidst.mojangapi.file.nbt.player; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jnbt.CompoundTag; +import org.jnbt.DoubleTag; +import org.jnbt.ListTag; +import org.jnbt.Tag; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.nbt.NBTTagKeys; +import amidst.mojangapi.file.nbt.NBTUtils; +import amidst.mojangapi.world.player.PlayerCoordinates; + +@Immutable +public enum PlayerLocationSaver { + ; + + public static void writeToPlayerFile(PlayerCoordinates coordinates, + File file) throws MojangApiParsingException { + try { + CompoundTag dataTag = NBTUtils.readTagFromFile(file); + CompoundTag modifiedDataTag = modifyPositionInDataTagMultiPlayer( + dataTag, coordinates); + NBTUtils.writeTagToFile(file, modifiedDataTag); + } catch (IOException | NullPointerException e) { + throw new MojangApiParsingException( + "cannot write player coordinates", e); + } + } + + public static void writeToLevelDat(PlayerCoordinates coordinates, File file) + throws MojangApiParsingException { + try { + CompoundTag baseTag = NBTUtils.readTagFromFile(file); + CompoundTag modifiedBaseTag = modifyPositionInBaseTagSinglePlayer( + baseTag, coordinates); + NBTUtils.writeTagToFile(file, modifiedBaseTag); + } catch (IOException | NullPointerException e) { + throw new MojangApiParsingException( + "cannot write player coordinates", e); + } + } + + private static CompoundTag modifyPositionInBaseTagSinglePlayer( + CompoundTag baseTag, PlayerCoordinates coordinates) { + Map baseMap = baseTag.getValue(); + Map modifiedBaseMap = modifyPositionInBaseMapSinglePlayer( + baseMap, coordinates); + return new CompoundTag(NBTTagKeys.TAG_KEY_BASE, modifiedBaseMap); + } + + private static Map modifyPositionInBaseMapSinglePlayer( + Map baseMap, PlayerCoordinates coordinates) { + Map result = new HashMap(); + CompoundTag dataTag = (CompoundTag) baseMap + .get(NBTTagKeys.TAG_KEY_DATA); + CompoundTag modifiedDataTag = modifyPositionInDataTagSinglePlayer( + dataTag, coordinates); + result.put(NBTTagKeys.TAG_KEY_DATA, modifiedDataTag); + return result; + } + + private static CompoundTag modifyPositionInDataTagSinglePlayer( + CompoundTag dataTag, PlayerCoordinates coordinates) { + Map dataMap = dataTag.getValue(); + Map modifiedDataMap = modifyPositionInDataMapSinglePlayer( + dataMap, coordinates); + return new CompoundTag(NBTTagKeys.TAG_KEY_DATA, modifiedDataMap); + } + + private static Map modifyPositionInDataMapSinglePlayer( + Map dataMap, PlayerCoordinates coordinates) { + Map result = new HashMap(dataMap); + CompoundTag playerTag = (CompoundTag) dataMap + .get(NBTTagKeys.TAG_KEY_PLAYER); + CompoundTag modifiedPlayerTag = modifyPositionInPlayerTagSinglePlayer( + playerTag, coordinates); + result.put(NBTTagKeys.TAG_KEY_PLAYER, modifiedPlayerTag); + return result; + } + + private static CompoundTag modifyPositionInPlayerTagSinglePlayer( + CompoundTag playerTag, PlayerCoordinates coordinates) { + Map playerMap = playerTag.getValue(); + Map modifiedPlayerMap = modifyPositionInPlayerMap( + playerMap, coordinates); + return new CompoundTag(NBTTagKeys.TAG_KEY_PLAYER, modifiedPlayerMap); + } + + private static CompoundTag modifyPositionInDataTagMultiPlayer( + CompoundTag dataTag, PlayerCoordinates coordinates) { + Map playerMap = dataTag.getValue(); + Map modifiedPlayerMap = modifyPositionInPlayerMap( + playerMap, coordinates); + return new CompoundTag(NBTTagKeys.TAG_KEY_DATA, modifiedPlayerMap); + } + + private static Map modifyPositionInPlayerMap( + Map playerMap, PlayerCoordinates coordinates) { + Map result = new HashMap(playerMap); + ListTag posTag = (ListTag) playerMap.get(NBTTagKeys.TAG_KEY_POS); + ListTag modifiedPosTag = modifyPositionInPosTag(posTag, coordinates); + result.put(NBTTagKeys.TAG_KEY_POS, modifiedPosTag); + return result; + } + + private static ListTag modifyPositionInPosTag(ListTag posTag, + PlayerCoordinates coordinates) { + List posList = posTag.getValue(); + List modifiedPosList = modifyPositionInPosList(posList, + coordinates); + return new ListTag(NBTTagKeys.TAG_KEY_POS, DoubleTag.class, + modifiedPosList); + } + + private static List modifyPositionInPosList(List posList, + PlayerCoordinates coordinates) { + List result = new ArrayList(posList); + result.set(0, new DoubleTag("x", coordinates.getX())); + result.set(1, new DoubleTag("y", coordinates.getY())); + result.set(2, new DoubleTag("z", coordinates.getZ())); + return result; + } +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/player/PlayerNbt.java b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerNbt.java new file mode 100644 index 000000000..4068f1815 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerNbt.java @@ -0,0 +1,32 @@ +package amidst.mojangapi.file.nbt.player; + +import java.io.IOException; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.world.player.Player; +import amidst.mojangapi.world.player.PlayerCoordinates; +import amidst.mojangapi.world.player.PlayerInformationCache; + +@Immutable +public abstract class PlayerNbt { + public boolean tryWriteCoordinates(PlayerCoordinates coordinates) + throws MojangApiParsingException { + if (tryBackup()) { + doWriteCoordinates(coordinates); + return true; + } else { + return false; + } + } + + protected abstract boolean tryBackup(); + + protected abstract void doWriteCoordinates(PlayerCoordinates coordinates) + throws MojangApiParsingException; + + public abstract PlayerCoordinates readCoordinates() throws IOException, + MojangApiParsingException; + + public abstract Player createPlayer(PlayerInformationCache cache); +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/player/PlayerdataPlayerNbt.java b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerdataPlayerNbt.java new file mode 100644 index 000000000..fbd6dd5b2 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/player/PlayerdataPlayerNbt.java @@ -0,0 +1,46 @@ +package amidst.mojangapi.file.nbt.player; + +import java.io.IOException; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.directory.SaveDirectory; +import amidst.mojangapi.file.nbt.NBTUtils; +import amidst.mojangapi.world.player.Player; +import amidst.mojangapi.world.player.PlayerCoordinates; +import amidst.mojangapi.world.player.PlayerInformationCache; + +@Immutable +public class PlayerdataPlayerNbt extends PlayerNbt { + private final SaveDirectory saveDirectory; + private final String playerUUID; + + public PlayerdataPlayerNbt(SaveDirectory saveDirectory, String playerUUID) { + this.saveDirectory = saveDirectory; + this.playerUUID = playerUUID; + } + + @Override + protected boolean tryBackup() { + return saveDirectory.tryBackupPlayerdataFile(playerUUID); + } + + @Override + protected void doWriteCoordinates(PlayerCoordinates coordinates) + throws MojangApiParsingException { + PlayerLocationSaver.writeToPlayerFile(coordinates, + saveDirectory.getPlayerdataFile(playerUUID)); + } + + @Override + public PlayerCoordinates readCoordinates() throws IOException, + MojangApiParsingException { + return PlayerLocationLoader.readFromPlayerFile(NBTUtils + .readTagFromFile(saveDirectory.getPlayerdataFile(playerUUID))); + } + + @Override + public Player createPlayer(PlayerInformationCache cache) { + return new Player(cache.getByUUID(playerUUID), this); + } +} diff --git a/src/main/java/amidst/mojangapi/file/nbt/player/PlayersPlayerNbt.java b/src/main/java/amidst/mojangapi/file/nbt/player/PlayersPlayerNbt.java new file mode 100644 index 000000000..a02dd42b4 --- /dev/null +++ b/src/main/java/amidst/mojangapi/file/nbt/player/PlayersPlayerNbt.java @@ -0,0 +1,46 @@ +package amidst.mojangapi.file.nbt.player; + +import java.io.IOException; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.directory.SaveDirectory; +import amidst.mojangapi.file.nbt.NBTUtils; +import amidst.mojangapi.world.player.Player; +import amidst.mojangapi.world.player.PlayerCoordinates; +import amidst.mojangapi.world.player.PlayerInformationCache; + +@Immutable +public class PlayersPlayerNbt extends PlayerNbt { + private final SaveDirectory saveDirectory; + private final String playerName; + + public PlayersPlayerNbt(SaveDirectory saveDirectory, String playerName) { + this.saveDirectory = saveDirectory; + this.playerName = playerName; + } + + @Override + protected boolean tryBackup() { + return saveDirectory.tryBackupPlayersFile(playerName); + } + + @Override + protected void doWriteCoordinates(PlayerCoordinates coordinates) + throws MojangApiParsingException { + PlayerLocationSaver.writeToPlayerFile(coordinates, + saveDirectory.getPlayersFile(playerName)); + } + + @Override + public PlayerCoordinates readCoordinates() throws IOException, + MojangApiParsingException { + return PlayerLocationLoader.readFromPlayerFile(NBTUtils + .readTagFromFile(saveDirectory.getPlayersFile(playerName))); + } + + @Override + public Player createPlayer(PlayerInformationCache cache) { + return new Player(cache.getByName(playerName), this); + } +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/MinecraftInterface.java b/src/main/java/amidst/mojangapi/minecraftinterface/MinecraftInterface.java new file mode 100644 index 000000000..b0f03abfb --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/MinecraftInterface.java @@ -0,0 +1,42 @@ +package amidst.mojangapi.minecraftinterface; + +import amidst.documentation.ThreadSafe; +import amidst.mojangapi.world.WorldType; + +/** + * Acts as an additional layer of abstraction for interfacing with Minecraft. + * This allows for other sources of data other than direct reflection against a + * loaded jar of Minecraft. + * + * Implementing classes need to be thread-safe! + * + * One minecraft interface can only handle one world at a time. + */ +@ThreadSafe +public interface MinecraftInterface { + /** + * @param useQuarterResolution + * Minecraft calculates biomes at quarter-resolution, then + * noisily interpolates the biome-map up to 1:1 resolution when + * needed, set useQuarterResolutionMap to true to read from the + * quarter-resolution map, or false to read values that have been + * interpolated up to full resolution. + * + * When useQuarterResolution is true, the x, y, width, and height + * paramaters must all correspond to a quarter of the Minecraft + * block coordinates/sizes you wish to obtain the biome data for. + * + * Amidst displays the quarter-resolution biome map, however full + * resolution is required to determine the position and nature of + * structures, as the noisy interpolation can change which biome + * a structure is located in (if the structure is located on a + * biome boundary). + */ + public int[] getBiomeData(int x, int y, int width, int height, + boolean useQuarterResolution) throws MinecraftInterfaceException; + + public void createWorld(long seed, WorldType worldType, + String generatorOptions) throws MinecraftInterfaceException; + + public RecognisedVersion getRecognisedVersion(); +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/MinecraftInterfaceException.java b/src/main/java/amidst/mojangapi/minecraftinterface/MinecraftInterfaceException.java new file mode 100644 index 000000000..b61b6669b --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/MinecraftInterfaceException.java @@ -0,0 +1,8 @@ +package amidst.mojangapi.minecraftinterface; + +@SuppressWarnings("serial") +public class MinecraftInterfaceException extends Exception { + public MinecraftInterfaceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/RecognisedVersion.java b/src/main/java/amidst/mojangapi/minecraftinterface/RecognisedVersion.java new file mode 100644 index 000000000..d87b74278 --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/RecognisedVersion.java @@ -0,0 +1,126 @@ +package amidst.mojangapi.minecraftinterface; + +import java.lang.reflect.Field; + +import amidst.documentation.Immutable; +import amidst.logging.Log; + +/** + * Information about what each supported version is + */ +@Immutable +public enum RecognisedVersion { + // @formatter:off + v15w44b("qtoombkapb[Llq;mn[J[[Jmj"), + V15w31c("oxnvlnjt[Llg;lz[J[[Jlv"), + V1_8_8("orntlljs[Lle;lx[J[[Jlt"), // 1.8.4, 1.8.5, 1.8.6, 1.8.7, and 1.8.8 all have the same typeDump version ID. They are all security issue fixes. + V1_8_3("osnulmjt[Llf;ly[J[[Jlu"), // 1.8.3 and 1.8.2 have the same typeDump version ID - probably because 1.8.2 -> 1.8.3 was a fix for a server-side bug (https://mojang.com/2015/02/minecraft-1-8-2-is-now-available/) + V1_8_1("wduyrdnq[Lqu;sp[J[[Jsa"), + V1_8("wbuwrcnp[Lqt;sn[J[[Jry"), + V14w21b("tjseoylw[Loq;qd[J[[Jpo"), + V1_7_10("riqinckb[Lmt;oi[J[[Jns"), + V1_7_9("rhqhnbkb[Lms;oh[J[[Jnr"), + V14w02a("qrponkki[Lnb;lv[J[[J"), + V1_7_4("pzozmvjs[Lmm;lg[J[[J"), + V1_7_2("pvovmsjp[Lmj;ld[J[[J"), + V13w39a_or_b("npmp[Lkn;jh[J[J[J[J[J[[J"), + V13w37b_or_38a("ntmt[Lkm;jg[J[J[J[J[J[[J"), + V13w37a("nsms[Lkl;jf[J[J[J[J[J[[J"), + V13w36b("nkmk[Lkd;hw[J[J[J[J[J[[J"), + V13w36a("nkmk[Lkd;hx[J[J[J[J[J[[J"), + V1_6_4("mvlv[Ljs;hn[J[J[J[J[J[[J"), + V1_6_2("mulu[Ljr;hm[J[J[J[J[J[[J"), + V1_6_1("msls[Ljp;hk[J[J[J[J[J[[J"), + V1_5_2("[Bbdzbdrbawemabdsbfybdvngngbeuawfbgeawvawvaxrawbbfqausbjgaycawwaraavybkcavwbjubkila"), + V1_5_1("[Bbeabdsbawemabdtbfzbdwngngbevawfbgfawvawvaxrawbbfrausbjhaycawwaraavybkdavwbjvbkila"), + V1_5_0("Invalid"), // TODO: This makes no sense? 1.5.0 is not on the version list! + V1_4_6("[Baywayoaaszleaypbavaysmdazratabbaatqatqaulaswbanarnbdzauwatraohastbevasrbenbezbdmbdjkh"), // Includes 1.4.7 + V1_4_5("[Bayoaygaasrleayhbakaykmdazfassbapatjatjaueasobacarfbdoaupatkanzaslbekasjbecbenbdbbcykh"), + V1_4_2("[Baxgawyaarjkpawzayyaxclnaxxarkazcasbasbaswargaytaqabcbathascamuardbcxarbbcpbdabbobbljy"), + V1_3_2("[Batkatcaaofjbatdavbatgjwaubaogavfaovaovapnaocauwamxaxvapyaowajqanzayqanxayjaytaxkaxhik"), + V1_3_1("adb"), + V1_3pre("acl"), + V12w27a("acs"), + V12w26a("acl"), + V12w25a("acg"), + V12w24a("aca"), + V12w23b("acg"), + V12w22a("ace"), + V12w21b("aby"), + V12w21a("abm"), + V12w19a("aau"), + V1_2_4("[Bkivmaftxdlvqacqcwfcaawnlnlvpjclrckqdaiyxgplhusdakagi[J[Jalfqabv"), // Includes 1.2.5 + V1_2_2("wl"), + V12w08a("wj"), + V12w07b("wd"), + V12w06a("wb"), + V12w05a("vy"), + V12w04a("vu"), + V12w03a("vj"), + V1_1("[Bjsudadrvqluhaarcqevyzmqmqugiokzcepgagqvsonhhrgahqfy[J[Jaitpdbo"), + V1_0("[Baesmmaijryafvdinqfdrzhabeabexexwadtnglkqdfagvkiahmhsadk[J[Jtkgkyu"), + V1_9pre6("uk"), // TODO: Remove these versions? + V1_9pre5("ug"), + V1_9pre4("uh"), //TODO stronghold reset?? + V1_9pre3("to"), + V1_9pre2("sv"), + V1_9pre1("sq"), + Vbeta_1_8_1("[Bhwqpyrrviqswdbzdqurkhqrgviwbomnabjrxmafvoeacfer[J[Jaddmkbb"), // Had to rename from V1_8_1 - should it just be removed? + UNKNOWN(null); // Make sure this is the last entry, so UNKNOWN.isAtLeast(...) returns always false. + // @formatter:on + + public static RecognisedVersion from(Field[] fields) { + return from(generateMagicString(fields)); + } + + private static String generateMagicString(Field[] fields) { + String result = ""; + for (Field field : fields) { + String typeString = field.getType().toString(); + if (typeString.startsWith("class ") && !typeString.contains(".")) { + result += typeString.substring(6); + } + } + return result; + } + + public static RecognisedVersion from(String magicString) { + for (RecognisedVersion recognisedVersion : RecognisedVersion.values()) { + if (magicString.equals(recognisedVersion.magicString)) { + Log.i("Recognised Minecraft Version " + + recognisedVersion.getName() + + " with the magic string \"" + magicString + "\"."); + return recognisedVersion; + } + } + Log.i("Unable to recognise Minecraft Version with the magic string \"" + + magicString + "\"."); + return RecognisedVersion.UNKNOWN; + } + + private final String name; + private final String magicString; + + private RecognisedVersion(String magicString) { + this.name = super.toString().replace("_", "."); + this.magicString = magicString; + } + + public String getName() { + return name; + } + + public String getMagicString() { + return magicString; + } + + @Deprecated + public boolean isSaveEnabled() { + return this != V12w21a && this != V12w21b && this != V12w22a + && this != UNKNOWN; + } + + public boolean isAtLeast(RecognisedVersion other) { + return this.ordinal() <= other.ordinal(); + } +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/local/DefaultClassTranslator.java b/src/main/java/amidst/mojangapi/minecraftinterface/local/DefaultClassTranslator.java new file mode 100644 index 000000000..b95b27d7b --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/local/DefaultClassTranslator.java @@ -0,0 +1,76 @@ +package amidst.mojangapi.minecraftinterface.local; + +import amidst.clazz.real.AccessFlags; +import amidst.clazz.real.RealClass; +import amidst.clazz.translator.ClassTranslator; +import amidst.documentation.Immutable; + +@Immutable +public enum DefaultClassTranslator { + INSTANCE; + + private static final int WILDCARD = RealClass.CLASS_DATA_WILDCARD; + + private final ClassTranslator classTranslator = createClassTranslator(); + + public ClassTranslator get() { + return classTranslator; + } + + // @formatter:off + private ClassTranslator createClassTranslator() { + return ClassTranslator + .builder() + .ifDetect() + .wildcardBytes(createIntCacheWildcardBytes()) + .or() + .stringContaining(", tcache: ") + .thenDeclareRequired(SymbolicNames.CLASS_INT_CACHE) + .requiredMethod(SymbolicNames.METHOD_INT_CACHE_RESET_INT_CACHE, "a").end() + .next() + .ifDetect() + .stringContaining("default_1_1") + .thenDeclareOptional(SymbolicNames.CLASS_WORLD_TYPE) + .requiredField(SymbolicNames.FIELD_WORLD_TYPE_DEFAULT, "b") + .requiredField(SymbolicNames.FIELD_WORLD_TYPE_FLAT, "c") + .requiredField(SymbolicNames.FIELD_WORLD_TYPE_LARGE_BIOMES, "d") + .requiredField(SymbolicNames.FIELD_WORLD_TYPE_AMPLIFIED, "e") + .requiredField(SymbolicNames.FIELD_WORLD_TYPE_CUSTOMIZED, "f") + .next() + .ifDetect() + .longs(1000L, 2001L, 2000L) + .thenDeclareRequired(SymbolicNames.CLASS_GEN_LAYER) + // one if the initializeAllBiomeGenerators-methods is required! + .optionalMethod(SymbolicNames.METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_1, "a").real("long").end() + .optionalMethod(SymbolicNames.METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_2, "a").real("long").symbolic("WorldType").end() + .optionalMethod(SymbolicNames.METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_3, "a").real("long").symbolic("WorldType").real("String").end() + .requiredMethod(SymbolicNames.METHOD_GEN_LAYER_GET_INTS, "a").real("int") .real("int") .real("int") .real("int").end() + .next() + .ifDetect() + .numberOfConstructors(0) + .numberOfMethods(6) + .numberOfFields(3) + .fieldFlags(AccessFlags.PRIVATE | AccessFlags.STATIC, 0, 1, 2) + .utf8EqualTo("isDebugEnabled") + .or() + .numberOfConstructors(0) + .numberOfMethods(6) + .numberOfFields(3) + .fieldFlags(AccessFlags.PUBLIC | AccessFlags.STATIC, 0) + .fieldFlags(AccessFlags.PRIVATE | AccessFlags.STATIC, 1, 2) + .utf8EqualTo("isDebugEnabled") + .thenDeclareOptional(SymbolicNames.CLASS_BLOCK_INIT) + .requiredMethod(SymbolicNames.METHOD_BLOCK_INIT_INITIALIZE, "c").end() + .construct(); + } + // @formatter:on + + private int[] createIntCacheWildcardBytes() { + return new int[] { 0x11, 0x01, 0x00, 0xB3, 0x00, WILDCARD, 0xBB, 0x00, + WILDCARD, 0x59, 0xB7, 0x00, WILDCARD, 0xB3, 0x00, WILDCARD, + 0xBB, 0x00, WILDCARD, 0x59, 0xB7, 0x00, WILDCARD, 0xB3, 0x00, + WILDCARD, 0xBB, 0x00, WILDCARD, 0x59, 0xB7, 0x00, WILDCARD, + 0xB3, 0x00, WILDCARD, 0xBB, 0x00, WILDCARD, 0x59, 0xB7, 0x00, + WILDCARD, 0xB3, 0x00, WILDCARD, 0xB1 }; + } +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterface.java b/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterface.java new file mode 100644 index 000000000..64292c80a --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterface.java @@ -0,0 +1,115 @@ +package amidst.mojangapi.minecraftinterface.local; + +import java.lang.reflect.InvocationTargetException; + +import amidst.clazz.symbolic.SymbolicClass; +import amidst.clazz.symbolic.SymbolicObject; +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.minecraftinterface.MinecraftInterface; +import amidst.mojangapi.minecraftinterface.MinecraftInterfaceException; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.WorldType; + +@ThreadSafe +public class LocalMinecraftInterface implements MinecraftInterface { + /** + * A GenLayer instance, at quarter scale to the final biome layer (i.e. both + * axis are divided by 4). Minecraft calculates biomes at + * quarter-resolution, then noisily interpolates the biome-map up to 1:1 + * resolution when needed, this is the biome GenLayer before it is + * interpolated. + */ + private volatile SymbolicObject quarterResolutionBiomeGenerator; + + /** + * A GenLayer instance, the biome layer. (1:1 scale) Minecraft calculates + * biomes at quarter-resolution, then noisily interpolates the biome-map up + * to 1:1 resolution when needed, this is the interpolated biome GenLayer. + */ + private volatile SymbolicObject fullResolutionBiomeGenerator; + + private final SymbolicClass intCacheClass; + private final SymbolicClass blockInitClass; + private final SymbolicClass genLayerClass; + private final SymbolicClass worldTypeClass; + private final RecognisedVersion recognisedVersion; + + LocalMinecraftInterface(SymbolicClass intCacheClass, + SymbolicClass blockInitClass, SymbolicClass genLayerClass, + SymbolicClass worldTypeClass, RecognisedVersion recognisedVersion) { + this.intCacheClass = intCacheClass; + this.blockInitClass = blockInitClass; + this.genLayerClass = genLayerClass; + this.worldTypeClass = worldTypeClass; + this.recognisedVersion = recognisedVersion; + } + + // @formatter:off + @Override + public synchronized int[] getBiomeData(int x, int y, int width, int height, boolean useQuarterResolution) + throws MinecraftInterfaceException { + try { + intCacheClass.callStaticMethod(SymbolicNames.METHOD_INT_CACHE_RESET_INT_CACHE); + return (int[]) getBiomeGenerator(useQuarterResolution).callMethod(SymbolicNames.METHOD_GEN_LAYER_GET_INTS, x, y, width, height); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new MinecraftInterfaceException("unable to get biome data", e); + } + } + + private SymbolicObject getBiomeGenerator(boolean useQuarterResolution) { + if (useQuarterResolution) { + return quarterResolutionBiomeGenerator; + } else { + return fullResolutionBiomeGenerator; + } + } + + @Override + public synchronized void createWorld(long seed, WorldType worldType, String generatorOptions) + throws MinecraftInterfaceException { + try { + Log.debug("Attempting to create world with seed: " + seed + ", type: " + worldType.getName() + ", and the following generator options:"); + Log.debug(generatorOptions); + initializeBlock(); + Object[] genLayers = getGenLayers(seed, worldType, generatorOptions); + quarterResolutionBiomeGenerator = new SymbolicObject(genLayerClass, genLayers[0]); + fullResolutionBiomeGenerator = new SymbolicObject(genLayerClass, genLayers[1]); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new MinecraftInterfaceException("unable to create world", e); + } + } + + /** + * Minecraft 1.8 and higher require block initialization to be called before + * creating a biome generator. + */ + private void initializeBlock() + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + if (blockInitClass != null) { + blockInitClass.callStaticMethod(SymbolicNames.METHOD_BLOCK_INIT_INITIALIZE); + } + } + + private Object[] getGenLayers(long seed, WorldType worldType, String generatorOptions) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + if (worldTypeClass == null) { + return (Object[]) genLayerClass.callStaticMethod(SymbolicNames.METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_1, seed); + } else if (genLayerClass.hasMethod(SymbolicNames.METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_3)) { + return (Object[]) genLayerClass.callStaticMethod(SymbolicNames.METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_3, seed, getWorldType(worldType).getObject(), generatorOptions); + } else { + return (Object[]) genLayerClass.callStaticMethod(SymbolicNames.METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_2, seed, getWorldType(worldType).getObject()); + } + } + + private SymbolicObject getWorldType(WorldType worldType) + throws IllegalArgumentException, IllegalAccessException { + return (SymbolicObject) worldTypeClass.getStaticFieldValue(worldType.getSymbolicFieldName()); + } + // @formatter:on + + @Override + public RecognisedVersion getRecognisedVersion() { + return recognisedVersion; + } +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterfaceBuilder.java b/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterfaceBuilder.java new file mode 100644 index 000000000..5c9943d83 --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterfaceBuilder.java @@ -0,0 +1,71 @@ +package amidst.mojangapi.minecraftinterface.local; + +import java.io.FileNotFoundException; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URLClassLoader; +import java.util.Map; + +import amidst.clazz.Classes; +import amidst.clazz.real.JarFileParsingException; +import amidst.clazz.symbolic.SymbolicClass; +import amidst.clazz.symbolic.SymbolicClassGraphCreationException; +import amidst.clazz.translator.ClassTranslator; +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.logging.Log; +import amidst.mojangapi.file.directory.VersionDirectory; +import amidst.mojangapi.minecraftinterface.MinecraftInterface; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; + +@Immutable +public class LocalMinecraftInterfaceBuilder { + private static final String CLIENT_CLASS_RESOURCE = "net/minecraft/client/Minecraft.class"; + private static final String CLIENT_CLASS = "net.minecraft.client.Minecraft"; + private static final String SERVER_CLASS_RESOURCE = "net/minecraft/server/MinecraftServer.class"; + private static final String SERVER_CLASS = "net.minecraft.server.MinecraftServer"; + + private final ClassTranslator translator; + + public LocalMinecraftInterfaceBuilder(ClassTranslator translator) { + this.translator = translator; + } + + @NotNull + public MinecraftInterface create(VersionDirectory versionDirectory) + throws LocalMinecraftInterfaceCreationException { + try { + URLClassLoader classLoader = versionDirectory.createClassLoader(); + RecognisedVersion recognisedVersion = RecognisedVersion + .from(getMainClassFields(classLoader)); + Map symbolicClassMap = Classes + .createSymbolicClassMap(versionDirectory.getJar(), + classLoader, translator); + Log.i("Minecraft load complete."); + return new LocalMinecraftInterface( + symbolicClassMap.get(SymbolicNames.CLASS_INT_CACHE), + symbolicClassMap.get(SymbolicNames.CLASS_BLOCK_INIT), + symbolicClassMap.get(SymbolicNames.CLASS_GEN_LAYER), + symbolicClassMap.get(SymbolicNames.CLASS_WORLD_TYPE), + recognisedVersion); + } catch (MalformedURLException | ClassNotFoundException + | FileNotFoundException | JarFileParsingException + | SymbolicClassGraphCreationException e) { + throw new LocalMinecraftInterfaceCreationException( + "unable to create local minecraft interface", e); + } + } + + @NotNull + private Field[] getMainClassFields(URLClassLoader classLoader) + throws ClassNotFoundException { + if (classLoader.findResource(CLIENT_CLASS_RESOURCE) != null) { + return classLoader.loadClass(CLIENT_CLASS).getDeclaredFields(); + } else if (classLoader.findResource(SERVER_CLASS_RESOURCE) != null) { + return classLoader.loadClass(SERVER_CLASS).getDeclaredFields(); + } else { + throw new ClassNotFoundException( + "unable to find the main class in the given jar file"); + } + } +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterfaceCreationException.java b/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterfaceCreationException.java new file mode 100644 index 000000000..11c989f77 --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/local/LocalMinecraftInterfaceCreationException.java @@ -0,0 +1,16 @@ +package amidst.mojangapi.minecraftinterface.local; + +import amidst.documentation.Immutable; + +@SuppressWarnings("serial") +@Immutable +public class LocalMinecraftInterfaceCreationException extends Exception { + public LocalMinecraftInterfaceCreationException(String message, + Throwable cause) { + super(message, cause); + } + + public LocalMinecraftInterfaceCreationException(String message) { + super(message); + } +} diff --git a/src/main/java/amidst/mojangapi/minecraftinterface/local/SymbolicNames.java b/src/main/java/amidst/mojangapi/minecraftinterface/local/SymbolicNames.java new file mode 100644 index 000000000..7838c1d5f --- /dev/null +++ b/src/main/java/amidst/mojangapi/minecraftinterface/local/SymbolicNames.java @@ -0,0 +1,27 @@ +package amidst.mojangapi.minecraftinterface.local; + +import amidst.documentation.Immutable; + +@Immutable +public enum SymbolicNames { + ; + + public static final String CLASS_INT_CACHE = "IntCache"; + public static final String METHOD_INT_CACHE_RESET_INT_CACHE = "resetIntCache"; + + public static final String CLASS_WORLD_TYPE = "WorldType"; + public static final String FIELD_WORLD_TYPE_DEFAULT = "default"; + public static final String FIELD_WORLD_TYPE_FLAT = "flat"; + public static final String FIELD_WORLD_TYPE_LARGE_BIOMES = "largeBiomes"; + public static final String FIELD_WORLD_TYPE_AMPLIFIED = "amplified"; + public static final String FIELD_WORLD_TYPE_CUSTOMIZED = "customized"; + + public static final String CLASS_GEN_LAYER = "GenLayer"; + public static final String METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_1 = "initializeAllBiomeGenerators1"; + public static final String METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_2 = "initializeAllBiomeGenerators2"; + public static final String METHOD_GEN_LAYER_INITIALIZE_ALL_BIOME_GENERATORS_3 = "initializeAllBiomeGenerators3"; + public static final String METHOD_GEN_LAYER_GET_INTS = "getInts"; + + public static final String CLASS_BLOCK_INIT = "BlockInit"; + public static final String METHOD_BLOCK_INIT_INITIALIZE = "initialize"; +} diff --git a/src/main/java/amidst/mojangapi/world/SeedHistoryLogger.java b/src/main/java/amidst/mojangapi/world/SeedHistoryLogger.java new file mode 100644 index 000000000..bdd2627c4 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/SeedHistoryLogger.java @@ -0,0 +1,63 @@ +package amidst.mojangapi.world; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.sql.Timestamp; +import java.util.Date; + +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; + +@ThreadSafe +public class SeedHistoryLogger { + private final File file; + + public SeedHistoryLogger(String filename) { + this.file = getHistoryFile(filename); + } + + private File getHistoryFile(String filename) { + if (filename != null) { + return new File(filename); + } else { + return null; + } + } + + public synchronized void log(WorldSeed seed) { + if (file != null) { + if (!file.exists()) { + tryCreateFile(); + } + if (file.exists() && file.isFile()) { + writeLine(seed); + } else { + Log.w("unable to write seed to seed log file"); + } + } + } + + private void tryCreateFile() { + try { + file.createNewFile(); + } catch (IOException e) { + Log.w("Unable to create history file: " + file); + e.printStackTrace(); + } + } + + private void writeLine(WorldSeed seed) { + try (FileWriter writer = new FileWriter(file, true)) { + writer.append(createLine(seed)); + } catch (IOException e) { + Log.w("Unable to write to history file."); + e.printStackTrace(); + } + } + + private String createLine(WorldSeed seed) { + return new Timestamp(new Date().getTime()) + " " + seed.getLong() + + "\r\n"; + } +} diff --git a/src/main/java/amidst/mojangapi/world/World.java b/src/main/java/amidst/mojangapi/world/World.java new file mode 100644 index 000000000..615f2e566 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/World.java @@ -0,0 +1,131 @@ +package amidst.mojangapi.world; + +import java.util.List; + +import amidst.documentation.CalledByAny; +import amidst.documentation.NotThreadSafe; +import amidst.mojangapi.world.icon.CachedWorldIconProducer; +import amidst.mojangapi.world.icon.WorldIcon; +import amidst.mojangapi.world.icon.WorldIconProducer; +import amidst.mojangapi.world.oracle.BiomeDataOracle; +import amidst.mojangapi.world.oracle.SlimeChunkOracle; +import amidst.mojangapi.world.player.MovablePlayerList; + +/** + * The SlimeChunkOracle as well as the WorldIconProducers that are no + * CacheWorldIconProducers cannot be used by multiple threads. The other parts + * of this class can be used multi-threaded. + */ +@NotThreadSafe +public class World { + private final WorldSeed seed; + private final WorldType worldType; + private final String generatorOptions; + private final MovablePlayerList movablePlayerList; + + private final BiomeDataOracle biomeDataOracle; + private final SlimeChunkOracle slimeChunkOracle; + private final CachedWorldIconProducer spawnProducer; + private final CachedWorldIconProducer strongholdProducer; + private final CachedWorldIconProducer playerProducer; + private final WorldIconProducer templeProducer; + private final WorldIconProducer villageProducer; + private final WorldIconProducer oceanMonumentProducer; + private final WorldIconProducer netherFortressProducer; + + World(WorldSeed seed, WorldType worldType, String generatorOptions, + MovablePlayerList movablePlayerList, + BiomeDataOracle biomeDataOracle, SlimeChunkOracle slimeChunkOracle, + CachedWorldIconProducer spawnProducer, + CachedWorldIconProducer strongholdProducer, + CachedWorldIconProducer playerProducer, + WorldIconProducer templeProducer, + WorldIconProducer villageProducer, + WorldIconProducer oceanMonumentProducer, + WorldIconProducer netherFortressProducer) { + this.seed = seed; + this.worldType = worldType; + this.generatorOptions = generatorOptions; + this.movablePlayerList = movablePlayerList; + this.biomeDataOracle = biomeDataOracle; + this.slimeChunkOracle = slimeChunkOracle; + this.spawnProducer = spawnProducer; + this.strongholdProducer = strongholdProducer; + this.playerProducer = playerProducer; + this.templeProducer = templeProducer; + this.villageProducer = villageProducer; + this.oceanMonumentProducer = oceanMonumentProducer; + this.netherFortressProducer = netherFortressProducer; + } + + @CalledByAny + public WorldSeed getWorldSeed() { + return seed; + } + + @CalledByAny + public WorldType getWorldType() { + return worldType; + } + + @CalledByAny + public String getGeneratorOptions() { + return generatorOptions; + } + + public MovablePlayerList getMovablePlayerList() { + return movablePlayerList; + } + + public BiomeDataOracle getBiomeDataOracle() { + return biomeDataOracle; + } + + public SlimeChunkOracle getSlimeChunkOracle() { + return slimeChunkOracle; + } + + public WorldIconProducer getSpawnProducer() { + return spawnProducer; + } + + public WorldIconProducer getStrongholdProducer() { + return strongholdProducer; + } + + public WorldIconProducer getPlayerProducer() { + return playerProducer; + } + + public WorldIconProducer getTempleProducer() { + return templeProducer; + } + + public WorldIconProducer getVillageProducer() { + return villageProducer; + } + + public WorldIconProducer getOceanMonumentProducer() { + return oceanMonumentProducer; + } + + public WorldIconProducer getNetherFortressProducer() { + return netherFortressProducer; + } + + public WorldIcon getSpawnWorldIcon() { + return spawnProducer.getFirstWorldIcon(); + } + + public List getStrongholdWorldIcons() { + return strongholdProducer.getWorldIcons(); + } + + public List getPlayerWorldIcons() { + return playerProducer.getWorldIcons(); + } + + public void reloadPlayerWorldIcons() { + playerProducer.resetCache(); + } +} diff --git a/src/main/java/amidst/mojangapi/world/WorldBuilder.java b/src/main/java/amidst/mojangapi/world/WorldBuilder.java new file mode 100644 index 000000000..d3694c466 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/WorldBuilder.java @@ -0,0 +1,91 @@ +package amidst.mojangapi.world; + +import java.io.IOException; + +import amidst.documentation.Immutable; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.directory.SaveDirectory; +import amidst.mojangapi.file.nbt.LevelDatNbt; +import amidst.mojangapi.minecraftinterface.MinecraftInterface; +import amidst.mojangapi.minecraftinterface.MinecraftInterfaceException; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.icon.NetherFortressProducer; +import amidst.mojangapi.world.icon.OceanMonumentProducer; +import amidst.mojangapi.world.icon.PlayerProducer; +import amidst.mojangapi.world.icon.SpawnProducer; +import amidst.mojangapi.world.icon.StrongholdProducer; +import amidst.mojangapi.world.icon.TempleProducer; +import amidst.mojangapi.world.icon.VillageProducer; +import amidst.mojangapi.world.oracle.BiomeDataOracle; +import amidst.mojangapi.world.oracle.SlimeChunkOracle; +import amidst.mojangapi.world.player.MovablePlayerList; +import amidst.mojangapi.world.player.PlayerInformationCache; +import amidst.mojangapi.world.player.WorldPlayerType; + +@Immutable +public class WorldBuilder { + private final PlayerInformationCache playerInformationCache; + private final SeedHistoryLogger seedHistoryLogger; + + public WorldBuilder(PlayerInformationCache playerInformationCache, + SeedHistoryLogger seedHistoryLogger) { + this.playerInformationCache = playerInformationCache; + this.seedHistoryLogger = seedHistoryLogger; + } + + public World fromSeed(MinecraftInterface minecraftInterface, + WorldSeed seed, WorldType worldType) + throws MinecraftInterfaceException { + return create(minecraftInterface, seed, worldType, "", + MovablePlayerList.dummy()); + } + + public World fromFile(MinecraftInterface minecraftInterface, + SaveDirectory saveDirectory) throws IOException, + MinecraftInterfaceException, MojangApiParsingException { + LevelDatNbt levelDat = saveDirectory.createLevelDat(); + MovablePlayerList movablePlayerList = new MovablePlayerList( + playerInformationCache, saveDirectory, + isSaveEnabled(minecraftInterface), WorldPlayerType.from( + saveDirectory, levelDat)); + return create(minecraftInterface, + WorldSeed.fromFile(levelDat.getSeed()), + levelDat.getWorldType(), levelDat.getGeneratorOptions(), + movablePlayerList); + } + + // TODO: @skiphs why does it depend on the loaded minecraft version whether + // we can save player locations or not? we do not use the minecraft jar file + // to save player locations and it does not depend on the jar file which + // worlds can be loaded. + @Deprecated + private boolean isSaveEnabled(MinecraftInterface minecraftInterface) { + return minecraftInterface.getRecognisedVersion().isSaveEnabled(); + } + + private World create(MinecraftInterface minecraftInterface, WorldSeed seed, + WorldType worldType, String generatorOptions, + MovablePlayerList movablePlayerList) + throws MinecraftInterfaceException { + seedHistoryLogger.log(seed); + // @formatter:off + minecraftInterface.createWorld(seed.getLong(), worldType, generatorOptions); + RecognisedVersion recognisedVersion = minecraftInterface.getRecognisedVersion(); + BiomeDataOracle biomeDataOracle = new BiomeDataOracle(minecraftInterface); + return new World( + seed, + worldType, + generatorOptions, + movablePlayerList, + biomeDataOracle, + new SlimeChunkOracle( seed.getLong()), + new SpawnProducer( recognisedVersion, seed.getLong(), biomeDataOracle), + new StrongholdProducer( recognisedVersion, seed.getLong(), biomeDataOracle), + new PlayerProducer( recognisedVersion, movablePlayerList), + new TempleProducer( recognisedVersion, seed.getLong(), biomeDataOracle), + new VillageProducer( recognisedVersion, seed.getLong(), biomeDataOracle), + new OceanMonumentProducer( recognisedVersion, seed.getLong(), biomeDataOracle), + new NetherFortressProducer(recognisedVersion, seed.getLong(), biomeDataOracle)); + // @formatter:on + } +} diff --git a/src/main/java/amidst/mojangapi/world/WorldSeed.java b/src/main/java/amidst/mojangapi/world/WorldSeed.java new file mode 100644 index 000000000..615e8c4d8 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/WorldSeed.java @@ -0,0 +1,81 @@ +package amidst.mojangapi.world; + +import java.util.Random; + +import amidst.documentation.Immutable; + +@Immutable +public class WorldSeed { + public static enum WorldSeedType { + // @formatter:off + TEXT ("Text Seed"), + NUMERIC("Numeric Seed"), + FILE ("File Seed"), + RANDOM ("Random Seed"); + // @formatter:on + + private final String labelPrefix; + + private WorldSeedType(String labelPrefix) { + this.labelPrefix = labelPrefix; + } + + private String getLabel(long seed, String text) { + if (this == TEXT) { + return labelPrefix + ": '" + text + "' (" + seed + ")"; + } else { + return labelPrefix + " (" + seed + ")"; + } + } + } + + public static WorldSeed random() { + return new WorldSeed(new Random().nextLong(), null, + WorldSeedType.RANDOM); + } + + public static WorldSeed fromUserInput(String input) { + if (input.isEmpty()) { + return random(); + } + try { + long seed = Long.parseLong(input); + return new WorldSeed(seed, null, WorldSeedType.NUMERIC); + } catch (NumberFormatException err) { + int seed = input.hashCode(); + return new WorldSeed(seed, input, WorldSeedType.TEXT); + } + } + + public static WorldSeed fromFile(long seed) { + return new WorldSeed(seed, null, WorldSeedType.FILE); + } + + private final long seed; + private final String text; + private final WorldSeedType type; + private final String label; + + private WorldSeed(long seed, String text, WorldSeedType type) { + this.seed = seed; + this.text = text; + this.type = type; + this.label = type.getLabel(seed, text); + } + + public long getLong() { + return seed; + } + + public String getText() { + return text; + } + + public WorldSeedType getType() { + return type; + } + + public String getLabel() { + return label; + } +} diff --git a/src/main/java/amidst/mojangapi/world/WorldType.java b/src/main/java/amidst/mojangapi/world/WorldType.java new file mode 100644 index 000000000..63cbc55c3 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/WorldType.java @@ -0,0 +1,91 @@ +package amidst.mojangapi.world; + +import java.util.Arrays; +import java.util.List; + +import amidst.documentation.Immutable; +import amidst.logging.Log; +import amidst.mojangapi.minecraftinterface.local.SymbolicNames; + +@Immutable +public enum WorldType { + // @formatter:off + DEFAULT ("Default", SymbolicNames.FIELD_WORLD_TYPE_DEFAULT), + FLAT ("Flat", SymbolicNames.FIELD_WORLD_TYPE_FLAT), + LARGE_BIOMES ("Large Biomes", SymbolicNames.FIELD_WORLD_TYPE_LARGE_BIOMES), + AMPLIFIED ("Amplified", SymbolicNames.FIELD_WORLD_TYPE_AMPLIFIED), + CUSTOMIZED ("Customized", SymbolicNames.FIELD_WORLD_TYPE_CUSTOMIZED); + // @formatter:on + + public static final String PROMPT_EACH_TIME = "Prompt each time"; + + // @formatter:off + private static final List SELECTABLE_WORLD_TYPES = Arrays.asList( + WorldType.DEFAULT, + WorldType.FLAT, + WorldType.LARGE_BIOMES, + WorldType.AMPLIFIED + ); + // @formatter:on + + // @formatter:off + private static final String[] WORLD_TYPE_SETTING_AVAILABLE_VALUES = new String[] { + PROMPT_EACH_TIME, + WorldType.DEFAULT.getName(), + WorldType.FLAT.getName(), + WorldType.LARGE_BIOMES.getName(), + WorldType.AMPLIFIED.getName() + }; + // @formatter:on + + public static List getSelectable() { + return SELECTABLE_WORLD_TYPES; + } + + public static String[] getWorldTypeSettingAvailableValues() { + return WORLD_TYPE_SETTING_AVAILABLE_VALUES; + } + + public static WorldType from(String nameOrSymbolicFieldName) { + WorldType result = findInstance(nameOrSymbolicFieldName); + if (result != null) { + return result; + } else { + Log.e("Unable to find World Type: " + nameOrSymbolicFieldName + + ". Falling back to default world type."); + return DEFAULT; + } + } + + private static WorldType findInstance(String nameOrValue) { + for (WorldType worldType : values()) { + if (worldType.name.equalsIgnoreCase(nameOrValue) + || worldType.symbolicFieldName + .equalsIgnoreCase(nameOrValue)) { + return worldType; + } + } + return null; + } + + private final String name; + private final String symbolicFieldName; + + private WorldType(String name, String symbolicFieldName) { + this.name = name; + this.symbolicFieldName = symbolicFieldName; + } + + public String getName() { + return name; + } + + public String getSymbolicFieldName() { + return symbolicFieldName; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/amidst/mojangapi/world/biome/Biome.java b/src/main/java/amidst/mojangapi/world/biome/Biome.java new file mode 100644 index 000000000..e6909e470 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/biome/Biome.java @@ -0,0 +1,210 @@ +package amidst.mojangapi.world.biome; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import amidst.documentation.Immutable; +import amidst.documentation.NotThreadSafe; + +@Immutable +public class Biome { + @Immutable + private static class BiomeIterable implements Iterable { + @Override + public Iterator iterator() { + return new BiomeIterator(); + } + } + + @NotThreadSafe + private static class BiomeIterator implements Iterator { + private int nextBiomeIndex = 0; + + private BiomeIterator() { + findNextValid(); + } + + @Override + public boolean hasNext() { + return nextBiomeIndex < biomes.length; + } + + @Override + public Biome next() { + Biome result = biomes[nextBiomeIndex]; + nextBiomeIndex++; + findNextValid(); + return result; + } + + private void findNextValid() { + while (nextBiomeIndex < biomes.length + && biomes[nextBiomeIndex] == null) { + nextBiomeIndex++; + } + } + } + + // @formatter:off + private static final Map biomeMap = new HashMap(); + private static final Biome[] biomes = new Biome[256]; + + public static final Biome ocean = new Biome("Ocean", 0, BiomeColor.from( 0, 0, 112), BiomeType.TYPE_C); + public static final Biome plains = new Biome("Plains", 1, BiomeColor.from(141, 179, 96), BiomeType.TYPE_A); + public static final Biome desert = new Biome("Desert", 2, BiomeColor.from(250, 148, 24), BiomeType.TYPE_E); + public static final Biome extremeHills = new Biome("Extreme Hills", 3, BiomeColor.from( 96, 96, 96), BiomeType.TYPE_I); + public static final Biome forest = new Biome("Forest", 4, BiomeColor.from( 5, 102, 33), BiomeType.TYPE_A); + public static final Biome taiga = new Biome("Taiga", 5, BiomeColor.from( 11, 102, 89), BiomeType.TYPE_F); + public static final Biome swampland = new Biome("Swampland", 6, BiomeColor.from( 7, 249, 178), BiomeType.TYPE_M); + public static final Biome river = new Biome("River", 7, BiomeColor.from( 0, 0, 255), BiomeType.TYPE_B); + public static final Biome hell = new Biome("Hell", 8, BiomeColor.from(255, 0, 0), BiomeType.TYPE_A); + public static final Biome sky = new Biome("Sky", 9, BiomeColor.from(128, 128, 255), BiomeType.TYPE_A); + public static final Biome frozenOcean = new Biome("Frozen Ocean", 10, BiomeColor.from(144, 144, 160), BiomeType.TYPE_C); + public static final Biome frozenRiver = new Biome("Frozen River", 11, BiomeColor.from(160, 160, 255), BiomeType.TYPE_B); + public static final Biome icePlains = new Biome("Ice Plains", 12, BiomeColor.from(255, 255, 255), BiomeType.TYPE_E); + public static final Biome iceMountains = new Biome("Ice Mountains", 13, BiomeColor.from(160, 160, 160), BiomeType.TYPE_G); + public static final Biome mushroomIsland = new Biome("Mushroom Island", 14, BiomeColor.from(255, 0, 255), BiomeType.TYPE_L); + public static final Biome mushroomIslandShore = new Biome("Mushroom Island Shore", 15, BiomeColor.from(160, 0, 255), BiomeType.TYPE_J); + public static final Biome beach = new Biome("Beach", 16, BiomeColor.from(250, 222, 85), BiomeType.TYPE_J); + public static final Biome desertHills = new Biome("Desert Hills", 17, BiomeColor.from(210, 95, 18), BiomeType.TYPE_G); + public static final Biome forestHills = new Biome("Forest Hills", 18, BiomeColor.from( 34, 85, 28), BiomeType.TYPE_G); + public static final Biome taigaHills = new Biome("Taiga Hills", 19, BiomeColor.from( 22, 57, 51), BiomeType.TYPE_G); + public static final Biome extremeHillsEdge = new Biome("Extreme Hills Edge", 20, BiomeColor.from(114, 120, 154), BiomeType.TYPE_I.getExtreme()); + public static final Biome jungle = new Biome("Jungle", 21, BiomeColor.from( 83, 123, 9), BiomeType.TYPE_A); + public static final Biome jungleHills = new Biome("Jungle Hills", 22, BiomeColor.from( 44, 66, 5), BiomeType.TYPE_G); + public static final Biome jungleEdge = new Biome("Jungle Edge", 23, BiomeColor.from( 98, 139, 23), BiomeType.TYPE_A); + public static final Biome deepOcean = new Biome("Deep Ocean", 24, BiomeColor.from( 0, 0, 48), BiomeType.TYPE_D); + public static final Biome stoneBeach = new Biome("Stone Beach", 25, BiomeColor.from(162, 162, 132), BiomeType.TYPE_K); + public static final Biome coldBeach = new Biome("Cold Beach", 26, BiomeColor.from(250, 240, 192), BiomeType.TYPE_J); + public static final Biome birchForest = new Biome("Birch Forest", 27, BiomeColor.from( 48, 116, 68), BiomeType.TYPE_A); + public static final Biome birchForestHills = new Biome("Birch Forest Hills", 28, BiomeColor.from( 31, 95, 50), BiomeType.TYPE_G); + public static final Biome roofedForest = new Biome("Roofed Forest", 29, BiomeColor.from( 64, 81, 26), BiomeType.TYPE_A); + public static final Biome coldTaiga = new Biome("Cold Taiga", 30, BiomeColor.from( 49, 85, 74), BiomeType.TYPE_F); + public static final Biome coldTaigaHills = new Biome("Cold Taiga Hills", 31, BiomeColor.from( 36, 63, 54), BiomeType.TYPE_G); + public static final Biome megaTaiga = new Biome("Mega Taiga", 32, BiomeColor.from( 89, 102, 81), BiomeType.TYPE_F); + public static final Biome megaTaigaHills = new Biome("Mega Taiga Hills", 33, BiomeColor.from( 69, 79, 62), BiomeType.TYPE_G); + public static final Biome extremeHillsPlus = new Biome("Extreme Hills+", 34, BiomeColor.from( 80, 112, 80), BiomeType.TYPE_I); + public static final Biome savanna = new Biome("Savanna", 35, BiomeColor.from(189, 178, 95), BiomeType.TYPE_E); + public static final Biome savannaPlateau = new Biome("Savanna Plateau", 36, BiomeColor.from(167, 157, 100), BiomeType.TYPE_H); + public static final Biome mesa = new Biome("Mesa", 37, BiomeColor.from(217, 69, 21), BiomeType.TYPE_A); + public static final Biome mesaPlateauF = new Biome("Mesa Plateau F", 38, BiomeColor.from(176, 151, 101), BiomeType.TYPE_H); + public static final Biome mesaPlateau = new Biome("Mesa Plateau", 39, BiomeColor.from(202, 140, 101), BiomeType.TYPE_H); + + public static final Biome oceanM = new Biome("Ocean M", 128, BiomeColor.from( 0, 0, 112)); + public static final Biome sunflowerPlains = new Biome("Sunflower Plains", 129, BiomeColor.from(141, 179, 96)); + public static final Biome desertM = new Biome("Desert M", 130, BiomeColor.from(250, 148, 24)); + public static final Biome extremeHillsM = new Biome("Extreme Hills M", 131, BiomeColor.from( 96, 96, 96)); + public static final Biome flowerForest = new Biome("Flower Forest", 132, BiomeColor.from( 5, 102, 33)); + public static final Biome taigaM = new Biome("Taiga M", 133, BiomeColor.from( 11, 102, 89)); + public static final Biome swamplandM = new Biome("Swampland M", 134, BiomeColor.from( 7, 249, 178)); + public static final Biome riverM = new Biome("River M", 135, BiomeColor.from( 0, 0, 255)); + public static final Biome hellM = new Biome("Hell M", 136, BiomeColor.from(255, 0, 0)); + public static final Biome skyM = new Biome("Sky M", 137, BiomeColor.from(128, 128, 255)); + public static final Biome frozenOceanM = new Biome("Frozen Ocean M", 138, BiomeColor.from(144, 144, 160)); + public static final Biome frozenRiverM = new Biome("Frozen River M", 139, BiomeColor.from(160, 160, 255)); + public static final Biome icePlainsSpikes = new Biome("Ice Plains Spikes", 140, BiomeColor.from(140, 180, 180)); + public static final Biome iceMountainsM = new Biome("Ice Mountains M", 141, BiomeColor.from(160, 160, 160)); + public static final Biome mushroomIslandM = new Biome("Mushroom Island M", 142, BiomeColor.from(255, 0, 255)); + public static final Biome mushroomIslandShoreM = new Biome("Mushroom Island Shore M", 143, BiomeColor.from(160, 0, 255)); + public static final Biome beachM = new Biome("Beach M", 144, BiomeColor.from(250, 222, 85)); + public static final Biome desertHillsM = new Biome("Desert Hills M", 145, BiomeColor.from(210, 95, 18)); + public static final Biome forestHillsM = new Biome("Forest Hills M", 146, BiomeColor.from( 34, 85, 28)); + public static final Biome taigaHillsM = new Biome("Taiga Hills M", 147, BiomeColor.from( 22, 57, 51)); + public static final Biome extremeHillsEdgeM = new Biome("Extreme Hills Edge M", 148, BiomeColor.from(114, 120, 154)); + public static final Biome jungleM = new Biome("Jungle M", 149, BiomeColor.from( 83, 123, 9)); + public static final Biome jungleHillsM = new Biome("Jungle Hills M", 150, BiomeColor.from( 44, 66, 5)); + public static final Biome jungleEdgeM = new Biome("Jungle Edge M", 151, BiomeColor.from( 98, 139, 23)); + public static final Biome deepOceanM = new Biome("Deep Ocean M", 152, BiomeColor.from( 0, 0, 48)); + public static final Biome stoneBeachM = new Biome("Stone Beach M", 153, BiomeColor.from(162, 162, 132)); + public static final Biome coldBeachM = new Biome("Cold Beach M", 154, BiomeColor.from(250, 240, 192)); + public static final Biome birchForestM = new Biome("Birch Forest M", 155, BiomeColor.from( 48, 116, 68)); + public static final Biome birchForestHillsM = new Biome("Birch Forest Hills M", 156, BiomeColor.from( 31, 95, 50)); + public static final Biome roofedForestM = new Biome("Roofed Forest M", 157, BiomeColor.from( 64, 81, 26)); + public static final Biome coldTaigaM = new Biome("Cold Taiga M", 158, BiomeColor.from( 49, 85, 74)); + public static final Biome coldTaigaHillsM = new Biome("Cold Taiga Hills M", 159, BiomeColor.from( 36, 63, 54)); + public static final Biome megaSpruceTaiga = new Biome("Mega Spruce Taiga", 160, BiomeColor.from( 89, 102, 81)); + public static final Biome megaSpurceTaigaHills = new Biome("Mega Spruce Taiga (Hills)", 161, BiomeColor.from( 69, 79, 62)); + public static final Biome extremeHillsPlusM = new Biome("Extreme Hills+ M", 162, BiomeColor.from( 80, 112, 80)); + public static final Biome savannaM = new Biome("Savanna M", 163, BiomeColor.from(189, 178, 95)); + public static final Biome savannaPlateauM = new Biome("Savanna Plateau M", 164, BiomeColor.from(167, 157, 100)); + public static final Biome mesaBryce = new Biome("Mesa (Bryce)", 165, BiomeColor.from(217, 69, 21)); + public static final Biome mesaPlateauFM = new Biome("Mesa Plateau F M", 166, BiomeColor.from(176, 151, 101)); + public static final Biome mesaPlateauM = new Biome("Mesa Plateau M", 167, BiomeColor.from(202, 140, 101)); + // @formatter:on + + private static final BiomeIterable ITERABLE = new BiomeIterable(); + + public static Iterable allBiomes() { + return ITERABLE; + } + + public static Biome getByIndex(int index) throws UnknownBiomeIndexException { + if (index < 0 || index >= biomes.length || biomes[index] == null) { + throw new UnknownBiomeIndexException( + "unsupported biome index detected: " + index); + } else { + return biomes[index]; + } + } + + public static int getBiomesLength() { + return biomes.length; + } + + public static Biome getByName(String name) { + return biomeMap.get(name); + } + + public static boolean exists(String name) { + return biomeMap.containsKey(name); + } + + public static boolean isSpecialBiomeIndex(int index) { + return index >= 128; + } + + public static int compareByIndex(String name1, String name2) { + return getByName(name1).getIndex() - getByName(name2).getIndex(); + } + + private final String name; + private final int index; + private final BiomeColor defaultColor; + private final BiomeType type; + + public Biome(String name, int index, BiomeColor defaultColor) { + this(name, index, defaultColor.createLightenedBiomeColor(), + biomes[index - 128].type.getRare()); + } + + public Biome(String name, int index, BiomeColor defaultColor, BiomeType type) { + this.name = name; + this.index = index; + this.defaultColor = defaultColor; + this.type = type; + biomes[index] = this; + biomeMap.put(name, this); + } + + public String getName() { + return name; + } + + public int getIndex() { + return index; + } + + public BiomeColor getDefaultColor() { + return defaultColor; + } + + public BiomeType getType() { + return type; + } + + @Override + public String toString() { + return "[Biome " + name + "]"; + } +} diff --git a/src/main/java/amidst/mojangapi/world/biome/BiomeColor.java b/src/main/java/amidst/mojangapi/world/biome/BiomeColor.java new file mode 100644 index 000000000..d4fed7aef --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/biome/BiomeColor.java @@ -0,0 +1,92 @@ +package amidst.mojangapi.world.biome; + +import java.awt.Color; + +import amidst.documentation.Immutable; +import amidst.settings.biomecolorprofile.BiomeColorJson; + +@Immutable +public class BiomeColor { + public static BiomeColor from(int r, int g, int b) { + return new BiomeColor(r, g, b); + } + + public static BiomeColor unknown() { + return UNKNOWN_BIOME_COLOR; + } + + private static final BiomeColor UNKNOWN_BIOME_COLOR = new BiomeColor(0, 0, + 0); + + private static final int DESELECT_NUMBER = 30; + private static final int LIGHTEN_BRIGHTNESS = 40; + + private final int r; + private final int g; + private final int b; + private final int rgb; + private final int deselectRGB; + private final Color color; + + private BiomeColor(int r, int g, int b) { + this.r = r; + this.g = g; + this.b = b; + this.rgb = createRGB(r, g, b); + this.deselectRGB = createDeselectRGB(r, g, b); + this.color = new Color(r, g, b); + } + + private int createRGB(int r, int g, int b) { + int result = 0xFF000000; + result |= 0xFF0000 & (r << 16); + result |= 0xFF00 & (g << 8); + result |= 0xFF & b; + return result; + } + + private int createDeselectRGB(int r, int g, int b) { + int sum = r + g + b; + return createRGB(deselect(r, sum), deselect(g, sum), deselect(b, sum)); + } + + private int deselect(int x, int average) { + return (x + average) / DESELECT_NUMBER; + } + + public int getR() { + return r; + } + + public int getG() { + return g; + } + + public int getB() { + return b; + } + + public int getRGB() { + return rgb; + } + + public int getDeselectRGB() { + return deselectRGB; + } + + public Color getColor() { + return color; + } + + public BiomeColorJson createBiomeColorJson() { + return new BiomeColorJson(r, g, b); + } + + public BiomeColor createLightenedBiomeColor() { + return BiomeColor.from(lighten(r), lighten(g), lighten(b)); + } + + private int lighten(int x) { + return Math.min(x + LIGHTEN_BRIGHTNESS, 0xFF); + } +} diff --git a/src/main/java/amidst/mojangapi/world/biome/BiomeType.java b/src/main/java/amidst/mojangapi/world/biome/BiomeType.java new file mode 100644 index 000000000..30ad0295b --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/biome/BiomeType.java @@ -0,0 +1,47 @@ +package amidst.mojangapi.world.biome; + +import amidst.documentation.Immutable; + +// TODO: Rename once we figure out what this actually is! +@Immutable +public class BiomeType { + // @formatter:off + public static final BiomeType TYPE_A = new BiomeType( 0.1F, 0.2F); + public static final BiomeType TYPE_B = new BiomeType(-0.5F, 0.0F); + public static final BiomeType TYPE_C = new BiomeType(-1.0F, 0.1F); + public static final BiomeType TYPE_D = new BiomeType(-1.8F, 0.1F); + public static final BiomeType TYPE_E = new BiomeType( 0.125F, 0.05F); + public static final BiomeType TYPE_F = new BiomeType( 0.2F, 0.2F); + public static final BiomeType TYPE_G = new BiomeType( 0.45F, 0.3F); + public static final BiomeType TYPE_H = new BiomeType( 1.5F, 0.025F); + public static final BiomeType TYPE_I = new BiomeType( 1.0F, 0.5F); + public static final BiomeType TYPE_J = new BiomeType( 0.0F, 0.025F); + public static final BiomeType TYPE_K = new BiomeType( 0.1F, 0.8F); + public static final BiomeType TYPE_L = new BiomeType( 0.2F, 0.3F); + public static final BiomeType TYPE_M = new BiomeType(-0.2F, 0.1F); + // @formatter:on + + private final float value1; + private final float value2; + + public BiomeType(float value1, float value2) { + this.value1 = value1; + this.value2 = value2; + } + + public BiomeType getExtreme() { + return new BiomeType(value1 * 0.8F, value2 * 0.6F); + } + + public BiomeType getRare() { + return new BiomeType(value1 + 0.1F, value2 + 0.2F); + } + + public float getValue1() { + return value1; + } + + public float getValue2() { + return value2; + } +} diff --git a/src/main/java/amidst/mojangapi/world/biome/UnknownBiomeIndexException.java b/src/main/java/amidst/mojangapi/world/biome/UnknownBiomeIndexException.java new file mode 100644 index 000000000..f129223a4 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/biome/UnknownBiomeIndexException.java @@ -0,0 +1,11 @@ +package amidst.mojangapi.world.biome; + +import amidst.documentation.Immutable; + +@SuppressWarnings("serial") +@Immutable +public class UnknownBiomeIndexException extends Exception { + public UnknownBiomeIndexException(String message) { + super(message); + } +} diff --git a/src/main/java/amidst/mojangapi/world/coordinates/CoordinateUtils.java b/src/main/java/amidst/mojangapi/world/coordinates/CoordinateUtils.java new file mode 100644 index 000000000..9ca17c761 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/coordinates/CoordinateUtils.java @@ -0,0 +1,41 @@ +package amidst.mojangapi.world.coordinates; + +import amidst.documentation.Immutable; +import amidst.fragment.Fragment; + +@Immutable +public enum CoordinateUtils { + ; + + public static boolean isInBounds(long x, long y, long offsetX, + long offsetY, long width, long height) { + return x >= offsetX && x < offsetX + width && y >= offsetY + && y < offsetY + height; + } + + /** + * @return world coordinates relative to its fragment corner (corrected + * modulo) + */ + public static long toFragmentRelative(long inWorld) { + return modulo(inWorld, Fragment.SIZE); + } + + /** + * @return world coordinates (addition) + */ + public static long toWorld(long inWorldOfFragment, long inFragment) { + return inWorldOfFragment + inFragment; + } + + /** + * @return world coordinates of the corner of the given coordinates fragment + */ + public static long toFragmentCorner(long inWorld) { + return inWorld - modulo(inWorld, Fragment.SIZE); + } + + private static long modulo(long a, long b) { + return ((a % b) + b) % b; + } +} diff --git a/src/main/java/amidst/mojangapi/world/coordinates/CoordinatesInWorld.java b/src/main/java/amidst/mojangapi/world/coordinates/CoordinatesInWorld.java new file mode 100644 index 000000000..8bf6cb7aa --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/coordinates/CoordinatesInWorld.java @@ -0,0 +1,178 @@ +package amidst.mojangapi.world.coordinates; + +import java.awt.Point; + +import amidst.documentation.Immutable; + +@Immutable +public class CoordinatesInWorld { + public static CoordinatesInWorld tryParse(String coordinates) { + String[] parsedCoordinates = coordinates.replaceAll(" ", "").split(","); + if (parsedCoordinates.length != 2) { + return null; + } + try { + return CoordinatesInWorld.from( + Long.parseLong(parsedCoordinates[0]), + Long.parseLong(parsedCoordinates[1])); + } catch (NumberFormatException e) { + return null; + } + } + + public static CoordinatesInWorld from(long xInWorld, long yInWorld) { + return new CoordinatesInWorld(xInWorld, yInWorld); + } + + private static CoordinatesInWorld from(CoordinatesInWorld base, + long deltaXInWorld, long deltaYInWorld) { + return new CoordinatesInWorld(base.xInWorld + deltaXInWorld, + base.yInWorld + deltaYInWorld); + } + + public static CoordinatesInWorld origin() { + return ORIGIN; + } + + private static final CoordinatesInWorld ORIGIN = CoordinatesInWorld.from(0, + 0); + + private final long xInWorld; + private final long yInWorld; + + public CoordinatesInWorld(long xInWorld, long yInWorld) { + this.xInWorld = xInWorld; + this.yInWorld = yInWorld; + } + + public long getX() { + return xInWorld; + } + + public long getY() { + return yInWorld; + } + + public long getXAs(Resolution targetResolution) { + return targetResolution.convertFromWorldToThis(xInWorld); + } + + public long getYAs(Resolution targetResolution) { + return targetResolution.convertFromWorldToThis(yInWorld); + } + + public long getXCornerOfFragment() { + return CoordinateUtils.toFragmentCorner(xInWorld); + } + + public long getYCornerOfFragment() { + return CoordinateUtils.toFragmentCorner(yInWorld); + } + + public long getXCornerOfFragmentAs(Resolution targetResolution) { + return targetResolution.convertFromWorldToThis(CoordinateUtils + .toFragmentCorner(xInWorld)); + } + + public long getYCornerOfFragmentAs(Resolution targetResolution) { + return targetResolution.convertFromWorldToThis(CoordinateUtils + .toFragmentCorner(yInWorld)); + } + + public long getXRelativeToFragment() { + return CoordinateUtils.toFragmentRelative(xInWorld); + } + + public long getYRelativeToFragment() { + return CoordinateUtils.toFragmentRelative(yInWorld); + } + + public long getXRelativeToFragmentAs(Resolution targetResolution) { + return targetResolution.convertFromWorldToThis(CoordinateUtils + .toFragmentRelative(xInWorld)); + } + + public long getYRelativeToFragmentAs(Resolution targetResolution) { + return targetResolution.convertFromWorldToThis(CoordinateUtils + .toFragmentRelative(yInWorld)); + } + + public CoordinatesInWorld toFragmentCorner() { + return from(getXCornerOfFragment(), getYCornerOfFragment()); + } + + public double getDistance(CoordinatesInWorld other) { + return Point.distance(xInWorld, yInWorld, other.xInWorld, + other.yInWorld); + } + + public double getDistance(long xInWorld, long yInWorld) { + return Point.distance(this.xInWorld, this.yInWorld, xInWorld, yInWorld); + } + + public double getDistanceSq(CoordinatesInWorld other) { + return Point.distanceSq(xInWorld, yInWorld, other.xInWorld, + other.yInWorld); + } + + public double getDistanceSq(long xInWorld, long yInWorld) { + return Point.distanceSq(this.xInWorld, this.yInWorld, xInWorld, + yInWorld); + } + + public CoordinatesInWorld add(CoordinatesInWorld other) { + return add(other.xInWorld, other.yInWorld); + } + + public CoordinatesInWorld add(long xInWorld, long yInWorld) { + return from(this, xInWorld, yInWorld); + } + + public CoordinatesInWorld substract(CoordinatesInWorld other) { + return substract(other.xInWorld, other.yInWorld); + } + + public CoordinatesInWorld substract(long xInWorld, long yInWorld) { + return from(this, -xInWorld, -yInWorld); + } + + public boolean isInBoundsOf(CoordinatesInWorld corner, long size) { + return CoordinateUtils.isInBounds(xInWorld, yInWorld, corner.xInWorld, + corner.yInWorld, size, size); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (xInWorld ^ (xInWorld >>> 32)); + result = prime * result + (int) (yInWorld ^ (yInWorld >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof CoordinatesInWorld)) { + return false; + } + CoordinatesInWorld other = (CoordinatesInWorld) obj; + if (xInWorld != other.xInWorld) { + return false; + } + if (yInWorld != other.yInWorld) { + return false; + } + return true; + } + + @Override + public String toString() { + return "[" + xInWorld + ", " + yInWorld + "]"; + } +} diff --git a/src/main/java/amidst/mojangapi/world/coordinates/Resolution.java b/src/main/java/amidst/mojangapi/world/coordinates/Resolution.java new file mode 100644 index 000000000..a5deb0933 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/coordinates/Resolution.java @@ -0,0 +1,30 @@ +package amidst.mojangapi.world.coordinates; + +import amidst.documentation.Immutable; + +@Immutable +public enum Resolution { + WORLD(0), QUARTER(2), CHUNK(4), FRAGMENT(9); + + private final int shift; + + private Resolution(int shift) { + this.shift = shift; + } + + public int getStep() { + return 1 << shift; + } + + public int getStepsPerFragment() { + return 1 << (FRAGMENT.shift - shift); + } + + public long convertFromWorldToThis(long coordinateInWorld) { + return coordinateInWorld >> shift; + } + + public long convertFromThisToWorld(long coordinateInThisResolution) { + return coordinateInThisResolution << shift; + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/CachedWorldIconProducer.java b/src/main/java/amidst/mojangapi/world/icon/CachedWorldIconProducer.java new file mode 100644 index 000000000..12822a941 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/CachedWorldIconProducer.java @@ -0,0 +1,83 @@ +package amidst.mojangapi.world.icon; + +import java.util.Collections; +import java.util.List; + +import amidst.documentation.ThreadSafe; +import amidst.fragment.Fragment; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; + +@ThreadSafe +public abstract class CachedWorldIconProducer extends WorldIconProducer { + protected final RecognisedVersion recognisedVersion; + + private final Object cacheLock = new Object(); + private volatile List cache; + + public CachedWorldIconProducer(RecognisedVersion recognisedVersion) { + this.recognisedVersion = recognisedVersion; + } + + @Override + public void produce(CoordinatesInWorld corner, WorldIconConsumer consumer) { + for (WorldIcon icon : getCache()) { + if (icon.getCoordinates().isInBoundsOf(corner, Fragment.SIZE)) { + consumer.consume(icon); + } + } + } + + public List getWorldIcons() { + return getCache(); + } + + public WorldIcon getFirstWorldIcon() { + List cache = getCache(); + if (cache.isEmpty()) { + return null; + } else { + return cache.get(0); + } + } + + public void resetCache() { + cache = null; + } + + /** + * Gets the list of WorldIcons. Returns the cache and creates it, if + * necessary. This will never return null. This also ensures that + * createCache is only called by one thread at a time. + */ + private List getCache() { + List result = cache; + if (result == null) { + synchronized (cacheLock) { + if (cache == null) { + cache = createCache(); + } + result = cache; + } + } + return result; + } + + /** + * Creates the list of WorldIcons. This will never return null. + */ + private List createCache() { + List result = doCreateCache(); + if (result == null) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableList(result); + } + } + + /** + * This actually create the cache. This can return null. This will only be + * called by one thread at a time. + */ + protected abstract List doCreateCache(); +} diff --git a/src/main/java/amidst/mojangapi/world/icon/DefaultWorldIconTypes.java b/src/main/java/amidst/mojangapi/world/icon/DefaultWorldIconTypes.java new file mode 100644 index 000000000..f961de4fa --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/DefaultWorldIconTypes.java @@ -0,0 +1,46 @@ +package amidst.mojangapi.world.icon; + +import java.awt.image.BufferedImage; + +import amidst.ResourceLoader; +import amidst.documentation.Immutable; + +/** + * This is only a helper enum to have a central place where these constants can + * be collected. However, it should not be used as a type. Note, that the name + * of the enum elements represent the icon filename at the same time! + */ +@Immutable +public enum DefaultWorldIconTypes { + // @formatter:off + NETHER_FORTRESS("Nether Fortress"), + PLAYER("Player"), + STRONGHOLD("Stronghold"), + JUNGLE("Jungle Temple"), + DESERT("Desert Temple"), + VILLAGE("Village"), + SPAWN("Default World Spawn"), + WITCH("Witch Hut"), + OCEAN_MONUMENT("Ocean Monument"); + // @formatter:on + + private final String name; + private final BufferedImage image; + + private DefaultWorldIconTypes(String name) { + this.name = name; + this.image = ResourceLoader.getImage(getFilename()); + } + + private String getFilename() { + return "/amidst/gui/main/icon/" + toString().toLowerCase() + ".png"; + } + + public String getName() { + return name; + } + + public BufferedImage getImage() { + return image; + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/NetherFortressProducer.java b/src/main/java/amidst/mojangapi/world/icon/NetherFortressProducer.java new file mode 100644 index 000000000..2420d43b1 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/NetherFortressProducer.java @@ -0,0 +1,81 @@ +package amidst.mojangapi.world.icon; + +import java.util.List; + +import amidst.documentation.NotThreadSafe; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@NotThreadSafe +public class NetherFortressProducer extends StructureProducer { + public NetherFortressProducer(RecognisedVersion recognisedVersion, + long seed, BiomeDataOracle biomeDataOracle) { + super(seed, biomeDataOracle, recognisedVersion); + } + + @Override + protected boolean isValidLocation() { + int i = chunkX >> 4; + int j = chunkY >> 4; + random.setSeed(i ^ j << 4 ^ seed); + random.nextInt(); + if (random.nextInt(3) != 0) { + return false; + } + if (chunkX != (i << 4) + 4 + random.nextInt(8)) { + return false; + } + return chunkY == (j << 4) + 4 + random.nextInt(8); + } + + @Override + protected DefaultWorldIconTypes getWorldIconType() { + return DefaultWorldIconTypes.NETHER_FORTRESS; + } + + @Override + protected List getValidBiomesForStructure() { + return null; // not used + } + + @Override + protected List getValidBiomesAtMiddleOfChunk() { + return null; // not used + } + + @Override + protected int updateValue(int value) { + return -1; // not used + } + + @Override + protected long getMagicNumberForSeed1() { + return -1; // not used + } + + @Override + protected long getMagicNumberForSeed2() { + return -1; // not used + } + + @Override + protected long getMagicNumberForSeed3() { + return -1; // not used + } + + @Override + protected byte getMaxDistanceBetweenScatteredFeatures() { + return -1; // not used + } + + @Override + protected byte getMinDistanceBetweenScatteredFeatures() { + return -1; // not used + } + + @Override + protected int getStructureSize() { + return -1; // not used + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/OceanMonumentProducer.java b/src/main/java/amidst/mojangapi/world/icon/OceanMonumentProducer.java new file mode 100644 index 000000000..c81a08a59 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/OceanMonumentProducer.java @@ -0,0 +1,96 @@ +package amidst.mojangapi.world.icon; + +import java.util.Arrays; +import java.util.List; + +import amidst.documentation.NotThreadSafe; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@NotThreadSafe +public class OceanMonumentProducer extends StructureProducer { + public OceanMonumentProducer(RecognisedVersion recognisedVersion, + long seed, BiomeDataOracle biomeDataOracle) { + super(seed, biomeDataOracle, recognisedVersion); + } + + @Override + protected boolean isValidLocation() { + return isSuccessful() && isValidBiomeAtMiddleOfChunk() + && isValidBiomeForStructure(); + } + + @Override + protected DefaultWorldIconTypes getWorldIconType() { + return DefaultWorldIconTypes.OCEAN_MONUMENT; + } + + @Override + protected List getValidBiomesForStructure() { + // @formatter:off + // Not sure if the extended biomes count + return Arrays.asList( + Biome.ocean, + Biome.deepOcean, + Biome.frozenOcean, + Biome.river, + Biome.frozenRiver, + Biome.oceanM, + Biome.deepOceanM, + Biome.frozenOceanM, + Biome.riverM, + Biome.frozenRiverM + ); + // @formatter:on + } + + @Override + protected List getValidBiomesAtMiddleOfChunk() { + // @formatter:off + // Not sure if the extended biomes count + return Arrays.asList( + Biome.deepOcean + // Biome.deepOceanM + ); + // @formatter:on + } + + @Override + protected int updateValue(int value) { + value *= maxDistanceBetweenScatteredFeatures; + value += (random.nextInt(distanceBetweenScatteredFeaturesRange) + random + .nextInt(distanceBetweenScatteredFeaturesRange)) / 2; + return value; + } + + @Override + protected long getMagicNumberForSeed1() { + return 341873128712L; + } + + @Override + protected long getMagicNumberForSeed2() { + return 132897987541L; + } + + @Override + protected long getMagicNumberForSeed3() { + return 10387313L; + } + + @Override + protected byte getMaxDistanceBetweenScatteredFeatures() { + return 32; + } + + @Override + protected byte getMinDistanceBetweenScatteredFeatures() { + return 5; + } + + @Override + protected int getStructureSize() { + return 29; + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/PlayerProducer.java b/src/main/java/amidst/mojangapi/world/icon/PlayerProducer.java new file mode 100644 index 000000000..e9aac2118 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/PlayerProducer.java @@ -0,0 +1,31 @@ +package amidst.mojangapi.world.icon; + +import java.util.LinkedList; +import java.util.List; + +import amidst.documentation.ThreadSafe; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.player.MovablePlayerList; +import amidst.mojangapi.world.player.Player; + +@ThreadSafe +public class PlayerProducer extends CachedWorldIconProducer { + private final MovablePlayerList movablePlayerList; + + public PlayerProducer(RecognisedVersion recognisedVersion, + MovablePlayerList movablePlayerList) { + super(recognisedVersion); + this.movablePlayerList = movablePlayerList; + } + + @Override + protected List doCreateCache() { + List result = new LinkedList(); + for (Player player : movablePlayerList) { + result.add(new WorldIcon(player.getPlayerCoordinates() + .getCoordinatesInWorld(), player.getPlayerName(), player + .getHead())); + } + return result; + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/SpawnProducer.java b/src/main/java/amidst/mojangapi/world/icon/SpawnProducer.java new file mode 100644 index 000000000..4fa7fbafb --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/SpawnProducer.java @@ -0,0 +1,68 @@ +package amidst.mojangapi.world.icon; + +import java.awt.Point; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@ThreadSafe +public class SpawnProducer extends CachedWorldIconProducer { + // @formatter:off + private static final List VALID_BIOMES = Arrays.asList( + Biome.forest, + Biome.plains, + Biome.taiga, + Biome.taigaHills, + Biome.forestHills, + Biome.jungle, + Biome.jungleHills + ); + // @formatter:on + + private final long seed; + private final BiomeDataOracle biomeDataOracle; + + public SpawnProducer(RecognisedVersion recognisedVersion, long seed, + BiomeDataOracle biomeDataOracle) { + super(recognisedVersion); + this.seed = seed; + this.biomeDataOracle = biomeDataOracle; + } + + @Override + protected List doCreateCache() { + return Arrays.asList(createSpawnWorldIcon()); + } + + private WorldIcon createSpawnWorldIcon() { + Point spawnCenter = getSpawnCenterInWorldCoordinates(); + if (spawnCenter != null) { + return createSpawnWorldIconAt(CoordinatesInWorld.from( + spawnCenter.x, spawnCenter.y)); + } else { + CoordinatesInWorld origin = CoordinatesInWorld.origin(); + Log.debug("Unable to find spawn biome. Falling back to " + + origin.toString() + "."); + return createSpawnWorldIconAt(origin); + } + } + + private WorldIcon createSpawnWorldIconAt(CoordinatesInWorld coordinates) { + return new WorldIcon(coordinates, + DefaultWorldIconTypes.SPAWN.getName(), + DefaultWorldIconTypes.SPAWN.getImage()); + } + + private Point getSpawnCenterInWorldCoordinates() { + Random random = new Random(seed); + return biomeDataOracle.findValidLocation(0, 0, 256, VALID_BIOMES, + random); + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/StrongholdProducer.java b/src/main/java/amidst/mojangapi/world/icon/StrongholdProducer.java new file mode 100644 index 000000000..c046707b8 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/StrongholdProducer.java @@ -0,0 +1,157 @@ +package amidst.mojangapi.world.icon; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import amidst.documentation.ThreadSafe; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@ThreadSafe +public class StrongholdProducer extends CachedWorldIconProducer { + // @formatter:off + private static final List VALID_BIOMES_DEFAULT = Arrays.asList( + Biome.desert, + Biome.forest, + Biome.extremeHills, + Biome.swampland + ); + + private static final List VALID_BIOMES_1_0 = Arrays.asList( + Biome.desert, + Biome.forest, + Biome.extremeHills, + Biome.swampland, + Biome.taiga, + Biome.icePlains, + Biome.iceMountains + ); + + private static final List VALID_BIOMES_1_1 = Arrays.asList( + Biome.desert, + Biome.forest, + Biome.extremeHills, + Biome.swampland, + Biome.taiga, + Biome.icePlains, + Biome.iceMountains, + Biome.desertHills, + Biome.forestHills, + Biome.extremeHillsEdge + ); + + private static final List VALID_BIOMES_12w03a = Arrays.asList( + Biome.desert, + Biome.forest, + Biome.extremeHills, + Biome.swampland, + Biome.taiga, + Biome.icePlains, + Biome.iceMountains, + Biome.desertHills, + Biome.forestHills, + Biome.extremeHillsEdge, + Biome.jungle, + Biome.jungleHills + ); + // @formatter:on + + private final long seed; + private final BiomeDataOracle biomeDataOracle; + private final List validBiomes; + + public StrongholdProducer(RecognisedVersion recognisedVersion, long seed, + BiomeDataOracle biomeDataOracle) { + super(recognisedVersion); + this.seed = seed; + this.biomeDataOracle = biomeDataOracle; + this.validBiomes = getValidBiomes(); + } + + private List getValidBiomes() { + if (recognisedVersion.isAtLeast(RecognisedVersion.V13w36a)) { + return getValidBiomesV13w36a(); + } else if (recognisedVersion.isAtLeast(RecognisedVersion.V12w03a)) { + return VALID_BIOMES_12w03a; + } else if (recognisedVersion == RecognisedVersion.V1_1) { + return VALID_BIOMES_1_1; + } else if (recognisedVersion == RecognisedVersion.V1_9pre6 + || recognisedVersion == RecognisedVersion.V1_0) { + return VALID_BIOMES_1_0; + } else { + return VALID_BIOMES_DEFAULT; + } + } + + private List getValidBiomesV13w36a() { + List result = new ArrayList(); + for (Biome biome : Biome.allBiomes()) { + if (isValidBiomeV13w36a(biome)) { + result.add(biome); + } + } + return result; + } + + private boolean isValidBiomeV13w36a(Biome biome) { + return biome.getType().getValue1() > 0; + } + + @Override + protected List doCreateCache() { + List result = new LinkedList(); + Random random = new Random(seed); + double angle = createAngle(random); + for (int i = 0; i < 3; i++) { + double distance = nextDistance(random); + int x = getX(angle, distance) << 4; + int y = getY(angle, distance) << 4; + Point strongholdLocation = findStronghold(random, x, y); + if (strongholdLocation != null) { + result.add(createWorldIcon(strongholdLocation.x, + strongholdLocation.y)); + } else { + result.add(createWorldIcon(x, y)); + } + angle = updateAngle(angle); + } + return result; + } + + private double createAngle(Random random) { + return random.nextDouble() * 3.141592653589793D * 2.0D; + } + + private double nextDistance(Random random) { + return (1.25D + random.nextDouble()) * 32.0D; + } + + private int getX(double angle, double distance) { + return (int) Math.round(Math.cos(angle) * distance); + } + + private int getY(double angle, double distance) { + return (int) Math.round(Math.sin(angle) * distance); + } + + private Point findStronghold(Random random, int x, int y) { + return biomeDataOracle.findValidLocation(x + 8, y + 8, 112, + validBiomes, random); + } + + private WorldIcon createWorldIcon(int x, int y) { + return new WorldIcon(CoordinatesInWorld.from(x, y), + DefaultWorldIconTypes.STRONGHOLD.getName(), + DefaultWorldIconTypes.STRONGHOLD.getImage()); + } + + private double updateAngle(double angle) { + return angle + 6.283185307179586D / 3.0D; + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/StructureProducer.java b/src/main/java/amidst/mojangapi/world/icon/StructureProducer.java new file mode 100644 index 000000000..54ff6dc6e --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/StructureProducer.java @@ -0,0 +1,188 @@ +package amidst.mojangapi.world.icon; + +import java.util.List; +import java.util.Random; + +import amidst.documentation.NotThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.minecraftinterface.MinecraftInterfaceException; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.biome.UnknownBiomeIndexException; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.coordinates.Resolution; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@NotThreadSafe +public abstract class StructureProducer extends WorldIconProducer { + protected final long seed; + protected final BiomeDataOracle biomeDataOracle; + protected final RecognisedVersion recognisedVersion; + protected final Resolution resolution; + protected final int size; + + protected final List validBiomesForStructure; + protected final List validBiomesAtMiddleOfChunk; + protected final long magicNumberForSeed1; + protected final long magicNumberForSeed2; + protected final long magicNumberForSeed3; + protected final byte maxDistanceBetweenScatteredFeatures; + protected final byte minDistanceBetweenScatteredFeatures; + protected final int distanceBetweenScatteredFeaturesRange; + protected final int structureSize; + protected final Random random; + + private CoordinatesInWorld corner; + private WorldIconConsumer consumer; + private int xRelativeToFragmentAsChunkResolution; + private int yRelativeToFragmentAsChunkResolution; + protected int chunkX; + protected int chunkY; + private int middleOfChunkX; + private int middleOfChunkY; + + public StructureProducer(long seed, BiomeDataOracle biomeDataOracle, + RecognisedVersion recognisedVersion) { + this.seed = seed; + this.biomeDataOracle = biomeDataOracle; + this.recognisedVersion = recognisedVersion; + this.resolution = Resolution.CHUNK; + this.size = resolution.getStepsPerFragment(); + validBiomesForStructure = getValidBiomesForStructure(); + validBiomesAtMiddleOfChunk = getValidBiomesAtMiddleOfChunk(); + magicNumberForSeed1 = getMagicNumberForSeed1(); + magicNumberForSeed2 = getMagicNumberForSeed2(); + magicNumberForSeed3 = getMagicNumberForSeed3(); + maxDistanceBetweenScatteredFeatures = getMaxDistanceBetweenScatteredFeatures(); + minDistanceBetweenScatteredFeatures = getMinDistanceBetweenScatteredFeatures(); + distanceBetweenScatteredFeaturesRange = getDistanceBetweenScatteredFeaturesRange(); + structureSize = getStructureSize(); + random = new Random(); + } + + private int getDistanceBetweenScatteredFeaturesRange() { + return maxDistanceBetweenScatteredFeatures + - minDistanceBetweenScatteredFeatures; + } + + @Override + public void produce(CoordinatesInWorld corner, WorldIconConsumer consumer) { + this.corner = corner; + this.consumer = consumer; + for (xRelativeToFragmentAsChunkResolution = 0; xRelativeToFragmentAsChunkResolution < size; xRelativeToFragmentAsChunkResolution++) { + for (yRelativeToFragmentAsChunkResolution = 0; yRelativeToFragmentAsChunkResolution < size; yRelativeToFragmentAsChunkResolution++) { + generateAt(); + } + } + } + + // TODO: use longs? + private void generateAt() { + chunkX = xRelativeToFragmentAsChunkResolution + + (int) corner.getXAs(resolution); + chunkY = yRelativeToFragmentAsChunkResolution + + (int) corner.getYAs(resolution); + middleOfChunkX = middleOfChunk(chunkX); + middleOfChunkY = middleOfChunk(chunkY); + if (isValidLocation()) { + DefaultWorldIconTypes worldIconType = getWorldIconType(); + if (worldIconType != null) { + consumer.consume(new WorldIcon(createCoordinates(), + worldIconType.getName(), worldIconType.getImage())); + } + } + } + + private CoordinatesInWorld createCoordinates() { + long xInWorld = resolution + .convertFromThisToWorld(xRelativeToFragmentAsChunkResolution); + long yInWorld = resolution + .convertFromThisToWorld(yRelativeToFragmentAsChunkResolution); + return corner.add(xInWorld, yInWorld); + } + + protected boolean isSuccessful() { + int n = getInitialValue(chunkX); + int i1 = getInitialValue(chunkY); + updateSeed(n, i1); + n = updateValue(n); + i1 = updateValue(i1); + return isSuccessful(n, i1); + } + + private int getInitialValue(int value) { + return getModified(value) / maxDistanceBetweenScatteredFeatures; + } + + private int getModified(int value) { + if (value < 0) { + return value - maxDistanceBetweenScatteredFeatures + 1; + } else { + return value; + } + } + + private void updateSeed(int n, int i1) { + random.setSeed(getSeed(n, i1)); + } + + private long getSeed(int n, int i1) { + return n * magicNumberForSeed1 + i1 * magicNumberForSeed2 + seed + + magicNumberForSeed3; + } + + private boolean isSuccessful(int n, int i1) { + return chunkX == n && chunkY == i1; + } + + private int middleOfChunk(int value) { + return value * 16 + 8; + } + + protected boolean isValidBiomeAtMiddleOfChunk() { + try { + return validBiomesAtMiddleOfChunk + .contains(getBiomeAtMiddleOfChunk()); + } catch (UnknownBiomeIndexException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return false; + } catch (MinecraftInterfaceException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return false; + } + } + + protected Biome getBiomeAtMiddleOfChunk() + throws UnknownBiomeIndexException, MinecraftInterfaceException { + return biomeDataOracle.getBiomeAt(middleOfChunkX, middleOfChunkY); + } + + protected boolean isValidBiomeForStructure() { + return biomeDataOracle.isValidBiome(middleOfChunkX, middleOfChunkY, + structureSize, validBiomesForStructure); + } + + protected abstract boolean isValidLocation(); + + protected abstract DefaultWorldIconTypes getWorldIconType(); + + protected abstract List getValidBiomesForStructure(); + + protected abstract List getValidBiomesAtMiddleOfChunk(); + + protected abstract int updateValue(int value); + + protected abstract long getMagicNumberForSeed1(); + + protected abstract long getMagicNumberForSeed2(); + + protected abstract long getMagicNumberForSeed3(); + + protected abstract byte getMaxDistanceBetweenScatteredFeatures(); + + protected abstract byte getMinDistanceBetweenScatteredFeatures(); + + protected abstract int getStructureSize(); +} diff --git a/src/main/java/amidst/mojangapi/world/icon/TempleProducer.java b/src/main/java/amidst/mojangapi/world/icon/TempleProducer.java new file mode 100644 index 000000000..46c490c33 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/TempleProducer.java @@ -0,0 +1,119 @@ +package amidst.mojangapi.world.icon; + +import java.util.Arrays; +import java.util.List; + +import amidst.documentation.NotThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.minecraftinterface.MinecraftInterfaceException; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.biome.UnknownBiomeIndexException; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@NotThreadSafe +public class TempleProducer extends StructureProducer { + public TempleProducer(RecognisedVersion recognisedVersion, long seed, + BiomeDataOracle biomeDataOracle) { + super(seed, biomeDataOracle, recognisedVersion); + } + + @Override + protected boolean isValidLocation() { + return isSuccessful() && isValidBiomeAtMiddleOfChunk(); + } + + @Override + protected DefaultWorldIconTypes getWorldIconType() { + try { + Biome chunkBiome = getBiomeAtMiddleOfChunk(); + if (chunkBiome == Biome.swampland) { + return DefaultWorldIconTypes.WITCH; + } else if (chunkBiome.getName().contains("Jungle")) { + return DefaultWorldIconTypes.JUNGLE; + } else if (chunkBiome.getName().contains("Desert")) { + return DefaultWorldIconTypes.DESERT; + } else { + Log.e("No known structure for this biome type: " + + chunkBiome.getName()); + return null; + } + } catch (UnknownBiomeIndexException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return null; + } catch (MinecraftInterfaceException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return null; + } + } + + @Override + protected List getValidBiomesForStructure() { + return null; // not used + } + + @Override + protected List getValidBiomesAtMiddleOfChunk() { + // @formatter:off + if (recognisedVersion.isAtLeast(RecognisedVersion.V1_4_2)) { + return Arrays.asList( + Biome.desert, + Biome.desertHills, + Biome.jungle, + Biome.jungleHills, + Biome.swampland + ); + } else if (recognisedVersion.isAtLeast(RecognisedVersion.V12w22a)) { + return Arrays.asList( + Biome.desert, + Biome.desertHills, + Biome.jungle + ); + } else { + return Arrays.asList( + Biome.desert, + Biome.desertHills + ); + } + // @formatter:on + } + + @Override + protected int updateValue(int value) { + value *= maxDistanceBetweenScatteredFeatures; + value += random.nextInt(distanceBetweenScatteredFeaturesRange); + return value; + } + + @Override + protected long getMagicNumberForSeed1() { + return 341873128712L; + } + + @Override + protected long getMagicNumberForSeed2() { + return 132897987541L; + } + + @Override + protected long getMagicNumberForSeed3() { + return 14357617L; + } + + @Override + protected byte getMaxDistanceBetweenScatteredFeatures() { + return 32; + } + + @Override + protected byte getMinDistanceBetweenScatteredFeatures() { + return 8; + } + + @Override + protected int getStructureSize() { + return -1; // not used + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/VillageProducer.java b/src/main/java/amidst/mojangapi/world/icon/VillageProducer.java new file mode 100644 index 000000000..2c1dc3aed --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/VillageProducer.java @@ -0,0 +1,80 @@ +package amidst.mojangapi.world.icon; + +import java.util.Arrays; +import java.util.List; + +import amidst.documentation.NotThreadSafe; +import amidst.mojangapi.minecraftinterface.RecognisedVersion; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.oracle.BiomeDataOracle; + +@NotThreadSafe +public class VillageProducer extends StructureProducer { + public VillageProducer(RecognisedVersion recognisedVersion, long seed, + BiomeDataOracle biomeDataOracle) { + super(seed, biomeDataOracle, recognisedVersion); + } + + @Override + protected boolean isValidLocation() { + return isSuccessful() && isValidBiomeForStructure(); + } + + @Override + protected DefaultWorldIconTypes getWorldIconType() { + return DefaultWorldIconTypes.VILLAGE; + } + + @Override + protected List getValidBiomesForStructure() { + // @formatter:off + return Arrays.asList( + Biome.plains, + Biome.desert, + Biome.savanna + ); + // @formatter:on + } + + @Override + protected List getValidBiomesAtMiddleOfChunk() { + return null; // not used + } + + @Override + protected int updateValue(int value) { + value *= maxDistanceBetweenScatteredFeatures; + value += random.nextInt(distanceBetweenScatteredFeaturesRange); + return value; + } + + @Override + protected long getMagicNumberForSeed1() { + return 341873128712L; + } + + @Override + protected long getMagicNumberForSeed2() { + return 132897987541L; + } + + @Override + protected long getMagicNumberForSeed3() { + return 10387312L; + } + + @Override + protected byte getMaxDistanceBetweenScatteredFeatures() { + return 32; + } + + @Override + protected byte getMinDistanceBetweenScatteredFeatures() { + return 8; + } + + @Override + protected int getStructureSize() { + return 0; + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/WorldIcon.java b/src/main/java/amidst/mojangapi/world/icon/WorldIcon.java new file mode 100644 index 000000000..bcbed125e --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/WorldIcon.java @@ -0,0 +1,37 @@ +package amidst.mojangapi.world.icon; + +import java.awt.image.BufferedImage; + +import amidst.documentation.Immutable; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; + +@Immutable +public class WorldIcon { + private final CoordinatesInWorld coordinates; + private final String name; + private final BufferedImage image; + + public WorldIcon(CoordinatesInWorld coordinates, String name, + BufferedImage image) { + this.coordinates = coordinates; + this.name = name; + this.image = image; + } + + public CoordinatesInWorld getCoordinates() { + return coordinates; + } + + public String getName() { + return name; + } + + public BufferedImage getImage() { + return image; + } + + @Override + public String toString() { + return name + " " + coordinates.toString(); + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/WorldIconCollector.java b/src/main/java/amidst/mojangapi/world/icon/WorldIconCollector.java new file mode 100644 index 000000000..62bbf2224 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/WorldIconCollector.java @@ -0,0 +1,32 @@ +package amidst.mojangapi.world.icon; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class WorldIconCollector implements WorldIconConsumer { + private List worldIcons; + + @Override + public void consume(WorldIcon worldIcon) { + initListIfNecessary(); + worldIcons.add(worldIcon); + } + + private void initListIfNecessary() { + if (worldIcons == null) { + worldIcons = new LinkedList(); + } + } + + public List get() { + if (worldIcons == null) { + return Collections.emptyList(); + } else { + return worldIcons; + } + } +} diff --git a/src/main/java/amidst/mojangapi/world/icon/WorldIconConsumer.java b/src/main/java/amidst/mojangapi/world/icon/WorldIconConsumer.java new file mode 100644 index 000000000..62b31730e --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/WorldIconConsumer.java @@ -0,0 +1,5 @@ +package amidst.mojangapi.world.icon; + +public interface WorldIconConsumer { + void consume(WorldIcon worldIcon); +} diff --git a/src/main/java/amidst/mojangapi/world/icon/WorldIconProducer.java b/src/main/java/amidst/mojangapi/world/icon/WorldIconProducer.java new file mode 100644 index 000000000..ed87a864a --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/icon/WorldIconProducer.java @@ -0,0 +1,22 @@ +package amidst.mojangapi.world.icon; + +import java.util.List; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; + +@NotThreadSafe +public abstract class WorldIconProducer { + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public abstract void produce(CoordinatesInWorld corner, + WorldIconConsumer consumer); + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public List getAt(CoordinatesInWorld corner) { + WorldIconCollector collector = new WorldIconCollector(); + produce(corner, collector); + return collector.get(); + } +} diff --git a/src/main/java/amidst/mojangapi/world/oracle/BiomeDataOracle.java b/src/main/java/amidst/mojangapi/world/oracle/BiomeDataOracle.java new file mode 100644 index 000000000..73072818c --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/oracle/BiomeDataOracle.java @@ -0,0 +1,147 @@ +package amidst.mojangapi.world.oracle; + +import java.awt.Point; +import java.util.List; +import java.util.Random; + +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.minecraftinterface.MinecraftInterface; +import amidst.mojangapi.minecraftinterface.MinecraftInterfaceException; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.biome.UnknownBiomeIndexException; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; +import amidst.mojangapi.world.coordinates.Resolution; + +@ThreadSafe +public class BiomeDataOracle { + private final MinecraftInterface minecraftInterface; + + public BiomeDataOracle(MinecraftInterface minecraftInterface) { + this.minecraftInterface = minecraftInterface; + } + + public void populateArrayUsingQuarterResolution(CoordinatesInWorld corner, + short[][] result) { + int width = result.length; + if (width > 0) { + int height = result[0].length; + int left = (int) corner.getXAs(Resolution.QUARTER); + int top = (int) corner.getYAs(Resolution.QUARTER); + try { + copyToResult(result, width, height, + getQuarterResolutionBiomeData(left, top, width, height)); + } catch (MinecraftInterfaceException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + } + } + } + + private void copyToResult(short[][] result, int width, int height, + int[] biomeData) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + result[x][y] = (short) biomeData[getBiomeDataIndex(x, y, width)]; + } + } + } + + private int getBiomeDataIndex(int x, int y, int width) { + return x + y * width; + } + + public boolean isValidBiome(int x, int y, int size, List validBiomes) { + int left = x - size >> 2; + int top = y - size >> 2; + int right = x + size >> 2; + int bottom = y + size >> 2; + int width = right - left + 1; + int height = bottom - top + 1; + try { + int[] biomeData = getQuarterResolutionBiomeData(left, top, width, + height); + for (int i = 0; i < width * height; i++) { + if (!validBiomes.contains(Biome.getByIndex(biomeData[i]))) { + return false; + } + } + return true; + } catch (UnknownBiomeIndexException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return false; + } catch (MinecraftInterfaceException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return false; + } + } + + // TODO: Find out if we should useQuarterResolution or not + public Point findValidLocation(int x, int y, int size, + List validBiomes, Random random) { + int left = x - size >> 2; + int top = y - size >> 2; + int right = x + size >> 2; + int bottom = y + size >> 2; + int width = right - left + 1; + int height = bottom - top + 1; + try { + int[] biomeData = getQuarterResolutionBiomeData(left, top, width, + height); + Point location = null; + int numberOfValidLocations = 0; + for (int i = 0; i < width * height; i++) { + if (validBiomes.contains(Biome.getByIndex(biomeData[i])) + && (location == null || random + .nextInt(numberOfValidLocations + 1) == 0)) { + location = createLocation(left, top, width, i); + numberOfValidLocations++; + } + } + return location; + } catch (UnknownBiomeIndexException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return null; + } catch (MinecraftInterfaceException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return null; + } + } + + private Point createLocation(int left, int top, int width, int i) { + int x = left + i % width << 2; + int y = top + i / width << 2; + return new Point(x, y); + } + + /** + * Gets the biome located at the block-coordinates. This is not a fast + * routine, it was added for rare things like accurately testing structures. + * (uses the 1:1 scale biome-map) + */ + public Biome getBiomeAt(int x, int y) throws UnknownBiomeIndexException, + MinecraftInterfaceException { + int[] biomeData = getFullResolutionBiomeData(x, y, 1, 1); + return Biome.getByIndex(biomeData[0]); + } + + private int[] getQuarterResolutionBiomeData(int x, int y, int width, + int height) throws MinecraftInterfaceException { + return getBiomeData(x, y, width, height, true); + } + + private int[] getFullResolutionBiomeData(int x, int y, int width, int height) + throws MinecraftInterfaceException { + return getBiomeData(x, y, width, height, false); + } + + private int[] getBiomeData(int x, int y, int width, int height, + boolean useQuarterResolution) throws MinecraftInterfaceException { + return minecraftInterface.getBiomeData(x, y, width, height, + useQuarterResolution); + } +} diff --git a/src/main/java/amidst/mojangapi/world/oracle/SlimeChunkOracle.java b/src/main/java/amidst/mojangapi/world/oracle/SlimeChunkOracle.java new file mode 100644 index 000000000..fe588514e --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/oracle/SlimeChunkOracle.java @@ -0,0 +1,39 @@ +package amidst.mojangapi.world.oracle; + +import java.util.Random; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public class SlimeChunkOracle { + private final long seed; + private final Random random = new Random(); + + public SlimeChunkOracle(long seed) { + this.seed = seed; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + public boolean isSlimeChunk(long chunkX, long chunkY) { + updateSeed(chunkX, chunkY); + return isSlimeChunk(); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private void updateSeed(long chunkX, long chunkY) { + random.setSeed(getSeed(chunkX, chunkY)); + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private long getSeed(long chunkX, long chunkY) { + return seed + chunkX * chunkX * 0x4c1906 + chunkX * 0x5ac0db + chunkY + * chunkY * 0x4307a7L + chunkY * 0x5f24f ^ 0x3ad8025f; + } + + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + private boolean isSlimeChunk() { + return random.nextInt(10) == 0; + } +} diff --git a/src/main/java/amidst/mojangapi/world/player/MovablePlayerList.java b/src/main/java/amidst/mojangapi/world/player/MovablePlayerList.java new file mode 100644 index 000000000..3164c8425 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/player/MovablePlayerList.java @@ -0,0 +1,106 @@ +package amidst.mojangapi.world.player; + +import java.util.Iterator; +import java.util.concurrent.ConcurrentLinkedQueue; + +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.file.directory.SaveDirectory; +import amidst.mojangapi.file.nbt.player.PlayerNbt; +import amidst.threading.WorkerExecutor; +import amidst.threading.WorkerWithoutResult; + +@ThreadSafe +public class MovablePlayerList implements Iterable { + private static final MovablePlayerList DUMMY = new MovablePlayerList(null, + null, false, WorldPlayerType.NONE); + + public static MovablePlayerList dummy() { + return DUMMY; + } + + private final PlayerInformationCache playerInformationCache; + private final SaveDirectory saveDirectory; + private final boolean isSaveEnabled; + + private volatile WorldPlayerType worldPlayerType; + private volatile ConcurrentLinkedQueue players = new ConcurrentLinkedQueue(); + + public MovablePlayerList(PlayerInformationCache playerInformationCache, + SaveDirectory saveDirectory, boolean isSaveEnabled, + WorldPlayerType worldPlayerType) { + this.playerInformationCache = playerInformationCache; + this.saveDirectory = saveDirectory; + this.isSaveEnabled = isSaveEnabled; + this.worldPlayerType = worldPlayerType; + } + + public WorldPlayerType getWorldPlayerType() { + return worldPlayerType; + } + + public void setWorldPlayerType(WorldPlayerType worldPlayerType) { + this.worldPlayerType = worldPlayerType; + } + + public boolean canLoad() { + return saveDirectory != null; + } + + public void load(WorkerExecutor workerExecutor, + Runnable onPlayerFinishedLoading) { + if (saveDirectory != null) { + Log.i("loading player locations"); + ConcurrentLinkedQueue players = new ConcurrentLinkedQueue(); + this.players = players; + loadPlayersLater(players, workerExecutor, onPlayerFinishedLoading); + } + } + + private void loadPlayersLater(final ConcurrentLinkedQueue players, + final WorkerExecutor workerExecutor, + final Runnable onPlayerFinishedLoading) { + workerExecutor.invokeLater(new WorkerWithoutResult() { + @Override + protected void main() { + for (PlayerNbt playerNbt : worldPlayerType + .createPlayerNbts(saveDirectory)) { + executeFork(playerNbt); + } + } + + @Override + protected void fork(PlayerNbt playerNbt) { + Player player = playerNbt.createPlayer(playerInformationCache); + if (player.tryLoadLocation()) { + players.offer(player); + } + } + + @Override + protected void onForkFinished() { + onPlayerFinishedLoading.run(); + } + }); + } + + public boolean canSave() { + return isSaveEnabled; + } + + public void save() { + if (isSaveEnabled) { + Log.i("saving player locations"); + for (Player player : players) { + player.trySaveLocation(); + } + } else { + Log.i("not saving player locations, because it is disabled for this version"); + } + } + + @Override + public Iterator iterator() { + return players.iterator(); + } +} diff --git a/src/main/java/amidst/mojangapi/world/player/Player.java b/src/main/java/amidst/mojangapi/world/player/Player.java new file mode 100644 index 000000000..7e2d3d12b --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/player/Player.java @@ -0,0 +1,92 @@ +package amidst.mojangapi.world.player; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.nbt.player.PlayerNbt; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; + +@ThreadSafe +public class Player { + private final PlayerInformation playerInformation; + private final PlayerNbt playerNbt; + private volatile PlayerCoordinates savedCoordinates; + private volatile PlayerCoordinates currentCoordinates; + + public Player(PlayerInformation playerInformation, PlayerNbt playerNbt) { + this.playerInformation = playerInformation; + this.playerNbt = playerNbt; + } + + public String getPlayerName() { + return playerInformation.getNameOrUUID(); + } + + public BufferedImage getHead() { + return playerInformation.getHead(); + } + + public PlayerCoordinates getPlayerCoordinates() { + return currentCoordinates; + } + + public void moveTo(CoordinatesInWorld coordinates, long height) { + this.currentCoordinates = new PlayerCoordinates(coordinates, height); + } + + public boolean trySaveLocation() { + try { + if (saveLocation()) { + return true; + } else { + Log.w("skipping to save player location, because the backup file cannot be created for player: " + + getPlayerName()); + return false; + } + } catch (MojangApiParsingException e) { + Log.w("error while writing player location for player: " + + getPlayerName()); + e.printStackTrace(); + return false; + } + } + + /** + * Returns true if the player was not moved or the new location was + * successfully saved. + */ + public synchronized boolean saveLocation() throws MojangApiParsingException { + PlayerCoordinates currentCoordinates = this.currentCoordinates; + if (savedCoordinates != currentCoordinates) { + if (playerNbt.tryWriteCoordinates(currentCoordinates)) { + savedCoordinates = currentCoordinates; + return true; + } else { + return false; + } + } else { + return true; + } + } + + public boolean tryLoadLocation() { + try { + loadLocation(); + return true; + } catch (IOException | MojangApiParsingException e) { + Log.w("error while reading player location for player: " + + getPlayerName()); + e.printStackTrace(); + return false; + } + } + + public synchronized void loadLocation() throws IOException, + MojangApiParsingException { + this.savedCoordinates = playerNbt.readCoordinates(); + this.currentCoordinates = savedCoordinates; + } +} diff --git a/src/main/java/amidst/mojangapi/world/player/PlayerCoordinates.java b/src/main/java/amidst/mojangapi/world/player/PlayerCoordinates.java new file mode 100644 index 000000000..973f4c95e --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/player/PlayerCoordinates.java @@ -0,0 +1,36 @@ +package amidst.mojangapi.world.player; + +import amidst.documentation.Immutable; +import amidst.mojangapi.world.coordinates.CoordinatesInWorld; + +@Immutable +public class PlayerCoordinates { + private final CoordinatesInWorld coordinates; + private final long y; + + public PlayerCoordinates(long x, long y, long z) { + this.coordinates = CoordinatesInWorld.from(x, z); + this.y = y; + } + + public PlayerCoordinates(CoordinatesInWorld coordinates, long y) { + this.coordinates = coordinates; + this.y = y; + } + + public long getX() { + return coordinates.getX(); + } + + public long getY() { + return y; + } + + public long getZ() { + return coordinates.getY(); + } + + public CoordinatesInWorld getCoordinatesInWorld() { + return coordinates; + } +} diff --git a/src/main/java/amidst/mojangapi/world/player/PlayerInformation.java b/src/main/java/amidst/mojangapi/world/player/PlayerInformation.java new file mode 100644 index 000000000..5ed58f56b --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/player/PlayerInformation.java @@ -0,0 +1,111 @@ +package amidst.mojangapi.world.player; + +import java.awt.image.BufferedImage; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.file.MojangApiParsingException; +import amidst.mojangapi.file.json.PlayerInformationRetriever; +import amidst.mojangapi.file.json.player.PlayerJson; +import amidst.mojangapi.world.icon.DefaultWorldIconTypes; + +@Immutable +public class PlayerInformation { + private static final BufferedImage DEFAULT_HEAD = DefaultWorldIconTypes.PLAYER + .getImage(); + private static final PlayerInformation THE_SINGLEPLAYER_PLAYER = new PlayerInformation( + null, "The Singleplayer Player", DEFAULT_HEAD); + + @NotNull + public static PlayerInformation fromUUID(String uuid) { + PlayerJson player = PlayerInformationRetriever + .tryGetPlayerJsonByUUID(uuid); + BufferedImage head; + if (player != null) { + head = tryGetPlayerHeadBySkinUrl(player); + if (head != null) { + return new PlayerInformation(player.getId(), player.getName(), + head); + } else { + return new PlayerInformation(player.getId(), player.getName(), + DEFAULT_HEAD); + } + } else { + return new PlayerInformation(uuid, null, DEFAULT_HEAD); + } + } + + @NotNull + public static PlayerInformation fromName(String name) { + PlayerJson player = PlayerInformationRetriever + .tryGetPlayerJsonByName(name); + BufferedImage head; + if (player != null) { + head = tryGetPlayerHeadBySkinUrl(player); + if (head != null) { + return new PlayerInformation(player.getId(), player.getName(), + head); + } else { + head = PlayerInformationRetriever.tryGetPlayerHeadByName(name); + if (head != null) { + return new PlayerInformation(player.getId(), + player.getName(), head); + } else { + return new PlayerInformation(player.getId(), + player.getName(), DEFAULT_HEAD); + } + } + } else { + head = PlayerInformationRetriever.tryGetPlayerHeadByName(name); + if (head != null) { + return new PlayerInformation(null, name, head); + } else { + return new PlayerInformation(null, name, DEFAULT_HEAD); + } + } + } + + private static BufferedImage tryGetPlayerHeadBySkinUrl(PlayerJson player) { + try { + return PlayerInformationRetriever.tryGetPlayerHeadBySkinUrl(player + .getSkinUrl()); + } catch (MojangApiParsingException e) { + return null; + } + } + + @NotNull + public static PlayerInformation theSingleplayerPlayer() { + return THE_SINGLEPLAYER_PLAYER; + } + + private final String uuid; + private final String name; + private final BufferedImage head; + + private PlayerInformation(String uuid, String name, BufferedImage head) { + this.uuid = uuid; + this.name = name; + this.head = head; + } + + public String getUUID() { + return uuid; + } + + public String getName() { + return name; + } + + public BufferedImage getHead() { + return head; + } + + public String getNameOrUUID() { + if (name != null) { + return name; + } else { + return uuid; + } + } +} diff --git a/src/main/java/amidst/mojangapi/world/player/PlayerInformationCache.java b/src/main/java/amidst/mojangapi/world/player/PlayerInformationCache.java new file mode 100644 index 000000000..2b9df86d9 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/player/PlayerInformationCache.java @@ -0,0 +1,64 @@ +package amidst.mojangapi.world.player; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import amidst.documentation.NotNull; +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; + +/** + * Even though this class is thread-safe, it is possible that the same player + * information is loaded more than once. This will be the case, if the first + * request did not finish before the second request arrives. However, this use + * case is very unlikely to happen for amidst. If it happens, it should not be a + * problem. + */ +@ThreadSafe +public class PlayerInformationCache { + private final Map byUUID = new ConcurrentHashMap(); + private final Map byName = new ConcurrentHashMap(); + + @NotNull + public PlayerInformation getByUUID(String uuid) { + uuid = getCleanUUID(uuid); + PlayerInformation result = byUUID.get(uuid); + if (result != null) { + return result; + } else { + Log.i("requesting player information for uuid: " + uuid); + result = PlayerInformation.fromUUID(uuid); + put(result); + return result; + } + } + + @NotNull + public PlayerInformation getByName(String name) { + PlayerInformation result = byName.get(name); + if (result != null) { + return result; + } else { + Log.i("requesting player information for name: " + name); + result = PlayerInformation.fromName(name); + put(result); + return result; + } + } + + private void put(PlayerInformation result) { + if (result.getUUID() != null) { + byUUID.put(result.getUUID(), result); + } + if (result.getName() != null) { + byName.put(result.getName(), result); + } + } + + /** + * The uuid in the filename contains dashes that are not allowed in the url. + */ + private String getCleanUUID(String uuid) { + return uuid.replace("-", ""); + } +} diff --git a/src/main/java/amidst/mojangapi/world/player/WorldPlayerType.java b/src/main/java/amidst/mojangapi/world/player/WorldPlayerType.java new file mode 100644 index 000000000..2d1bf0303 --- /dev/null +++ b/src/main/java/amidst/mojangapi/world/player/WorldPlayerType.java @@ -0,0 +1,73 @@ +package amidst.mojangapi.world.player; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import amidst.documentation.Immutable; +import amidst.documentation.NotNull; +import amidst.mojangapi.file.directory.SaveDirectory; +import amidst.mojangapi.file.nbt.LevelDatNbt; +import amidst.mojangapi.file.nbt.player.PlayerNbt; + +@Immutable +public enum WorldPlayerType { + // Only the selectable options need a name. + // @formatter:off + NONE(null), + SINGLEPLAYER("Singleplayer"), + MULTIPLAYER("Multiplayer"), + BOTH("Both"); + // @formatter:on + + private static final List SELECTABLE = Arrays.asList( + SINGLEPLAYER, MULTIPLAYER, BOTH); + + public static List getSelectable() { + return SELECTABLE; + } + + public static WorldPlayerType from(SaveDirectory saveDirectory, + LevelDatNbt levelDat) { + if (saveDirectory.hasMultiplayerPlayers()) { + if (levelDat.hasPlayer()) { + return BOTH; + } else { + return MULTIPLAYER; + } + } else { + return SINGLEPLAYER; + } + } + + private final String name; + + private WorldPlayerType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @NotNull + public List createPlayerNbts(SaveDirectory saveDirectory) { + if (this == NONE) { + return Collections.emptyList(); + } else if (this == SINGLEPLAYER) { + return saveDirectory.createSingleplayerPlayerNbts(); + } else if (this == MULTIPLAYER) { + return saveDirectory.createMultiplayerPlayerNbts(); + } else { + List result = saveDirectory + .createMultiplayerPlayerNbts(); + result.addAll(saveDirectory.createSingleplayerPlayerNbts()); + return result; + } + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/amidst/settings/BooleanSetting.java b/src/main/java/amidst/settings/BooleanSetting.java new file mode 100644 index 000000000..b98299698 --- /dev/null +++ b/src/main/java/amidst/settings/BooleanSetting.java @@ -0,0 +1,51 @@ +package amidst.settings; + +import java.util.prefs.Preferences; + +import javax.swing.JToggleButton.ToggleButtonModel; + +import amidst.documentation.ThreadSafe; + +@ThreadSafe +public class BooleanSetting extends SettingBase { + @SuppressWarnings("serial") + private class BooleanButtonModel extends ToggleButtonModel { + @Override + public boolean isSelected() { + return get(); + } + + @Override + public void setSelected(boolean isSelected) { + set(isSelected); + } + + private void update() { + super.setSelected(isSelected()); + } + } + + private final BooleanButtonModel buttonModel; + + public BooleanSetting(Preferences preferences, String key, + boolean defaultValue) { + super(preferences, key); + this.buttonModel = new BooleanButtonModel(); + restore(defaultValue); + } + + @Override + protected Boolean getInitialValue(Boolean defaultValue) { + return preferences.getBoolean(key, defaultValue); + } + + @Override + protected void update(Boolean value) { + this.preferences.putBoolean(key, value); + this.buttonModel.update(); + } + + public BooleanButtonModel getButtonModel() { + return buttonModel; + } +} diff --git a/src/main/java/amidst/settings/ImmutableSetting.java b/src/main/java/amidst/settings/ImmutableSetting.java new file mode 100644 index 000000000..3c3e800b1 --- /dev/null +++ b/src/main/java/amidst/settings/ImmutableSetting.java @@ -0,0 +1,28 @@ +package amidst.settings; + +import amidst.documentation.Immutable; + +@Immutable +public class ImmutableSetting implements Setting { + private final T value; + + public ImmutableSetting(T value) { + this.value = value; + } + + @Override + public String getKey() { + throw new UnsupportedOperationException("ImmutableSetting has no key!"); + } + + @Override + public T get() { + return value; + } + + @Override + public void set(T value) { + throw new UnsupportedOperationException( + "ImmutableSetting cannot be modified!"); + } +} diff --git a/src/main/java/amidst/settings/MultipleStringsSetting.java b/src/main/java/amidst/settings/MultipleStringsSetting.java new file mode 100644 index 000000000..61062c898 --- /dev/null +++ b/src/main/java/amidst/settings/MultipleStringsSetting.java @@ -0,0 +1,81 @@ +package amidst.settings; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.prefs.Preferences; + +import javax.swing.JToggleButton.ToggleButtonModel; + +import amidst.documentation.ThreadSafe; + +@ThreadSafe +public class MultipleStringsSetting extends SettingBase { + @SuppressWarnings("serial") + public class SelectButtonModel extends ToggleButtonModel { + private final String name; + + public SelectButtonModel(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public boolean isSelected() { + return name.equals(get()); + } + + @Override + public void setSelected(boolean isSelected) { + if (isSelected) { + set(name); + } + } + + private void update() { + super.setSelected(isSelected()); + } + } + + private final Iterable buttonModels; + + public MultipleStringsSetting(Preferences preferences, String key, + String defaultValue, String[] values) { + super(preferences, key); + this.buttonModels = createButtonModels(values); + restore(defaultValue); + } + + private Iterable createButtonModels(String[] values) { + List result = new ArrayList( + values.length); + for (String value : values) { + result.add(new SelectButtonModel(value)); + } + return Collections.unmodifiableList(result); + } + + @Override + protected String getInitialValue(String defaultValue) { + return preferences.get(key, defaultValue); + } + + @Override + protected void update(String value) { + this.preferences.put(key, value); + updateButtonModels(); + } + + private void updateButtonModels() { + for (SelectButtonModel buttonModel : buttonModels) { + buttonModel.update(); + } + } + + public Iterable getButtonModels() { + return buttonModels; + } +} diff --git a/src/main/java/amidst/settings/Setting.java b/src/main/java/amidst/settings/Setting.java new file mode 100644 index 000000000..0dc381b21 --- /dev/null +++ b/src/main/java/amidst/settings/Setting.java @@ -0,0 +1,9 @@ +package amidst.settings; + +public interface Setting { + String getKey(); + + T get(); + + void set(T value); +} diff --git a/src/main/java/amidst/settings/SettingBase.java b/src/main/java/amidst/settings/SettingBase.java new file mode 100644 index 000000000..3c9a7c592 --- /dev/null +++ b/src/main/java/amidst/settings/SettingBase.java @@ -0,0 +1,41 @@ +package amidst.settings; + +import java.util.prefs.Preferences; + +import amidst.documentation.ThreadSafe; + +@ThreadSafe +public abstract class SettingBase implements Setting { + protected final Preferences preferences; + protected final String key; + private volatile T value; + + public SettingBase(Preferences preferences, String key) { + this.preferences = preferences; + this.key = key; + } + + protected void restore(T defaultValue) { + set(getInitialValue(defaultValue)); + } + + @Override + public String getKey() { + return key; + } + + @Override + public T get() { + return value; + } + + @Override + public synchronized void set(T value) { + this.value = value; + update(value); + } + + protected abstract T getInitialValue(T defaultValue); + + protected abstract void update(T value); +} diff --git a/src/main/java/amidst/settings/StringSetting.java b/src/main/java/amidst/settings/StringSetting.java new file mode 100644 index 000000000..51448acb7 --- /dev/null +++ b/src/main/java/amidst/settings/StringSetting.java @@ -0,0 +1,24 @@ +package amidst.settings; + +import java.util.prefs.Preferences; + +import amidst.documentation.ThreadSafe; + +@ThreadSafe +public class StringSetting extends SettingBase { + public StringSetting(Preferences preferences, String key, + String defaultValue) { + super(preferences, key); + restore(defaultValue); + } + + @Override + protected String getInitialValue(String defaultValue) { + return preferences.get(key, defaultValue); + } + + @Override + protected void update(String value) { + preferences.put(key, value); + } +} diff --git a/src/main/java/amidst/settings/biomecolorprofile/BiomeColorJson.java b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorJson.java new file mode 100644 index 000000000..b4ce240bd --- /dev/null +++ b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorJson.java @@ -0,0 +1,38 @@ +package amidst.settings.biomecolorprofile; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.mojangapi.world.biome.BiomeColor; + +@Immutable +public class BiomeColorJson { + private int r; + private int g; + private int b; + + @GsonConstructor + public BiomeColorJson() { + } + + public BiomeColorJson(int r, int g, int b) { + this.r = r; + this.g = g; + this.b = b; + } + + public int getR() { + return r; + } + + public int getG() { + return g; + } + + public int getB() { + return b; + } + + public BiomeColor createBiomeColor() { + return BiomeColor.from(r, g, b); + } +} diff --git a/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfile.java b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfile.java new file mode 100644 index 000000000..13d16d6e4 --- /dev/null +++ b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfile.java @@ -0,0 +1,148 @@ +package amidst.settings.biomecolorprofile; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import amidst.documentation.GsonConstructor; +import amidst.documentation.Immutable; +import amidst.logging.Log; +import amidst.mojangapi.world.biome.Biome; +import amidst.mojangapi.world.biome.BiomeColor; + +@Immutable +public class BiomeColorProfile { + public static void saveDefaultProfileIfNecessary() { + if (!isEnabled()) { + Log.i("Unable to find biome color profile directory."); + } else { + Log.i("Found biome color profile directory."); + if (DEFAULT_PROFILE_FILE.isFile()) { + Log.i("Found default biome color profile."); + } else if (DEFAULT_PROFILE.save(DEFAULT_PROFILE_FILE)) { + Log.i("Saved default biome color profile."); + } else { + Log.i("Attempted to save default biome color profile, but encountered an error."); + } + } + } + + private static Map createDefaultColorMap() { + Map result = new HashMap(); + for (Biome biome : Biome.allBiomes()) { + result.put(biome.getName(), biome.getDefaultColor() + .createBiomeColorJson()); + } + return result; + } + + public static BiomeColorProfile getDefaultProfile() { + return DEFAULT_PROFILE; + } + + public static boolean isEnabled() { + return PROFILE_DIRECTORY.isDirectory(); + } + + private static final BiomeColorProfile DEFAULT_PROFILE = new BiomeColorProfile( + "default", null, createDefaultColorMap()); + public static final File PROFILE_DIRECTORY = new File("./biome"); + public static final File DEFAULT_PROFILE_FILE = new File(PROFILE_DIRECTORY, + "default.json"); + + private volatile String name; + private volatile String shortcut; + private volatile Map colorMap; + + @GsonConstructor + public BiomeColorProfile() { + } + + private BiomeColorProfile(String name, String shortcut, + Map colorMap) { + this.name = name; + this.shortcut = shortcut; + this.colorMap = colorMap; + } + + public String getName() { + return name; + } + + public String getShortcut() { + return shortcut; + } + + public void validate() { + for (String biomeName : colorMap.keySet()) { + if (!Biome.exists(biomeName)) { + Log.i("Failed to find biome for: " + biomeName + + " in profile: " + name); + } + } + } + + public BiomeColor[] createBiomeColorArray() { + BiomeColor[] result = new BiomeColor[Biome.getBiomesLength()]; + for (Biome biome : Biome.allBiomes()) { + result[biome.getIndex()] = getBiomeColor(biome); + } + return result; + } + + private BiomeColor getBiomeColor(Biome biome) { + if (colorMap.containsKey(biome.getName())) { + return colorMap.get(biome.getName()).createBiomeColor(); + } else { + return biome.getDefaultColor(); + } + } + + public boolean save(File file) { + return writeToFile(file, serialize()); + } + + private String serialize() { + String output = "{ \"name\":\"" + name + "\", \"colorMap\":[\r\n"; + output += serializeColorMap(); + return output + " ] }\r\n"; + } + + /** + * This method uses the sorted color map, so the serialization will have a + * reproducible order. + */ + private String serializeColorMap() { + String output = ""; + for (Map.Entry pairs : getSortedColorMapEntries()) { + output += "[ \"" + pairs.getKey() + "\", { "; + output += "\"r\":" + pairs.getValue().getR() + ", "; + output += "\"g\":" + pairs.getValue().getG() + ", "; + output += "\"b\":" + pairs.getValue().getB() + " } ],\r\n"; + } + return output.substring(0, output.length() - 3); + } + + private Set> getSortedColorMapEntries() { + SortedMap result = new TreeMap( + Biome::compareByIndex); + result.putAll(colorMap); + return result.entrySet(); + } + + private boolean writeToFile(File file, String output) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + writer.write(output); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileLoader.java b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileLoader.java new file mode 100644 index 000000000..4805984b7 --- /dev/null +++ b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileLoader.java @@ -0,0 +1,64 @@ +package amidst.settings.biomecolorprofile; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import amidst.documentation.Immutable; +import amidst.logging.Log; + +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +@Immutable +public class BiomeColorProfileLoader { + private static final Gson GSON = new Gson(); + + public void visitProfiles(BiomeColorProfileVisitor visitor) { + visitProfiles(BiomeColorProfile.PROFILE_DIRECTORY, visitor); + } + + private void visitProfiles(File directory, BiomeColorProfileVisitor visitor) { + boolean entered = false; + for (File file : directory.listFiles()) { + if (file.isFile()) { + BiomeColorProfile profile = createFromFile(file); + if (profile != null) { + if (!entered) { + entered = true; + visitor.enterDirectory(directory.getName()); + } + visitor.visitProfile(profile); + } + } else { + visitProfiles(file, visitor); + } + } + if (entered) { + visitor.leaveDirectory(); + } + } + + private BiomeColorProfile createFromFile(File file) { + BiomeColorProfile profile = null; + if (file.exists() && file.isFile()) { + try { + profile = readProfile(file); + profile.validate(); + } catch (JsonSyntaxException | JsonIOException | IOException e) { + Log.w("Unable to load file: " + file); + e.printStackTrace(); + } + } + return profile; + } + + private BiomeColorProfile readProfile(File file) throws IOException, + JsonSyntaxException, JsonIOException { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + return GSON.fromJson(reader, BiomeColorProfile.class); + } + } +} diff --git a/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileSelection.java b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileSelection.java new file mode 100644 index 000000000..c34c6819a --- /dev/null +++ b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileSelection.java @@ -0,0 +1,42 @@ +package amidst.settings.biomecolorprofile; + +import amidst.documentation.ThreadSafe; +import amidst.logging.Log; +import amidst.mojangapi.world.biome.BiomeColor; +import amidst.mojangapi.world.biome.UnknownBiomeIndexException; + +@ThreadSafe +public class BiomeColorProfileSelection { + private volatile BiomeColor[] biomeColors; + + public BiomeColorProfileSelection(BiomeColorProfile biomeColorProfile) { + set(biomeColorProfile); + } + + public BiomeColor getBiomeColorOrUnknown(int index) { + try { + return getBiomeColor(index); + } catch (UnknownBiomeIndexException e) { + Log.e(e.getMessage()); + e.printStackTrace(); + return BiomeColor.unknown(); + } + } + + public BiomeColor getBiomeColor(int index) + throws UnknownBiomeIndexException { + BiomeColor[] biomeColors = this.biomeColors; + if (index < 0 || index >= biomeColors.length + || biomeColors[index] == null) { + throw new UnknownBiomeIndexException( + "unsupported biome index detected: " + index); + } else { + return biomeColors[index]; + } + } + + public void set(BiomeColorProfile biomeColorProfile) { + this.biomeColors = biomeColorProfile.createBiomeColorArray(); + Log.i("Biome color profile activated."); + } +} diff --git a/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileVisitor.java b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileVisitor.java new file mode 100644 index 000000000..1ddd7be41 --- /dev/null +++ b/src/main/java/amidst/settings/biomecolorprofile/BiomeColorProfileVisitor.java @@ -0,0 +1,9 @@ +package amidst.settings.biomecolorprofile; + +public interface BiomeColorProfileVisitor { + void enterDirectory(String name); + + void visitProfile(BiomeColorProfile profile); + + void leaveDirectory(); +} diff --git a/src/main/java/amidst/threading/SimpleWorker.java b/src/main/java/amidst/threading/SimpleWorker.java new file mode 100644 index 000000000..8e9573aff --- /dev/null +++ b/src/main/java/amidst/threading/SimpleWorker.java @@ -0,0 +1,44 @@ +package amidst.threading; + +import java.util.concurrent.ExecutorService; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; + +public abstract class SimpleWorker { + @CalledByAny + public void executeMain(ExecutorService executorService) { + new Worker() { + @Override + protected M main() throws Exception { + return SimpleWorker.this.main(); + } + + @Override + protected void onMainFinished(M result) { + SimpleWorker.this.onMainFinished(result); + } + + @Override + protected void onMainFinishedWithException(Exception e) { + SimpleWorker.this.onMainFinishedWithException(e); + } + }.executeMain(executorService); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected abstract M main() throws Exception; + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinished(M result) { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinishedWithException(Exception e) { + throw new UnsupportedOperationException( + "you need to override the method SimpleWorker.onMainFinishedWithException()", + e); + } +} diff --git a/src/main/java/amidst/threading/SimpleWorkerWithoutResult.java b/src/main/java/amidst/threading/SimpleWorkerWithoutResult.java new file mode 100644 index 000000000..41999ebd8 --- /dev/null +++ b/src/main/java/amidst/threading/SimpleWorkerWithoutResult.java @@ -0,0 +1,45 @@ +package amidst.threading; + +import java.util.concurrent.ExecutorService; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; + +public abstract class SimpleWorkerWithoutResult { + @CalledByAny + public void executeMain(ExecutorService executorService) { + new Worker() { + @Override + protected Void main() throws Exception { + SimpleWorkerWithoutResult.this.main(); + return null; + } + + @Override + protected void onMainFinished(Void result) { + SimpleWorkerWithoutResult.this.onMainFinished(); + } + + @Override + protected void onMainFinishedWithException(Exception e) { + SimpleWorkerWithoutResult.this.onMainFinishedWithException(e); + } + }.executeMain(executorService); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected abstract void main() throws Exception; + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinished() { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinishedWithException(Exception e) { + throw new UnsupportedOperationException( + "you need to override the method SimpleWorkerWithoutResult.onMainFinishedWithException()", + e); + } +} diff --git a/src/main/java/amidst/threading/TaskQueue.java b/src/main/java/amidst/threading/TaskQueue.java new file mode 100644 index 000000000..6b2f1b74e --- /dev/null +++ b/src/main/java/amidst/threading/TaskQueue.java @@ -0,0 +1,25 @@ +package amidst.threading; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import amidst.documentation.ThreadSafe; + +/** + * This class executes all invoked runnables in the thread that calls + * processTasks. + */ +@ThreadSafe +public class TaskQueue { + private final ConcurrentLinkedQueue tasks = new ConcurrentLinkedQueue(); + + public void processTasks() { + Runnable task; + while ((task = tasks.poll()) != null) { + task.run(); + } + } + + public void invoke(Runnable runnable) { + tasks.offer(runnable); + } +} diff --git a/src/main/java/amidst/threading/ThreadMaster.java b/src/main/java/amidst/threading/ThreadMaster.java new file mode 100644 index 000000000..99f77d282 --- /dev/null +++ b/src/main/java/amidst/threading/ThreadMaster.java @@ -0,0 +1,118 @@ +package amidst.threading; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.ThreadSafe; + +@ThreadSafe +public class ThreadMaster { + private static final Runnable NOOP = new Runnable() { + public void run() { + // noop + } + }; + + private final ScheduledExecutorService repaintExecutorService; + private final ScheduledExecutorService fragmentLoaderExecutorService; + private final ExecutorService workerExecutorService; + private final WorkerExecutor workerExecutor; + + private volatile Runnable onRepaintTick; + private volatile Runnable onFragmentLoadTick; + + public ThreadMaster() { + this.repaintExecutorService = createRepaintExecutorService(); + this.fragmentLoaderExecutorService = createFragmentLoaderExecutorService(); + this.workerExecutorService = createWorkerExecutorService(); + this.workerExecutor = createWorkerExecutor(); + this.onRepaintTick = NOOP; + this.onFragmentLoadTick = NOOP; + startRepainter(); + startFragmentLoader(); + } + + private ScheduledExecutorService createRepaintExecutorService() { + return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + } + }); + } + + private ScheduledExecutorService createFragmentLoaderExecutorService() { + return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setPriority(Thread.MIN_PRIORITY); + return thread; + } + }); + } + + private ExecutorService createWorkerExecutorService() { + return Executors.newCachedThreadPool(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setPriority(Thread.MIN_PRIORITY); + return thread; + } + }); + } + + private WorkerExecutor createWorkerExecutor() { + return new WorkerExecutor(workerExecutorService); + } + + private void startRepainter() { + repaintExecutorService.scheduleAtFixedRate(new Runnable() { + @CalledOnlyBy(AmidstThread.REPAINTER) + @Override + public void run() { + onRepaintTick.run(); + } + }, 0, 20, TimeUnit.MILLISECONDS); + } + + private void startFragmentLoader() { + fragmentLoaderExecutorService.scheduleWithFixedDelay(new Runnable() { + @CalledOnlyBy(AmidstThread.FRAGMENT_LOADER) + @Override + public void run() { + onFragmentLoadTick.run(); + } + }, 0, 20, TimeUnit.MILLISECONDS); + } + + public WorkerExecutor getWorkerExecutor() { + return workerExecutor; + } + + public void setOnRepaintTick(Runnable onRepaintTick) { + this.onRepaintTick = onRepaintTick; + } + + public void setOnFragmentLoadTick(Runnable onFragmentLoadTick) { + this.onFragmentLoadTick = onFragmentLoadTick; + } + + public void clearOnRepaintTick() { + this.onRepaintTick = NOOP; + } + + public void clearOnFragmentLoadTick() { + this.onFragmentLoadTick = NOOP; + } +} diff --git a/src/main/java/amidst/threading/Worker.java b/src/main/java/amidst/threading/Worker.java new file mode 100644 index 000000000..65494da6e --- /dev/null +++ b/src/main/java/amidst/threading/Worker.java @@ -0,0 +1,123 @@ +package amidst.threading; + +import java.util.concurrent.ExecutorService; + +import javax.swing.SwingUtilities; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public abstract class Worker { + private volatile ExecutorService executorService; + + @CalledByAny + public void executeMain(ExecutorService executorService) { + this.executorService = executorService; + executorService.execute(new Runnable() { + @CalledOnlyBy(AmidstThread.WORKER) + @Override + public void run() { + try { + executeOnMainFinishedLater(main()); + } catch (Exception e) { + executeOnMainFinishedWithExceptionLater(e); + } + } + }); + } + + @CalledOnlyBy(AmidstThread.WORKER) + private void executeOnMainFinishedLater(final M result) { + SwingUtilities.invokeLater(new Runnable() { + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void run() { + onMainFinished(result); + } + }); + } + + @CalledOnlyBy(AmidstThread.WORKER) + private void executeOnMainFinishedWithExceptionLater(final Exception e) { + SwingUtilities.invokeLater(new Runnable() { + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void run() { + onMainFinishedWithException(e); + } + }); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected void executeFork(final I input) { + executorService.execute(new Runnable() { + @CalledOnlyBy(AmidstThread.WORKER) + @Override + public void run() { + try { + executeOnForkFinishedLater(fork(input)); + } catch (Exception e) { + executeOnForkFinishedWithExceptionLater(e); + } + } + }); + } + + @CalledOnlyBy(AmidstThread.WORKER) + private void executeOnForkFinishedLater(final F result) { + SwingUtilities.invokeLater(new Runnable() { + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void run() { + onForkFinished(result); + } + }); + } + + @CalledOnlyBy(AmidstThread.WORKER) + private void executeOnForkFinishedWithExceptionLater(final Exception e) { + SwingUtilities.invokeLater(new Runnable() { + @CalledOnlyBy(AmidstThread.EDT) + @Override + public void run() { + onForkFinishedWithException(e); + } + }); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected abstract M main() throws Exception; + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinished(M result) { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinishedWithException(Exception e) { + throw new UnsupportedOperationException( + "you need to override the method Worker.onMainFinishedWithException()", + e); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected F fork(I input) throws Exception { + throw new UnsupportedOperationException( + "you need to override the method Worker.fork()"); + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onForkFinished(F result) { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onForkFinishedWithException(Exception e) { + throw new UnsupportedOperationException( + "you need to override the method Worker.onForkFinishedWithException()", + e); + } +} diff --git a/src/main/java/amidst/threading/WorkerExecutor.java b/src/main/java/amidst/threading/WorkerExecutor.java new file mode 100644 index 000000000..0c9ea85af --- /dev/null +++ b/src/main/java/amidst/threading/WorkerExecutor.java @@ -0,0 +1,30 @@ +package amidst.threading; + +import java.util.concurrent.ExecutorService; + +import amidst.documentation.ThreadSafe; + +@ThreadSafe +public class WorkerExecutor { + private final ExecutorService executorService; + + public WorkerExecutor(ExecutorService executorService) { + this.executorService = executorService; + } + + public void invokeLater(Worker worker) { + worker.executeMain(executorService); + } + + public void invokeLater(WorkerWithoutResult worker) { + worker.executeMain(executorService); + } + + public void invokeLater(SimpleWorker worker) { + worker.executeMain(executorService); + } + + public void invokeLater(SimpleWorkerWithoutResult worker) { + worker.executeMain(executorService); + } +} diff --git a/src/main/java/amidst/threading/WorkerWithoutResult.java b/src/main/java/amidst/threading/WorkerWithoutResult.java new file mode 100644 index 000000000..0fb07cf8a --- /dev/null +++ b/src/main/java/amidst/threading/WorkerWithoutResult.java @@ -0,0 +1,92 @@ +package amidst.threading; + +import java.util.concurrent.ExecutorService; + +import amidst.documentation.AmidstThread; +import amidst.documentation.CalledByAny; +import amidst.documentation.CalledOnlyBy; +import amidst.documentation.NotThreadSafe; + +@NotThreadSafe +public abstract class WorkerWithoutResult { + private final Worker worker; + + public WorkerWithoutResult() { + this.worker = new Worker() { + @Override + protected Void main() throws Exception { + WorkerWithoutResult.this.main(); + return null; + } + + @Override + protected void onMainFinished(Void result) { + WorkerWithoutResult.this.onMainFinished(); + } + + @Override + protected void onMainFinishedWithException(Exception e) { + WorkerWithoutResult.this.onMainFinishedWithException(e); + } + + @Override + protected Void fork(I input) throws Exception { + WorkerWithoutResult.this.fork(input); + return null; + } + + @Override + protected void onForkFinished(Void result) { + WorkerWithoutResult.this.onForkFinished(); + } + + @Override + protected void onForkFinishedWithException(Exception e) { + WorkerWithoutResult.this.onForkFinishedWithException(e); + } + }; + } + + @CalledByAny + public void executeMain(ExecutorService executorService) { + worker.executeMain(executorService); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected void executeFork(final I input) { + worker.executeFork(input); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected abstract void main() throws Exception; + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinished() { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onMainFinishedWithException(Exception e) { + throw new UnsupportedOperationException( + "you need to override the method WorkerWithoutResult.onMainFinishedWithException()", + e); + } + + @CalledOnlyBy(AmidstThread.WORKER) + protected void fork(I input) throws Exception { + throw new UnsupportedOperationException( + "you need to override the method WorkerWithoutResult.fork()"); + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onForkFinished() { + // noop + } + + @CalledOnlyBy(AmidstThread.EDT) + protected void onForkFinishedWithException(Exception e) { + throw new UnsupportedOperationException( + "you need to override the method WorkerWithoutResult.onForkFinishedWithException()", + e); + } +} diff --git a/src/amidst/resources/licenses/amidst.txt b/src/main/resources/amidst/gui/license/amidst.txt similarity index 100% rename from src/amidst/resources/licenses/amidst.txt rename to src/main/resources/amidst/gui/license/amidst.txt diff --git a/src/amidst/resources/licenses/args4j.txt b/src/main/resources/amidst/gui/license/args4j.txt similarity index 100% rename from src/amidst/resources/licenses/args4j.txt rename to src/main/resources/amidst/gui/license/args4j.txt diff --git a/src/amidst/resources/licenses/gson.txt b/src/main/resources/amidst/gui/license/gson.txt similarity index 100% rename from src/amidst/resources/licenses/gson.txt rename to src/main/resources/amidst/gui/license/gson.txt diff --git a/src/amidst/resources/licenses/jnbt.txt b/src/main/resources/amidst/gui/license/jnbt.txt similarity index 100% rename from src/amidst/resources/licenses/jnbt.txt rename to src/main/resources/amidst/gui/license/jnbt.txt diff --git a/src/amidst/resources/licenses/miglayout.txt b/src/main/resources/amidst/gui/license/miglayout.txt similarity index 100% rename from src/amidst/resources/licenses/miglayout.txt rename to src/main/resources/amidst/gui/license/miglayout.txt diff --git a/src/amidst/resources/dropshadow/inner_bottom.png b/src/main/resources/amidst/gui/main/dropshadow/inner_bottom.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_bottom.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_bottom.png diff --git a/src/amidst/resources/dropshadow/inner_bottom_left.png b/src/main/resources/amidst/gui/main/dropshadow/inner_bottom_left.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_bottom_left.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_bottom_left.png diff --git a/src/amidst/resources/dropshadow/inner_bottom_right.png b/src/main/resources/amidst/gui/main/dropshadow/inner_bottom_right.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_bottom_right.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_bottom_right.png diff --git a/src/amidst/resources/dropshadow/inner_left.png b/src/main/resources/amidst/gui/main/dropshadow/inner_left.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_left.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_left.png diff --git a/src/amidst/resources/dropshadow/inner_right.png b/src/main/resources/amidst/gui/main/dropshadow/inner_right.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_right.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_right.png diff --git a/src/amidst/resources/dropshadow/inner_top.png b/src/main/resources/amidst/gui/main/dropshadow/inner_top.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_top.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_top.png diff --git a/src/amidst/resources/dropshadow/inner_top_left.png b/src/main/resources/amidst/gui/main/dropshadow/inner_top_left.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_top_left.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_top_left.png diff --git a/src/amidst/resources/dropshadow/inner_top_right.png b/src/main/resources/amidst/gui/main/dropshadow/inner_top_right.png similarity index 100% rename from src/amidst/resources/dropshadow/inner_top_right.png rename to src/main/resources/amidst/gui/main/dropshadow/inner_top_right.png diff --git a/src/amidst/resources/dropshadow/outer_bottom.png b/src/main/resources/amidst/gui/main/dropshadow/outer_bottom.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_bottom.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_bottom.png diff --git a/src/amidst/resources/dropshadow/outer_bottom_left.png b/src/main/resources/amidst/gui/main/dropshadow/outer_bottom_left.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_bottom_left.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_bottom_left.png diff --git a/src/amidst/resources/dropshadow/outer_bottom_right.png b/src/main/resources/amidst/gui/main/dropshadow/outer_bottom_right.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_bottom_right.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_bottom_right.png diff --git a/src/amidst/resources/dropshadow/outer_left.png b/src/main/resources/amidst/gui/main/dropshadow/outer_left.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_left.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_left.png diff --git a/src/amidst/resources/dropshadow/outer_right.png b/src/main/resources/amidst/gui/main/dropshadow/outer_right.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_right.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_right.png diff --git a/src/amidst/resources/dropshadow/outer_top.png b/src/main/resources/amidst/gui/main/dropshadow/outer_top.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_top.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_top.png diff --git a/src/amidst/resources/dropshadow/outer_top_left.png b/src/main/resources/amidst/gui/main/dropshadow/outer_top_left.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_top_left.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_top_left.png diff --git a/src/amidst/resources/dropshadow/outer_top_right.png b/src/main/resources/amidst/gui/main/dropshadow/outer_top_right.png similarity index 100% rename from src/amidst/resources/dropshadow/outer_top_right.png rename to src/main/resources/amidst/gui/main/dropshadow/outer_top_right.png diff --git a/src/amidst/resources/highlighter.png b/src/main/resources/amidst/gui/main/highlighter.png similarity index 100% rename from src/amidst/resources/highlighter.png rename to src/main/resources/amidst/gui/main/highlighter.png diff --git a/src/amidst/resources/desert.png b/src/main/resources/amidst/gui/main/icon/desert.png similarity index 100% rename from src/amidst/resources/desert.png rename to src/main/resources/amidst/gui/main/icon/desert.png diff --git a/src/amidst/resources/grid.png b/src/main/resources/amidst/gui/main/icon/grid.png similarity index 100% rename from src/amidst/resources/grid.png rename to src/main/resources/amidst/gui/main/icon/grid.png diff --git a/src/amidst/resources/jungle.png b/src/main/resources/amidst/gui/main/icon/jungle.png similarity index 100% rename from src/amidst/resources/jungle.png rename to src/main/resources/amidst/gui/main/icon/jungle.png diff --git a/src/amidst/resources/nether_fortress.png b/src/main/resources/amidst/gui/main/icon/nether_fortress.png similarity index 100% rename from src/amidst/resources/nether_fortress.png rename to src/main/resources/amidst/gui/main/icon/nether_fortress.png diff --git a/src/amidst/resources/ocean_monument.png b/src/main/resources/amidst/gui/main/icon/ocean_monument.png similarity index 100% rename from src/amidst/resources/ocean_monument.png rename to src/main/resources/amidst/gui/main/icon/ocean_monument.png diff --git a/src/amidst/resources/player.png b/src/main/resources/amidst/gui/main/icon/player.png similarity index 100% rename from src/amidst/resources/player.png rename to src/main/resources/amidst/gui/main/icon/player.png diff --git a/src/amidst/resources/slime.png b/src/main/resources/amidst/gui/main/icon/slime.png similarity index 100% rename from src/amidst/resources/slime.png rename to src/main/resources/amidst/gui/main/icon/slime.png diff --git a/src/amidst/resources/spawn.png b/src/main/resources/amidst/gui/main/icon/spawn.png similarity index 100% rename from src/amidst/resources/spawn.png rename to src/main/resources/amidst/gui/main/icon/spawn.png diff --git a/src/amidst/resources/stronghold.png b/src/main/resources/amidst/gui/main/icon/stronghold.png similarity index 100% rename from src/amidst/resources/stronghold.png rename to src/main/resources/amidst/gui/main/icon/stronghold.png diff --git a/src/amidst/resources/village.png b/src/main/resources/amidst/gui/main/icon/village.png similarity index 100% rename from src/amidst/resources/village.png rename to src/main/resources/amidst/gui/main/icon/village.png diff --git a/src/amidst/resources/witch.png b/src/main/resources/amidst/gui/main/icon/witch.png similarity index 100% rename from src/amidst/resources/witch.png rename to src/main/resources/amidst/gui/main/icon/witch.png diff --git a/src/amidst/resources/active_profile.png b/src/main/resources/amidst/gui/profileselect/active.png similarity index 100% rename from src/amidst/resources/active_profile.png rename to src/main/resources/amidst/gui/profileselect/active.png diff --git a/src/amidst/resources/inactive_profile.png b/src/main/resources/amidst/gui/profileselect/inactive.png similarity index 100% rename from src/amidst/resources/inactive_profile.png rename to src/main/resources/amidst/gui/profileselect/inactive.png diff --git a/src/amidst/resources/loading_profile.png b/src/main/resources/amidst/gui/profileselect/loading.png similarity index 100% rename from src/amidst/resources/loading_profile.png rename to src/main/resources/amidst/gui/profileselect/loading.png diff --git a/src/amidst/resources/icon.png b/src/main/resources/amidst/icon.png similarity index 100% rename from src/amidst/resources/icon.png rename to src/main/resources/amidst/icon.png diff --git a/src/main/resources/amidst/metadata.properties b/src/main/resources/amidst/metadata.properties new file mode 100644 index 000000000..370cf2b93 --- /dev/null +++ b/src/main/resources/amidst/metadata.properties @@ -0,0 +1,7 @@ +project.build.sourceEncoding=UTF-8 +amidst.build.filename=amidst-v4.0-alpha3 +amidst.build.jdk.version=1.8 + +amidst.version.major=4 +amidst.version.minor=0 +amidst.gui.mainWindow.title=Amidst v4.0-alpha3 diff --git a/src/amidst/resources/versions.json b/src/main/resources/amidst/mojangapi/versions.json similarity index 100% rename from src/amidst/resources/versions.json rename to src/main/resources/amidst/mojangapi/versions.json diff --git a/src/test/java/amidst/mojangapi/world/coordinates/CoordinateUtilsTest.java b/src/test/java/amidst/mojangapi/world/coordinates/CoordinateUtilsTest.java new file mode 100644 index 000000000..f576eca03 --- /dev/null +++ b/src/test/java/amidst/mojangapi/world/coordinates/CoordinateUtilsTest.java @@ -0,0 +1,24 @@ +package amidst.mojangapi.world.coordinates; + +import org.junit.Assert; +import org.junit.Test; + +public class CoordinateUtilsTest { + @Test + public void testCoordinateConversion() { + Assert.assertTrue(ensureCoordinateConversionWorks()); + } + + private static boolean ensureCoordinateConversionWorks() { + for (long inWorld = -1000; inWorld < 1000; inWorld++) { + long inFragment = CoordinateUtils.toFragmentRelative(inWorld); + long inWorldOfFragment = CoordinateUtils.toFragmentCorner(inWorld); + long actualInWorld = CoordinateUtils.toWorld(inWorldOfFragment, + inFragment); + if (inWorld != actualInWorld) { + return false; + } + } + return true; + } +} diff --git a/src/test/resources/.gitkeep b/src/test/resources/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/travis-ci/scripts/create-mac-icon.sh b/travis-ci/scripts/create-mac-icon.sh new file mode 100644 index 000000000..2c10a92fd --- /dev/null +++ b/travis-ci/scripts/create-mac-icon.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +convert src/main/resources/amidst/icon.png target/icon.icns diff --git a/travis-ci/scripts/create-windows-icon.sh b/travis-ci/scripts/create-windows-icon.sh new file mode 100644 index 000000000..81f8fcc65 --- /dev/null +++ b/travis-ci/scripts/create-windows-icon.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +convert src/main/resources/amidst/icon.png target/icon.ico diff --git a/travis-ci/scripts/get-amidst-filename.sh b/travis-ci/scripts/get-amidst-filename.sh new file mode 100644 index 000000000..da97d64b6 --- /dev/null +++ b/travis-ci/scripts/get-amidst-filename.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +grep amidst.build.filename src/main/resources/amidst/metadata.properties | cut -d "=" -f 2 diff --git a/travis-ci/scripts/zip-and-move-wrapper-for-mac.sh b/travis-ci/scripts/zip-and-move-wrapper-for-mac.sh new file mode 100644 index 000000000..6b63c0392 --- /dev/null +++ b/travis-ci/scripts/zip-and-move-wrapper-for-mac.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +filename="`source travis-ci/scripts/get-amidst-filename.sh`" +cd "travis-ci/wrapper-for-mac/target/${filename}/" +zip -r "${filename}.zip" "Amidst.app/" +mv "${filename}.zip" "../../../../target/" diff --git a/travis-ci/wrapper-for-mac/pom.xml b/travis-ci/wrapper-for-mac/pom.xml new file mode 100644 index 000000000..4830e6cac --- /dev/null +++ b/travis-ci/wrapper-for-mac/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + amidst + wrapper-for-mac + 0.0.1-SNAPSHOT + + ${amidst.build.filename} + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + initialize + + read-project-properties + + + + ../../src/main/resources/amidst/metadata.properties + + + + + + + sh.tak.appbundler + appbundle-maven-plugin + 1.0.4 + + + package + + bundle + + + amidst.Amidst + ../../target/icon.icns + ${amidst.build.jdk.version}+ + Amidst + + + + + + + + + amidst + amidst + 0.0.1-SNAPSHOT + + + sh.tak.appbundler + appbundle-maven-plugin + 1.0.4 + maven-plugin + provided + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + maven-plugin + provided + + + diff --git a/travis-ci/wrapper-for-windows/pom.xml b/travis-ci/wrapper-for-windows/pom.xml new file mode 100644 index 000000000..331ce5b59 --- /dev/null +++ b/travis-ci/wrapper-for-windows/pom.xml @@ -0,0 +1,77 @@ + + 4.0.0 + amidst + wrapper-for-windows + 0.0.1-SNAPSHOT + + ${amidst.build.filename} + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + initialize + + read-project-properties + + + + ../../src/main/resources/amidst/metadata.properties + + + + + + + com.akathist.maven.plugins.launch4j + launch4j-maven-plugin + 1.7.8 + + + launch4j + package + + launch4j + + + gui + ../../target/${amidst.build.filename}.exe + ../../target/${amidst.build.filename}.jar + false + Error in Launcher + + amidst.Amidst + false + + ../../target/icon.ico + + ${amidst.build.jdk.version}.0 + 512 + 1024 + + + + + + + + + + com.akathist.maven.plugins.launch4j + launch4j-maven-plugin + 1.7.8 + maven-plugin + provided + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + maven-plugin + provided + + + \ No newline at end of file