Compare commits

..

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

85 changed files with 2333 additions and 1791 deletions

View file

@ -1,57 +0,0 @@
<?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

@ -0,0 +1,20 @@
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

View file

@ -1,25 +0,0 @@
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,7 +1,2 @@
target/ /target/
/.settings/
# IDE files
org.eclipse.*
.vscode/
.classpath
.project

View file

@ -1,10 +0,0 @@
# 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 vendored Normal file
View file

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

13
.idea/compiler.xml Normal file
View file

@ -0,0 +1,13 @@
<?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>

7
.idea/encodings.xml Normal file
View file

@ -0,0 +1,7 @@
<?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

@ -0,0 +1,8 @@
<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>

30
.idea/jarRepositories.xml Normal file
View file

@ -0,0 +1,30 @@
<?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 Normal file
View file

@ -0,0 +1,12 @@
<?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 Normal file
View file

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

124
.idea/uiDesigner.xml Normal file
View file

@ -0,0 +1,124 @@
<?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 Normal file
View file

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

View file

@ -1,34 +0,0 @@
<?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>

11
DOMAINS-FIREWALL.md Normal file
View file

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

View file

@ -615,61 +615,3 @@ 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,2 +1,15 @@
# Moved # realweather
v1 is here: https://git.724.rocks/Minecon724/realweather
For MC 1.19.4+ 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
View file

@ -1,20 +0,0 @@
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

@ -1,63 +0,0 @@
<?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>

73
notes.txt Normal file
View file

@ -0,0 +1,73 @@
goal: realtime to in game time conversion
There is no need to keep days
minecraft day is 0 - 24000 ticks
where 6000 ticks is noon (peak sun) and 18000 is midnight (peak moon)
irl day is 0 - 86400 seconds
0. s = epoch % 86400 to get seconds since midnight
^
(* scale) here
1. t = s / 72.0 to fit into minecraft day
2. t = t * 20 to convert that to ticks
3. t = t - 6000 to align noon and midnight
this leaves us with negative time, so
4. t = floorMod(t, 24000) to wrap if negative
example:
epoch = 1713593340
0. getting seconds since midnight
s = epoch % 86400
s = 1713593340 % 86400
s = 22140
1. conversion to minecraft day length
gs = s / 72.0
gs = 22140 / 72.0
gs = 307.5
2. to ticks
t = gs * 20
t = 307.5 * 20
t = 6150
3. step 3
t = t - 6000
t = 6150 - 6000
t = 150
4. wrapping
t = floorMod(150, 24000)
t = 150
goal: frequency of time update
t = 72 / scale
t is the period, in ticks of course
to see how many irl seconds a tick represents:
s = 3.6 * scale
(from 1 / (1/72 * 20 * scale))
however, some scales result in fractions
here's how many in game aligned seconds have passed at the end of a real day:
84000 / 72 * scale * floor(72/scale)
for scale 0.99: 83160
so we'll be 14 minutes behind
solution? for now let's warn and update time every tick
to check:
scale * floor(72/scale) == 72
goal: offsetting by player position
t = (longitude / 15) * 1000 * scale
accounting for sunrise and sunset
TODO, idk yet without
update: this is now possible with 0.8.0 api

126
pom.xml
View file

@ -1,12 +1,17 @@
<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>pl.minecon724</groupId> <groupId>eu.m724</groupId>
<artifactId>realweather</artifactId> <artifactId>realweather</artifactId>
<version>0.5.1-DEV</version> <version>1.0.0-alpha-6-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<jarsigner.keystore>${project.basedir}/testkeystore.jks</jarsigner.keystore>
<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> </properties>
<repositories> <repositories>
@ -14,20 +19,39 @@
<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>
<id>m724</id>
<url>https://git.m724.eu/api/packages/Minecon724/maven</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version> <version>1.19.4-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
<!-- Fix warning about vulnerabilities of things we don't use -->
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>eu.m724</groupId>
<artifactId>gson</artifactId> <artifactId>wtapi</artifactId>
<version>2.10.1</version> <version>0.8.3</version>
<scope>provided</scope> </dependency>
<dependency>
<groupId>eu.m724</groupId>
<artifactId>jarupdater</artifactId>
<version>0.1.8</version>
</dependency> </dependency>
</dependencies> </dependencies>
@ -38,5 +62,89 @@
<filtering>true</filtering> <filtering>true</filtering>
</resource> </resource>
</resources> </resources>
<plugins> <!-- versions: https://maven.apache.org/plugins/ -->
<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> </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>

18
realweather.iml Normal file
View file

@ -0,0 +1,18 @@
<?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

@ -0,0 +1,22 @@
package eu.m724.realweather;
import eu.m724.realweather.mapper.MapperConfig;
import eu.m724.realweather.thunder.ThunderConfig;
import eu.m724.realweather.time.TimeConfig;
import eu.m724.realweather.updater.UpdaterConfig;
import eu.m724.realweather.weather.WeatherConfig;
// TODO replaces GlobalConstants for configs
public class Configs {
static WeatherConfig weatherConfig;
static TimeConfig timeConfig;
static ThunderConfig thunderConfig;
static MapperConfig mapperConfig;
static UpdaterConfig updaterConfig;
public static WeatherConfig weatherConfig() { return weatherConfig; }
public static TimeConfig timeConfig() { return timeConfig; }
public static ThunderConfig thunderConfig() { return thunderConfig; }
public static MapperConfig mapperConfig() { return mapperConfig; }
public static UpdaterConfig updaterConfig() { return updaterConfig; }
}

View file

@ -0,0 +1,18 @@
package eu.m724.realweather;
import java.util.logging.Logger;
public class DebugLogger {
static Logger baseLogger;
static int debugLevel;
public static int getDebugLevel() {
return debugLevel;
}
public static void info(String message, int minDebugLevel, Object... format) {
if (debugLevel >= minDebugLevel)
baseLogger.info(message.formatted(format));
}
}

View file

@ -0,0 +1,25 @@
package eu.m724.realweather;
import org.bukkit.plugin.Plugin;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.realweather.weather.PlayerWeatherCache;
// perhaps replace with a singleton
// TODO actually, remove it altogether
public class GlobalConstants {
static Mapper mapper;
static Plugin plugin;
static PlayerWeatherCache playerWeatherCache;
public static Mapper getMapper() {
return mapper;
}
public static Plugin getPlugin() {
return plugin;
}
public static PlayerWeatherCache getPlayerWeatherCache() {
return playerWeatherCache;
}
}

