Initial commit

This commit is contained in:
ayunami2000 2022-04-21 08:32:33 -04:00
commit 959f6da907
8 changed files with 546 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.idea/
lib/
target/classes/
target/generated-sources/
target/maven-archiver/
target/maven-status/
ayunEagVidMap.iml
run/

25
pom.xml Normal file
View File

@ -0,0 +1,25 @@
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.ayunami2000</groupId>
<artifactId>ayunEagVidMap</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dont.care</groupId>
<artifactId>didnt-ask</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/craftbukkit-1.5.2-R1.0.jar</systemPath>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,111 @@
package me.ayunami2000.ayunEagVidMap;
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 org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class Main extends JavaPlugin implements CommandExecutor, Listener {
private boolean playing = false;
private VideoMapPacketCodecBukkit videoMapCodec = null;
private int[][] mapIds;
private Location audioLoc;
private String url;
@Override
public void onEnable(){
this.saveDefaultConfig();
this.getCommand("ayunvid").setExecutor(this);
int width = this.getConfig().getInt("width");
int height = this.getConfig().getInt("width");
mapIds = new int[height][width];
int offset = this.getConfig().getInt("offset");
for (int y = 0; y < mapIds.length; y++) {
for (int x = 0; x < mapIds[y].length; x++) {
mapIds[y][x] = offset++;
}
}
audioLoc.setX(this.getConfig().getDouble("audio.x"));
audioLoc.setY(this.getConfig().getDouble("audio.y"));
audioLoc.setZ(this.getConfig().getDouble("audio.z"));
videoMapCodec = new VideoMapPacketCodecBukkit(mapIds, audioLoc.getX(), audioLoc.getY(), audioLoc.getZ(), 0.5f);
url = this.getConfig().getString("url");
this.getServer().getScheduler().scheduleSyncRepeatingTask(this, () -> {
for (Player player : this.getServer().getOnlinePlayers()) {
VideoMapPacketCodecBukkit.nativeSendPacketToPlayer(player, videoMapCodec.syncPlaybackWithPlayersBukkit());
}
}, 10000, 10000); // sync every 10 seconds
}
@Override
public void onDisable(){
videoMapCodec.disableVideoBukkit();
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
if (playing) {
VideoMapPacketCodecBukkit.nativeSendPacketToPlayer(event.getPlayer(), videoMapCodec.);
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0){
sender.sendMessage("usage");
return true;
}
switch (args[0].toLowerCase()) {
case "u":
case "url":
if (args.length < 2) {
sender.sendMessage("no url specified!");
break;
}
this.getConfig().set("url", args[1]);
this.saveConfig();
sender.sendMessage("seturl");
break;
case "l":
case "loc":
case "location":
sender.sendMessage("set location of audio");
break;
case "p":
case "play":
case "pause":
sender.sendMessage("resuming & loading if needed, or pausing");
new VideoMapPacketCodecBukkit()
break;
case "s":
case "size":
if (args.length < 3) {
sender.sendMessage("must specify width & height to set! current vals are...");
break;
}
int width;
int height;
try {
width = Math.max(1, Integer.parseInt(args[1]));
height = Math.max(1, Integer.parseInt(args[2]));
} catch(NumberFormatException e) {
sender.sendMessage("");
break;
}
this.getConfig().set("width", width);
this.getConfig().set("height", height);
this.saveConfig();
sender.sendMessage("set width & height");
break;
default:
sender.sendMessage("invalid");
}
return true;
}
}

View File

