Compare commits

...

No commits in common. "master" and "0.x" have entirely different histories.
master ... 0.x

87 changed files with 1790 additions and 2596 deletions

57
.classpath Normal file
View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View file

@ -1,20 +0,0 @@
on: [push]
jobs:
deploy:
runs-on: docker
container: debian:sid
steps:
- name: Prepare for installation
run: apt update
- name: Install JDK
run: apt install --no-install-recommends -y openjdk-21-jdk-headless maven git nodejs
- name: Clone repository
run: git clone https://git.m724.eu/Minecon724/realweather.git .
- name: Build
run: mvn clean package
- name: Upload artifacts
uses: https://github.com/actions/upload-artifact@v3
with:
path: target/realweather-*.jar

25
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: build
run-name: Build and package
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17 for x64
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
architecture: x64
- name: mvn clean package
run: mvn clean package
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: Package
path: target/*.jar
if-no-files-found: error

9
.gitignore vendored
View file

@ -1,2 +1,7 @@
/target/ target/
/.settings/
# IDE files
org.eclipse.*
.vscode/
.classpath
.project

10
.gitpod.yml Normal file
View file

@ -0,0 +1,10 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
image: gitpod/workspace-java-17
tasks:
- init: mvn clean install

3
.idea/.gitignore generated vendored
View file

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

13
.idea/compiler.xml generated
View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="realweather" />
</profile>
</annotationProcessing>
</component>
</project>

View file

@ -1,6 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright (c) &amp;#36;originalComment.match(&quot;Copyright \(c\) (\d+)&quot;, 1, &quot;-&quot;, &quot;&amp;#36;today.year&quot;)&amp;#36;today.year RealWeather Authors&#10;RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text." />
<option name="myName" value="gpl3" />
</copyright>
</component>

View file

@ -1,7 +0,0 @@
<component name="CopyrightManager">
<settings default="gpl3">
<module2copyright>
<element module="All" copyright="gpl3" />
</module2copyright>
</settings>
</component>

7
.idea/encodings.xml generated
View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

View file

@ -1,8 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="VulnerableLibrariesLocal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="isIgnoringEnabled" value="true" />
</inspection_tool>
</profile>
</component>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="m724" />
<option name="name" value="m724" />
<option name="url" value="https://git.m724.eu/api/packages/Minecon724/maven" />
</remote-repository>
<remote-repository>
<option name="id" value="spigot-repo" />
<option name="name" value="spigot-repo" />
<option name="url" value="https://hub.spigotmc.org/nexus/content/repositories/snapshots/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

12
.idea/misc.xml generated
View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK" />
</project>

8
.idea/modules.xml generated
View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/realweather.iml" filepath="$PROJECT_DIR$/.idea/realweather.iml" />
</modules>
</component>
</project>

13
.idea/realweather.iml generated
View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>SPIGOT</platformType>
</autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion>
</configuration>
</facet>
</component>
</module>

124
.idea/uiDesigner.xml generated
View file

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml generated
View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

34
.project Normal file
View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>realweather</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
<filteredResources>
<filter>
<id>1693298440613</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -1,11 +0,0 @@
If you're using a firewall, you must allow the following hosts:
- updater:
* git.m724.eu
- weather:
* api.open-meteo.com
- thunder:
* ws1.blitzortung.org
* ws7.blitzortung.org
* ws8.blitzortung.org
Subject to change!

View file

@ -615,3 +615,61 @@ reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee. copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper
mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands \`show w' and \`show c' should show the
appropriate parts of the General Public License. Of course, your
program's commands might be different; for a GUI interface, you would
use an "about box".
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU GPL, see <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your
program into proprietary programs. If your program is a subroutine
library, you may consider it more useful to permit linking proprietary
applications with the library. If this is what you want to do, use the
GNU Lesser General Public License instead of this License. But first,
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.

View file

@ -1,15 +1,2 @@
# realweather # Moved
v1 is here: https://git.724.rocks/Minecon724/realweather
For MC 1.16.5+ and Java 21+
### Building
To compile, clone this repo and `mvn clean package`. \
JAR will be in `target/`. Use the one without `original-`. \
By default, it's signed with the test key.
#### Signing
A test (and default) keystore is provided:
- keystore: `testkeystore.jks`
- storepass: `123456`
- alias: `testkey`
Override with `-Djarsigner.`

20
TODO.md Normal file
View file

@ -0,0 +1,20 @@
Current:
- weather forecast https://openweathermap.org/forecast5
- multiple conditions
- on join event
Milestone: yesterday
- fix bugs
Milestone: 0.5.1
- local maxmind
- readd metrics
- cache cleaning
Milestone: 0.6.0
- account for real sun movement, not just time
- release / debug separate versions
Milestone: future
- weather simulator (weather is clientside rn and doesnt have things such as lightning or other effects)
- tests

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.minecon724</groupId>
<artifactId>realweather</artifactId>
<version>0.0.1</version>
<build>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<minimizeJar>true</minimizeJar>
<artifactSet>
<includes>
<include>org.json:json</include>
</includes>
</artifactSet>
<filters>
<filter>
<artifact>org.json:*</artifact>
<excludes>
<exclude>META-INF/*.MF</exclude>
</excludes>
</filter>
</filters>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.18.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
</project>

185
pom.xml
View file

@ -1,155 +1,42 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>eu.m724</groupId> <groupId>pl.minecon724</groupId>
<artifactId>realweather</artifactId> <artifactId>realweather</artifactId>
<version>1.0.0-alpha-8-SNAPSHOT</version> <version>0.5.1-DEV</version>
<properties> <properties>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<jarsigner.keystore>${project.basedir}/testkeystore.jks</jarsigner.keystore> </properties>
<jarsigner.alias>testkey</jarsigner.alias>
<jarsigner.storepass>123456</jarsigner.storepass>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<repositories> <repositories>
<repository> <repository>
<id>spigot-repo</id> <id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository> </repository>
<repository> </repositories>
<id>m724</id>
<url>https://git.m724.eu/api/packages/Minecon724/maven</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.16.5-R0.1-SNAPSHOT</version> <version>1.20.4-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
<!-- Fix warning about vulnerabilities of things we don't use --> </dependency>
<exclusions> <dependency>
<exclusion> <groupId>com.google.code.gson</groupId>
<groupId>com.google.guava</groupId> <artifactId>gson</artifactId>
<artifactId>guava</artifactId> <version>2.10.1</version>
</exclusion> <scope>provided</scope>
<exclusion> </dependency>
<groupId>org.yaml</groupId> </dependencies>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>eu.m724</groupId>
<artifactId>wtapi</artifactId>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>eu.m724</groupId>
<artifactId>jarupdater</artifactId>
<version>0.1.8</version>
</dependency>
<dependency>
<groupId>eu.m724</groupId>
<artifactId>mstats-spigot</artifactId>
<version>0.1.2</version>
</dependency>
</dependencies>
<build> <build>
<resources> <resources>
<resource> <resource>
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<filtering>true</filtering> <filtering>true</filtering>
</resource> </resource>
</resources> </resources>
<plugins> <!-- versions: https://maven.apache.org/plugins/ --> </build>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<allowTimestampedSnapshots>true</allowTimestampedSnapshots>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<createDependencyReducedPom>false</createDependencyReducedPom>
<artifactSet>
<includes>
<include>eu.m724:wtapi</include>
<include>eu.m724:jarupdater</include>
<!-- <include>org.java-websocket:Java-WebSocket</include> -->
<!-- it's in plugin.yml and downloaded by server -->
<!-- gson is bundled with spigot -->
</includes>
</artifactSet>
<filters>
<filter>
<artifact>*</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>sign</id>
<goals>
<goal>sign</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<keystore>${jarsigner.keystore}</keystore>
<alias>${jarsigner.alias}</alias>
<storepass>${jarsigner.storepass}</storepass>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>m724</id>
<url>https://git.m724.eu/api/packages/Minecon724/maven</url>
</repository>
<snapshotRepository>
<id>m724</id>
<url>https://git.m724.eu/api/packages/Minecon724/maven</url>
</snapshotRepository>
</distributionManagement>
<scm>
<developerConnection>scm:git:git@git.m724.eu:Minecon724/realweather.git</developerConnection>
<tag>HEAD</tag>
</scm>
</project> </project>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/test" isTestSource="true" />
</content>
</component>
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>SPIGOT</platformType>
</autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion>
</configuration>
</facet>
</component>
</module>

View file

@ -1,19 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather;
import eu.m724.realweather.map.MapConfig;
import eu.m724.realweather.thunder.ThunderConfig;
import eu.m724.realweather.time.TimeConfig;
import eu.m724.realweather.weather.WeatherChanger;
public record Configs(
WeatherChanger weatherConfig,
TimeConfig timeConfig,
ThunderConfig thunderConfig,
MapConfig mapConfig
) {
}

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class DebugLogger {
private DebugLogger() {}
static Logger logger;
public static void info(String message, Object... format) {
log(Level.INFO, message, format);
}
public static void warning(String message, Object... format) {
log(Level.WARNING, message, format);
}
public static void severe(String message, Object... format) {
log(Level.SEVERE, message, format);
}
public static void fine(String message, Object... format) {
log(Level.FINE, message, format);
}
public static void finer(String message, Object... format) {
log(Level.FINER, message, format);
}
private static void log(Level level, String message, Object... format) {
if (!logger.isLoggable(level)) {
return;
}
message = message.formatted(format);
if (logger.getLevel().intValue() <= Level.FINE.intValue()) {
message = "[" + getCaller() + "] " + message;
}
if (level.intValue() < Level.INFO.intValue()) { // levels below info are never logged even if set for some reason
// colors text gray (cyan is close to gray)
if (level == Level.FINE) {
message = "\033[38;5;250m" + message + "\033[39m";
} else {
message = "\033[38;5;245m" + message + "\033[39m";
}
level = Level.INFO;
}
logger.log(level, message);
}
private static String getCaller() {
String caller = Thread.currentThread().getStackTrace()[4].getClassName();
if (caller.startsWith("eu.m724.realweather.")) {
caller = caller.substring("eu.m724.realweather.".length());
String[] packages = caller.split("\\.");
caller = IntStream.range(0, packages.length - 1)
.mapToObj(i -> packages[i].substring(0, 2))
.collect(Collectors.joining(".")) + "." + packages[packages.length - 1];
}
return caller;
}
}

View file

@ -1,268 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import eu.m724.mstats.MStatsPlugin;
import eu.m724.realweather.map.WorldList;
import eu.m724.realweather.updater.UpdateNotifier;
import org.bukkit.configuration.file.YamlConfiguration;
import eu.m724.realweather.commands.AdminCommand;
import eu.m724.realweather.map.command.GeoCommand;
import eu.m724.realweather.time.command.LocalTimeCommand;
import eu.m724.realweather.weather.command.LocalWeatherCommand;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.realweather.map.MapConfig;
import eu.m724.realweather.thunder.ThunderConfig;
import eu.m724.realweather.thunder.ThunderMaster;
import eu.m724.realweather.time.TimeConfig;
import eu.m724.realweather.time.TimeMaster;
import eu.m724.realweather.updater.PluginUpdater;
import eu.m724.realweather.weather.WeatherConfig;
import eu.m724.realweather.weather.WeatherMaster;
// TODO unmess this too
public class RealWeatherPlugin extends MStatsPlugin {
private Configs configs;
private WorldList worldList;
private WeatherMaster weatherMaster;
private TimeMaster timeMaster;
private ThunderMaster thunderMaster;
private static RealWeatherPlugin INSTANCE;
private CoordinatesLocationConverter coordinatesLocationConverter;
@Override
public void onEnable() {
long start = System.nanoTime();
INSTANCE = this;
File dataFolder = getDataFolder();
File modulesFolder = new File("modules");
modulesFolder.mkdir();
boolean firstRun = !new File(dataFolder, "config.yml").exists();
YamlConfiguration configuration;
try {
configuration = getConfig("config.yml");
} catch (IOException e) {
DebugLogger.severe("Failed to load configuration:");
DebugLogger.severe(" " + e);
getServer().getPluginManager().disablePlugin(this);
return;
}
DebugLogger.logger = getLogger();
getLogger().setLevel(configuration.getBoolean("debug") ? Level.FINEST : Level.INFO);
if (firstRun) {
DebugLogger.warning("This is your first time running this plugin.");
DebugLogger.warning("Please *shut down* the server, review the config files (enable modules, enter API keys, etc.)");
DebugLogger.warning("Don't forget to enable the plugin in config.yml");
getServer().getPluginManager().disablePlugin(this);
return;
}
if (configuration.getBoolean("disabled")) {
DebugLogger.warning("Plugin disabled per config. Enable it in config.yml");
getServer().getPluginManager().disablePlugin(this);
return;
}
try {
loadMapModules();
} catch (Exception e) {
DebugLogger.severe("Failed to load the Map module:");
DebugLogger.severe(" " + e);
getServer().getPluginManager().disablePlugin(this);
return;
}
try {
loadWeatherModule();
} catch (Exception e) {
DebugLogger.severe("Failed to load the Weather module:");
DebugLogger.severe(" " + e);
getServer().getPluginManager().disablePlugin(this);
return;
}
try {
loadThunderModule();
} catch (Exception e) {
DebugLogger.severe("Failed to load the Thunder module:");
DebugLogger.severe(" " + e);
getServer().getPluginManager().disablePlugin(this);
return;
}
try {
loadTimeModule();
} catch (Exception e) {
DebugLogger.severe("Failed to load the Time module:");
DebugLogger.severe(" " + e);
getServer().getPluginManager().disablePlugin(this);
return;
}
DebugLogger.fine("Loading Updater");
PluginUpdater updater = PluginUpdater.build(this.getFile(), configuration.getString("updater.channel"));
if (configuration.getBoolean("updater.notify")) {
UpdateNotifier updateNotifier = new UpdateNotifier(updater);
updateNotifier.register();
}
DebugLogger.fine("Done loading Updater");
getCommand("rwadmin").setExecutor(new AdminCommand(this, updater));
getCommand("geo").setExecutor(new GeoCommand(coordinatesLocationConverter));
mStats(2);
DebugLogger.fine("Plugin enabled. Took %.3f milliseconds", (System.nanoTime() - start) / 1000000.0);
}
// TODO repeating here!
private void loadMapModules() throws IOException {
DebugLogger.fine("Loading the Map modules");
YamlConfiguration yamlConfiguration = getConfig("map.yml");
MapConfig mapConfig = MapConfig.fromConfiguration(yamlConfiguration);
this.coordinatesLocationConverter = new CoordinatesLocationConverter(mapConfig);
this.worldList = new WorldList(mapConfig.worldNames(), mapConfig.worldNamesIsBlacklist());
getServer().getPluginManager().registerEvents(worldList, this);
DebugLogger.finer("Done loading Map modules");
}
private void loadWeatherModule() throws IOException {
DebugLogger.fine("Loading the Weather module");
YamlConfiguration yamlConfiguration = getConfig("modules/weather.yml");
WeatherConfig weatherConfig = WeatherConfig.fromConfiguration(yamlConfiguration);
if (!weatherConfig.enabled()) {
DebugLogger.finer("Weather module disabled per config");
return;
}
this.weatherMaster = new WeatherMaster(this, weatherConfig);
weatherMaster.init();
getCommand("localweather").setExecutor(new LocalWeatherCommand(weatherMaster.getPlayerWeatherStore()));
DebugLogger.finer("Enabled Weather module");
}
private void loadThunderModule() throws IOException {
DebugLogger.fine("Loading the Thunder module");
YamlConfiguration yamlConfiguration = getConfig("modules/thunder.yml");
ThunderConfig thunderConfig = ThunderConfig.fromConfiguration(yamlConfiguration);
if (!thunderConfig.enabled()) {
DebugLogger.finer("Thunder module disabled per config");
return;
}
this.thunderMaster = new ThunderMaster(this, thunderConfig);
thunderMaster.init();
DebugLogger.finer("Enabled Thunder module");
}
private void loadTimeModule() throws IOException {
DebugLogger.fine("Loading the Time module");
YamlConfiguration yamlConfiguration = getConfig("modules/time.yml");
TimeConfig timeConfig = TimeConfig.fromConfiguration(yamlConfiguration);
if (!timeConfig.enabled()) {
DebugLogger.finer("Time module disabled per config");
return;
}
this.timeMaster = new TimeMaster(this, timeConfig);
timeMaster.init();
getCommand("localtime").setExecutor(new LocalTimeCommand(timeMaster, coordinatesLocationConverter));
DebugLogger.finer("Enabled Time module");
}
public YamlConfiguration getConfig(String configFilePath) throws IOException {
File configFile = new File(this.getDataFolder(), configFilePath);
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
if (!configFile.exists()) {
try (InputStream defConfigStream = getResource(configFilePath)) {
if (defConfigStream == null)
return null;
config = YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream, StandardCharsets.UTF_8));
}
config.save(configFile);
}
return config;
}
/**
* Gets the instance of RealWeather plugin.
*
* @return The instance of RealWeather plugin.
*/
public static RealWeatherPlugin getInstance() {
return INSTANCE;
}
/**
* Gets the coordinates to location converter.
*
* @return The coordinates to location converter.
*/
public CoordinatesLocationConverter getCoordinatesLocationConverter() {
return coordinatesLocationConverter;
}
public WeatherMaster getWeatherMaster() {
return weatherMaster;
}
public TimeMaster getTimeMaster() {
return timeMaster;
}
public ThunderMaster getThunderMaster() {
return thunderMaster;
}
public WorldList getWorldList() {
return worldList;
}
}