View file

@ -0,0 +1,169 @@
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.Logger;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import eu.m724.realweather.commands.AdminCommand;
import eu.m724.realweather.commands.GeoCommand;
import eu.m724.realweather.commands.LocalTimeCommand;
import eu.m724.realweather.commands.LocalWeatherCommand;
import eu.m724.realweather.exception.UserError;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.realweather.mapper.MapperConfig;
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.updater.UpdaterConfig;
import eu.m724.realweather.weather.PlayerWeatherCache;
import eu.m724.realweather.weather.WeatherConfig;
import eu.m724.realweather.weather.WeatherMaster;
import eu.m724.wtapi.provider.exception.NoSuchProviderException;
import eu.m724.wtapi.provider.exception.ProviderException;
// TODO unmess this too
public class RealWeatherPlugin extends JavaPlugin {
private WeatherMaster weatherMaster;
private ThunderMaster thunderMaster;
private TimeMaster timeMaster;
private PluginUpdater updater;
private Logger logger;
@Override
public void onEnable() {
logger = getLogger();
File dataFolder = getDataFolder();
File modulesFolder = new File("modules");
modulesFolder.mkdir();
YamlConfiguration configuration,
mapConfiguration, weatherConfiguration,
thunderConfiguration, timeConfiguration;
DebugLogger.info("loading configurations", 1);
boolean firstRun = !new File(dataFolder, "config.yml").exists();
try {
configuration = getConfig("config.yml");
mapConfiguration = getConfig("map.yml");
weatherConfiguration = getConfig("modules/weather.yml");
thunderConfiguration = getConfig("modules/thunder.yml");
timeConfiguration = getConfig("modules/time.yml");
} catch (IOException e) {
logger.severe("Failed to load config!");
throw new RuntimeException(e);
}
if (firstRun) {
logger.warning("This is your first time running this plugin.");
logger.warning("Please *shut down* the server, review the config files (enable modules, enter your API keys, etc.)");
logger.warning("Don't forget to enable the plugin in config.yml");
getServer().getPluginManager().disablePlugin(this);
return;
}
DebugLogger.baseLogger = logger;
DebugLogger.debugLevel = configuration.getInt("debug");
if (!configuration.getBoolean("enabled")) {
logger.warning("Plugin disabled by administrator. Enable it in config.yml");
getServer().getPluginManager().disablePlugin(this);
return;
}
GlobalConstants.plugin = this;
GlobalConstants.playerWeatherCache = new PlayerWeatherCache();
DebugLogger.info("loading mapper", 1);
Configs.mapperConfig = MapperConfig.fromConfiguration(mapConfiguration);
GlobalConstants.mapper = new Mapper();
GlobalConstants.mapper.registerEvents(this);
try {
DebugLogger.info("loading weather", 1);
Configs.weatherConfig = WeatherConfig.fromConfiguration(weatherConfiguration);
weatherMaster = new WeatherMaster();
weatherMaster.init(this);
DebugLogger.info("loading thunder", 1);
Configs.thunderConfig = ThunderConfig.fromConfiguration(thunderConfiguration);
thunderMaster = new ThunderMaster();
thunderMaster.init(this);
DebugLogger.info("loading time", 1);
Configs.timeConfig = TimeConfig.fromConfiguration(timeConfiguration);
timeMaster = new TimeMaster();
timeMaster.init();
Configs.updaterConfig = UpdaterConfig.fromConfiguration(configuration.getConfigurationSection("updater"));
updater = PluginUpdater.build(this, this.getFile());
//updater.init();
} catch (UserError | NoSuchProviderException e) {
logger.severe("There are errors in your config:");
logger.severe(e.getMessage());
getServer().getPluginManager().disablePlugin(this);
return;
} catch (ProviderException e) {
logger.severe("Couldn't initialize provider!");
logger.severe("Possible causes:");
logger.severe("1. Your API key is invalid, or was added too recently");
logger.severe("2. The provider or your internet is down");
e.printStackTrace();
getServer().getPluginManager().disablePlugin(this);
return;
}
getCommand("rwadmin").setExecutor(new AdminCommand(updater, thunderMaster));
getCommand("geo").setExecutor(new GeoCommand());
if (Configs.timeConfig.enabled())
getCommand("localtime").setExecutor(new LocalTimeCommand(timeMaster.getTimeConverter()));
if (Configs.weatherConfig.enabled()) {
getCommand("localweather").setExecutor(new LocalWeatherCommand());
}
/*Metrics metrics = new Metrics(this, 15020);
metrics.addCustomChart(new SimplePie("weather_provider", () ->
GlobalConstants.weatherConfig.enabled ? GlobalConstants.weatherConfig.provider : "off"
));
metrics.addCustomChart(new SimplePie("thunder_provider", () ->
GlobalConstants.thunderConfig.enabled ? GlobalConstants.thunderConfig.provider : "off"
));
metrics.addCustomChart(new SimplePie("real_time", () ->
GlobalConstants.timeConfig.enabled() ? "on" : "off"
));*/
DebugLogger.info("ended loading", 1);
}
public YamlConfiguration getConfig(String configFilePath) throws IOException {
File configFile = new File(this.getDataFolder(), configFilePath);
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
if (!configFile.exists()) {
final InputStream defConfigStream = getResource(configFilePath);
if (defConfigStream == null)
return null;
config = YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream, StandardCharsets.UTF_8));
config.save(configFile);
}
return config;
}
}

View file

