Creating a custom weather (0.9 - 1.0) [programming]

How's custom weather defined?/What it is?

When you say custom weather, it's understood( at least by ProperWeather) as an event, which modifies current weather conditions in some world and occurs with some probability.

[programming]

This section is about creating a custom weather using a plugin. As of 0.7 release, you can use YAML file to create a weather.

Changes from 0.8 (recommended to read)

There are 2 major changes that affect custom weather development. The first one is the WeatherDescription class, which allows user to customize your weather in a yaml file(weathers.yml). Instead of implementing bunch of methods, we use single field of type sk.tomsik68.pw.api.WeatherDefaults annotated by sk.tomsik68.pw.Defaults . It really doesn't matter what the name of field is. Important thing is: it's public static, final & annotated by @Defaults. There's also available a sample implementation of WeatherDefaults. It's sk.tomsik68.pw.impl.BasicWeatherDefaults. It allows you to specify all default options of your weather in single constructor. The second change is region system. If you red previous(0.7-0.8) tutorial for creating a custom weather, you've noticed, that in onRandomTime() method, we iterate through all loaded blocks of the world using 3 for cycles. Now, we only need 1 which is for(Block block : region) where region is region where the weather is active. Don't worry, you'll get it in a moment... :) or look at implemetation of basic weathers here at github.

Let's get started!

To implement a class, we should know what we're doing, so I paste Weather class here, so you'll know what to implement:

package sk.tomsik68.pw.api;

import sk.tomsik68.pw.config.WeatherDescription;
import sk.tomsik68.pw.plugin.ProperWeather;
/** The weather.
 * 
 * @author Tomsik68
 *
 */
public abstract class Weather implements Cloneable {
    private int regionID;
    protected WeatherDescription wd;

    public Weather(WeatherDescription wd1, Integer region) {
        this.regionID = region.intValue();
        this.wd = wd1;
    }
    /**
     * 
     * @return {@link WeatherController} to be used by the weather.
     */
    public final WeatherController getController() {
        return ProperWeather.instance().getWeatherSystem().getWeatherController(regionID);
    }
    /**
     * 
     * @param previousID
     * @return Whether this weather can be started after previous one. This option is specified by weather's {@link WeatherDescription}. Devs can only make defaults. 
     */
    public final boolean canBeStarted(Integer previousID) {
        return this.wd.canBeAfter(previousID.intValue());
    }
    /**
     * 
     * @return Random time probability.
     */
    public final int getRandomTimeProbability() {
        if (this.wd == null)
            this.wd = ProperWeather.instance().getWeatherDescription(getClass().getSimpleName().replace("Weather", ""));
        return this.wd.getRandomTimeProbability();
    }
    /**
     * 
     * @return Probability of this weather.
     */
    public final int getProbability() {
        return this.wd.getProbability();
    }
    /** Inits weather (clear sky, start raining etc.)
     * 
     */
    public abstract void initWeather();
    /** What happens on random time of the weather
     *  (if nothing, random time probability should be 0.
     */
    public void onRandomTime() {
    }

    public final int getMaxDuration() {
        return this.wd.getMaxDuration();
    }
    
    public final String getName() {
        return this.wd.getName();
    }

    public String toString() {
        return getName();
    }
}

Implementation of Weather method by method

Weather Defaults first

WeatherDefaults tell system, what to use first time user starts your weather. User can after that change the options in yml. Don't worry, option loading is done by ProperWeather :). What you need to do to get WeatherDefaults working is, you need to create a public static final field in your weather class. The field MUST be annotated by sk.tomsik68.pw.Defaults and its superinterface must be sk.tomsik68.api.WeatherDefaults. The default and mostly used implementation is sk.tomsik68.pw.impl.BasicWeatherDefaults. So just define your options and continue:

@Defaults
public static final WeatherDefaults def = new BasicWeatherDefaults(18000, 35, 75, new String[] { "MeteorStorm", "Storm", "ItemRain" });

The code above says: This weather can't be on longer than 18000 ticks. Its probability that it'll turn on is 35%. Its random time probability is 75%. It can't be after Meteor Storm, Storm & Item Rain. Those names can be got by removing "Weather" from any class name. Like: "WeatherItemRain" -> "ItemRain" and so on.

initWeather()

Your weather was chosen by system( or user ;) ) to run. You need to start the basic conditions of your weather. Example Usages: Change sky color, Start raining, Allow Thunders,... Example Implementation:

    public void initWeather(){
          WeatherController wc = getController();
          wc.clear();
          wc.allowThundering();
          wc.setSkyColor(java.awt.Color.BLACK);
    }

onRandomTime()

Here we get to previously mentioned random time. Example Usages: Thundering, Falling Meteorite, Spawning Custom Entities,... Example Implementation(with thunders):

public void onRandomTime() {
		final WeatherController controller = getController();
                final World world = controller.getRegion().getWorld();
                final Region region = controller.getRegion();
                final Random rand = new Random(world.getSeed() * world.getFullTime());
                for (Block block : region) {
                      if (block == null)
                          continue;
                      if (rand.nextInt(100000) != 0 || block.getType() == Material.SAND)
                          continue;
                      controller.strike(block.getLocation());
                }
	}

If you wanted to modify blocks, or spawn entities, you need to use region's method or you'll get the "TickNextTick list out of synch" error. To prevent that, you need to use: To change blocks:

Region region = getController().getRegion();
//block can be whatever block you want to update
Block block = getController().getRegion().getWorld().getBlockAt(1,2,3);
BlockState state = block.getState();
state.setType(Material.DIAMOND_BLOCK);
region.updateBlockState(state);

To spawn entities:

Region region = getController().getRegion();
//velocity & location should be changed :)
Vector velocity = new Vector(1,2,3);
Location location = new Location(region.getWorld(),1,2,3);
region.spawnEntity(Ghast.class,location,velocity);

How to register weather?

So far, we've implemented our weather, but that's not enought, because ProperWeather can't find it itself, so registration is needed. Luckily, I've made the registration simple:

WeatherManager.register(MyWeather.class);

Let's add the code into main skeleton:

import sk.tomsik68.pw.WeatherManager
import sk.tomsik68.pw.plugin.ProperWeather;
public void setupBridge() {
		Plugin test = getServer().getPluginManager().getPlugin("ProperWeather");
		if (test != null && test instanceof ProperWeather) {
			ProperWeather pw = (ProperWeather) test;
			System.out.println("[MyPlugin] Hooked into ProperWeather");
                        //MyWeather is the Weather implementation you've just created
			WeatherManager.register(MyWeather.class);
		}
	}

Cheers!

You've just made your first (and hopefully not last) Weather for Minecraft! Now go ahead, compile it and try it. If you've got any questions, suggestions or anything to tell me, just PM me or comment ProperWeather.


Comments

Posts Quoted:
Reply
Clear All Quotes