View file

@ -1,19 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.api.weather;
import eu.m724.wtapi.object.Weather;
/**
* Called when weather is <em>updated</em> for the <strong>server</strong>
* <br>
* This is only used on <em>static</em> mode. For the dynamic mode counterpart, see {@link AsyncPlayerWeatherUpdateEvent}
*/
public class AsyncGlobalWeatherUpdateEvent extends AsyncWeatherUpdateEvent {
public AsyncGlobalWeatherUpdateEvent(Weather weather) {
super(weather);
}
}

View file

@ -1,33 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.api.weather;
import eu.m724.wtapi.object.Weather;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
/**
* Called when weather is <em>updated</em> for a player
* <br>
* This is only used on <em>dynamic</em> mode. For the static mode counterpart, see {@link AsyncGlobalWeatherUpdateEvent}
*/
public class AsyncPlayerWeatherUpdateEvent extends AsyncWeatherUpdateEvent {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player;
public AsyncPlayerWeatherUpdateEvent(Player player, Weather weather) {
super(weather);
this.player = player;
}
/**
* @return The player to update weather for
*/
public Player getPlayer() {
return player;
}
}

View file

@ -1,55 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.api.weather;
import eu.m724.wtapi.object.Weather;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
class AsyncWeatherUpdateEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final Weather weather;
private boolean cancelled;
public AsyncWeatherUpdateEvent(Weather weather) {
super(true);
this.weather = weather;
}
/**
* @return The new weather
*/
public Weather getWeather() {
return weather;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Cancel the weather change
*
* @param cancelled Whether to cancel
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View file

@ -1,86 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.commands;
import eu.m724.realweather.RealWeatherPlugin;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.realweather.time.TimeMaster;
import eu.m724.realweather.updater.command.UpdateCommand;
import eu.m724.realweather.weather.WeatherMaster;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import eu.m724.realweather.thunder.ThunderMaster;
import eu.m724.realweather.updater.PluginUpdater;
import net.md_5.bungee.api.ChatColor;
public class AdminCommand implements CommandExecutor {
private final RealWeatherPlugin plugin;
private final UpdateCommand updateCommand;
private final WeatherMaster weatherMaster;
private final TimeMaster timeMaster;
private final ThunderMaster thunderMaster;
private final CoordinatesLocationConverter coordinatesLocationConverter;
public AdminCommand(RealWeatherPlugin plugin, PluginUpdater updater) {
this.plugin = plugin;
this.updateCommand = new UpdateCommand(updater);
this.weatherMaster = plugin.getWeatherMaster();
this.timeMaster = plugin.getTimeMaster();
this.thunderMaster = plugin.getThunderMaster();
this.coordinatesLocationConverter = plugin.getCoordinatesLocationConverter();
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length > 0 && args[0].equals("update")) {
updateCommand.updateCommand(sender, args);
return true;
}
colorize(sender, "\n&eRealWeather %s\n\n", plugin.getDescription().getVersion().replace("-SNAPSHOT", "&c-SNAPSHOT"));
colorize(sender, "&6Coordinate scale: &b%f, %f &7blocks / deg", coordinatesLocationConverter.getScaleLatitude(), coordinatesLocationConverter.getScaleLongitude());
colorize(sender, "&6Static point: &b%f, %f &7lat, lon", coordinatesLocationConverter.getStaticPoint().latitude(), coordinatesLocationConverter.getStaticPoint().longitude());
sender.sendMessage("");
if (weatherMaster != null) {
colorize(sender, "&6Weather: %s", weatherMaster.isDynamic() ? "&aYes, dynamic" : "&aYes, static");
colorize(sender, " &6Provider: &b%s", weatherMaster.getProviderName());
colorize(sender, " &6/localweather to see current weather");
} else {
colorize(sender, "&6Weather: %s", "&cDisabled");
}
sender.sendMessage("");
if (timeMaster != null) {
colorize(sender, "&6Time: %s", timeMaster.isDynamic() ? "&aYes, dynamic" : "&aYes, static");
colorize(sender, " &6Scale: &b%s&7x", timeMaster.getTimeConverter().getScale());
colorize(sender, " &6/localtime to see current time");
} else {
colorize(sender, "&6Time: &cDisabled");
}
sender.sendMessage("");
if (thunderMaster != null) {
colorize(sender, "&6Thunder: &aYes, dynamic");
colorize(sender, " &6Provider: &b%s", thunderMaster.getProviderName());
colorize(sender, " &6API latency: &b%d&7ms", thunderMaster.getLatency());
} else {
colorize(sender, "&6Thunder: &cDisabled");
}
return true;
}
private void colorize(CommandSender sender, String text, Object... format) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', text.formatted(format)));
}
}

View file