@ -0,0 +1,65 @@
package eu.m724.realweather.api.weather;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import eu.m724.wtapi.object.Weather;
/**
* Fired when a weather state is retrieved<br>
* It doesn't mean the weather has changed, just that we retrieved the state<br>
*/
public class AsyncWeatherUpdateEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player;
private final Weather weather;
private boolean cancelled;
public AsyncWeatherUpdateEvent(Player player, Weather weather) {
super(true);
this.player = player;
this.weather = weather;
}
/**
* @return a player that the weather is for, null if worldwide (static mode)
*/
public Player getPlayer() {
return player;
}
/**
* @return the weather state that was just changed
*/
public Weather getWeather() {
return weather;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Cancel weather change<br>
* It will only cancel changing the actual weather by the plugin, not retrieving and caching it
* @param cancelled to cancel or not
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View file

@ -0,0 +1,73 @@
package eu.m724.realweather.commands;
import eu.m724.realweather.Configs;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.MapperConfig;
import eu.m724.realweather.thunder.ThunderConfig;
import eu.m724.realweather.thunder.ThunderMaster;
import eu.m724.realweather.time.TimeConfig;
import eu.m724.realweather.updater.PluginUpdater;
import eu.m724.realweather.weather.WeatherConfig;
import net.md_5.bungee.api.ChatColor;
// TODO unmess this all
public class AdminCommand implements CommandExecutor {
private final UpdateCommand updateCommand;
private final Plugin plugin = GlobalConstants.getPlugin();
private final WeatherConfig weatherConfig = Configs.weatherConfig();
private final TimeConfig timeConfig = Configs.timeConfig();
private final ThunderConfig thunderConfig = Configs.thunderConfig();
private final MapperConfig mapperConfig = Configs.mapperConfig();
private final ThunderMaster thunderMaster;
public AdminCommand(PluginUpdater updater, ThunderMaster thunderMaster) {
this.updateCommand = new UpdateCommand(updater);
this.thunderMaster = thunderMaster;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length > 0 && args[0].equals("update")) {
return updateCommand.onCommand(sender, command, label, args);
}
colorize(sender, "\n&eRealWeather %s\n\n", plugin.getDescription().getVersion().replace("-SNAPSHOT", "&c-SNAPSHOT"));
colorize(sender, "&6Coordinate scale: &b%d, %d &7blocks / deg", mapperConfig.scaleLatitude, mapperConfig.scaleLongitude);
colorize(sender, "\n&6Weather: %s", weatherConfig.enabled() ? (weatherConfig.dynamic() ? "&aYes, dynamic" : "&aYes, static") : "&cDisabled");
if (weatherConfig.enabled()) {
colorize(sender, " &6Provider: &b%s", weatherConfig.provider());
colorize(sender, " &6/localweather to see current weather");
}
colorize(sender, "\n&6Time: %s", timeConfig.enabled() ? (timeConfig.dynamic() ? "&aYes, dynamic" : "&aYes, static") : "&cDisabled");
if (timeConfig.enabled()) {
colorize(sender, " &6Scale: &b%s&7x", timeConfig.scale());
colorize(sender, " &6/localtime to see current time");
}
colorize(sender, "\n&6Thunder: %s", thunderConfig.enabled() ? "&aYes, dynamic" : "&cDisabled");
if (thunderConfig.enabled()) {
colorize(sender, " &6Provider: &b%s", thunderConfig.provider());
colorize(sender, " &6Refresh period: &b%d &7ticks", thunderConfig.refreshPeriod());
colorize(sender, " &6API latency: &b%d&7ms", thunderMaster.getLatency());
}
return true;
}
private void colorize(CommandSender sender, String text, Object... format) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', text.formatted(format)));
}
}

View file

@ -0,0 +1,106 @@
package eu.m724.realweather.commands;
import net.md_5.bungee.api.chat.*;
import net.md_5.bungee.api.chat.hover.content.Content;
import net.md_5.bungee.api.chat.hover.content.Text;
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.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.realweather.weather.PlayerWeatherCache;
import eu.m724.wtapi.object.Coordinates;
import eu.m724.wtapi.object.Weather;
import net.md_5.bungee.api.ChatColor;
public class GeoCommand implements CommandExecutor {
private final PlayerWeatherCache playerWeatherCache = GlobalConstants.getPlayerWeatherCache();
private final Mapper mapper = GlobalConstants.getMapper();
@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 = mapper.locationToCoordinates(location);
Weather weather = playerWeatherCache.getWeather(player);
String address = formatAddress(weather);
colorize(player, "");
colorize(player, "&6Geolocation: &b%f&7, %b%f &7(lat, lon)", coordinates.latitude, coordinates.longitude);
colorize(player, "&7Position: &3%f&8, %3%f &8(x, z)", location.getX(), location.getZ());
colorize(player, "&7City: &3%s", address);
colorize(player, "");
}
} 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 = mapper.locationToCoordinates(location);
colorize(sender, "");
colorize(sender, "&6Position: &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 position, because you separated with a space");
colorize(sender, "");
return true;
} 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 = mapper.coordinatesToLocation(null, coordinates);
colorize(sender, "");
colorize(sender, "&6Position: &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, because you separated with a comma");
colorize(sender, "");
}
return true;
}
private void colorize(CommandSender sender, String text, Object... format) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', text.formatted(format)));
}
private String formatAddress(Weather weather) {
if (weather == null) return "Not retrieved yet";
Coordinates coordinates = weather.coordinates;
if (coordinates.country == null && coordinates.city == null)
return "Unknown";
else if (coordinates.city == null)
return "Somewhere in " + coordinates.country;
else if (coordinates.country == null)
return coordinates.city;
return coordinates.city + ", " + coordinates.country;
}
}

View file

