Is it possible, with a mod, to essentially emulate:
1. Clicking on create new world button.
2. Once the world is generated, move the player to certain coords.
3. Take a screenshot.
4. Change player rotation.
5. Take a screenshot.
6. Exit the world.
7. Delete the world
8. Repeat steps to 1 to 7 x 100 times?
This is for QC / testing / debugging purposes and would absolutely save me HOURS of manual QC!
I'm open to other methods of emulating this, ie, using the runServer gradle task and maybe somehow scripting external renderers. I'm quite at home with bash so that is definitely an option. The goal is to simply check the player view from several angles and certain coordinates.
With a mod I highly doubt that's possible. It would have to be written to perfectly match the amount of time it takes for the world to actually load and render the world since a screenshot would need to wait until that actually happens, which presumably would change each time, pretty much making that impossible.
The only thing I could think of would be to find (or write) a program that you can set up to perform a series of timed commands on your screen.
For example, move the mouse to a point on the screen, click the mouse, wait 10 seconds (would vary depending on your computer), trigger F2 key, mouse mouse to rotate player, trigger F2 key, trigger Escape key, mouse mouse to a point over Exit button, click the mouse, etc,etc, repeat.
I'm sure there's software out there to do it, I just don't know of any specific one.
Though I suppose you could make a mod that emulates time elapsed until it's safe to take a screenshot with a tick handler, but the rest would require some pretty heavy coding.
Just handle the EntityJoinedWorldEvent and check for it being your test player, if it is then move them (or move the view entity representing the camera) and take the screen shot.
For the looping part, you can make a public static int field in your mod's main class and initialize that to the number of loops. Then you can handle the GuiOpenEvent and check for the new world gui, and if the loop counter is >0 then decrement it and cancel the vanilla one and just run the same code that would run if the user hit the New World button.
I think that would work. To start the whole thing you'd just try to open the new world gui manually and then it should take over from there. It will loop through generating the new world and as soon as it is generated it will get your player to join which will give you opportunity to move the player and take the screen shot.
Note, It is possible the joined world event fires a little bit early, plus if you teleport to a part of the world that isn't loaded yet it might take a few ticks, so you might either want to check that the chunks are loaded at the position before taking the screenshot, or just put in a delay you are certain will allow loading (maybe 5 seconds).
I've been looking into doing this with bash and chunky, and it definitely seems possible from that end.
However, chunky is not really the right tool for the job here. It definitely takes way too long, and the goal with chunky anyway is to produce something raytraced and super polished, I just want to check the ingame vanilla look, so I can go "yeah, this generated correctly" etc.
jabelar: I'm going to look into your approach and see if I can do things in that fashion.
What exactly are the methods I would call to move the player, change the rotation of the player, and take a screenshot?
Ok so I tried this. My mod basically just implements IWorldGenerator so all the real meat of the mod is in my_generation_code(), don't worry about that, that part works great.
public class TestWorldGenerator implements IWorldGenerator {
@Override
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider)
{
my_generation_code();
}
@SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent event)
{
if (event.getEntity() instanceof EntityPlayer) {
EntityPlayer entity = (EntityPlayer) event.getEntity();
entity.setPositionAndRotationDirect(1, 102, 1, 0, 0, 0, true);
}
}
}
(Basically just a random snippet I found from googling.) This didn't work however. The player still starts at a random spawn location.
Also I still can't seem to find the method that is needed to take a screenshot.
Apologies as it looks like the formatting got mangled from pasting.
So for event handling I have a tutorial here. I suspect you didn't register your handler properly -- the class needs further annotation and the method needs to be static.
For coding where the sequence of execution might get complicated, I suggest putting in console print statements throughout your code to help you confirm proper operation (and help debug when things go wrong). So for example, in your event handling method I would put a System.out.println("Entity "+event.getEntity()+" joined world.") so you can confirm that the event is even firing.
Get your events working and then when you get stuck on the next topic, I can try to help further.
Regarding your question "What docs should I look through to find a method and class to do XYZ?" there isn't really much documentation except the code itself. There are tutorials (check out mine at jabelarminecraft.blogspot.com) though that can help you find your bearing.
But otherwise I tend to just read the vanilla source code directly and try to figure it out. For example, just think about the things in vanilla Minecraft that are similar to what you're trying to do. In your case you might want to look at how "spectator" mode works since that involves a fairly free camera movement sort of thing that might be similar to what you need to do. And there is already a vanilla screenshot hotkey so look at how the code for that works for your screen capture.
I originally tried putting it all in onEntityJoinWorld including the delay, but MC wouldn't start rendering until that delay had elapsed, it didn't have an OpenGL context until then also. So it seems like putting the delay in onEntityJoinWorld is blocking. Putting it in RenderWorldLastEvent just renders a black screen if I add that boolean, if I remove the boolean it just takes screenshots until I kill the process, but the last screenshot it takes does take a screenshot correctly.
Also, it looks like the resolution is not correct, it just uses the resolution of the window. Not a big deal, but slightly annoying
I feel like there must be some event to use here I just can't think of. I was considering also having onEntityJoinWorld just create a new thread and sleep for a minute or so, then try to take a screenshot with that thread....but that could get ugly and I'm no multithreading programming expert.
In terms of searching the vanilla source code, for anyone else reading this, what I did was find some class that is implemented by the vanilla source, ie: EntityPlayer, right click on that word in IntelliJ IDEA, Go To...Implementation and it automatically extracted the source from the jar and opened it in a new tab.
When inside this file, then I right clicked on the tab and did "Copy Path"
This gives me the base path, but the vanilla source is inside a jar. A jar is just a zip file, so I unzipped, then just grepped the root for "screenshot".
So forge seems to have a copy of the vanilla minecraft source? I didn't realize that minecraft was open source! Is that correct?
What exactly are you testing? It sounds like you want to make sure that a structure/biome/etc generated correctly; when I test something like this I greatly increase the chances of it generating; for example, when I test a new biome I make it replace 50% or so of all normal biomes, which makes it very common while still bordering adjacent biomes, I still have the following debug code in my custom GenLayerBiomes class from the last time I added a new biome (in this case, Iceland, which replaced 40% of normal biomes with the rest being various other "special" biomes to make sure the code that replaces blocks in them world correctly after adding a new biome and blocks to it):
// Used to force a particular biome for debugging
//if (!(this.isBiomeOceanic(biome) || biome == BiomeGenBase.mushroomIsland.biomeID))
//{
// int index = this.nextInt(10);
// if (index < 4) biome = BiomeGenBase.iceland.biomeID;
// if (index == 4) biome = BiomeGenBase.icePlainsSpikes.biomeID;
// if (index == 5) biome = BiomeGenBase.iceHills.biomeID;
// if (index == 6) biome = BiomeGenBase.desertM.biomeID;
// if (index == 7) biome = BiomeGenBase.quartzDesert.biomeID;
// if (index == 8) biome = BiomeGenBase.mesa.biomeID;
// if (index == 9) biome = BiomeGenBase.badlands.biomeID;
//}
(the utility I used here is Minutor, which supports mod blocks and biomes, even ones that alter or replace vanilla ones as my mod does, by editing a definitions file (note the data value for "blue ice", vanilla only has 174:0, and this is a mod based on 1.6.4, which doesn't even have it; likewise, many of the biomes that I added use IDs taken by vanilla biomes in 1.7+, and some not used at all yet).
Likewise, when I add a new structure I make it and its biome generate much more often than otherwise (up to as often as it can without overlap), along with adding code that counts the number of attempts and successes and prints out the ratio out so I know how common it should be to offset the failure rate:
// Woodland mansions use a grid size of 20 chunks and a spacing of 10 chunks. About 3/4 of attempts fail
// for a frequency of one every 1600 chunks (47500 chunks when factoring in frequency of Roofed Forest)
if (TYPE == WOODLAND_MANSION && this.isLocationValid(chunkX, chunkZ, 20, 10))
{
return WoodlandMansion.biomeCheck(this.worldObj, chunkX, chunkZ);
}
I still have debug code that prints out the locations of several features as they generate so I can check any out that generate in the process of testing other things in normal worlds:
Also, it is possible to write a standalone program (not within the game) that finds the locations of structures and even maps them, as I did to help test my mod's cave generation; I wrote a program which finds the locations of various types of caves and prints them out and another one which can also generate a map of the underground by running a slightly modified version of my cave generator on simulated chunk data (the cave generator already operates on a simple array of numeric values so all I had to do was replace references to e.g. Block.lavaStill.blockID with their numeric value, 11). This lets me quickly analyze large areas; the first part shows how long it took to generate 1000 "chunks" per line (in milliseconds), followed by the total volume and percentage of air blocks, and the last part shows how common each type of cave was, and overall, along with a map (the total area covered here is 16,384 chunks, or an in-game level 4 map, and took about half a minute to generate, including the map, which itself is similar to what Unmined produces; I'd consider a utility like Unmined to be essential if you do anything with the underground):
I appreciate this. I really just want to see how things look in the game.
I generate my structure 100% of the time, but I want to view it from about 20 different locations / angles each time I generate it.
I essentially am just randomizing all of several dozen parameters each time I generate, to try and narrow down combinations that look good.
If I find a nice combination of params that look good, I'll call that a "style" and name it. When the mod is done, I'll generate one of those "styles" chosen randomly, with certain parameters restricted to narrow ranges, but much wider ranges open on other parameters.
This is definitely what I need to do. I've been doing it by hand for a bit and I've been extremely happy with the results. I just need to automate it now.
Actually you shouldn't need to do all that. If you set up your workspace using gradlew setupDecompWorkspace, then you should be able to see all the vanilla source as a Referenced Library within your IDE. En Eclipse it is just in the Referenced Libraries -- I can't remember where it is in IntelliJ but it is definitely there too.
Minecraft is not open source, but Java is a weird language because it was designed for "just in time" compilation meaning that the "source code" (well actually an intermediate type of obfuscated code) is distributed as the executable. This means that the JAR file is readable, except it might not be EASY to read because all the field and method names might be very cryptic and there won't be any comments. So MCP provided a crowd-sourced "mapping" to rename all the fields and methods so they are more understandable.
You'll notice that Forge doesn't distribute Minecraft (that violates the license) but it will map a JAR that you download to make it more readable.
Minecraft EULA explicitly allows modding, so nothing nefarious is going on.
I feel like there must be some event to use here I just can't think of. I was considering also having onEntityJoinWorld just create a new thread and sleep for a minute or so, then try to take a screenshot with that thread....but that could get ugly and I'm no multithreading programming expert.
The screenshot needs to be taken on the client side, since that is where the rendering happens. I think you should try using PlayerTickEvent but checking that you're on the client side. The "SP" player is the client-side player, so you could set a counter when SP player joins world and then count down in PlayerTickEvent and then like one second later initiate the same code as in the screenshot key binding.
@SubscribeEvent
public static void onEntityJoinWorld(EntityJoinWorldEvent event) {
if (event.getEntity() instanceof EntityPlayerMP) {
System.out.println("Entity " + event.getEntity() + " joined world.");
player_handle = (EntityLivingBase) event.getEntity();
}
}
To give my main class a "handle" to the player entity. (Since the player doesn't join the world over and over, I need some way to trigger setting the player position besides just the player joining the world.)
This would work great with one exception. It doesn't move the player! It takes all the screenshots correctly though and prints all debug output as I would expect it to.
Why am I able to set the player position within the world join event but not generally within the tick event?
Is it possible, with a mod, to essentially emulate:
1. Clicking on create new world button.
2. Once the world is generated, move the player to certain coords.
3. Take a screenshot.
4. Change player rotation.
5. Take a screenshot.
6. Exit the world.
7. Delete the world
8. Repeat steps to 1 to 7 x 100 times?
This is for QC / testing / debugging purposes and would absolutely save me HOURS of manual QC!
I'm open to other methods of emulating this, ie, using the runServer gradle task and maybe somehow scripting external renderers. I'm quite at home with bash so that is definitely an option. The goal is to simply check the player view from several angles and certain coordinates.
Thanks!
With a mod I highly doubt that's possible. It would have to be written to perfectly match the amount of time it takes for the world to actually load and render the world since a screenshot would need to wait until that actually happens, which presumably would change each time, pretty much making that impossible.
The only thing I could think of would be to find (or write) a program that you can set up to perform a series of timed commands on your screen.
For example, move the mouse to a point on the screen, click the mouse, wait 10 seconds (would vary depending on your computer), trigger F2 key, mouse mouse to rotate player, trigger F2 key, trigger Escape key, mouse mouse to a point over Exit button, click the mouse, etc,etc, repeat.
I'm sure there's software out there to do it, I just don't know of any specific one.
Though I suppose you could make a mod that emulates time elapsed until it's safe to take a screenshot with a tick handler, but the rest would require some pretty heavy coding.
It shouldn't be too hard.
Just handle the EntityJoinedWorldEvent and check for it being your test player, if it is then move them (or move the view entity representing the camera) and take the screen shot.
For the looping part, you can make a public static int field in your mod's main class and initialize that to the number of loops. Then you can handle the GuiOpenEvent and check for the new world gui, and if the loop counter is >0 then decrement it and cancel the vanilla one and just run the same code that would run if the user hit the New World button.
I think that would work. To start the whole thing you'd just try to open the new world gui manually and then it should take over from there. It will loop through generating the new world and as soon as it is generated it will get your player to join which will give you opportunity to move the player and take the screen shot.
Note, It is possible the joined world event fires a little bit early, plus if you teleport to a part of the world that isn't loaded yet it might take a few ticks, so you might either want to check that the chunks are loaded at the position before taking the screenshot, or just put in a delay you are certain will allow loading (maybe 5 seconds).
I've been looking into doing this with bash and chunky, and it definitely seems possible from that end.
However, chunky is not really the right tool for the job here. It definitely takes way too long, and the goal with chunky anyway is to produce something raytraced and super polished, I just want to check the ingame vanilla look, so I can go "yeah, this generated correctly" etc.
jabelar: I'm going to look into your approach and see if I can do things in that fashion.
What exactly are the methods I would call to move the player, change the rotation of the player, and take a screenshot?
I looked through:
https://mcforge.readthedocs.io/en/latest/events/intro/
But didn't find anything and looks like search doesn't work. Not sure I'm looking in the right place.
More generally: What docs should I look through to find a method and class to do XYZ?
Thank you!!
Ok so I tried this. My mod basically just implements IWorldGenerator so all the real meat of the mod is in my_generation_code(), don't worry about that, that part works great.
(Basically just a random snippet I found from googling.) This didn't work however. The player still starts at a random spawn location.
Also I still can't seem to find the method that is needed to take a screenshot.
Apologies as it looks like the formatting got mangled from pasting.
Thanks!
So for event handling I have a tutorial here. I suspect you didn't register your handler properly -- the class needs further annotation and the method needs to be static.
For coding where the sequence of execution might get complicated, I suggest putting in console print statements throughout your code to help you confirm proper operation (and help debug when things go wrong). So for example, in your event handling method I would put a System.out.println("Entity "+event.getEntity()+" joined world.") so you can confirm that the event is even firing.
Get your events working and then when you get stuck on the next topic, I can try to help further.
Regarding your question "What docs should I look through to find a method and class to do XYZ?" there isn't really much documentation except the code itself. There are tutorials (check out mine at jabelarminecraft.blogspot.com) though that can help you find your bearing.
But otherwise I tend to just read the vanilla source code directly and try to figure it out. For example, just think about the things in vanilla Minecraft that are similar to what you're trying to do. In your case you might want to look at how "spectator" mode works since that involves a fairly free camera movement sort of thing that might be similar to what you need to do. And there is already a vanilla screenshot hotkey so look at how the code for that works for your screen capture.
Thank you so much! You are awesome
Thank you for writing tutorials also, that is sorely needed!
Ok so I added that debug println, you are correct, I wasn't registering the class correctly.
So now I can confirm that I get output when entities join the world, and I filtered based on the player.
I see two lines, one for the playerMP entity joining the world, and one for the playerSP joining the world.
I'm not sure what the difference is there, but setting position on player MP works!
Now I'm not sure what event to use for the actual taking of the screenshot. (Tried copying and pasting keeping the formatting from IntelliJ)
I originally tried putting it all in onEntityJoinWorld including the delay, but MC wouldn't start rendering until that delay had elapsed, it didn't have an OpenGL context until then also. So it seems like putting the delay in onEntityJoinWorld is blocking. Putting it in RenderWorldLastEvent just renders a black screen if I add that boolean, if I remove the boolean it just takes screenshots until I kill the process, but the last screenshot it takes does take a screenshot correctly.
Also, it looks like the resolution is not correct, it just uses the resolution of the window. Not a big deal, but slightly annoying
I feel like there must be some event to use here I just can't think of. I was considering also having onEntityJoinWorld just create a new thread and sleep for a minute or so, then try to take a screenshot with that thread....but that could get ugly and I'm no multithreading programming expert.
In terms of searching the vanilla source code, for anyone else reading this, what I did was find some class that is implemented by the vanilla source, ie: EntityPlayer, right click on that word in IntelliJ IDEA, Go To...Implementation and it automatically extracted the source from the jar and opened it in a new tab.
When inside this file, then I right clicked on the tab and did "Copy Path"
This gives me the base path, but the vanilla source is inside a jar. A jar is just a zip file, so I unzipped, then just grepped the root for "screenshot".
So forge seems to have a copy of the vanilla minecraft source? I didn't realize that minecraft was open source! Is that correct?
Thanks again!
What exactly are you testing? It sounds like you want to make sure that a structure/biome/etc generated correctly; when I test something like this I greatly increase the chances of it generating; for example, when I test a new biome I make it replace 50% or so of all normal biomes, which makes it very common while still bordering adjacent biomes, I still have the following debug code in my custom GenLayerBiomes class from the last time I added a new biome (in this case, Iceland, which replaced 40% of normal biomes with the rest being various other "special" biomes to make sure the code that replaces blocks in them world correctly after adding a new biome and blocks to it):
(the utility I used here is Minutor, which supports mod blocks and biomes, even ones that alter or replace vanilla ones as my mod does, by editing a definitions file (note the data value for "blue ice", vanilla only has 174:0, and this is a mod based on 1.6.4, which doesn't even have it; likewise, many of the biomes that I added use IDs taken by vanilla biomes in 1.7+, and some not used at all yet).
Likewise, when I add a new structure I make it and its biome generate much more often than otherwise (up to as often as it can without overlap), along with adding code that counts the number of attempts and successes and prints out the ratio out so I know how common it should be to offset the failure rate:
I still have debug code that prints out the locations of several features as they generate so I can check any out that generate in the process of testing other things in normal worlds:
Also, it is possible to write a standalone program (not within the game) that finds the locations of structures and even maps them, as I did to help test my mod's cave generation; I wrote a program which finds the locations of various types of caves and prints them out and another one which can also generate a map of the underground by running a slightly modified version of my cave generator on simulated chunk data (the cave generator already operates on a simple array of numeric values so all I had to do was replace references to e.g. Block.lavaStill.blockID with their numeric value, 11). This lets me quickly analyze large areas; the first part shows how long it took to generate 1000 "chunks" per line (in milliseconds), followed by the total volume and percentage of air blocks, and the last part shows how common each type of cave was, and overall, along with a map (the total area covered here is 16,384 chunks, or an in-game level 4 map, and took about half a minute to generate, including the map, which itself is similar to what Unmined produces; I'd consider a utility like Unmined to be essential if you do anything with the underground):
TheMasterCaver's First World - possibly the most caved-out world in Minecraft history - includes world download.
TheMasterCaver's World - my own version of Minecraft largely based on my views of how the game should have evolved since 1.6.4.
Why do I still play in 1.6.4?
Thank you!!
I appreciate this. I really just want to see how things look in the game.
I generate my structure 100% of the time, but I want to view it from about 20 different locations / angles each time I generate it.
I essentially am just randomizing all of several dozen parameters each time I generate, to try and narrow down combinations that look good.
If I find a nice combination of params that look good, I'll call that a "style" and name it. When the mod is done, I'll generate one of those "styles" chosen randomly, with certain parameters restricted to narrow ranges, but much wider ranges open on other parameters.
This is definitely what I need to do. I've been doing it by hand for a bit and I've been extremely happy with the results. I just need to automate it now.
Thanks!
Actually you shouldn't need to do all that. If you set up your workspace using gradlew setupDecompWorkspace, then you should be able to see all the vanilla source as a Referenced Library within your IDE. En Eclipse it is just in the Referenced Libraries -- I can't remember where it is in IntelliJ but it is definitely there too.
Minecraft is not open source, but Java is a weird language because it was designed for "just in time" compilation meaning that the "source code" (well actually an intermediate type of obfuscated code) is distributed as the executable. This means that the JAR file is readable, except it might not be EASY to read because all the field and method names might be very cryptic and there won't be any comments. So MCP provided a crowd-sourced "mapping" to rename all the fields and methods so they are more understandable.
You'll notice that Forge doesn't distribute Minecraft (that violates the license) but it will map a JAR that you download to make it more readable.
Minecraft EULA explicitly allows modding, so nothing nefarious is going on.
The screenshot needs to be taken on the client side, since that is where the rendering happens. I think you should try using PlayerTickEvent but checking that you're on the client side. The "SP" player is the client-side player, so you could set a counter when SP player joins world and then count down in PlayerTickEvent and then like one second later initiate the same code as in the screenshot key binding.
Thank you so much!
Success! I did:
So next up, I need to make an array of screenshot locations / rotations and loop through those.
Thanks again!
Ok so I came up with this:
To give my main class a "handle" to the player entity. (Since the player doesn't join the world over and over, I need some way to trigger setting the player position besides just the player joining the world.)
Then:
This would work great with one exception. It doesn't move the player! It takes all the screenshots correctly though and prints all debug output as I would expect it to.
Why am I able to set the player position within the world join event but not generally within the tick event?
Thank you!
Bump.
Thank you!