@ -0,0 +1,257 @@
package me.ayunami2000.ayunEagVidMap;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class VideoMapPacketCodec {
public final int[][] mapIds;
private boolean loop;
private String url;
private int duration;
private long timestamp;
private long pauseTimestamp;
private double posX;
private double posY;
private double posZ;
private float volume;
private int frameRate;
private boolean requiresFullResetPacket;
private boolean requiresPositionPacket;
private boolean isDisabled;
/**
* @param mapIds 2D grid of map IDs that make up the screen (mapIds[y][x])
* @param posX audio playback X coord
* @param posY audio playback Y coord
* @param posZ audio playback Z coord
* @param volume the volume of the clip
*/
public VideoMapPacketCodec(int[][] mapIds, double posX, double posY, double posZ, float volume) {
this.mapIds = mapIds;
this.url = null;
this.posX = posX;
this.posY = posY;
this.posZ = posZ;
this.volume = 1.0f;
this.frameRate = 60;
this.requiresPositionPacket = true;
this.requiresFullResetPacket = true;
this.isDisabled = true;
}
/**
* @param mapIds 2D grid of map IDs that make up the screen (mapIds[y][x])
* @param posX audio playback X coord
* @param posY audio playback Y coord
* @param posZ audio playback Z coord
*/
public VideoMapPacketCodec(int[][] mapIds, double posX, double posY, double posZ) {
this(mapIds, posX, posY, posZ, 1.0f);
}
/**
* @param posX audio playback X coord
* @param posY audio playback Y coord
* @param posZ audio playback Z coord
* @param volume the volume of the clip
* @return packet to send to players
*/
public byte[] moveAudioSource(double posX, double posY, double posZ, float volume) {
this.posX = posX;
this.posY = posY;
this.posZ = posZ;
this.volume = volume;
this.requiresPositionPacket = true;
return syncPlaybackWithPlayers();
}
/**
* unloads video and resets all map object to vanilla renderer
* @return packet to send to players
*/
public byte[] disableVideo() {
isDisabled = true;
return syncPlaybackWithPlayers();
}
/**
* syncs the server side video timestamp with players
* @return packet to send to players
*/
public byte[] syncPlaybackWithPlayers() {
try {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
DataOutputStream str = new DataOutputStream(bao);
if(isDisabled) {
str.write(0);
int x = mapIds[0].length;
int y = mapIds.length;
str.write((x << 4) | y);
for(int yy = 0; yy < y; ++yy) {
for(int xx = 0; xx < x; ++xx) {
str.writeShort(mapIds[yy][xx]);
}
}
return bao.toByteArray();
}
int packetType = 1;
if(requiresFullResetPacket) {
packetType = packetType | 2;
}
if(requiresFullResetPacket || requiresPositionPacket) {
packetType = packetType | 4;
}
str.write(packetType);
if(requiresFullResetPacket) {
int x = mapIds[0].length;
int y = mapIds.length;
str.write((x << 4) | y);
for(int yy = 0; yy < y; ++yy) {
for(int xx = 0; xx < x; ++xx) {
str.writeShort(mapIds[yy][xx]);
}
}
str.write(frameRate);
str.writeInt(duration);
str.writeUTF(url);
}
if(requiresFullResetPacket || requiresPositionPacket) {
str.writeFloat(volume);
str.writeDouble(posX);
str.writeDouble(posY);
str.writeDouble(posZ);
}
str.writeInt(getElapsedMillis());
str.writeBoolean(loop);
str.writeBoolean(pauseTimestamp > 0l);
requiresFullResetPacket = false;
requiresPositionPacket = false;
return bao.toByteArray();
}catch(IOException e) {
throw new RuntimeException("serialization error", e);
}
}
/**
* this is dual purpose, it calculates elapsed time but also loops or pauses the video if it is finished playing
*/
private int getElapsedMillis() {
if(pauseTimestamp > 0l) {
return (int)(pauseTimestamp - timestamp);
}
int t = (int)(System.currentTimeMillis() - timestamp);
if(loop) {
while(t > duration) {
t -= duration;
timestamp += duration;
}
}else {
if(t > duration) {
timestamp = (int)(System.currentTimeMillis() - duration);
return duration;
}
}
return t;
}
/**
* @param url URL to an MP4 or other HTML5 supported video file
* @param loop If the video file should loop
* @param duration duration of the video in seconds
* @return packet to send to players
*/
public byte[] beginPlayback(String url, boolean loop, float duration) {
this.url = url;
this.loop = loop;
this.duration = (int)(duration * 1000.0f);
this.pauseTimestamp = 0l;
this.timestamp = 0l;
this.requiresFullResetPacket = true;
this.isDisabled = false;
return syncPlaybackWithPlayers();
}
/**
* @return the duration of the current clip
*/
public float getDuration() {
return duration * 0.001f;
}
/**
* @return the URL of the current clip
*/
public String getURL() {
return url;
}
/**
* @return the server's current timestamp
*/
public float getPlaybackTime() {
return getElapsedMillis() * 0.001f;
}
/**
* @param time time in seconds to seek the video to
*/
public byte[] setPlaybackTime(float time) {
timestamp = System.currentTimeMillis() - (int)(time * 1000.0f);
return syncPlaybackWithPlayers();
}
/**
* @return if playback is complete (false if loop)
*/
public boolean isPlaybackFinished() {
return !loop && getElapsedMillis() == duration;
}
/**
* @param loop video should loop
*/
public byte[] setLoopEnable(boolean loop) {
this.loop = loop;
return syncPlaybackWithPlayers();
}
/**
* @return if loop is enabled
*/
public boolean isLoopEnable() {
return loop;
}
/**
* @param pause set if video should pause
* @return packet to send to players
*/
public byte[] setPaused(boolean pause) {
getElapsedMillis();
if(pause && pauseTimestamp <= 0l) {
pauseTimestamp = System.currentTimeMillis();
}else if(!pause && pauseTimestamp > 0l) {
timestamp = System.currentTimeMillis() - (pauseTimestamp - timestamp);
pauseTimestamp = 0l;
}
return syncPlaybackWithPlayers();
}
/**
* @return if video is currently paused
*/
public boolean isPaused() {
return pauseTimestamp > 0l;
}
}

