Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/com/github/pop4959/srbot/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static void main(String[] args) {
add(new Players(config, steamWebApiClient));
add(new Playtime(config, steamWebApiClient));
add(new Points(config, steamWebApiClient));
add(new PreviewTrail());
add(new Private(config));
add(new RandomCharacter(config));
add(new Say());
Expand Down
170 changes: 170 additions & 0 deletions src/main/java/com/github/pop4959/srbot/commands/PreviewTrail.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package com.github.pop4959.srbot.commands;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import org.jetbrains.annotations.NotNull;

import com.github.pop4959.srbot.trail.Camera;
import com.github.pop4959.srbot.trail.Color;
import com.github.pop4959.srbot.trail.Path;
import com.github.pop4959.srbot.trail.RendererCpu;
import com.github.pop4959.srbot.trail.Trail;
import com.github.pop4959.srbot.trail.TrailAnimation;
import com.github.pop4959.srbot.trail.TrailLayer;
import com.github.pop4959.srbot.trail.TrailParticleEmitter;
import com.github.pop4959.srbot.trail.TrailStripe;
import com.github.pop4959.srbot.trail.Vector2;

import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.utils.FileUpload;

public class PreviewTrail extends Command {
public PreviewTrail() {
super("preview_trail", "Get a preview of the trail you will get when you use the command");
}

private void cpuRender(RendererCpu cpu, Trail trail) {
for (TrailLayer layer : trail.layers) {
if (layer.getLayerType() == 0) {
TrailStripe stripe = (TrailStripe) layer;
if (stripe.getEnabled() == 0 || !stripe.getIsVisible())
continue;
cpu.setTexture(trail.getLoadedImages().getOrDefault(stripe.getImage(), null));
for (int i = 0; i < stripe.getSegmentCount(); i++) {
TrailStripe.Segment segment = stripe.getSegment(i);
cpu.drawTriangleStrip(stripe.vertexArray, segment.startIndex, segment.endIndex);
}
} else if (layer.getLayerType() == 1) {
TrailParticleEmitter emitter = (TrailParticleEmitter) layer;
if (emitter.getEnabled() == 0 || !emitter.getIsVisible())
continue;
cpu.setTexture(trail.getLoadedImages().getOrDefault(emitter.getImage(), null));
cpu.drawQuads(emitter.vertexArray);
} else if (layer.getLayerType() == 2) {
TrailAnimation animation = (TrailAnimation) layer;
if (animation.getEnabled() == 0 || !animation.getIsVisible())
continue;
cpu.setTexture(trail.getLoadedImages().getOrDefault(animation.getImage(), null));
cpu.drawQuads(animation.vertexArray);
}
}
}

private BufferedImage renderTrail(String filePath, Color clearColor) throws IOException {
Trail trail = new Trail(filePath);

Camera camera = new Camera();
RendererCpu cpu = new RendererCpu(1024, 512);
cpu.camera = camera;
camera.position = new Vector2(-512, -256);

float startTime = 5.2f;
float endTime = (float) Math.PI * 2.0f - 0.05f;
int pointCount = 600;
float timeStep = (endTime - startTime) / (float) pointCount;
Path pathA = new Path(timeStep);

float startSpeed = Trail.AFTERIMAGE_THRESHOLD;
float endSpeed = Trail.SUPERSPEED_THRESHOLD * 1.35f;
float speedStep = (endSpeed - startSpeed) / (float) pointCount;

for (int i = 0; i < pointCount; i++) {
float currentTime = startTime + i * timeStep;
float currentSpeed = startSpeed + i * speedStep;
Vector2 position = pathA.infinityPath(currentTime, 430, new Vector2(0, 0), 3.0f);
Vector2 velocity = pathA.calculateVelocity(position);
velocity = Vector2.normalize(velocity);
velocity = Vector2.scale(velocity, currentSpeed);

trail.update(timeStep, position, velocity);
}

cpu.clear(clearColor);
cpuRender(cpu, trail);
return cpu.getFramebuffer();
}

@Override
public SlashCommandData getSlashCommand() {
return super.getSlashCommand()
.addOption(OptionType.STRING, "message_id", "The message id of the message you want to preview the trail of", true)
.addOption(OptionType.BOOLEAN, "transparent", "Whether the background should be transparent or not", true);
}

@Override
public void execute(@NotNull SlashCommandInteractionEvent event) {
String messageId = event.getOption("message_id").getAsString();

long id;
try {
id = Long.parseLong(messageId);
} catch (NumberFormatException e) {
event.reply("Invalid message id").setEphemeral(true).queue();
return;
}

event.getChannel().retrieveMessageById(id).queue(
message -> processMessage(event, message),
error -> event.reply("Message not found.").setEphemeral(true).queue()
);
}

private void processMessage(SlashCommandInteractionEvent event, Message message) {
message.getAttachments().stream()
.filter(a -> a.getFileName().toLowerCase().endsWith(".srt"))
.findFirst()
.ifPresentOrElse(
attachment -> processAttachment(event, attachment),
() -> event.reply("No .srt attachment found.").setEphemeral(true).queue()
);
}

private void processAttachment(SlashCommandInteractionEvent event, Message.Attachment attachment) {
java.nio.file.Path tempPath;
boolean transparent = event.getOption("transparent") != null && event.getOption("transparent").getAsBoolean();
try {
tempPath = Files.createTempFile("attachment", ".srt");
System.out.println("Created temporary file: " + tempPath.toString());
} catch (IOException e) {
event.reply("Failed to create temporary file.").setEphemeral(true).queue();
return;
}
attachment.getProxy().downloadToFile(tempPath.toFile()).thenRun(() -> {
try {
Color clearColor = transparent ? new Color(0, 0, 0, 0) : new Color(0.2f, 0.2f, 0.2f, 1.0f);
BufferedImage preview = renderTrail(tempPath.toString(), clearColor);

try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
ImageIO.write(preview, "png", os);
byte[] imageData = os.toByteArray();
FileUpload upload = FileUpload.fromData(imageData, "preview.png");
ArrayList<MessageEmbed> embeds = new ArrayList<MessageEmbed>();

EmbedBuilder embed = new EmbedBuilder()
.setImage("attachment://preview.png")
.setColor(event.getGuild().getSelfMember().getColor());
embeds.add(embed.build());

event.replyEmbeds(embeds).addFiles(upload).queue();
} catch (IOException e) {
event.reply("Failed to create temporary image file.").setEphemeral(true).queue();
return;
}

} catch (IOException e) {
event.reply("Failed to read trail file.").setEphemeral(true).queue();
}
});
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/github/pop4959/srbot/trail/Camera.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.pop4959.srbot.trail;

public class Camera {
public Vector2 position;
public float zoom;

public Camera() {
position = new Vector2(0, 0);
zoom = 1.0f;
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/github/pop4959/srbot/trail/Color.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.github.pop4959.srbot.trail;

public class Color {
public float r,g,b,a;

public Color(float r, float g, float b, float a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}

public static Color blend(Color src, Color dst) {
float outA = src.a + dst.a * (1 - src.a);
if (outA == 0) return new Color(0, 0, 0, 0);
float outR = (src.r * src.a + dst.r * dst.a * (1 - src.a)) / outA;
float outG = (src.g * src.a + dst.g * dst.a * (1 - src.a)) / outA;
float outB = (src.b * src.a + dst.b * dst.a * (1 - src.a)) / outA;
return new Color(outR, outG, outB, outA);
}

public static Color multiply(Color a, Color b) {
return new Color(a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a);
}

public int toRGB() {
int ia = (int)(a * 255.0);
int ir = (int)(r * 255.0);
int ig = (int)(g * 255.0);
int ib = (int)(b * 255.0);
return (ia << 24) | (ir << 16) | (ig << 8) | ib;
}

static public Color parse(String s) {
String[] parts = s.split(",");
if (parts.length == 3) {
return new Color(Float.parseFloat(parts[0]),
Float.parseFloat(parts[1]),
Float.parseFloat(parts[2]), 1.0f);
} else if (parts.length == 4) {
return new Color(Float.parseFloat(parts[0]),
Float.parseFloat(parts[1]),
Float.parseFloat(parts[2]),
Float.parseFloat(parts[3]));
} else {
throw new IllegalArgumentException("Invalid color format: " + s);
}
}
}
35 changes: 35 additions & 0 deletions src/main/java/com/github/pop4959/srbot/trail/Path.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.github.pop4959.srbot.trail;

public class Path {
private float deltaTime;
private Vector2 previousPosition;

public Path(float deltaTime) {
this.deltaTime = deltaTime;
}

public Vector2 calculateVelocity(Vector2 position) {
if (previousPosition == null) {
previousPosition = new Vector2(position.x, position.y);
}
Vector2 velocity = new Vector2(position.x - previousPosition.x, position.y - previousPosition.y);
previousPosition = new Vector2(position.x, position.y);
return Vector2.scale(velocity, 1.0f / deltaTime);
}

public void reset() {
previousPosition = null;
}

public Vector2 infinityPath(float time, float radius, Vector2 center, float speed) {
time *= speed;
return Vector2.sum(
center,
new Vector2(
radius * (float)Math.cos(time) / (1 + (float)Math.sin(time) * (float)Math.sin(time)),
radius * (float)Math.cos(time) * (float)Math.sin(time) / (1 + (float)Math.sin(time) * (float)Math.sin(time))
)
);
}

}
Loading