@ -0,0 +1,81 @@
package eu.m724.realweather.commands;
import java.time.Duration;
import eu.m724.realweather.Configs;
import eu.m724.realweather.time.TimeConverter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.realweather.time.TimeConfig;
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 Mapper mapper = GlobalConstants.getMapper();
private final TimeConfig timeConfig = Configs.timeConfig();
private final TimeConverter timeConverter;
public LocalTimeCommand(TimeConverter timeConverter) {
this.timeConverter = timeConverter;
}
@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 (timeConfig.dynamic() && player != null && player.hasPermission("realweather.dynamic")) {
Coordinates coordinates = mapper.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

@ -0,0 +1,67 @@
package eu.m724.realweather.commands;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
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.realweather.GlobalConstants;
import eu.m724.realweather.weather.PlayerWeatherCache;
import eu.m724.wtapi.object.Weather;
public class LocalWeatherCommand implements CommandExecutor {
private final PlayerWeatherCache playerWeatherCache = GlobalConstants.getPlayerWeatherCache();
@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 = playerWeatherCache.getWeather(player);
if (weather != null) {
long lastUpdate = playerWeatherCache.getLastUpdate(player);
colorize(sender, "\n&e" + weather.description);
if (weather.rainSeverity != null)
colorize(sender, "&6Rain: &b%s", weather.rainSeverity.toString());
if (weather.drizzleSeverity != null)
colorize(sender, "&6Drizzle: &b%s", weather.drizzleSeverity.toString());
if (weather.sleetSeverity != null)
colorize(sender, "&6Sleet: &b%s", weather.sleetSeverity.toString());
if (weather.snowSeverity != null)
colorize(sender, "&6Snow: &b%s", weather.snowSeverity.toString());
if (weather.thunderstormSeverity != null)
colorize(sender, "&6Thunderstorm: &b%s", weather.thunderstormSeverity.toString());
if (weather.shower)
colorize(sender, "&6Shower");
colorize(sender, "&6Cloudiness: &b%f&7%%", weather.cloudiness * 100);
colorize(sender, "&6Humidity: &b%f&7%%", weather.humidity * 100);
colorize(sender, "&6Temperature: &b%f&7°C (feels like %f°C)", weather.temperature - 273.15, weather.temperatureApparent - 273.15);
colorize(sender, "&6Wind: &b%f&7m/s (gust %fm/s)", weather.windSpeed, weather.windGust);
colorize(sender, "&6Last update: &b%s UTC\n", formatTime(lastUpdate));
} else {
colorize(sender, "&6No weather for you, try again in a second");
}
return true;
}
private void colorize(CommandSender sender, String text, Object... format) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', text.formatted(format)));
}
private String formatTime(long timestamp) {
return DateTimeFormatter.ofPattern("HH:mm:ss").format(Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.UTC));
}
}

View file

@ -0,0 +1,77 @@
package eu.m724.realweather.commands;
import java.nio.file.NoSuchFileException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import eu.m724.jarupdater.updater.Updater;
import eu.m724.jarupdater.object.Version;
/**
* not actually a command but deserves a separate file
*/
public class UpdateCommand {
private final Updater updater;
public UpdateCommand(Updater updater) {
this.updater = updater;
}
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("realweather.admin.update")) return false;
sender.sendMessage("Please wait");
CompletableFuture<Version> latestFuture = updater.getLatestVersion();
if (args.length == 0) {
latestFuture.thenAccept(metadata -> {
if (metadata != null) {
sender.sendMessage("An update is available!");
sender.sendMessage("RealWeather %s released %s".formatted(metadata.getLabel(), formatDate(metadata.getTimestamp())));
sender.sendMessage("To download: /rwadmin update download");
} else {
sender.sendMessage("No new updates"); // TODO color
}
});
} else {
String action = args[1]; // remember this function is proxied
if (action.equals("download")) {
sender.sendMessage("Started download");
updater.downloadLatestVersion().handle((file, ex) -> {
sender.sendMessage("Download failed. See console for details.");
ex.printStackTrace();
return null;
}).thenAccept(file -> {
if (file != null)
sender.sendMessage("Download finished, install with /rwadmin update install");
});
} else if (action.equals("install")) {
try {
updater.installLatestVersion().handle((v, ex) -> {
sender.sendMessage("Install failed. See console for details.");
ex.printStackTrace();
return null;
}).thenAccept(v -> {
sender.sendMessage("Installation completed, restart server to apply");
});
} catch (NoSuchFileException e) {
sender.sendMessage("Download the update first");
}
} else return false;
}
return true;
}
private String formatDate(long timestamp) { // TODO move this
return DateTimeFormatter.ofPattern("dd.MM.yyyy").format(Instant.ofEpochSecond(timestamp));
}
}

View file

@ -0,0 +1,11 @@
package eu.m724.realweather.exception;
public class UserError extends Error {
private static final long serialVersionUID = 7152429719832602384L;
public UserError(String message) {
super(message);
}
}

View file