View File

@ -0,0 +1,122 @@
package me.ayunami2000.ayunEagVidMap;
import java.util.List;
import org.bukkit.craftbukkit.v1_5_R3.entity.CraftPlayer;
import org.bukkit.entity.Player;
import net.minecraft.server.v1_5_R3.Packet;
import net.minecraft.server.v1_5_R3.Packet131ItemData;
public class VideoMapPacketCodecBukkit extends VideoMapPacketCodec {
/**
* @param mapIds 2D grid of map IDs that make up the screen (mapIds[y][x])
* @param posX audio playback X coord
* @param posY audio playback Y coord
* @param posZ audio playback Z coord
* @param volume the volume of the clip
*/
public VideoMapPacketCodecBukkit(int[][] mapIds, double posX, double posY, double posZ, float volume) {
super(mapIds, posX, posY, posZ, volume);
}
/**
* @param mapIds 2D grid of map IDs that make up the screen (mapIds[y][x])
* @param posX audio playback X coord
* @param posY audio playback Y coord
* @param posZ audio playback Z coord
*/
public VideoMapPacketCodecBukkit(int[][] mapIds, double posX, double posY, double posZ) {
super(mapIds, posX, posY, posZ, 1.0f);
}
public class VideoMapPacket {
protected final Object packet;
protected VideoMapPacket(byte[] packet) {
this.packet = new Packet131ItemData((short)104, (short)0, packet);
}
public Object getNativePacket() {
return packet;
}
public void send(Player p) {
nativeSendPacketToPlayer(p, packet);
}
public void send(Player... p) {
for(Player pp : p) {
nativeSendPacketToPlayer(pp, packet);
}
}
public void send(List<Player> p) {
for(Player pp : p) {
nativeSendPacketToPlayer(pp, packet);
}
}
}
/**
* @param posX audio playback X coord
* @param posY audio playback Y coord
* @param posZ audio playback Z coord
* @param volume the volume of the clip
* @return packet to send to players
*/
public VideoMapPacket moveAudioSourceBukkit(double posX, double posY, double posZ, float volume) {
return new VideoMapPacket(moveAudioSource(posX, posY, posZ, volume));
}
/**
* unloads video and resets all map object to vanilla renderer
* @return packet to send to players
*/
public VideoMapPacket disableVideoBukkit() {
return new VideoMapPacket(disableVideo());
}
/**
* syncs the server side video timestamp with players
* @return packet to send to players
*/
public VideoMapPacket syncPlaybackWithPlayersBukkit() {
return new VideoMapPacket(syncPlaybackWithPlayers());
}
/**
* @param url URL to an MP4 or other HTML5 supported video file
* @param loop If the video file should loop
* @param duration duration of the video in seconds
* @return packet to send to players
*/
public VideoMapPacket beginPlaybackBukkit(String url, boolean loop, float duration) {
return new VideoMapPacket(beginPlayback(url, loop, duration));
}
/**
* @param time time in seconds to seek the video to
*/
public VideoMapPacket setPlaybackTimeBukkit(float time) {
return new VideoMapPacket(setPlaybackTime(time));
}
/**
* @param loop video should loop
*/
public VideoMapPacket setLoopEnableBukkit(boolean loop) {
return new VideoMapPacket(setLoopEnable(loop));
}
/**
* @param pause set if video should pause
* @return packet to send to players
*/
public VideoMapPacket setPausedBukkit(boolean pause) {
return new VideoMapPacket(setPaused(pause));
}
public static void nativeSendPacketToPlayer(Player player, Object obj) {
if(obj == null) {
return;
}
((CraftPlayer)player).getHandle().playerConnection.sendPacket((Packet)obj);
}
}

View File

@ -0,0 +1,12 @@
# URL of video to play
url: ""
# screen size
width: 3
height: 2
# map ID offset
offset: 0
# audio location
audio:
x: 0
y: 100
z: 0

View File

@ -0,0 +1,9 @@
name: ayunEagVidMap
version: 1.0
main: me.ayunami2000.ayunEagVidMap.Main
commands:
ayunvid:
description: Control ayunEagVidMap
usage: /<command>
permission: ayuneagvidmap.control
#todo: separate play, pause, and add queue