petite séance du matin
Co-authored-by: Damien <damguillotin@gmail.com>
This commit is contained in:
parent
ea5d13f218
commit
8fd4b6c144
|
@ -2,38 +2,71 @@ package com.tocard.cam;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
public class Camera extends JavaPlugin {
|
public class Camera extends JavaPlugin {
|
||||||
|
|
||||||
public static List<Location> locations;
|
public static ArrayList<Location> curve;
|
||||||
|
public static List<Location> controlPoints;
|
||||||
public static Logger logger;
|
public static Logger logger;
|
||||||
public static Plugin plugin;
|
public static Plugin plugin;
|
||||||
|
private static float dt = 0.01f;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
plugin = this;
|
plugin = this;
|
||||||
|
|
||||||
// setup commands
|
|
||||||
this.getCommand("point").setExecutor(new NewPoint());
|
|
||||||
this.getCommand("points").setExecutor(new ListPoints());
|
|
||||||
this.getCommand("goto").setExecutor(new GotoPoint());
|
|
||||||
this.getCommand("reset").setExecutor(new ClearPoints());
|
|
||||||
this.getCommand("test").setExecutor(new Test());
|
|
||||||
|
|
||||||
// init logger
|
// init logger
|
||||||
logger = getLogger();
|
logger = getLogger();
|
||||||
|
|
||||||
// init locations List
|
// init locations List
|
||||||
locations = new ArrayList<>();
|
controlPoints = new ArrayList<>();
|
||||||
|
|
||||||
|
// setup commands
|
||||||
|
this.getCommand("point").setExecutor(new NewPoint());
|
||||||
|
this.getCommand("points").setExecutor(new ListPoints());
|
||||||
|
this.getCommand("reset").setExecutor(new ClearPoints());
|
||||||
|
this.getCommand("exec").setExecutor(new ExecuteTraveling());
|
||||||
|
this.getCommand("show").setExecutor(new ShowCurve());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void compute() {
|
||||||
|
curve = new ArrayList<>();
|
||||||
|
|
||||||
|
for (float t = 0; t < 1; t += dt) {
|
||||||
|
ArrayList<Location> P = new ArrayList<>(Camera.controlPoints);
|
||||||
|
|
||||||
|
int N = P.size();
|
||||||
|
for (int k = N - 1; k > 0; k--) {
|
||||||
|
for (int i = 0; i < k; i++) {
|
||||||
|
P.set(i, P.get(i).clone().multiply(1 - t)
|
||||||
|
.add(P.get(i + 1).clone().multiply(t)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curve.add(P.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void broadlog(String msg) {
|
||||||
|
Camera.logger.log(Level.INFO, msg);
|
||||||
|
Bukkit.broadcastMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String prettyLocation(Location point) {
|
||||||
|
return String.format("X=%05.2f, Y=%05.2f, Z=%05.2f, P=%05.2f, Y=%05.2f",
|
||||||
|
point.getX(), point.getY(), point.getZ(),
|
||||||
|
point.getPitch(), point.getYaw());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
locations = null;
|
ClearPoints.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,12 @@ package com.tocard.cam;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
public class ClearPoints implements CommandExecutor {
|
public class ClearPoints implements CommandExecutor {
|
||||||
|
@ -12,8 +15,25 @@ public class ClearPoints implements CommandExecutor {
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
if (sender instanceof Player) {
|
if (sender instanceof Player) {
|
||||||
Camera.locations = new ArrayList<>();
|
clear();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
Camera.broadlog("Clearing points:");
|
||||||
|
ListPoints.listPoints();
|
||||||
|
|
||||||
|
Camera.controlPoints = new ArrayList<>();
|
||||||
|
Camera.broadlog("Points cleared !");
|
||||||
|
|
||||||
|
for (World world : Bukkit.getWorlds()) {
|
||||||
|
for (Entity e : world.getEntities()) {
|
||||||
|
if (e.getScoreboardTags().contains("controlPoint")) {
|
||||||
|
e.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
package com.tocard.cam;
|
package com.tocard.cam;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class Test implements CommandExecutor {
|
public class ExecuteTraveling implements CommandExecutor {
|
||||||
|
|
||||||
// This method is called, when somebody uses our command
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
if (sender instanceof Player) {
|
if (sender instanceof Player) {
|
||||||
|
@ -23,31 +19,13 @@ public class Test implements CommandExecutor {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Player player = (Player) sender;
|
Player player = (Player) sender;
|
||||||
|
|
||||||
ArrayList<Location> bezier = new ArrayList<>();
|
Iterator<Location> curveIterator = Camera.curve.iterator();
|
||||||
|
|
||||||
for (float t = 0; t < 1; t += 0.1) {
|
|
||||||
ArrayList<Location> P = new ArrayList<>(Camera.locations);
|
|
||||||
|
|
||||||
int N = P.size();
|
|
||||||
for (int k = N - 1; k > 0; k--) {
|
|
||||||
for (int i = 0; i < k; i++) {
|
|
||||||
P.set(i, P.get(i).clone().multiply(1 - t)
|
|
||||||
.add(P.get(i + 1).clone().multiply(t)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bezier.add(P.get(0));
|
|
||||||
|
|
||||||
t += 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<Location> bezierIt = bezier.iterator();
|
|
||||||
|
|
||||||
int taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Camera.plugin, new Runnable() {
|
int taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Camera.plugin, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
player.teleport(bezierIt.next());
|
player.teleport(curveIterator.next());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
package com.tocard.cam;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
public class GotoPoint implements CommandExecutor {
|
|
||||||
|
|
||||||
// This method is called, when somebody uses our command
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
|
||||||
if (sender instanceof Player) {
|
|
||||||
Player player = (Player) sender;
|
|
||||||
|
|
||||||
int index = Integer.parseInt(args[0]);
|
|
||||||
Location location = Camera.locations.get(index);
|
|
||||||
|
|
||||||
Camera.logger.log(Level.INFO, "teleporting player to point n°" + index);
|
|
||||||
player.teleport(location);
|
|
||||||
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,6 @@
|
||||||
package com.tocard.cam;
|
package com.tocard.cam;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.ListIterator;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
@ -11,15 +9,24 @@ import org.bukkit.entity.Player;
|
||||||
|
|
||||||
public class ListPoints implements CommandExecutor {
|
public class ListPoints implements CommandExecutor {
|
||||||
|
|
||||||
// This method is called, when somebody uses our command
|
private static ListIterator<Location> points;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
if (sender instanceof Player) {
|
if (sender instanceof Player) {
|
||||||
for (Location location : Camera.locations) {
|
Camera.broadlog("List of points:");
|
||||||
Camera.logger.log(Level.INFO, "points: " + Camera.locations);
|
listPoints();
|
||||||
Bukkit.broadcastMessage(location.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void listPoints() {
|
||||||
|
// get the controlPoints iterator
|
||||||
|
points = Camera.controlPoints.listIterator();
|
||||||
|
|
||||||
|
// print them one by one
|
||||||
|
while (points.hasNext()) {
|
||||||
|
Camera.broadlog("Point n°" + points.nextIndex() + ": " + Camera.prettyLocation(points.next()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package com.tocard.cam;
|
package com.tocard.cam;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
@ -10,14 +8,54 @@ import org.bukkit.entity.Player;
|
||||||
|
|
||||||
public class NewPoint implements CommandExecutor {
|
public class NewPoint implements CommandExecutor {
|
||||||
|
|
||||||
// This method is called, when somebody uses our command
|
private Location location;
|
||||||
|
private Player player;
|
||||||
|
private int index;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
try {
|
||||||
if (sender instanceof Player) {
|
if (sender instanceof Player) {
|
||||||
Player player = (Player) sender;
|
player = (Player) sender;
|
||||||
Location player_location = player.getLocation();
|
switch (args[0]) {
|
||||||
Camera.logger.log(Level.INFO, "new point: " + player_location);
|
case "add":
|
||||||
Camera.locations.add(player_location);
|
location = player.getLocation();
|
||||||
|
Camera.controlPoints.add(location);
|
||||||
|
Camera.broadlog("Point added: " + Camera.prettyLocation(location));
|
||||||
|
ShowCurve.add(location, player.getWorld());
|
||||||
|
break;
|
||||||
|
case "ins":
|
||||||
|
index = Integer.parseInt(args[1]);
|
||||||
|
location = player.getLocation();
|
||||||
|
Camera.controlPoints.add(index, location);
|
||||||
|
Camera.broadlog("Point added: " + Camera.prettyLocation(location));
|
||||||
|
ShowCurve.insert(index, location, player.getWorld());
|
||||||
|
break;
|
||||||
|
case "rm":
|
||||||
|
index = Integer.parseInt(args[1]);
|
||||||
|
location = Camera.controlPoints.get(index);
|
||||||
|
Camera.controlPoints.remove(location);
|
||||||
|
Camera.broadlog("Point deleted: " + Camera.prettyLocation(location));
|
||||||
|
ShowCurve.rm(index);
|
||||||
|
break;
|
||||||
|
case "set":
|
||||||
|
index = Integer.parseInt(args[1]);
|
||||||
|
location = player.getLocation();
|
||||||
|
Camera.controlPoints.set(index, location);
|
||||||
|
Camera.broadlog("Point n°" + index + " set: " + Camera.prettyLocation(location));
|
||||||
|
ShowCurve.set(index, location, player.getWorld());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Camera.broadlog("Wrong command usage");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Camera.controlPoints.size() > 0) {
|
||||||
|
Camera.compute(); // update camera path
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
129
src/main/java/com/tocard/cam/ShowCurve.java
Normal file
129
src/main/java/com/tocard/cam/ShowCurve.java
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package com.tocard.cam;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.ArmorStand;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
public class ShowCurve implements CommandExecutor {
|
||||||
|
|
||||||
|
public static List<ArmorStand> controlPointsArmorStands = new ArrayList<>();
|
||||||
|
private static ItemStack cameraHead = initCameraHead();
|
||||||
|
public static int showTaskID = -1;
|
||||||
|
|
||||||
|
private static ItemStack initCameraHead() {
|
||||||
|
ItemStack cameraHead = new ItemStack(Material.PLAYER_HEAD);
|
||||||
|
SkullMeta skullMeta = (SkullMeta) cameraHead.getItemMeta();
|
||||||
|
skullMeta.setOwningPlayer(
|
||||||
|
Bukkit.getOfflinePlayer(
|
||||||
|
UUID.fromString("c9560dfb-a792-4226-ad06-db1b6dc40b95")));
|
||||||
|
cameraHead.setItemMeta(skullMeta);
|
||||||
|
return cameraHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// c9560dfb-a792-4226-ad06-db1b6dc40b95
|
||||||
|
// c9560dfba7924226ad06db1b6dc40b95
|
||||||
|
|
||||||
|
// This method is called, when somebody uses our command
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (sender instanceof Player) {
|
||||||
|
try {
|
||||||
|
Bukkit.getScheduler().cancelTask(Integer.parseInt(args[0]));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
if (ShowCurve.showTaskID < 0) {
|
||||||
|
// Show control points
|
||||||
|
for (ArmorStand as : controlPointsArmorStands) {
|
||||||
|
as.getEquipment().setHelmet(cameraHead);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start showing curve
|
||||||
|
ShowCurve.showTaskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Camera.plugin,
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Get curve iterator
|
||||||
|
Iterator<Location> curveIterator = Camera.curve.iterator();
|
||||||
|
|
||||||
|
// Summon particles
|
||||||
|
while (curveIterator.hasNext()) {
|
||||||
|
player.getWorld().spawnParticle(Particle.FLAME,
|
||||||
|
curveIterator.next().clone().add(0, 1.8, 0), 1,
|
||||||
|
0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 0);
|
||||||
|
|
||||||
|
// Broadcast TaskID
|
||||||
|
Bukkit.broadcastMessage("Show curve : " + ShowCurve.showTaskID);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Cancel show task
|
||||||
|
Bukkit.getScheduler().cancelTask(ShowCurve.showTaskID);
|
||||||
|
ShowCurve.showTaskID = -1;
|
||||||
|
|
||||||
|
// Kill control points' armorstand
|
||||||
|
for (ArmorStand as : controlPointsArmorStands) {
|
||||||
|
as.getEquipment().setHelmet(new ItemStack(Material.AIR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void add(Location point, World world) {
|
||||||
|
ArmorStand as = world.spawn(point, ArmorStand.class);
|
||||||
|
as.setGravity(false);
|
||||||
|
as.setVisible(false);
|
||||||
|
as.setMarker(true);
|
||||||
|
as.addScoreboardTag("controlPoint");
|
||||||
|
if (showTaskID > 0)
|
||||||
|
as.getEquipment().setHelmet(cameraHead);
|
||||||
|
controlPointsArmorStands.add(as);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void insert(int index, Location point, World world) {
|
||||||
|
ArmorStand as = world.spawn(point, ArmorStand.class);
|
||||||
|
as.setGravity(false);
|
||||||
|
as.setVisible(false);
|
||||||
|
as.setMarker(true);
|
||||||
|
as.addScoreboardTag("controlPoint");
|
||||||
|
if (showTaskID > 0)
|
||||||
|
as.getEquipment().setHelmet(cameraHead);
|
||||||
|
controlPointsArmorStands.add(index, as);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(int index, Location point, World world) {
|
||||||
|
ArmorStand as = world.spawn(point, ArmorStand.class);
|
||||||
|
as.setGravity(false);
|
||||||
|
as.setVisible(false);
|
||||||
|
as.setMarker(true);
|
||||||
|
as.addScoreboardTag("controlPoint");
|
||||||
|
if (showTaskID > 0)
|
||||||
|
as.getEquipment().setHelmet(cameraHead);
|
||||||
|
controlPointsArmorStands.set(index, as).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void rm(int index) {
|
||||||
|
controlPointsArmorStands.remove(index).remove();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,22 +6,22 @@ author: Turbo Tocard
|
||||||
description: This plugin is so fucking lit !
|
description: This plugin is so fucking lit !
|
||||||
commands:
|
commands:
|
||||||
point:
|
point:
|
||||||
description: Add a new camera point
|
description: Manage camera points
|
||||||
usage: /point
|
usage: /point
|
||||||
permission: com.tocard.cam.addPoint
|
permission: com.tocard.cam.managePoints
|
||||||
points:
|
points:
|
||||||
description: List camera points
|
description: List camera points
|
||||||
usage: /points
|
usage: /points
|
||||||
permission: com.tocard.cam.listPoints
|
permission: com.tocard.cam.listPoints
|
||||||
goto:
|
|
||||||
description: Goto camera n°i
|
|
||||||
usage: /goto i
|
|
||||||
permission: com.tocard.cam.gotoPoint
|
|
||||||
reset:
|
reset:
|
||||||
description: Clear camera points
|
description: Clear camera points
|
||||||
usage: /reset
|
usage: /reset
|
||||||
permission: com.tocard.cam.resetPoints
|
permission: com.tocard.cam.resetPoints
|
||||||
test:
|
exec:
|
||||||
description: Test
|
description: Execute the traveling path
|
||||||
usage: /test
|
usage: /exec
|
||||||
permission: com.tocard.cam.test
|
permission: com.tocard.cam.exec
|
||||||
|
show:
|
||||||
|
description: Show the traveling path
|
||||||
|
usage: /show
|
||||||
|
permission: com.tocard.cam.showCurve
|
||||||
|
|
Loading…
Reference in a new issue