@ -1,67 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.map;
import org.bukkit.Location;
import org.bukkit.World;
import eu.m724.wtapi.object.Coordinates;
public class CoordinatesLocationConverter {
private final double scaleLatitude;
private final double scaleLongitude;
private final Coordinates staticPoint;
public CoordinatesLocationConverter(MapConfig config) {
this.scaleLatitude = config.scaleLatitude();
this.scaleLongitude = config.scaleLongitude();
this.staticPoint = config.point();
}
/**
* Converts a {@link Location} to {@link Coordinates}<br>
* The result is scaled and wrapped
*
* @param location the location to convert
* @return the coordinates
*/
public Coordinates locationToCoordinates(Location location) {
// it's <-90, 90> (inclusive), but there's no point to make it that way here, because it's easier, and nice precision is enough
double latitude = (-location.getZ() / scaleLatitude) % 90;
// here it's <-180, 180) so it's correct, and we don't need excuses
double longitude = (location.getX() / scaleLongitude) % 180;
return new Coordinates(latitude, longitude);
}
/**
* Converts {@link Coordinates} to a {@link Location}<br>
* The result is scaled, but not wrapped to world border or anything
*
* @param world the world of the location
* @param coordinates the coordinates to convert
* @return the location in {@code world}
*/
public Location coordinatesToLocation(World world, Coordinates coordinates) {
double x = coordinates.longitude() * scaleLongitude;
double z = -coordinates.latitude() * scaleLatitude;
return new Location(world, x, 0, z);
}
public Coordinates getStaticPoint() {
return staticPoint;
}
public double getScaleLatitude() {
return scaleLatitude;
}
public double getScaleLongitude() {
return scaleLongitude;
}
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.map;
import java.util.List;
import org.bukkit.configuration.ConfigurationSection;
import eu.m724.wtapi.object.Coordinates;
public record MapConfig(
List<String> worldNames,
boolean worldNamesIsBlacklist,
double scaleLatitude,
double scaleLongitude,
Coordinates point
) {
public static MapConfig fromConfiguration(ConfigurationSection configuration) {
List<String> worldNames = configuration.getStringList("worlds");
boolean worldBlacklist = configuration.getBoolean("isBlacklist");
double scaleLatitude = configuration.getDouble("dimensions.latitude");
double scaleLongitude = configuration.getDouble("dimensions.longitude");
Coordinates point = new Coordinates(
configuration.getDouble("point.latitude"),
configuration.getDouble("point.longitude")
);
return new MapConfig(worldNames, worldBlacklist, scaleLatitude, scaleLongitude, point);
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.map;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class WorldList implements Listener {
private final List<String> worldNames;
private final boolean isBlacklist;
private final List<World> worlds = new ArrayList<>();
public WorldList(List<String> worldNames, boolean isBlacklist) {
this.worldNames = worldNames;
this.isBlacklist = isBlacklist;
}
@EventHandler
void onWorldLoad(WorldLoadEvent e) {
if (worldNames.contains(e.getWorld().getName()) ^ isBlacklist) {
worlds.add(e.getWorld());
}
}
@EventHandler
void onWorldUnload(WorldUnloadEvent e) {
worlds.remove(e.getWorld());
}
public List<World> getIncludedWorlds() {
return Collections.unmodifiableList(this.worlds);
}
public boolean isWorldIncluded(World world) {
return worlds.contains(world);
}
}

View file

@ -1,90 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.map.command;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.wtapi.object.Coordinates;
import net.md_5.bungee.api.ChatColor;
public class GeoCommand implements CommandExecutor {
private final CoordinatesLocationConverter coordinatesLocationConverter;
public GeoCommand(CoordinatesLocationConverter coordinatesLocationConverter) {
this.coordinatesLocationConverter = coordinatesLocationConverter;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
Player player = sender instanceof Player ? (Player) sender : null;
if (args.length == 0) {
if (player != null) {
Location location = player.getLocation();
Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(location);
colorize(player, "");
colorize(player, "&6Geolocation: &b%f&7, &b%f &7(lat, lon)", coordinates.latitude(), coordinates.longitude());
colorize(player, "&7In-game Position: &3%f&8, &3%f &8(x, z)", location.getX(), location.getZ());
colorize(player, "");
} else {
sender.sendMessage("You can't run this command without arguments as console");
}
} else if (args.length >= 3) {
colorize(sender, "&cInvalid arguments, &7make sure it's &a\"/geo lat,lon\" &7or &a\"/geo x z\" &7or just &a\"/geo\"");
} else if (args.length == 2) {
double x, z;
try {
x = Double.parseDouble(args[0]);
z = Double.parseDouble(args[1]);
} catch (NumberFormatException e) {
colorize(sender, "&cInvalid arguments, &7make sure it's &a\"/geo lat,lon\" &7or &a\"/geo x z\" &7or just &a\"/geo\"");
return true;
}
Location location = new Location(null, x, 0, z);
Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(location);
colorize(sender, "");
colorize(sender, "&6Geolocation: &b%f&8, &b%f &7(lat, lon)", coordinates.latitude(), coordinates.longitude());
colorize(sender, "&7In-game Position: &3%f&7, &3%f &8(x, z)", location.getX(), location.getZ());
colorize(sender, "&7Input interpreted as position, space used as separator");
colorize(sender, "");
} else {
double latitude, longitude;
try {
String[] split = args[0].split(",");
latitude = Double.parseDouble(split[0]);
longitude = Double.parseDouble(split[1]);
} catch (NumberFormatException e) {
colorize(sender, "&cInvalid arguments, &7make sure it's &a\"/geo lat,lon\" &7or &a\"/geo x z\" &7or just &a\"/geo\"");
return true;
}
Coordinates coordinates = new Coordinates(latitude, longitude);
Location location = coordinatesLocationConverter.coordinatesToLocation(null, coordinates);
colorize(sender, "");
colorize(sender, "&6In-game Position: &b%f&7, &b%f &7(x, z)", location.getX(), location.getZ());
colorize(sender, "&7Geolocation: &3%f&8, &3%f &8(lat, lon)", coordinates.latitude(), coordinates.longitude());
colorize(sender, "&7Input interpreted as geolocation, comma used as separator");
colorize(sender, "");
}
return true;
}
private void colorize(CommandSender sender, String text, Object... format) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', text.formatted(format)));
}
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.thunder;
import eu.m724.wtapi.provider.thunder.TimedStrike;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* Called on a lightning strike.
*/
public class AsyncLightningStrikeEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final TimedStrike timedStrike;
private boolean cancelled;
public AsyncLightningStrikeEvent(TimedStrike timedStrike) {
super(true);
this.timedStrike = timedStrike;
}
public TimedStrike getTimedStrike() {
return timedStrike;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.thunder;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.realweather.map.WorldList;
import eu.m724.wtapi.provider.thunder.TimedStrike;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
public class LightningListener implements Listener {
private final CoordinatesLocationConverter coordinatesLocationConverter;
private final WorldList worldList;
public LightningListener(CoordinatesLocationConverter coordinatesLocationConverter, WorldList worldList) {
this.coordinatesLocationConverter = coordinatesLocationConverter;
this.worldList = worldList;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onLightningStrike(AsyncLightningStrikeEvent event) {
if (event.isCancelled()) return;
TimedStrike strike = event.getTimedStrike();
DebugLogger.finer("Strike: %f %f", strike.coordinates().latitude(), strike.coordinates().longitude());
worldList.getIncludedWorlds().forEach(w -> {
Location location = coordinatesLocationConverter.coordinatesToLocation(w, strike.coordinates());
DebugLogger.finer("In %s that converts to: %d %d", w.getName(), location.getBlockX(), location.getBlockZ());
// World#isLoaded, Chunk#isLoaded and probably others using Chunk, load the chunk and always return true
if (w.isChunkLoaded(location.getBlockX() / 16, location.getBlockZ() / 16)) {
location.setY(w.getHighestBlockYAt(location) + 1);
w.strikeLightning(location);
DebugLogger.finer("Spawned lightning in %s on y level %d", w.getName(), location.getBlockY());
}
});
}
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.thunder;
import org.bukkit.configuration.ConfigurationSection;
/**
* Configuration of the thunder module
*
* @param enabled Whether the thunder module is enabled
* @param provider The provider name
* @param apiKey API key for the provider, null if not necessary
*/
public record ThunderConfig(
boolean enabled,
String provider,
String apiKey
) {
public static ThunderConfig fromConfiguration(ConfigurationSection configuration) {
return new ThunderConfig(
configuration.getBoolean("enabled"),
configuration.getString("provider"),
configuration.getString("apiKey")
);
}
}

View file

@ -1,67 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.thunder;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.RealWeatherPlugin;
import eu.m724.wtapi.provider.exception.NoSuchProviderException;
import eu.m724.wtapi.provider.Providers;
import eu.m724.wtapi.provider.exception.ProviderException;
import eu.m724.wtapi.provider.thunder.ThunderProvider;
public class ThunderMaster {
private final RealWeatherPlugin plugin;
private final String providerName;
private final String apiKey;
private ThunderProvider provider;
public ThunderMaster(RealWeatherPlugin plugin, ThunderConfig config) {
this.plugin = plugin;
this.providerName = config.provider();
this.apiKey = config.apiKey();
}
/**
* initializes, tests and starts
*
* @throws ProviderException if provider initialization failed
* @throws NoSuchProviderException config issue
*/
public void init() throws ProviderException, NoSuchProviderException {
provider = Providers.getThunderProvider(providerName, apiKey);
// TODO is this good? Probably not
provider.registerStrikeConsumer(strike -> {
plugin.getServer().getPluginManager().callEvent(
new AsyncLightningStrikeEvent(strike)
);
});
provider.registerEventConsumer(event -> {
DebugLogger.fine("Thunder provider says: %s", event.message());
if (event.exception() != null) {
DebugLogger.severe("Thunder provider exception: %s", event.message());
DebugLogger.severe(" " + event.exception());
}
});
provider.start();
DebugLogger.finer("Done initializing");
}
public long getLatency() {
return provider.getLatency();
}
// TODO should this be exposed?
public String getProviderName() {
return providerName;
}
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.time;
import eu.m724.realweather.map.WorldList;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.wtapi.object.Coordinates;
public class AsyncPlayerTimeTask extends BukkitRunnable {
private final TimeConverter timeConverter;
private final Server server;
private final CoordinatesLocationConverter coordinatesLocationConverter;
private final WorldList worldList;
AsyncPlayerTimeTask(TimeConverter timeConverter, Server server, CoordinatesLocationConverter coordinatesLocationConverter, WorldList worldList) {
this.timeConverter = timeConverter;
this.server = server;
this.coordinatesLocationConverter = coordinatesLocationConverter;
this.worldList = worldList;
}
@Override
public void run() {
for (Player player : server.getOnlinePlayers()) {
if (!player.hasPermission("realweather.dynamic")) continue;
if (!worldList.isWorldIncluded(player.getWorld())) continue;
Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(player.getLocation());
long time = timeConverter.calculateZoneOffset(coordinates.longitude());
long ticks = timeConverter.millisToTicks(time);
player.setPlayerTime(ticks, true);
DebugLogger.finer("Time for %s: %d", player.getName(), time);
}
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.time;
import eu.m724.realweather.map.WorldList;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.map.CoordinatesLocationConverter;
/**
* This does world time, player time is basically offset of this, like timezone +0
*/
public class SyncTimeUpdateTask extends BukkitRunnable {
private final TimeConverter timeConverter;
private final WorldList worldList;
private final long zoneOffset;
SyncTimeUpdateTask(TimeConverter timeConverter, WorldList worldList, CoordinatesLocationConverter coordinatesLocationConverter, boolean dynamic) {
this.timeConverter = timeConverter;
this.worldList = worldList;
this.zoneOffset = !dynamic ? timeConverter.calculateZoneOffset(coordinatesLocationConverter.getStaticPoint().longitude()) : 0;
}
@Override
public void run() {
long time = System.currentTimeMillis();
time = timeConverter.scale(time);
long ticks = timeConverter.millisToTicks(time + zoneOffset);
DebugLogger.finer("Updating world time: %d", ticks);
worldList.getIncludedWorlds().forEach(world -> world.setFullTime(ticks));
// TODO don't calculate time each run
}
}

View file

@ -1,28 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.time;
import org.bukkit.configuration.ConfigurationSection;
/**
*
* @param enabled is time module enabled
* @param dynamic is time dynamic, that is per player
* @param scale timescale, time goes Nx slower (0.5 - 2x faster)
*/
public record TimeConfig(
boolean enabled,
boolean dynamic,
double scale
) {
public static TimeConfig fromConfiguration(ConfigurationSection configuration) {
return new TimeConfig(
configuration.getBoolean("enabled"),
configuration.getBoolean("dynamic"),
configuration.getDouble("scale")
);
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.time;
public class TimeConverter {
private final double scale;
public TimeConverter(double scale) {
if (scale <= 0.0) {
throw new IllegalArgumentException("Scale must be greater than 0.0");
}
this.scale = scale;
}
/**
* Divides time by predefined scale<br>
* ...slowing it down (or speeding up)
*
* @param time unix milliseconds
* @return scaled unix milliseconds
*/
public long scale(long time) {
return (long) (time / scale);
}
/**
* Converts unix timestamp to in game ticks
*
* @param time unix millis
* @return in-game ticks
*/
public long millisToTicks(long time) {
return Math.floorMod(time / 3600 - 6000, 24000);
}
/**
* Calculates how often (or how rarely) we can update time<br>
* That is, to not do stuff when we don't have to<br>
* In ticks, because it's used with Bukkit scheduler
*
* @return update period IN TICKS
*/
public long calculateUpdatePeriod() {
return (long) (72 / scale);
}
/**
* Calculates time offset (in millis) for specified longitude
*
* @param longitude the longitude
* @return milliseconds of offset from the center of the world (0)
*/
public long calculateZoneOffset(double longitude) {
return (long) (longitude * 240000);
}
public double getScale() {
return scale;
}
}

View file

@ -1,62 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.time;
import eu.m724.realweather.RealWeatherPlugin;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.realweather.map.WorldList;
public class TimeMaster {
private final RealWeatherPlugin plugin;
private final TimeConverter timeConverter;
private final boolean dynamic;
public TimeMaster(RealWeatherPlugin plugin, TimeConfig timeConfig) {
this.plugin = plugin;
this.timeConverter = new TimeConverter(timeConfig.scale());
this.dynamic = timeConfig.dynamic();
}
public void init() {
long period = timeConverter.calculateUpdatePeriod();
DebugLogger.fine("Updates every %d ticks", period);
if (timeConverter.getScale() * period != 72.0) {
// TODO does it matter in practice?
DebugLogger.warning("Time scale is not optimal. Time might be inaccurate or choppy.");
}
WorldList worldList = plugin.getWorldList();
CoordinatesLocationConverter coordinatesLocationConverter = plugin.getCoordinatesLocationConverter();
new SyncTimeUpdateTask(timeConverter, worldList, coordinatesLocationConverter, dynamic)
.runTaskTimer(plugin, 0, period);
if (dynamic) {
// Not period because this is offset
new AsyncPlayerTimeTask(timeConverter, plugin.getServer(), coordinatesLocationConverter, worldList)
.runTaskTimerAsynchronously(plugin, 0, 5 * 20); // 5 seconds
}
// TODO replace that
// coordinatesLocationConverter.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false));
// coordinatesLocationConverter.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, true));
DebugLogger.finer("Done initializing");
}
public TimeConverter getTimeConverter() {
return timeConverter;
}
public boolean isDynamic() {
return dynamic;
}
}

View file

@ -1,86 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.time.command;
import java.time.Duration;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.realweather.time.TimeConverter;
import eu.m724.realweather.time.TimeMaster;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import eu.m724.wtapi.object.Coordinates;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
public class LocalTimeCommand implements CommandExecutor {
private final TimeMaster timeMaster;
private final TimeConverter timeConverter;
private final CoordinatesLocationConverter coordinatesLocationConverter;
public LocalTimeCommand(TimeMaster timeMaster, CoordinatesLocationConverter coordinatesLocationConverter) {
this.coordinatesLocationConverter = coordinatesLocationConverter;
this.timeMaster = timeMaster;
this.timeConverter = timeMaster.getTimeConverter();
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
Player player = sender instanceof Player ? (Player) sender : null;
long worldTime = System.currentTimeMillis();
worldTime = timeConverter.scale(worldTime) % 86400000;
long worldTimeTicks = timeConverter.millisToTicks(worldTime);
Duration worldTimeDuration = Duration.ofMillis(worldTime);
String worldTimeFormatted = String.format("%d:%02d:%02d",
worldTimeDuration.toHours(),
worldTimeDuration.toMinutesPart(),
worldTimeDuration.toSecondsPart());
BaseComponent[] component = new ComponentBuilder("World time: ").color(ChatColor.GRAY)
.append(worldTimeFormatted).color(ChatColor.DARK_AQUA)
.create();
sender.spigot().sendMessage(component);
if (timeMaster.isDynamic() && player != null && player.hasPermission("realweather.dynamic")) {
Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(player.getLocation());
long offsetTime = timeConverter.calculateZoneOffset(coordinates.longitude());
long offsetTimeTicks = timeConverter.millisToTicks(offsetTime);
boolean negative = offsetTime < 0;
Duration localTimeDuration = Duration.ofMillis(Math.floorMod(worldTime + offsetTime, 86400000));
Duration offsetTimeDuration = Duration.ofMillis(Math.floorMod(negative ? -offsetTime : offsetTime, 86400000));
String offsetTimeFormatted = String.format("%s%d:%02d:%02d",
(negative ? "-" : "+"),
offsetTimeDuration.toHours(),
offsetTimeDuration.toMinutesPart(),
offsetTimeDuration.toSecondsPart());
String localTimeFormatted = String.format("%d:%02d:%02d",
localTimeDuration.toHours(),
localTimeDuration.toMinutesPart(),
localTimeDuration.toSecondsPart());
component = new ComponentBuilder("Local time: ").color(ChatColor.GOLD)
.append(localTimeFormatted + " %dt".formatted(worldTimeTicks)).color(ChatColor.AQUA)
.append(" " + offsetTimeFormatted + " %dt".formatted(offsetTimeTicks)).color(ChatColor.GRAY)
.create();
sender.spigot().sendMessage(component);
}
return true;
}
}

View file

@ -1,52 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.updater;
import eu.m724.jarupdater.environment.ConstantEnvironment;
import eu.m724.jarupdater.updater.Updater;
import eu.m724.jarupdater.verify.SignatureVerifier;
import eu.m724.jarupdater.verify.Verifier;
import eu.m724.jarupdater.download.Downloader;
import eu.m724.jarupdater.download.SimpleDownloader;
import eu.m724.jarupdater.environment.Environment;
import eu.m724.jarupdater.live.GiteaMetadataDAO;
import eu.m724.jarupdater.live.MetadataDAO;
import eu.m724.jarupdater.live.MetadataFacade;
import eu.m724.realweather.RealWeatherPlugin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class PluginUpdater extends Updater {
private PluginUpdater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, Verifier verifier) {
super(environment, metadataProvider, downloader, verifier);
}
public static PluginUpdater build(File file, String channel) {
RealWeatherPlugin plugin = RealWeatherPlugin.getInstance();
Environment environment = new ConstantEnvironment(
plugin.getDescription().getVersion(),
channel,
file.toPath()
);
MetadataDAO metadataDAO = new GiteaMetadataDAO("https://git.m724.eu/Minecon724/realweather-metadata", "master");
MetadataFacade metadataFacade = new MetadataFacade(environment, metadataDAO);
Downloader downloader = new SimpleDownloader("realweather");
SignatureVerifier verifier = new SignatureVerifier();
try (InputStream inputStream = plugin.getResource("verifies_downloaded_jars.pem")) {
verifier.loadPublicKey(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
return new PluginUpdater(environment, metadataFacade, downloader, verifier);
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.updater;
import java.util.concurrent.CompletionException;
import eu.m724.realweather.RealWeatherPlugin;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.jarupdater.object.Version;
import eu.m724.realweather.DebugLogger;
public class UpdateNotifier extends BukkitRunnable implements Listener { // TODO move this to jarupdater
private final Plugin plugin = RealWeatherPlugin.getInstance();
private final PluginUpdater updater;
private Version latestVersion;
public UpdateNotifier(PluginUpdater updater) {
this.updater = updater;
}
public void register() {
this.runTaskTimerAsynchronously(plugin, 0, 6 * 3600 * 20); // 6h
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public void run() {
DebugLogger.finer("Updater running");
try {
latestVersion = updater.getLatestVersion().join();
} catch (CompletionException e) {
DebugLogger.warning("Error trying to contact update server:");
DebugLogger.warning(" %s", e.getCause().toString());
}
if (latestVersion == null) {
DebugLogger.fine("Plugin is up to date");
return;
}
DebugLogger.warning("RealWeather is outdated. /rwadmin update");
for (Player player : plugin.getServer().getOnlinePlayers()) {
if (player.hasPermission("realweather.update.notify")) {
player.sendMessage(ChatColor.GOLD + "RealWeather is outdated. /rwadmin update");
}
}
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent e) {
Player player = e.getPlayer();
if (latestVersion != null && player.hasPermission("realweather.update.notify")) {
player.sendMessage(ChatColor.GOLD + "RealWeather is outdated. /rwadmin update");
}
}
}

View file

@ -1,116 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.updater.command;
import java.nio.file.NoSuchFileException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.updater.PluginUpdater;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class UpdateCommand {
private final PluginUpdater updater;
private boolean updatePending = false;
public UpdateCommand(PluginUpdater updater) {
this.updater = updater;
}
private void sendChangelogMessage(CommandSender sender, String changelogUrl) {
if (changelogUrl != null) {
if (sender instanceof Player) {
TextComponent textComponent = new TextComponent("Click here to open changelog");
textComponent.setUnderlined(true);
textComponent.setColor(ChatColor.AQUA);
textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(changelogUrl)));
textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, changelogUrl));
sender.spigot().sendMessage(textComponent);
} else {
sender.sendMessage("Changelog: " + changelogUrl);
}
}
}
public void updateCommand(CommandSender sender, String[] args) {
sender.sendMessage(ChatColor.GRAY + "Please wait...");
sender.sendMessage(ChatColor.GRAY + "Channel: " + updater.getEnvironment().getChannel());
if (updatePending) {
sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "(!) Server restart required");
}
String action = args.length > 1 ? args[1] : null; // remember this function is proxied
if (action == null) {
updater.getLatestVersion().thenAccept(metadata -> {
updater.getCurrentVersion().thenAccept(metadata2 -> {
sender.sendMessage(ChatColor.GOLD + "You're on RealWeather" + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp()));
sendChangelogMessage(sender, metadata2.getChangelogUrl());
}).exceptionally(e -> {
sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details.");
DebugLogger.severe("Error retrieving information about current version:");
DebugLogger.severe(" " + e);
return null;
});
if (metadata != null) {
sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "An update is available!");
sender.sendMessage(ChatColor.AQUA + "RealWeather " + metadata.getLabel() + ChatColor.GOLD + " released " + formatDate(metadata.getTimestamp()));
sendChangelogMessage(sender, metadata.getChangelogUrl());
sender.sendMessage(ChatColor.GOLD + "To download: /rwadmin update download");
} else {
sender.sendMessage(ChatColor.GRAY + "No new updates");
}
}).exceptionally(e -> {
sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details.");
DebugLogger.severe("Error checking for update:");
DebugLogger.severe(" " + e);
return null;
});
} else {
if (action.equals("download")) {
sender.sendMessage(ChatColor.GRAY + "Started download");
updater.downloadLatestVersion().thenAccept(file -> {
sender.sendMessage(ChatColor.GREEN + "Download finished, install with /rwadmin update install"); // TODO make this clickable
}).exceptionally(e -> {
sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details.");
DebugLogger.severe("Error downloading update:");
DebugLogger.severe(" " + e);
return null;
});
} else if (action.equals("install")) {
try {
updater.installLatestVersion().thenAccept(v -> {
sender.sendMessage(ChatColor.GREEN + "Installation completed, restart server to apply.");
updatePending = true;
}).exceptionally(e -> {
sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details.");
DebugLogger.severe("Error installing update:");
DebugLogger.severe(" " + e);
return null;
});
} catch (NoSuchFileException e) {
sender.sendMessage(ChatColor.YELLOW + "Download the update first: /rwadmin update download");
}
}
}
}
private String formatDate(long timestamp) {
return LocalDate.ofEpochDay(timestamp / 86400).format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
}
}

View file

@ -1,105 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.weather;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import eu.m724.realweather.RealWeatherPlugin;
import eu.m724.realweather.api.weather.AsyncPlayerWeatherUpdateEvent;
import eu.m724.wtapi.provider.weather.WeatherQueryResult;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.wtapi.object.Coordinates;
import eu.m724.wtapi.object.Weather;
import eu.m724.wtapi.provider.weather.WeatherProvider;
public class DynamicWeatherRetriever extends BukkitRunnable {
private final RealWeatherPlugin plugin;
private final WeatherProvider weatherProvider;
private final PlayerWeatherStore playerWeatherStore;
private final CoordinatesLocationConverter coordinatesLocationConverter;
private final List<World> worlds;
public DynamicWeatherRetriever(RealWeatherPlugin plugin, WeatherProvider weatherProvider, PlayerWeatherStore playerWeatherStore) {
this.plugin = plugin;
this.weatherProvider = weatherProvider;
this.playerWeatherStore = playerWeatherStore;
this.coordinatesLocationConverter = plugin.getCoordinatesLocationConverter();
this.worlds = plugin.getWorldList().getIncludedWorlds();
}
@Override
public void run() {
DebugLogger.finer("Updating weather");
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
float maxHourlyUpdates = (float) weatherProvider.getQuota().getHourlyQuota() / plugin.getServer().getOnlinePlayers().size();
long updateDelay = Math.max(60, (long) (3600 / maxHourlyUpdates));
DebugLogger.finer("Update delay: %d seconds", updateDelay);
Player[] playersToUpdate = plugin.getServer().getOnlinePlayers().stream().filter(player -> {
LocalDateTime lastUpdate = playerWeatherStore.getLastUpdate(player);
if (!player.hasPermission("realweather.dynamic")) return false;
if (!worlds.contains(player.getWorld())) return false;
if (lastUpdate == null) return true;
DebugLogger.finer("Player %s's last update: %s", player.getName(), lastUpdate.toString());
if (Duration.between(lastUpdate, now).getSeconds() > updateDelay) return true;
// TODO also by distance
return false;
}).toArray(Player[]::new);
if (playersToUpdate.length == 0) {
DebugLogger.finer("Nobody needs updating");
return;
}
Coordinates[] coordinatesArray = Arrays.stream(playersToUpdate).map(player ->
coordinatesLocationConverter.locationToCoordinates(player.getLocation())
).toArray(Coordinates[]::new);
CompletableFuture<WeatherQueryResult> weathersFuture =
weatherProvider.getWeather(coordinatesArray);
WeatherQueryResult result = weathersFuture.join();
if (result.exception() != null) {
DebugLogger.severe("An error has occurred retrieving weather data");
DebugLogger.warning(" " + result.exception());
return;
}
Weather[] weathers = result.weathers();
for (int i=0; i<weathers.length; i++) {
Weather weather = weathers[i];
Player player = playersToUpdate[i];
playerWeatherStore.put(player, weather);
AsyncPlayerWeatherUpdateEvent event =
new AsyncPlayerWeatherUpdateEvent(player, weather);
plugin.getServer().getPluginManager().callEvent(event);
}
DebugLogger.fine("Done updating weather");
}
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.weather;
import java.time.LocalDateTime;
import java.util.HashMap;
import org.bukkit.entity.Player;
import eu.m724.wtapi.object.Weather;
public class PlayerWeatherStore {
private final HashMap<Player, Weather> weathers = new HashMap<>();
void put(Player player, Weather weather) {
weathers.put(player, weather);
}
public Weather getWeather(Player player) {
return weathers.get(player);
}
public LocalDateTime getLastUpdate(Player player) {
Weather weather = weathers.get(player);
return weather != null ? weather.timestamp() : null;
}
}

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.weather;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.RealWeatherPlugin;
import eu.m724.realweather.api.weather.AsyncGlobalWeatherUpdateEvent;
import eu.m724.realweather.map.CoordinatesLocationConverter;
import eu.m724.wtapi.object.Coordinates;
import eu.m724.wtapi.object.Weather;
import eu.m724.wtapi.provider.weather.WeatherProvider;
import eu.m724.wtapi.provider.weather.WeatherQueryResult;
import org.bukkit.scheduler.BukkitRunnable;
public class StaticWeatherRetriever extends BukkitRunnable {
private final RealWeatherPlugin plugin;
private final WeatherProvider weatherProvider;
private final CoordinatesLocationConverter coordinatesLocationConverter;
public StaticWeatherRetriever(RealWeatherPlugin plugin, WeatherProvider weatherProvider) {
this.plugin = plugin;
this.weatherProvider = weatherProvider;
this.coordinatesLocationConverter = plugin.getCoordinatesLocationConverter();
}
@Override
public void run() {
DebugLogger.finer("Updating weather");
Coordinates point = coordinatesLocationConverter.getStaticPoint();
WeatherQueryResult result = weatherProvider.getWeather(point).join();
if (result.exception() != null) {
DebugLogger.severe("An error has occurred retrieving weather data");
DebugLogger.warning(" " + result.exception());
return;
}
Weather weather = result.weathers()[0];
AsyncGlobalWeatherUpdateEvent event =
new AsyncGlobalWeatherUpdateEvent(weather);
plugin.getServer().getPluginManager().callEvent(event);
DebugLogger.fine("Done updating weather");
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.weather;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.api.weather.AsyncGlobalWeatherUpdateEvent;
import eu.m724.realweather.api.weather.AsyncPlayerWeatherUpdateEvent;
import eu.m724.realweather.map.WorldList;
import eu.m724.wtapi.object.Weather;
import org.bukkit.WeatherType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
// TODO make weather more intricate
public class WeatherChanger implements Listener {
private final WorldList worldList;
public WeatherChanger(WorldList worldList) {
this.worldList = worldList;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onGlobalWeatherUpdate(AsyncGlobalWeatherUpdateEvent event) {
if (event.isCancelled()) return;
Weather weather = event.getWeather();
DebugLogger.finer("Changing weather static");
worldList.getIncludedWorlds().forEach(w -> {
if (weather.thundering()) {
DebugLogger.finer("Changing weather static in world %s to thunder", w.getName());
w.setClearWeatherDuration(0);
w.setWeatherDuration(120000);
w.setThunderDuration(120000);
} else if (weather.raining() || weather.snowing()) {
DebugLogger.finer("Changing weather static in world %s to rain", w.getName());
w.setClearWeatherDuration(0);
w.setWeatherDuration(120000);
w.setThunderDuration(0);
} else {
DebugLogger.finer("Changing weather static in world %s to clear", w.getName());
w.setClearWeatherDuration(120000);
w.setWeatherDuration(0);
w.setThunderDuration(0);
}
});
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerWeatherUpdate(AsyncPlayerWeatherUpdateEvent event) {
if (event.isCancelled()) return;
Player player = event.getPlayer();
Weather weather = event.getWeather();
if (weather.thundering() || weather.snowing() || weather.raining()) {
DebugLogger.finer("Changing weather for player %s to downfall", player.getName());
player.setPlayerWeather(WeatherType.DOWNFALL);
} else {
DebugLogger.finer("Changing weather for player %s to clear", player.getName());
player.setPlayerWeather(WeatherType.CLEAR);
}
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.weather;
import org.bukkit.configuration.ConfigurationSection;
/**
* Configuration of the weather module
*
* @param enabled Whether the weather module is enabled
* @param provider The provider name
* @param apiKey API key for the provider, null if not necessary
* @param dynamic dynamic mode, weather is per player or global
*/
public record WeatherConfig(
boolean enabled,
String provider,
String apiKey, // TODO don't expose that, it's only used in one place in init
boolean dynamic
) {
public static WeatherConfig fromConfiguration(ConfigurationSection configuration) {
return new WeatherConfig(
configuration.getBoolean("enabled"),
configuration.getString("provider"),
configuration.getString("apiKey"),
configuration.getBoolean("dynamic")
);
}
}

View file

@ -1,74 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.weather;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.RealWeatherPlugin;
import eu.m724.wtapi.provider.exception.NoSuchProviderException;
import eu.m724.wtapi.provider.Providers;
import eu.m724.wtapi.provider.exception.ProviderException;
import eu.m724.wtapi.provider.weather.WeatherProvider;
public class WeatherMaster {
private final RealWeatherPlugin plugin;
private final boolean dynamic;
private final String providerName;
private final String apiKey;
private final PlayerWeatherStore playerWeatherStore = new PlayerWeatherStore();
public WeatherMaster(RealWeatherPlugin plugin, WeatherConfig weatherConfig) {
this.plugin = plugin;
this.dynamic = weatherConfig.dynamic();
this.providerName = weatherConfig.provider();
this.apiKey = weatherConfig.apiKey();
}
/**
* initializes, tests and starts
* @throws ProviderException if provider initialization failed
* @throws NoSuchProviderException config issue
*/
public void init() throws ProviderException, NoSuchProviderException {
WeatherProvider provider = Providers.getWeatherProvider(providerName, apiKey);
provider.init();
if (dynamic) {
DebugLogger.finer("Weather is dynamic");
DynamicWeatherRetriever retriever = new DynamicWeatherRetriever(plugin, provider, playerWeatherStore);
retriever.runTaskTimerAsynchronously(plugin,0, 200);
} else {
DebugLogger.finer("Weather is static");
StaticWeatherRetriever retriever = new StaticWeatherRetriever(plugin, provider);
retriever.runTaskTimerAsynchronously(plugin,0, 60000);
}
plugin.getServer().getPluginManager().registerEvents(new WeatherChanger(plugin.getWorldList()), plugin);
DebugLogger.finer("Done initializing");
// TODO replace that
// coordinatesLocationConverter.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, false));
// coordinatesLocationConverter.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, true));
}
public boolean isDynamic() {
return dynamic;
}
// TODO should this be exposed?
public String getProviderName() {
return providerName;
}
public PlayerWeatherStore getPlayerWeatherStore() {
return playerWeatherStore;
}
}

View file

@ -1,69 +0,0 @@
/*
* Copyright (c) 2025 RealWeather Authors
* RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text.
*/
package eu.m724.realweather.weather.command;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import eu.m724.realweather.weather.PlayerWeatherStore;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import eu.m724.wtapi.object.Weather;
public class LocalWeatherCommand implements CommandExecutor {
private final PlayerWeatherStore playerWeatherStore;
public LocalWeatherCommand(PlayerWeatherStore playerWeatherStore) {
this.playerWeatherStore = playerWeatherStore;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("You must be a player to use this command");
return true;
}
Weather weather = playerWeatherStore.getWeather(player);
if (weather != null) {
LocalDateTime lastUpdate = playerWeatherStore.getLastUpdate(player);
colorize(sender, "&6Weather for: &b%f&7, &b%f &7(lat, lon)\n", weather.coordinates().latitude(), weather.coordinates().longitude());
List<String> states = new ArrayList<>(3);
if (weather.raining()) states.add("Raining");
if (weather.thundering()) states.add("Thundering");
if (weather.thundering()) states.add("Snowing");
if (!states.isEmpty()) {
colorize(sender, "&6" + String.join(", ", states));
}
colorize(sender, "&6Temperature: &b%.1f&7°C (feels like %.1f°C)", weather.temperatureCelsius(), weather.temperatureApparentCelsius());
colorize(sender, "&6Cloud cover (cloudiness): &b%.0f&7%%", weather.cloudCoverPercentage() * 100);
colorize(sender, "&6Relative humidity: &b%.0f&7%%", weather.relativeHumidityPercentage() * 100);
colorize(sender, "&6Last update: &b%s UTC\n", lastUpdate.format(DateTimeFormatter.ofPattern("HH:mm:ss")));
} else {
colorize(sender, "&6No weather for you yet, try again in a few seconds");
}
return true;
}
private void colorize(CommandSender sender, String text, Object... format) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', text.formatted(format)));
}
}

View file

@ -0,0 +1,70 @@
package pl.minecon724.realweather;
import java.io.IOException;
import java.util.logging.Logger;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.realtime.RealTimeCommander;
import pl.minecon724.realweather.weather.WeatherCommander;
import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException;
public class RealWeatherPlugin extends JavaPlugin {
private final Logger logger = getLogger();
private FileConfiguration config;
@Override
public void onEnable() {
saveDefaultConfig();
config = getConfig();
SubLogger.init(
logger,
config.getBoolean("logging", false)
);
ConfigurationSection mapConfigurationSection = config.getConfigurationSection("map");
try {
WorldMap.init(
mapConfigurationSection,
getDataFolder()
);
} catch (IOException e) {
logger.severe("Unable to initialize WorldMap:");
e.printStackTrace();
getServer().getPluginManager().disablePlugin(this);
}
WeatherCommander weatherCommander = new WeatherCommander(this);
try {
weatherCommander.init(
config.getConfigurationSection("weather")
);
weatherCommander.start();
} catch (ModuleDisabledException e) {
logger.info("Weather is disabled by user");
} catch (IllegalArgumentException e) {
logger.severe("Couldn't initialize weather provider:");
e.printStackTrace();
getServer().getPluginManager().disablePlugin(this);
}
RealTimeCommander realTimeCommander = new RealTimeCommander(this);
try {
realTimeCommander.init(
config.getConfigurationSection("time")
);
realTimeCommander.start();
} catch (ModuleDisabledException e) {
logger.info("Time is disabled by user");
}
}
}

View file

@ -0,0 +1,87 @@
package pl.minecon724.realweather;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SubLogger {
// TODO TODO too many static
private static Logger LOGGER;
private static boolean ENABLED;
private String name;
/**
* Initialize the SubLogger
* @param logger parent logger, usually JavaPlugin#getLogger()
* @param enabled is logging enabled
*/
static void init(Logger logger, boolean enabled) {
LOGGER = logger;
ENABLED = enabled;
}
/**
* Instantiate a SubLogger instance
* @param name name, it will be prefixing messages
*/
public SubLogger(String name) {
this.name = name;
}
/**
* Log a message
* @param level
* @param format message, formatted like {@link String#format(String, Object...)}
* @param args args for formatting
*/
public void log(Level level, String format, Object... args) {
if (!ENABLED) return;
Object[] combinedArgs = new Object[args.length + 1];
combinedArgs[0] = name;
System.arraycopy(args, 0, combinedArgs, 1, args.length);
LOGGER.log(level, String.format("[%s] " + format, combinedArgs));
}
/**
* Log an info message
* see {@link SubLogger#log(Level, String, Object...)}
* @param format message
* @param args args
*/
public void info(String format, Object... args) {
this.log(Level.INFO, format, args);
}
public void info(String message) {
this.log(Level.INFO, message, new Object[0]);
}
/**
* Log a severe message
* see {@link SubLogger#log(Level, String, Object...)}
* @param format message
* @param args args
*/
public void severe(String format, Object... args) {
this.log(Level.SEVERE, format, args);
}
public void severe(String message) {
this.log(Level.SEVERE, message, new Object[0]);
}
/**
* Log a warning message
* see {@link SubLogger#log(Level, String, Object...)}
* @param format message
* @param args args
*/
public void warning(String format, Object... args) {
this.log(Level.WARNING, format, args);
}
public void warning(String message) {
this.log(Level.WARNING, message, new Object[0]);
}
}

View file

@ -0,0 +1,60 @@
package pl.minecon724.realweather.geoip;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import pl.minecon724.realweather.SubLogger;
public class DatabaseDownloader {
private SubLogger subLogger = new SubLogger("download");
private URL downloadUrl;
public DatabaseDownloader(URL downloadUrl) {
this.downloadUrl = downloadUrl;
}
public long getDate(boolean ipv6) throws IOException {
URL url = new URL(downloadUrl, ipv6 ? "ipv6.geo.gz" : "ipv4.geo.gz");
URLConnection connection = url.openConnection();
connection.connect();
long lastModified = connection.getHeaderFieldDate("last-modified", 0);
return lastModified;
}
// TODO verify
public void download(File file, boolean ipv6) throws IOException {
URL url = new URL(downloadUrl, ipv6 ? "ipv6.geo.gz" : "ipv4.geo.gz");
URLConnection connection = url.openConnection();
connection.connect();
InputStream inputStream = connection.getInputStream();
ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream);
file.getParentFile().mkdirs();
file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel fileChannel = fileOutputStream.getChannel();
long size = connection.getHeaderFieldLong("Content-Length", 0);
long position = 0;
while (position < size) {
position += fileChannel.transferFrom(readableByteChannel, position, 1048576);
subLogger.info("%d%%", (int)(1.0 * position / size * 100));
}
fileChannel.close();
fileOutputStream.close();
readableByteChannel.close();
inputStream.close(); // ok
}
}

View file

@ -0,0 +1,93 @@
package pl.minecon724.realweather.geoip;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.zip.GZIPInputStream;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Shorts;
import pl.minecon724.realweather.map.Coordinates;
public class GeoIPDatabase {
private byte formatVersion;
private long timestamp;
public HashMap<Integer, Coordinates> entries = new HashMap<>();
public void read(File file, boolean head) throws IOException, FileNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException();
}
FileInputStream fileInputStream = new FileInputStream(file);
GZIPInputStream gzipInputStream = new GZIPInputStream(fileInputStream);
formatVersion = (byte) gzipInputStream.read();
byte[] bytes = gzipInputStream.readNBytes(2);
timestamp = recoverTime(bytes);
if (!head) {
byte[] address;
Coordinates coordinates;
while (true) { // TODO true?
System.out.println(gzipInputStream.available());
address = gzipInputStream.readNBytes(4);
if (address.length == 0)
break;
coordinates = recoverCoordinates(gzipInputStream.readNBytes(6));
entries.put(IPUtils.toInt(address), coordinates);
}
}
gzipInputStream.close();
fileInputStream.close();
}
public long getTimestamp() {
return timestamp;
}
public byte getFormatVersion() {
return formatVersion;
}
/**
* 2 bytes to 4 bytes wow magic
* @param bytes 2 bytes
* @return
*/
@SuppressWarnings("null") // TODO better way of this?
private long recoverTime(byte[] bytes) {
long timestamp = 1704067200; // first second of 2024
timestamp += Shorts.fromByteArray(bytes) * 60 * 10;
return timestamp;
}
/**
* Encoded to Coordinates
* @param bytes 3 bytes
* @return decoded Coordinates
*/
private Coordinates recoverCoordinates(byte[] bytes) {
int skewedLatitude = Ints.fromBytes(
(byte)0, bytes[0], bytes[1], bytes[2]
);
int skewedLongitude = Ints.fromBytes(
(byte)0, bytes[3], bytes[4], bytes[5]
);
double latitude = (skewedLatitude - 900000) / 10000.0;
double longitude = (skewedLongitude - 1800000) / 10000.0;
return new Coordinates(latitude, longitude);
}
}

View file

@ -0,0 +1,32 @@
package pl.minecon724.realweather.geoip;
import com.google.common.primitives.Ints;
public class IPUtils {
public static byte[] getSubnetStart(byte[] addressBytes, byte subnet) {
int address = toInt(addressBytes);
int mask = 0xFFFFFFFF << (32 - subnet);
return fromInt(address & mask);
}
@SuppressWarnings("null")
public static int toInt(byte[] address) {
return Ints.fromByteArray(address);
}
public static byte[] fromInt(int value) {
return Ints.toByteArray(value);
}
public static String toString(byte[] address) {
String s = "";
for (int i=0; i<4; i++) {
s += Integer.toString(address[i] & 0xFF);
if (i < 3) s += ".";
}
return s;
}
}

View file

@ -0,0 +1,39 @@
package pl.minecon724.realweather.map;
public class Coordinates {
public double latitude, longitude;
public Coordinates(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
/**
* converts Coordinates to double array
* @return array in order of latitude, longitude
*/
public double[] toDoubleArray() {
return new double[] {
this.latitude,
this.longitude
};
}
public Coordinates clamp() {
this.latitude = Math.max(-90.0, Math.min(90.0, this.latitude));
this.longitude = Math.max(-180.0, Math.min(180.0, this.longitude));
return this;
}
public Coordinates wrap() {
this.latitude = wrapDouble(-90.0, 90.0, this.latitude);
this.longitude = wrapDouble(-180.0, 180.0, this.longitude);
return this;
}
private static double wrapDouble(double min, double max, double val) {
return min + (val - min) % (max - min);
}
}

View file

@ -0,0 +1,103 @@
package pl.minecon724.realweather.map;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.geoip.DatabaseDownloader;
import pl.minecon724.realweather.geoip.GeoIPDatabase;
import pl.minecon724.realweather.geoip.IPUtils;
import pl.minecon724.realweather.map.exceptions.GeoIPException;
public class GeoLocator {
private static GeoLocator INSTANCE = null;
private SubLogger subLogger = new SubLogger("geolocator");
private GeoIPDatabase database;
private HashMap<InetAddress, Coordinates> cache = new HashMap<>();
public static void init(File databaseFile, String downloadUrl) throws IOException {
INSTANCE = new GeoLocator(
new GeoIPDatabase());
INSTANCE.load(databaseFile, downloadUrl);
}
public GeoLocator(GeoIPDatabase database) {
this.database = database;
}
public void load(File databaseFile, String downloadUrl) throws IOException {
subLogger.info("This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com");
DatabaseDownloader downloader = new DatabaseDownloader(new URL(downloadUrl));
try {
subLogger.info("Checking for update...");
database.read(databaseFile, true);
long lastModified = downloader.getDate(false);
if (database.getTimestamp() < lastModified) {
subLogger.info("Updating...");
downloader.download(databaseFile, false);
}
} catch (FileNotFoundException e) {
subLogger.info("Downloading...");
downloader.download(databaseFile, false);
}
subLogger.info("Loading, this may take a while...");
database.read(databaseFile, false);
subLogger.info("Database: %s",
new SimpleDateFormat("dd.MM.yyyy HH:mm:ss")
.format(new Date(INSTANCE.database.getTimestamp() * 1000)));
}
/**
* get location by IP
* @param address IP
* @return geolocation in vector
* @throws GeoIp2Exception
* @throws IOException
*/
public static Coordinates getCoordinates(InetAddress inetAddress)
throws GeoIPException {
Coordinates coordinates = INSTANCE.cache.get(inetAddress);
if (coordinates != null)
return coordinates;
byte[] address = inetAddress.getAddress();
byte subnet = 32;
while (coordinates == null) {
if (subnet == 0) {
INSTANCE.subLogger.info("Not found :(");
coordinates = new Coordinates(0, 0);
break;
}
int query = IPUtils.toInt(address);
coordinates = INSTANCE.database.entries.get(
query
);
INSTANCE.subLogger.info("trying %s/%d = %d", IPUtils.toString(address), subnet, query);
address = IPUtils.getSubnetStart(address, --subnet);
}
INSTANCE.subLogger.info("Done, caching");
INSTANCE.cache.put(inetAddress, coordinates);
return coordinates;
}
}

View file

@ -0,0 +1,26 @@
package pl.minecon724.realweather.map;
import org.bukkit.Location;
public class Globe {
private static double[] SCALE;
private static boolean WRAP;
public static void init(double scaleLatitude, double scaleLongitude, boolean wrap) {
SCALE = new double[] { scaleLatitude, scaleLongitude };
WRAP = wrap;
}
public static Coordinates toCoordiates(Location loc) {
Coordinates coordinates = new Coordinates(
-loc.getZ() * SCALE[0],
loc.getX() * SCALE[1]);
if (WRAP)
coordinates.wrap();
else
coordinates.clamp();
return coordinates;
}
}

View file

@ -0,0 +1,99 @@
package pl.minecon724.realweather.map;
import java.io.File;
import java.io.IOException;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import pl.minecon724.realweather.map.exceptions.GeoIPException;
public class WorldMap {
private static WorldMap INSTANCE;
private final Type type;
private Coordinates point;
public static WorldMap getInstance() {
if (INSTANCE == null)
throw new NullPointerException("No WorldMap");
return INSTANCE;
}
public WorldMap(Type type,
Coordinates point) {
this.type = type;
this.point = point;
}
public static void init(ConfigurationSection config, File dataFolder)
throws IOException {
Type type;
try {
type = Type.valueOf(config.getString("type").toUpperCase());
} catch (NullPointerException e) {
throw new IllegalArgumentException("Invalid type");
}
Coordinates point = null;
if (type == Type.POINT) {
point = new Coordinates(
config.getDouble("point.latitude"),
config.getDouble("point.longitude")
);
} else if (type == Type.PLAYER) {
GeoLocator.init(
dataFolder.toPath().resolve("geoip/ipv4.geo.gz").toFile(),
config.getString("player.download_url", "https://inferior.network/geoip/")
);
} else if (type == Type.GLOBE) {
Globe.init(
config.getDouble("globe.scale_latitude"),
config.getDouble("globe.scale_longitude"),
config.getBoolean("globe.wrap")
);
}
INSTANCE = new WorldMap(type, point);
}
/**
* get coordinates of player
* @param player the player
* @return Coordinates
* @throws GeoIPException
*/
public Coordinates getCoordinates(Player player) throws GeoIPException {
switch (this.type) {
case POINT:
return point;
case PLAYER:
if (player.getAddress().getAddress().isAnyLocalAddress())
throw new GeoIPException(player.getName() + "'s IP is local, check your proxy settings");
return GeoLocator.getCoordinates(
player.getAddress().getAddress()
);
case GLOBE:
return Globe.toCoordiates(player.getLocation());
}
// this wont happen because we cover each type
return null;
}
public Type getType() {
return this.type;
}
public static enum Type {
POINT, PLAYER, GLOBE
}
}

View file

@ -0,0 +1,7 @@
package pl.minecon724.realweather.map.exceptions;
public class GeoIPException extends Exception {
public GeoIPException(String message) {
super(message);
}
}

View file

@ -0,0 +1,75 @@
package pl.minecon724.realweather.realtime;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.map.exceptions.GeoIPException;
public class PlayerTimeSyncTask extends BukkitRunnable {
private WorldMap worldMap = WorldMap.getInstance();
private SubLogger subLogger = new SubLogger("playertime");
private double scale;
private List<World> worlds;
public PlayerTimeSyncTask(double scale, List<World> worlds) {
this.scale = scale;
this.worlds = worlds;
}
@Override
public void run() {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!worlds.contains(player.getWorld())) {
subLogger.info("resetting %s's time as they're in an excluded world", player.getName());
player.resetPlayerTime();
continue;
}
Coordinates coordinates;
try {
coordinates = worldMap.getCoordinates(player);
} catch (GeoIPException e) {
subLogger.info("Unable to determine GeoIP for %s (%s)",
player.getAddress().getHostString(), e.getMessage());
continue;
}
/*
* reasoning here:
* earth is divided into timezones each covering 15 deg longitude
* so first we find how many hours to offset
* a day is 24h (no way)
* in minecraft its 24000 ticks
* so, 1h in ticks: 24000t / 24h = 1000t
* seconds in day: 24h * 3600s = 86400s
* seconds to ticks: 86400s * 20t = 1728000t
* day irl is 1728000t, in minecraft its 24000t
* for each minecraft tick, ticks irl: 1728000t / 24000t = 72t
* we divide offset by that to sync time
* then multiply by scale, thats obvious
*/
double offset = coordinates.longitude / 15;
offset *= 1000;
offset *= scale;
// why no modulo? because we also modify day
long time = (long) offset;
player.setPlayerTime(time, true);
subLogger.info("%s's time is now off by %d ticks", player.getName(), time);
}
}
}

View file

@ -0,0 +1,92 @@
package pl.minecon724.realweather.realtime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.GameRule;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import pl.minecon724.realweather.RealWeatherPlugin;
import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException;
public class RealTimeCommander implements Listener {
private RealWeatherPlugin plugin;
private List<String> worldNames;
private double scale;
private ZoneId timezone;
private boolean perPlayer;
private volatile List<World> worlds = new ArrayList<>();
private Map<World, Boolean> savedGamerule = new HashMap<>();
private RealTimeTask task;
private PlayerTimeSyncTask playerTimeSyncTask;
public RealTimeCommander(RealWeatherPlugin plugin) {
this.plugin = plugin;
}
public void init(ConfigurationSection config)
throws ModuleDisabledException {
if (!config.getBoolean("enabled"))
throw new ModuleDisabledException();
try {
timezone = ZoneId.of(config.getString("timezone"));
} catch (Exception e) {
timezone = ZoneId.systemDefault();
}
worldNames = config.getStringList("worlds");
scale = config.getDouble("scale");
perPlayer = config.getBoolean("per_player");
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
public void start() {
// to save processing, run only when necessary
long period = (long) Math.ceil(72 / scale);
period = Math.max(period, 1);
task = new RealTimeTask(scale, timezone, worlds);
task.runTaskTimer(plugin, 0, period);
if (perPlayer) {
playerTimeSyncTask = new PlayerTimeSyncTask(scale, worlds);
playerTimeSyncTask.runTaskTimerAsynchronously(plugin, 0, 40);
}
}
@EventHandler
public void onWorldLoad(WorldLoadEvent event) {
World world = event.getWorld();
if (worldNames.contains(world.getName())) {
worlds.add(world);
savedGamerule.put(world, world.getGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE));
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
}
}
@EventHandler
public void onWorldUnload(WorldUnloadEvent event) {
World world = event.getWorld();
worlds.remove(world);
if (savedGamerule.containsKey(world)) {
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, savedGamerule.remove(world));
}
}
}

View file

@ -0,0 +1,43 @@
package pl.minecon724.realweather.realtime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitRunnable;
import pl.minecon724.realweather.SubLogger;
public class RealTimeTask extends BukkitRunnable {
private SubLogger subLogger = new SubLogger("timer");
double scale;
ZoneId timezone;
List<World> worlds;
public RealTimeTask(double scale, ZoneId timezone, List<World> worlds) {
this.scale = scale;
this.timezone = timezone;
this.worlds = worlds;
}
@Override
public void run() {
double now = ZonedDateTime.now(timezone).toInstant().getEpochSecond();
now /= 3600; // to hour
// explaination in PlayerTimeSyncTask line 47
now *= 1000; // reallife s to mc ticks
now -= 6000; // 0t is actually 6:00
now *= scale; // scale
now %= 24000;
long time = (long) now;
for (World w : worlds) {
w.setFullTime(time);
subLogger.info("Updated time for %s (to %d)", w.getName(), time);
}
}
}

View file

@ -0,0 +1,101 @@
package pl.minecon724.realweather.weather;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitRunnable;
import pl.minecon724.realweather.RealWeatherPlugin;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.map.WorldMap.Type;
import pl.minecon724.realweather.map.exceptions.GeoIPException;
import pl.minecon724.realweather.weather.WeatherState.State;
import pl.minecon724.realweather.weather.events.WeatherSyncEvent;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
import pl.minecon724.realweather.weather.provider.Provider;
public class GetStateTask extends BukkitRunnable {
private SubLogger subLogger = new SubLogger("weather updater");
private RealWeatherPlugin plugin;
private Provider provider;
private WorldMap worldMap;
private State storedState;
private Map<Player, State> playerStoredState = new HashMap<>();
private PluginManager pluginManager = Bukkit.getPluginManager();
public GetStateTask(
RealWeatherPlugin plugin,
Provider provider,
WorldMap worldMap
) {
this.plugin = plugin;
this.provider = provider;
this.worldMap = worldMap;
}
private void callEvent(Player player, State storedState, State state) {
new BukkitRunnable() {
@Override
public void run() {
pluginManager.callEvent(
new WeatherSyncEvent(player, storedState, state)
);
}
}.runTask(plugin);
}
@Override
public void run() {
try {
if (worldMap.getType() == Type.POINT) {
Coordinates coordinates;
try {
coordinates = worldMap.getCoordinates(null);
} catch (GeoIPException e) { return; }
State state = provider.request_state(coordinates);
if (!state.equals(storedState)) {
callEvent(null, storedState, state);
storedState = state;
}
} else {
for (Player player : Bukkit.getOnlinePlayers()) {
Coordinates coordinates;
try {
coordinates = worldMap.getCoordinates(player);
} catch (GeoIPException e) {
subLogger.info("GeoIP error for %s", player.getName());
e.printStackTrace();
continue;
}
State state = provider.request_state(coordinates);
if (!state.equals(playerStoredState.get(player))) {
callEvent(player,
playerStoredState.get(player),
state);
playerStoredState.put(player, state);
}
}
}
} catch (WeatherProviderException e) {
subLogger.info("Weather provider error");
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,32 @@
package pl.minecon724.realweather.weather;
import org.bukkit.configuration.ConfigurationSection;
import pl.minecon724.realweather.weather.provider.OpenWeatherMapProvider;
import pl.minecon724.realweather.weather.provider.Provider;
public class Providers {
/**
* get Provider by name
* @param name name of provider
* @param config configuration of provider
* @return subclass of Provider or null if invalid
* @throws ProviderException
* @see Provider
*/
public static Provider getByName(String name, ConfigurationSection config) {
switch (name) {
case "openweathermap":
return openWeatherMap(config);
}
return null;
}
public static OpenWeatherMapProvider openWeatherMap(ConfigurationSection config) {
return new OpenWeatherMapProvider(
config.getString("apiKey"));
}
}

View file

@ -0,0 +1,60 @@
package pl.minecon724.realweather.weather;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.weather.WeatherState.State;
import pl.minecon724.realweather.weather.events.WeatherSyncEvent;
public class WeatherChanger implements Listener {
private List<String> worldNames;
private List<World> worlds = new ArrayList<>();
private SubLogger subLogger = new SubLogger("weatherchanger");
public WeatherChanger(List<String> worldNames) {
this.worldNames = worldNames;
}
@EventHandler
public void onWorldLoad(WorldLoadEvent event) {
World world = event.getWorld();
subLogger.info("World %s has been loaded", world.getName());
if (worldNames.contains(world.getName())) {
worlds.add(world);
subLogger.info("World %s has been registered", world.getName());
}
}
@EventHandler
public void onWorldUnload(WorldUnloadEvent event) {
World world = event.getWorld();
worlds.remove(world);
subLogger.info("World %s unloaded", world.getName());
}
@EventHandler
public void onWeatherSync(WeatherSyncEvent event) {
Player player = event.getPlayer();
State state = event.getState();
if (player != null) {
subLogger.info("new weather for %s: %s %s", player.getName(), state.getCondition().name(), state.getLevel().name());
} else {
subLogger.info("new weather: %s %s", state.getCondition().name(), state.getLevel().name());
}
}
}

View file

@ -0,0 +1,79 @@
package pl.minecon724.realweather.weather;
import java.util.List;
import org.bukkit.configuration.ConfigurationSection;
import pl.minecon724.realweather.RealWeatherPlugin;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
import pl.minecon724.realweather.weather.provider.Provider;
public class WeatherCommander {
private WorldMap worldMap = WorldMap.getInstance();
private RealWeatherPlugin plugin;
private boolean enabled;
private List<String> worldNames;
private String providerName;
private Provider provider;
private ConfigurationSection providerConfig;
private GetStateTask getStateTask;
private SubLogger subLogger = new SubLogger("weather");
public WeatherCommander(RealWeatherPlugin plugin) {
this.plugin = plugin;
}
/**
* Initialize weather commander
* @param config "weather" ConfigurationSection
* @throws ModuleDisabledException if disabled in config
* @throws ProviderException if invalid provider config
*/
public void init(ConfigurationSection config)
throws ModuleDisabledException, IllegalArgumentException {
enabled = config.getBoolean("enabled");
if (!enabled)
throw new ModuleDisabledException();
worldNames = config.getStringList("worlds");
providerName = config.getString("provider.choice");
// this can be null
providerConfig = config.getConfigurationSection("provider")
.getConfigurationSection(providerName);
provider = Providers.getByName(providerName, providerConfig);
if (provider == null)
throw new IllegalArgumentException("Invalid provider: " + providerName);
provider.init();
try {
provider.request_state(new Coordinates(0, 0));
} catch (WeatherProviderException e) {
subLogger.severe("Provider test failed, errors may occur");
e.printStackTrace();
}
plugin.getServer().getPluginManager().registerEvents(
new WeatherChanger(worldNames), plugin);
subLogger.info("done");
}
public void start() {
getStateTask = new GetStateTask(plugin, provider, worldMap);
getStateTask.runTaskTimerAsynchronously(plugin, 0, 1200);
subLogger.info("started", new Object[0]);
}
}

View file

@ -0,0 +1,69 @@
package pl.minecon724.realweather.weather;
public class WeatherState {
// Enums
public enum Condition { THUNDER, DRIZZLE, RAIN, SNOW, CLEAR, CLOUDY };
public enum ConditionLevel { LIGHT, MODERATE, HEAVY, EXTREME };
public enum ConditionSimple { THUNDER, RAIN, CLEAR };
// State class
public static class State {
// Variables
private Condition condition;
private ConditionLevel level;
private ConditionSimple simple;
// Constructor
public State(Condition condition,
ConditionLevel level,
ConditionSimple simple) {
this.condition = condition;
this.level = level;
this.simple = simple;
}
public State(Condition condition,
ConditionLevel level) {
this.condition = condition;
this.level = level;
this.simple = null;
switch (condition) {
case THUNDER:
this.simple = ConditionSimple.THUNDER;
break;
case DRIZZLE:
case RAIN:
case SNOW:
this.simple = ConditionSimple.RAIN;
break;
case CLEAR:
case CLOUDY:
this.simple = ConditionSimple.CLEAR;
}
}
public Condition getCondition() {
return condition;
}
public ConditionLevel getLevel() {
return level;
}
public ConditionSimple getSimple() {
return simple;
}
public boolean equals(State state) {
return (state != null
&& this.getCondition() == state.getCondition()
&& this.getLevel() == state.getLevel());
}
}
}

View file

@ -0,0 +1,39 @@
package pl.minecon724.realweather.weather.events;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import pl.minecon724.realweather.weather.WeatherState.State;
public class WeatherSyncEvent extends PlayerEvent {
private static final HandlerList HANDLERS = new HandlerList();
private State oldState;
private State state;
public WeatherSyncEvent(Player player, State oldState, State state) {
super(player);
this.oldState = oldState;
this.state = state;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
public State getOldState() {
return oldState;
}
public State getState() {
return state;
}
}

View file

@ -0,0 +1,5 @@
package pl.minecon724.realweather.weather.exceptions;
public class ModuleDisabledException extends Exception {
}

View file

@ -0,0 +1,9 @@
package pl.minecon724.realweather.weather.exceptions;
public class WeatherProviderException extends Exception {
public WeatherProviderException(String message) {
super(message);
}
}

View file

@ -0,0 +1,176 @@
package pl.minecon724.realweather.weather.provider;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import com.google.common.base.Charsets;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.weather.WeatherState.*;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
public class OpenWeatherMapProvider implements Provider {
URL endpoint;
String apiKey;
public OpenWeatherMapProvider(String apiKey) {
this.apiKey = apiKey;
}
@Override
public String getName() {
return "OpenWeatherMap";
}
public void init() {
try {
endpoint = new URL("https://api.openweathermap.org");
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public State request_state(Coordinates coordinates) throws WeatherProviderException {
JsonObject jsonObject;
try {
URL url = new URL(
String.format("%s/data/2.5/weather?lat=%f&lon=%f&appid=%s",
endpoint, coordinates.latitude, coordinates.longitude, apiKey
));
InputStream is = url.openStream();
BufferedReader rd = new BufferedReader(
new InputStreamReader(is, Charsets.UTF_8));
JsonReader jsonReader = new JsonReader(rd);
jsonObject = new Gson().fromJson(jsonReader, JsonObject.class);
} catch (Exception e) {
e.printStackTrace();
throw new WeatherProviderException("Couldn't contact openweathermap");
}
int stateId;
try {
stateId = jsonObject.getAsJsonArray("weather")
.get(0).getAsJsonObject()
.get("id").getAsInt();
/*
* org.json comparison:
* stateId = json.getJSONArray("weather").getJSONObject(0).getInt("id");
* so is it truly worth it? yes see loading jsonobject from inputstream
*/
} catch (Exception e) {
e.printStackTrace();
throw new WeatherProviderException("Invalid data from openweathermap");
}
// Here comes the mess
Condition condition = Condition.CLEAR;
ConditionLevel level = ConditionLevel.LIGHT;
if (stateId < 300) {
condition = Condition.THUNDER;
switch (stateId % 10) {
case 0: // 200, 210, 230
level = ConditionLevel.LIGHT;
break;
case 1: // 201, 211, 221, 231
level = ConditionLevel.MODERATE;
break;
case 2: // 202, 212, 232
level = ConditionLevel.HEAVY;
}
} else if (stateId < 400) {
condition = Condition.DRIZZLE;
switch (stateId % 10) {
case 0: // 300, 310
level = ConditionLevel.LIGHT;
break;
case 1: // 301, 311, 321
case 3: // 313
level = ConditionLevel.MODERATE;
break;
case 2: // 302, 312
case 4: // 314
level = ConditionLevel.HEAVY;
}
} else if (stateId < 600) {
condition = Condition.RAIN;
switch (stateId % 10) {
case 0: // 500, 520
level = ConditionLevel.LIGHT;
break;
case 1: // 501, 511, 521, 531
level = ConditionLevel.MODERATE;
break;
case 2: // 502, 522
level = ConditionLevel.HEAVY;
break;
case 3: // 503
case 4: // 504
level = ConditionLevel.EXTREME;
}
} else if (stateId < 700) {
condition = Condition.SNOW;
switch (stateId) {
case 600:
case 612:
case 615:
case 620:
level = ConditionLevel.LIGHT;
break;
case 601:
case 611:
case 613:
case 616:
case 621:
level = ConditionLevel.MODERATE;
break;
case 602:
case 622:
level = ConditionLevel.HEAVY;
}
} else if (stateId > 800) {
condition = Condition.CLOUDY;
switch (stateId) {
case 801:
level = ConditionLevel.LIGHT;
break;
case 802:
level = ConditionLevel.MODERATE;
break;
case 803:
level = ConditionLevel.HEAVY;
break;
case 804:
level = ConditionLevel.EXTREME;
}
}
State state = new State(condition, level);
return state;
}
@Override
public State[] request_state(Coordinates[] coordinates) throws WeatherProviderException {
// OpenWeatherMap doesnt support bulk requests
int length = coordinates.length;
State[] states = new State[length];
for (int i=0; i<length; i++) {
states[i] = request_state(coordinates[i]);
}
return states;
}
}

View file

@ -0,0 +1,13 @@
package pl.minecon724.realweather.weather.provider;
import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.weather.WeatherState.State;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
public interface Provider {
public void init();
public String getName();
public State request_state(Coordinates coordinates) throws WeatherProviderException;
public State[] request_state(Coordinates[] coordinates) throws WeatherProviderException;
}

View file

@ -1,12 +1,65 @@
############################ weather:
### GENERAL SETTINGS ### enabled: true
############################
# Not much to be found here! Explore the other files. worlds:
- world
- second_world
- third_world
updater: provider:
# Notify players and console about plugin updates # Weather provider
# This also controls automatic checking choice: openweathermap
# You can still update with /rwadmin update # Configure it here
# Relevant permission node: realweather.update.notify openweathermap:
notify: true apiKey: 'd3d37fd3511ef1d4b44c7d574e9b56b8' # PLEASE get your own @ https://home.openweathermap.org/users/sign_up
# More providers soon!
map:
# "point" - static location
# "player" - player's IP location (fake weather)
# "globe" - world resembles a real-world globe
type: point
point:
latitude: 41.84201
longitude: -89.485937
player:
empty: for now
globe:
# Valid latitude range: -90 to 90
# Valid longitude range: -180 to 180
# 1 degree of latitude and longitude is about 111 km
# The defaults here assume 1 block = ~1 km
# 1 block = <scale> degrees
scale_latitude: 0.009
scale_longitude: 0.009
# What to do if player exceeds the range specified above
# false - do nothing (clamp to nearest allowed value)
# true - wrap the number
# for example; if a player's position on map converts to 94 degrees (out of bounds), it becomes -86 degrees
wrap: true
time:
# warning: this removes sleep
enabled: false
worlds:
- world
# "auto" to use server's timezone
# Alternatively choose one of these: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
# WARNING: it is purely cosmetical
timezone: 'auto'
# x day cycles / 24 hrs
# basically how many Minecraft days during a real day
scale: 1.0
# time based on... time?
# each player has time offset like timezones
# uses timezone as base, unless auto
# uses settings from map
per_player: false

View file

@ -1,23 +0,0 @@
############################
### MAP SETTINGS ###
############################
worlds:
- world
isBlacklist: false
dimensions:
# Blocks per 1 deg, must be integer
# latitude+ = Z-, longitude+ = X+ (to match with F3)
# The default (111,000) assumes 1 block = 1 meter
latitude: 111_000 # -9,990,000 to 9,990,000 Z. Wraps beyond that
longitude: 111_000 # -19,980,000 to 19,980,000 X
# To make it span world border to world border:
# latitude: 333_333
# longitude: 166_666
# For `static` mode
point:
latitude: 0
longitude: 0

View file

@ -1,12 +0,0 @@
############################
### THUNDER SETTINGS ###
############################
enabled: false
# Currently only blitzortung
provider: blitzortung
# No API key needed
# Lightning is DYNAMIC.
# Settings are in map.yml

View file

@ -1,20 +0,0 @@
############################
### TIME SETTINGS ###
############################
# Warning: this removes sleep. No, not a bug, as you can't skip time IRL.
enabled: false
# How time is applied:
# - static (false): time is the same across the world
# - dynamic (true): local time based on player's location. However, it's only cosmetic, so it will not match mobs spawning, etc.
# Settings for both are in map.yml
dynamic: true
# Real days per in-game day
# 2.0 - time goes 2x SLOWER
# 0.5 - time goes 2x FASTER
# If modified, time will no longer be in sync with real life. Unreal time.
# The division 72 / scale should equal an integer!
scale: 1.0

View file

@ -1,20 +0,0 @@
############################
### WEATHER SETTINGS ###
############################
# In Minecraft, it can only rain or not rain (or snow - but not both) and thunder or not thunder.
# In real life, rain, thunder, snow can be heavy, moderate, light and in between and can coexist. That's excluding many other conditions.
# For now, there's just rain and thunder
# This plugin will improve in the future, but there's other stuff to work on currently. I hope you understand.
enabled: false
# Currently only OpenMeteo
provider: openmeteo
# No API key needed
# How weather is applied:
# - static (false): weather is the same across the world
# - dynamic (true): weather is based on player's location. However, it's only cosmetic, so it will not match mobs spawning, etc.
# Settings for both are in map.yml
dynamic: true

View file

@ -1,64 +1,11 @@
name: RealWeather name: RealWeather
version: ${project.version}
author: Minecon724 author: Minecon724
website: https://www.spigotmc.org/resources/realweather-realtime.101599/
version: ${project.version}
api-version: 1.16 api-version: 1.16
load: STARTUP load: STARTUP
main: eu.m724.realweather.RealWeatherPlugin main: pl.minecon724.realweather.RealWeatherPlugin
libraries: libraries:
- org.java-websocket:Java-WebSocket:1.6.0 - com.google.code.gson:gson:2.10.1
- eu.m724:mstats-spigot:0.1.2
commands:
rwadmin:
description: RealWeather admin command
permission: realweather.admin
permission-message: You do not have permission to use this command.
geo:
description: Convert lat,lon <=> x,y,z
permission: realweather.command.geo
permission-message: You do not have permission to use this command.
localtime:
description: Get real time in current location
permission: realweather.command.localtime
permission-message: You do not have permission to use this command.
localweather:
description: Get weather in current location
permission: realweather.command.localweather
permission-message: You do not have permission to use this command.
permissions:
# Commands
realweather.admin:
description: Allows admin management with /rwadmin
realweather.admin.update:
description: Allows installing updates with /rwadmin update
realweather.command.geo:
description: Allows /geo
default: true
realweather.command.localtime:
description: Allows /localtime
default: true
realweather.command.localweather:
description: Allows /localweather
default: true
# Engine
realweather.dynamic:
description: Includes player in dynamic conditions
default: true
# Other
realweather.actionbar:
description: Displays status on player's action bar
realweather.update.notify:
description: Receive notifications for RealWeather updates

View file

@ -1,9 +0,0 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxjjjayrwlo3cnv+rX1EX
lJN9vHS9MNfvE7zFOHr2JEAx2fRosb2oRzNK0ssoHJFOgrwLWIqrLVS8bTHRujsF
asck2Z1RY5UGe34vNQ5u5MZvm4G25LggC6+ei2kEptoAfgp9kjmeKVPiSnruLn7N
YQc9U4nmr/vJg+SNmy00EkXFU5z3ZsLf8aCjx9rtogZzyZmVPXEDGY3ZjzZxOpv9
TAvSQlmrc6qmLlY7XZmJMtbzCTq+qqemZBKp6WpNmEogpPgXamOrET434+oE7OCz
+WCFKsVN8qbrQdFLf1HSjghvDoIjHcGfz6cP4nBonSKIfMcr+NziAVmimfqOiDxa
nwIDAQAB
-----END PUBLIC KEY-----

Binary file not shown.