@ -0,0 +1,103 @@
package eu.m724.realweather.mapper;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import eu.m724.realweather.Configs;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import eu.m724.wtapi.object.Coordinates;
public class Mapper {
private final MapperConfig config = Configs.mapperConfig();
private final List<World> worlds = new ArrayList<>();
private final List<Consumer<World>> worldLoadConsumers = new ArrayList<>();
private final List<Consumer<World>> worldUnloadConsumers = new ArrayList<>();
// TODO game rules, I think I meant handling by this class
/**
* Registers a consumer which will be called on world load
* @param consumer the consumer which will be called on world load
*/
public void registerWorldLoadConsumer(Consumer<World> consumer) {
this.worldLoadConsumers.add(consumer);
}
/**
* Registers a consumer which will be called on world unload
* @param consumer the consumer which will be called on world unload
*/
public void registerWorldUnloadConsumer(Consumer<World> consumer) {
this.worldUnloadConsumers.add(consumer);
}
/**
* Registers events handled by mapper.<br>
* This should be called once on plugin load.
* @param plugin the plugin to register events under
*/
public void registerEvents(Plugin plugin) {
plugin.getServer().getPluginManager().registerEvents(new MapperEventHandler(this), plugin);
}
/**
* 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() / config.scaleLatitude) % 90;
// here it's <-180, 180) so it's correct, and we don't need excuses
double longitude = (location.getX() / config.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 * config.scaleLongitude;
double z = -coordinates.latitude * config.scaleLatitude;
return new Location(world, x, 0, z);
}
public Coordinates getPoint() {
return config.point;
}
public List<World> getWorlds() {
return this.worlds;
}
boolean loadWorld(World world) {
boolean loaded = config.worlds.contains(world.getName()) ^ config.worldBlacklist;
if (loaded) {
worlds.add(world);
worldLoadConsumers.forEach(consumer -> consumer.accept(world));
}
return loaded;
}
void unloadWorld(World world) {
if (worlds.remove(world)) {
worldUnloadConsumers.forEach(consumer -> consumer.accept(world));
}
}
}

View file

@ -0,0 +1,33 @@
package eu.m724.realweather.mapper;
import java.util.List;
import org.bukkit.configuration.ConfigurationSection;
import eu.m724.wtapi.object.Coordinates;
public class MapperConfig {
public boolean worldBlacklist;
public List<String> worlds;
public int scaleLatitude;
public int scaleLongitude;
public Coordinates point;
public static MapperConfig fromConfiguration(ConfigurationSection configuration) {
MapperConfig mapperConfig = new MapperConfig();
mapperConfig.worldBlacklist = configuration.getBoolean("worldBlacklist");
mapperConfig.worlds = configuration.getStringList("worlds");
mapperConfig.scaleLatitude = configuration.getInt("dimensions.latitude");
mapperConfig.scaleLongitude = configuration.getInt("dimensions.longitude");
mapperConfig.point = new Coordinates(
configuration.getDouble("point.latitude"),
configuration.getDouble("point.longitude"));
return mapperConfig;
}
}

View file

@ -0,0 +1,24 @@
package eu.m724.realweather.mapper;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
public class MapperEventHandler implements Listener {
private final Mapper mapper;
public MapperEventHandler(Mapper mapper) {
this.mapper = mapper;
}
@EventHandler
public void onWorldLoad(WorldLoadEvent e) {
mapper.loadWorld(e.getWorld());
}
@EventHandler
public void onWorldUnload(WorldUnloadEvent e) {
mapper.unloadWorld(e.getWorld());
}
}

View file

@ -0,0 +1,23 @@
package eu.m724.realweather.thunder;
import org.bukkit.configuration.ConfigurationSection;
/**
*
* @param enabled is thunder module enabled
* @param provider The provider name, may or may not exist, if it doesn't, an error is thrown later
* @param refreshPeriod how often probe for strikes, in ticks
*/
public record ThunderConfig(
boolean enabled,
String provider,
int refreshPeriod
) {
public static ThunderConfig fromConfiguration(ConfigurationSection configuration) {
return new ThunderConfig(
configuration.getBoolean("enabled"),
configuration.getString("provider"),
configuration.getInt("refreshPeriod")
);
}
}

View file

@ -0,0 +1,38 @@
package eu.m724.realweather.thunder;
import eu.m724.realweather.Configs;
import org.bukkit.plugin.Plugin;
import eu.m724.realweather.DebugLogger;
import eu.m724.wtapi.provider.Providers;
import eu.m724.wtapi.provider.exception.NoSuchProviderException;
import eu.m724.wtapi.provider.exception.ProviderException;
import eu.m724.wtapi.provider.thunder.ThunderProvider;
public class ThunderMaster {
private final ThunderConfig config = Configs.thunderConfig();
private ThunderProvider provider;
/**
* initializes, tests and starts
* @throws ProviderException if provider initialization failed
* @throws NoSuchProviderException config issue
*/
public void init(Plugin plugin) throws ProviderException, NoSuchProviderException {
if (!config.enabled())
return;
provider = Providers.getThunderProvider(config.provider(), null);
provider.init();
ThunderTask thunderTask = new ThunderTask(provider);
thunderTask.init();
thunderTask.runTaskTimer(plugin, 0, config.refreshPeriod());
DebugLogger.info("thunder loaded", 1);
}
public long getLatency() {
return provider.getLatency();
}
}

View file

@ -0,0 +1,57 @@
package eu.m724.realweather.thunder;
import java.util.ArrayList;
import org.bukkit.Location;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.wtapi.provider.thunder.ThunderProvider;
import eu.m724.wtapi.provider.thunder.impl.blitzortung.TimedStrike;
class ThunderTask extends BukkitRunnable {
private final ThunderProvider thunderProvider;
private final Mapper mapper = GlobalConstants.getMapper();
private final ArrayList<TimedStrike> strikes = new ArrayList<>();
public ThunderTask(ThunderProvider thunderProvider) {
this.thunderProvider = thunderProvider;
}
public void init() {
thunderProvider.registerStrikeHandler(coords -> {
strikes.add(new TimedStrike(System.currentTimeMillis() + thunderProvider.getDelay(), coords));
});
thunderProvider.start();
DebugLogger.info("thunderprovider started", 3);
}
@Override
public void run() {
DebugLogger.info("thundertask running", 3);
thunderProvider.tick();
while (!strikes.isEmpty()) {
TimedStrike strike = strikes.removeFirst();
DebugLogger.info("strike: %f %f", 2, strike.coordinates.latitude, strike.coordinates.longitude);
mapper.getWorlds().forEach(w -> {
Location location = mapper.coordinatesToLocation(w, strike.coordinates);
DebugLogger.info("in %s that converts to: %d %d", 2, 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.info("spawned lightning in %s on y level %d", 2, w.getName(), location.getBlockY());
}
});
}
}
}

View file

@ -0,0 +1,37 @@
package eu.m724.realweather.time;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.wtapi.object.Coordinates;
public class AsyncPlayerTimeTask extends BukkitRunnable {
private final Server server = GlobalConstants.getPlugin().getServer();
private final Mapper mapper = GlobalConstants.getMapper();
private final TimeConverter timeConverter;
AsyncPlayerTimeTask(TimeConverter timeConverter) {
this.timeConverter = timeConverter;
}
@Override
public void run() {
for (Player player : server.getOnlinePlayers()) {
if (!player.hasPermission("realweather.dynamic")) continue;
if (!mapper.getWorlds().contains(player.getWorld())) continue;
Coordinates coordinates = mapper.locationToCoordinates(player.getLocation());
long time = timeConverter.calculateZoneOffset(coordinates.longitude);
long ticks = timeConverter.millisToTicks(time);
player.setPlayerTime(ticks, true);
DebugLogger.info("Time for %s: %d", 2, player.getName(), time);
}
}
}

View file

@ -0,0 +1,36 @@
package eu.m724.realweather.time;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
/**
* This does world time, player time is basically offset of this, like timezone +0
*/
public class SyncTimeUpdateTask extends BukkitRunnable {
private final Mapper mapper = GlobalConstants.getMapper();
private final TimeConverter timeConverter;
private final long zoneOffset;
SyncTimeUpdateTask(TimeConverter timeConverter, boolean dynamic) {
this.timeConverter = timeConverter;
this.zoneOffset = !dynamic ? timeConverter.calculateZoneOffset(mapper.getPoint().longitude) : 0;
}
@Override
public void run() {
long time = System.currentTimeMillis();
time = timeConverter.scale(time);
long ticks = timeConverter.millisToTicks(time + zoneOffset);
DebugLogger.info("Updating time: %d", 2, ticks);
mapper.getWorlds().forEach(world -> world.setFullTime(ticks));
// TODO add world handlers to mapper and don't calculate time each run
}
}

View file

@ -0,0 +1,23 @@
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

@ -0,0 +1,51 @@
package eu.m724.realweather.time;
public class TimeConverter {
public final double scale;
public TimeConverter(double scale) {
this.scale = scale;
}
/**
* Divides time by predefined scale<br>
* ...slowing it down
*
* @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);
}
}

View file

@ -0,0 +1,45 @@
package eu.m724.realweather.time;
import eu.m724.realweather.Configs;
import org.bukkit.GameRule;
import org.bukkit.plugin.Plugin;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
public class TimeMaster {
private final Mapper mapper = GlobalConstants.getMapper();
private final Plugin plugin = GlobalConstants.getPlugin();
private final TimeConfig timeConfig = Configs.timeConfig();
// TODO I don't want to initialize this here
private final TimeConverter timeConverter = new TimeConverter(timeConfig.scale());
// TODO this is only used once
public TimeConverter getTimeConverter() {
return timeConverter;
}
public void init() {
if (!timeConfig.enabled())
return;
long period = timeConverter.calculateUpdatePeriod();
if (timeConfig.scale() * period != 72.0) {
// TODO log this properly
DebugLogger.info("Warning: RealTime scale is not optimal. Time will be out of sync.", 0);
}
new SyncTimeUpdateTask(timeConverter, timeConfig.dynamic()).runTaskTimer(plugin, 0, period);
if (timeConfig.dynamic())
new AsyncPlayerTimeTask(timeConverter).runTaskTimerAsynchronously(plugin, 0, 60); // 5 seconds
mapper.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false));
mapper.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, true));
DebugLogger.info("time loaded, update every %d ticks", 1, period);
}
}

View file

@ -0,0 +1,58 @@
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.realweather.Configs;
import org.bukkit.plugin.Plugin;
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 java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class PluginUpdater extends Updater {
private final UpdaterConfig updaterConfig = Configs.updaterConfig();
final Plugin plugin;
PluginUpdater(Plugin plugin, Environment environment, MetadataFacade metadataProvider, Downloader downloader, Verifier verifier) {
super(environment, metadataProvider, downloader, verifier);
this.plugin = plugin;
}
public static PluginUpdater build(Plugin plugin, File file) {
Environment environment = new ConstantEnvironment(
plugin.getDescription().getVersion(),
Configs.updaterConfig().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(plugin, environment, metadataFacade, downloader, verifier);
}
public void init() {
if (!updaterConfig.alert()) return;
UpdateNotifier updateNotifier = new UpdateNotifier(this, (version) -> {});
updateNotifier.register();
}
}

View file

@ -0,0 +1,68 @@
package eu.m724.realweather.updater;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
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 PluginUpdater updater;
private final Consumer<Version> updateConsumer;
private final Plugin plugin;
private Version latestVersion;
public UpdateNotifier(PluginUpdater updater, Consumer<Version> updateConsumer) {
this.updater = updater;
this.updateConsumer = updateConsumer;
this.plugin = updater.plugin;
}
public void register() {
this.runTaskTimerAsynchronously(updater.plugin, 0, 432000); // 6h
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public void run() {
DebugLogger.info("update task running", 2);
try {
latestVersion = updater.getLatestVersion().join();
} catch (CompletionException e) {
Throwable ex = e.getCause();
DebugLogger.info("Error trying to contact update server: %s", 0, ex.getMessage());
if (DebugLogger.getDebugLevel() >= 1)
e.printStackTrace();
}
if (latestVersion == null) return;
DebugLogger.info("RealWeather is outdated. /rwadmin update", 0);
for (Player player : updater.plugin.getServer().getOnlinePlayers()) {
if (player.hasPermission("realweather.update.notify")) {
player.sendMessage("RealWeather is outdated. /rwadmin update");
}
}
updateConsumer.accept(latestVersion);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent e) {
Player player = e.getPlayer();
if (latestVersion != null && player.hasPermission("realweather.update.notify")) {
player.sendMessage("RealWeather is outdated. /rwadmin update");
}
}
}

View file

@ -0,0 +1,20 @@
package eu.m724.realweather.updater;
import org.bukkit.configuration.ConfigurationSection;
/**
*
* @param alert alert admins about updates
* @param channel update channel
*/
public record UpdaterConfig(
boolean alert, // this is different because I can't use notify in records sadly
String channel
) {
public static UpdaterConfig fromConfiguration(ConfigurationSection configuration) {
return new UpdaterConfig(
configuration.getBoolean("notify"),
configuration.getString("channel")
);
}
}

View file

@ -0,0 +1,137 @@
package eu.m724.realweather.weather;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.realweather.api.weather.AsyncWeatherUpdateEvent;
import eu.m724.wtapi.object.Coordinates;
import eu.m724.wtapi.object.Weather;
import eu.m724.wtapi.provider.weather.WeatherProvider;
public class DynamicWeatherRetriever extends BukkitRunnable implements Listener {
private final WeatherProvider weatherProvider;
private final Mapper mapper = GlobalConstants.getMapper();
private final Plugin plugin = GlobalConstants.getPlugin();
private final Server server = plugin.getServer();
private final PlayerWeatherCache playerWeatherCache = GlobalConstants.getPlayerWeatherCache();
// when to next update all players
private long nextUpdate = 0;
// players that need update asap
private final Set<Player> neededUpdate = new HashSet<>();
public DynamicWeatherRetriever(WeatherProvider weatherProvider) {
this.weatherProvider = weatherProvider;
}
private record CoordinatesResult(
Map<Coordinates, Player[]> coordinatesPlayersMap,
int coordinatesCount,
int playerCount
) {}
private CoordinatesResult makeCoordinates(Collection<? extends Player> players) {
Map<Coordinates, Player[]> map = new HashMap<>();
int coordinatesCount = 0;
int playerCount = 0;
long now = System.currentTimeMillis();
for (Player player : players) {
if (!player.hasPermission("realweather.dynamic")) continue;
if (!mapper.getWorlds().contains(player.getWorld())) continue;
Long lastUpdate = playerWeatherCache.getLastUpdate(player);
if (lastUpdate != null && now - lastUpdate < 10000) continue;
Coordinates coordinates = mapper.locationToCoordinates(player.getLocation());
Coordinates closestCoordinates = null;
for (Coordinates potential : map.keySet()) {
//double distance = Math.sqrt(Math.pow(potential.latitude - coordinates.latitude, 2) + Math.pow(potential.longitude - potential.latitude, 2));
// TODO setup for "bundling" that is one request for close players
}
if (closestCoordinates != null) {
Player[] oldPlayerArray = map.get(coordinates);
Player[] newPlayerArray = Arrays.copyOf(oldPlayerArray, oldPlayerArray.length + 1);
newPlayerArray[oldPlayerArray.length] = player;
map.put(coordinates, newPlayerArray);
} else {
map.put(coordinates, new Player[] { player });
coordinatesCount++;
}
playerCount++;
}
return new CoordinatesResult(map, coordinatesCount, playerCount);
}
@Override
public void run() {
DebugLogger.info("Weather retrieval", 3);
long now = System.currentTimeMillis();
CoordinatesResult coordinates;
if (now > nextUpdate) {
coordinates = makeCoordinates(server.getOnlinePlayers());
// calculate acceptable request rate based on weather provider quota and active players
float hourly = weatherProvider.getQuotaHourly() / (float)(weatherProvider.getBulkLimit() * coordinates.coordinatesCount());
nextUpdate = now + Math.max(60000, (long) (3600000 / hourly));
DebugLogger.info("Next update in %d", 3, nextUpdate);
} else { // immediate update for those that need it right now
if (neededUpdate.isEmpty()) return;
DebugLogger.info("Players in need of update: %d", 2, neededUpdate.size());
coordinates = makeCoordinates(neededUpdate);
neededUpdate.clear();
}
Coordinates[] coordinatesArray = coordinates.coordinatesPlayersMap().keySet().toArray(Coordinates[]::new);
// TODO change to Collection in wtapi? but some ordered kind
CompletableFuture<Weather[]> weathersFuture =
weatherProvider.getWeatherBulk(coordinatesArray);
try {
Weather[] weathers = weathersFuture.join();
for (int i=0; i<weathers.length; i++) {
Weather weather = weathers[i];
for (Player player : coordinates.coordinatesPlayersMap().get(coordinatesArray[i])) {
playerWeatherCache.put(player, weather, now);
AsyncWeatherUpdateEvent event =
new AsyncWeatherUpdateEvent(player, weather);
server.getPluginManager().callEvent(event);
}
}
} catch (CompletionException e) { // TODO handle finer exceptions
DebugLogger.info("An error occurred trying to retrieve weather data", 0);
if (DebugLogger.getDebugLevel() > 0)
e.printStackTrace();
}
DebugLogger.info("dynamic retriever done", 3);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
neededUpdate.add(player);
}
}

View file

@ -0,0 +1,28 @@
package eu.m724.realweather.weather;
import java.util.HashMap;
import org.bukkit.entity.Player;
import eu.m724.wtapi.object.Weather;
/**
* Stores player weathers and when they were last updated
*/
public class PlayerWeatherCache {
HashMap<Player, Weather> weathers = new HashMap<>();
HashMap<Player, Long> lastUpdates = new HashMap<>();
void put(Player player, Weather weather, Long lastUpdate) {
weathers.put(player, weather);
lastUpdates.put(player, lastUpdate);
}
public Weather getWeather(Player player) {
return weathers.get(player);
}
public Long getLastUpdate(Player player) {
return lastUpdates.get(player);
}
}

View file

@ -0,0 +1,47 @@
package eu.m724.realweather.weather;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.api.weather.AsyncWeatherUpdateEvent;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.wtapi.object.Coordinates;
import eu.m724.wtapi.object.Weather;
import eu.m724.wtapi.provider.weather.WeatherProvider;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
public class StaticWeatherRetriever extends BukkitRunnable {
private final Plugin plugin = GlobalConstants.getPlugin();
private final Mapper mapper = GlobalConstants.getMapper();
private final WeatherProvider weatherProvider;
public StaticWeatherRetriever(WeatherProvider weatherProvider) {
this.weatherProvider = weatherProvider;
}
@Override
public void run() {
Coordinates point = mapper.getPoint();
CompletableFuture<Weather> weatherFuture = weatherProvider.getWeather(point);
try {
Weather weather = weatherFuture.join();
AsyncWeatherUpdateEvent event =
new AsyncWeatherUpdateEvent(null, weather);
plugin.getServer().getPluginManager().callEvent(event);
} catch (CompletionException e) { // TODO handle finer exceptions
DebugLogger.info("An error occurred trying to retrieve weather data", 0);
if (DebugLogger.getDebugLevel() > 0)
e.printStackTrace();
}
DebugLogger.info("static weather retriever is done", 3);
}
}

View file

@ -0,0 +1,51 @@
package eu.m724.realweather.weather;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.api.weather.AsyncWeatherUpdateEvent;
import eu.m724.realweather.mapper.Mapper;
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 comprehensive
public class WeatherChanger implements Listener {
private final Mapper mapper = GlobalConstants.getMapper();
@EventHandler(priority = EventPriority.LOWEST)
public void onWeatherUpdate(AsyncWeatherUpdateEvent event) {
Player player = event.getPlayer();
Weather weather = event.getWeather();
if (player != null) { // dynamic mode
DebugLogger.info("Changing weather for player %s", 2, player.getName());
if (weather.isThundering() || weather.isSnowing() || weather.isRaining()) {
player.setPlayerWeather(WeatherType.DOWNFALL);
} else {
player.setPlayerWeather(WeatherType.CLEAR);
}
} else { // static mode
DebugLogger.info("Changing weather static", 3);
mapper.getWorlds().forEach(w -> {
DebugLogger.info("Changing weather static in world %s", 2, w.getName());
if (weather.isThundering()) {
w.setClearWeatherDuration(0);
w.setWeatherDuration(120000);
w.setThunderDuration(120000);
} else if (weather.isRaining() || weather.isSnowing()) {
w.setClearWeatherDuration(0);
w.setWeatherDuration(120000);
w.setThunderDuration(0);
} else {
w.setClearWeatherDuration(120000);
w.setWeatherDuration(0);
w.setThunderDuration(0);
}
});
}
}
}

View file

@ -0,0 +1,27 @@
package eu.m724.realweather.weather;
import org.bukkit.configuration.ConfigurationSection;
/**
* Configuration of the weather module
*
* @param enabled Is weather module enabled
* @param provider The provider name, may or may not exist, if it doesn't, an error is thrown later
* @param apiKey API key for the provider
* @param dynamic dynamic mode, weather is per player or global
*/
public record WeatherConfig(
boolean enabled,
String provider,
String apiKey, // TODO don't expose that, I mean 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

@ -0,0 +1,49 @@
package eu.m724.realweather.weather;
import eu.m724.realweather.Configs;
import org.bukkit.GameRule;
import org.bukkit.plugin.Plugin;
import eu.m724.realweather.DebugLogger;
import eu.m724.realweather.GlobalConstants;
import eu.m724.realweather.mapper.Mapper;
import eu.m724.wtapi.provider.Providers;
import eu.m724.wtapi.provider.exception.NoSuchProviderException;
import eu.m724.wtapi.provider.exception.ProviderException;
import eu.m724.wtapi.provider.weather.WeatherProvider;
public class WeatherMaster {
private final WeatherConfig config = Configs.weatherConfig();
private final Mapper mapper = GlobalConstants.getMapper();
/**
* initializes, tests and starts
* @throws ProviderException if provider initialization failed
* @throws NoSuchProviderException config issue
*/
public void init(Plugin plugin) throws ProviderException, NoSuchProviderException {
if (!config.enabled()) {
DebugLogger.info("weather module is disabled", 1);
return;
}
WeatherProvider provider = Providers.getWeatherProvider(config.provider(), config.apiKey());
provider.init();
if (config.dynamic()) {
DynamicWeatherRetriever retriever = new DynamicWeatherRetriever(provider);
retriever.runTaskTimerAsynchronously(plugin,0, 1000);
plugin.getServer().getPluginManager().registerEvents(retriever, plugin);
} else {
StaticWeatherRetriever retriever = new StaticWeatherRetriever(provider);
retriever.runTaskTimerAsynchronously(plugin,0, 60000);
}
plugin.getServer().getPluginManager().registerEvents(new WeatherChanger(), plugin);
mapper.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, false));
mapper.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, true));
DebugLogger.info("weather loaded", 1);
}
}

View file

@ -1,70 +0,0 @@
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

@ -1,87 +0,0 @@
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

@ -1,60 +0,0 @@
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

@ -1,93 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,39 +0,0 @@
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

@ -1,103 +0,0 @@
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

@ -1,26 +0,0 @@
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

@ -1,99 +0,0 @@
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

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

View file

@ -1,75 +0,0 @@
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

@ -1,92 +0,0 @@
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

@ -1,43 +0,0 @@
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

@ -1,101 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,60 +0,0 @@
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

@ -1,79 +0,0 @@
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

@ -1,69 +0,0 @@
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

@ -1,39 +0,0 @@
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

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

View file

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

View file

@ -1,176 +0,0 @@
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

@ -1,13 +0,0 @@
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,65 +1,23 @@
weather: ############################
enabled: true ### GENERAL SETTINGS ###
############################
worlds: # Master switch
- world enabled: false
- second_world
- third_world
provider: updater:
# Weather provider # Notify players and console about plugin updates
choice: openweathermap # This also controls automatic checking
# Configure it here # You can still update with /rwadmin update
openweathermap: # Relevant permission node: realweather.update.notify
apiKey: 'd3d37fd3511ef1d4b44c7d574e9b56b8' # PLEASE get your own @ https://home.openweathermap.org/users/sign_up notify: true
# More providers soon! # stable for stable releases
# testing for latest builds (untested hence the name)
# As there's no release yet, stable will just error
channel: testing
map: # 0 - no debug
# "point" - static location # 1 - debug loading modules
# "player" - player's IP location (fake weather) # 2 - also debug processing conditions
# "globe" - world resembles a real-world globe # 3 - also log tasks running, this will spam
type: point debug: 0
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

@ -0,0 +1,24 @@
############################
### MAP SETTINGS ###
############################
# true if the list below is a blacklist, false otherwise
worldBlacklist: false
worlds:
- world
dimensions:
# Blocks per 1 deg, can't be decimal
# latitude = -Z, longitude = X (to match with F3)
# The default (111000) assumes 1 block = 1 meter
latitude: 111000 # -9990000 to 9990000 Z
longitude: 111000 # -19980000 to 19980000 X
# To make it span world border to world border:
# latitude: 333333
# longitude: 166666
# For `static` mode
point:
latitude: 0
longitude: 0

View file

@ -0,0 +1,15 @@
############################
### THUNDER SETTINGS ###
############################
enabled: false
# Currently only blitzortung
provider: blitzortung
# How often should we poll for updates and spawn lightning
# This is a synchronous task
# Exaggerating, if you put it too low you'll have lag,
# But if you put it too high you'll have lag spikes and weird lightning
# In ticks, default 100 is 5 seconds so reduce if lightning seems weird, in my testing even 5 ticks is fine
refreshPeriod: 100

View file

@ -0,0 +1,21 @@
############################
### TIME SETTINGS ###
############################
# Warning: this removes sleep
# No, it's not a bug. It would de-synchronize, and can you skip time IRL?
# Can you believe that I actually used to consider this a bug?
enabled: false
# How this plugin affects your world:
# - static (false): time is the same across the world
# - dynamic (true): static + local time for each player, however it's only cosmetic so it will not match mobs spawning etc
# Settings for both are in map.yml
dynamic: true
# x in game day cycles in 1 irl day cycle
# 2.0 - time goes 2x SLOWER
# 0.5 - time goes 2x FASTER
# If modified, time will no longer be in sync with real life
scale: 1.0

View file

@ -0,0 +1,21 @@
############################
### 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 OpenWeatherMap
provider: openweathermap
# put your OpenWeatherMap api key
apiKey: REPLACE ME
# How this plugin affects your world:
# - static (false): weather is the same across the world
# - dynamic (true): weather is per player, 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,11 +1,63 @@
name: RealWeather name: RealWeather
author: Minecon724
version: ${project.version} version: ${project.version}
api-version: 1.16
author: Minecon724
website: https://www.spigotmc.org/resources/realweather-realtime.101599/
api-version: 1.19.4
load: STARTUP load: STARTUP
main: pl.minecon724.realweather.RealWeatherPlugin main: eu.m724.realweather.RealWeatherPlugin
libraries: libraries:
- com.google.code.gson:gson:2.10.1 - org.java-websocket:Java-WebSocket:1.5.7
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

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

BIN
testkeystore.jks Normal file

Binary file not shown.