mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-11 02:54:04 +09:00
added sources for Slick
Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054 Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
29
lib/slick-source/org/newdawn/slick/util/Bootstrap.java
Normal file
29
lib/slick-source/org/newdawn/slick/util/Bootstrap.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import org.newdawn.slick.AppGameContainer;
|
||||
import org.newdawn.slick.Game;
|
||||
|
||||
/**
|
||||
* Utility class to wrap up starting a game in a single line
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class Bootstrap {
|
||||
|
||||
/**
|
||||
* Start the game as an application
|
||||
*
|
||||
* @param game The game to be started
|
||||
* @param width The width of the window
|
||||
* @param height The height of the window
|
||||
* @param fullscreen True if the window should be fullscreen
|
||||
*/
|
||||
public static void runAsApplication(Game game, int width, int height, boolean fullscreen) {
|
||||
try {
|
||||
AppGameContainer container = new AppGameContainer(game, width, height, fullscreen);
|
||||
container.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
162
lib/slick-source/org/newdawn/slick/util/BufferedImageUtil.java
Normal file
162
lib/slick-source/org/newdawn/slick/util/BufferedImageUtil.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.newdawn.slick.opengl.ImageIOImageData;
|
||||
import org.newdawn.slick.opengl.InternalTextureLoader;
|
||||
import org.newdawn.slick.opengl.Texture;
|
||||
import org.newdawn.slick.opengl.TextureImpl;
|
||||
import org.newdawn.slick.opengl.renderer.Renderer;
|
||||
import org.newdawn.slick.opengl.renderer.SGL;
|
||||
|
||||
/**
|
||||
* This is a utility class that allows you to convert a BufferedImage into a
|
||||
* texture.
|
||||
*
|
||||
* @author James Chambers (Jimmy)
|
||||
* @author Jeremy Adams (elias_naur)
|
||||
* @author Kevin Glass (kevglass)
|
||||
*/
|
||||
|
||||
public class BufferedImageUtil {
|
||||
|
||||
/**
|
||||
* Load a texture
|
||||
*
|
||||
* @param resourceName
|
||||
* The location of the resource to load
|
||||
* @param resourceImage
|
||||
* The BufferedImage we are converting
|
||||
* @return The loaded texture
|
||||
* @throws IOException
|
||||
* Indicates a failure to access the resource
|
||||
*/
|
||||
public static Texture getTexture(String resourceName,
|
||||
BufferedImage resourceImage) throws IOException {
|
||||
Texture tex = getTexture(resourceName, resourceImage,
|
||||
SGL.GL_TEXTURE_2D, // target
|
||||
SGL.GL_RGBA8, // dest pixel format
|
||||
SGL.GL_LINEAR, // min filter (unused)
|
||||
SGL.GL_LINEAR);
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a texture
|
||||
*
|
||||
* @param resourceName
|
||||
* The location of the resource to load
|
||||
* @param resourceImage
|
||||
* The BufferedImage we are converting
|
||||
* @return The loaded texture
|
||||
* @throws IOException
|
||||
* Indicates a failure to access the resource
|
||||
*/
|
||||
public static Texture getTexture(String resourceName,
|
||||
BufferedImage resourceImage, int filter) throws IOException {
|
||||
Texture tex = getTexture(resourceName, resourceImage,
|
||||
SGL.GL_TEXTURE_2D, // target
|
||||
SGL.GL_RGBA8, // dest pixel format
|
||||
filter, // min filter (unused)
|
||||
filter);
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a texture into OpenGL from a BufferedImage
|
||||
*
|
||||
* @param resourceName
|
||||
* The location of the resource to load
|
||||
* @param resourceimage
|
||||
* The BufferedImage we are converting
|
||||
* @param target
|
||||
* The GL target to load the texture against
|
||||
* @param dstPixelFormat
|
||||
* The pixel format of the screen
|
||||
* @param minFilter
|
||||
* The minimising filter
|
||||
* @param magFilter
|
||||
* The magnification filter
|
||||
* @return The loaded texture
|
||||
* @throws IOException
|
||||
* Indicates a failure to access the resource
|
||||
*/
|
||||
public static Texture getTexture(String resourceName,
|
||||
BufferedImage resourceimage, int target, int dstPixelFormat,
|
||||
int minFilter, int magFilter) throws IOException {
|
||||
ImageIOImageData data = new ImageIOImageData();int srcPixelFormat = 0;
|
||||
|
||||
// create the texture ID for this texture
|
||||
int textureID = InternalTextureLoader.createTextureID();
|
||||
TextureImpl texture = new TextureImpl(resourceName, target, textureID);
|
||||
|
||||
// Enable texturing
|
||||
Renderer.get().glEnable(SGL.GL_TEXTURE_2D);
|
||||
|
||||
// bind this texture
|
||||
Renderer.get().glBindTexture(target, textureID);
|
||||
|
||||
BufferedImage bufferedImage = resourceimage;
|
||||
texture.setWidth(bufferedImage.getWidth());
|
||||
texture.setHeight(bufferedImage.getHeight());
|
||||
|
||||
if (bufferedImage.getColorModel().hasAlpha()) {
|
||||
srcPixelFormat = SGL.GL_RGBA;
|
||||
} else {
|
||||
srcPixelFormat = SGL.GL_RGB;
|
||||
}
|
||||
|
||||
// convert that image into a byte buffer of texture data
|
||||
ByteBuffer textureBuffer = data.imageToByteBuffer(bufferedImage, false, false, null);
|
||||
texture.setTextureHeight(data.getTexHeight());
|
||||
texture.setTextureWidth(data.getTexWidth());
|
||||
texture.setAlpha(data.getDepth() == 32);
|
||||
|
||||
if (target == SGL.GL_TEXTURE_2D) {
|
||||
Renderer.get().glTexParameteri(target, SGL.GL_TEXTURE_MIN_FILTER, minFilter);
|
||||
Renderer.get().glTexParameteri(target, SGL.GL_TEXTURE_MAG_FILTER, magFilter);
|
||||
|
||||
if (Renderer.get().canTextureMirrorClamp()) {
|
||||
Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT);
|
||||
Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT);
|
||||
} else {
|
||||
Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_CLAMP);
|
||||
Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_CLAMP);
|
||||
}
|
||||
}
|
||||
|
||||
Renderer.get().glTexImage2D(target,
|
||||
0,
|
||||
dstPixelFormat,
|
||||
texture.getTextureWidth(),
|
||||
texture.getTextureHeight(),
|
||||
0,
|
||||
srcPixelFormat,
|
||||
SGL.GL_UNSIGNED_BYTE,
|
||||
textureBuffer);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement of transform copy area for 1.4
|
||||
*
|
||||
* @param image The image to copy
|
||||
* @param x The x position to copy to
|
||||
* @param y The y position to copy to
|
||||
* @param width The width of the image
|
||||
* @param height The height of the image
|
||||
* @param dx The transform on the x axis
|
||||
* @param dy The transform on the y axis
|
||||
*/
|
||||
private static void copyArea(BufferedImage image, int x, int y, int width, int height, int dx, int dy) {
|
||||
Graphics2D g = (Graphics2D) image.getGraphics();
|
||||
|
||||
g.drawImage(image.getSubimage(x, y, width, height),x+dx,y+dy,null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* A resource location that searches the classpath
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class ClasspathLocation implements ResourceLocation {
|
||||
/**
|
||||
* @see org.newdawn.slick.util.ResourceLocation#getResource(java.lang.String)
|
||||
*/
|
||||
public URL getResource(String ref) {
|
||||
String cpRef = ref.replace('\\', '/');
|
||||
return ResourceLoader.class.getClassLoader().getResource(cpRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.ResourceLocation#getResourceAsStream(java.lang.String)
|
||||
*/
|
||||
public InputStream getResourceAsStream(String ref) {
|
||||
String cpRef = ref.replace('\\', '/');
|
||||
return ResourceLoader.class.getClassLoader().getResourceAsStream(cpRef);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* The default implementation that just spits the messages out to stdout
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class DefaultLogSystem implements LogSystem {
|
||||
/** The output stream for dumping the log out on */
|
||||
public static PrintStream out = System.out;
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param message The message describing the error
|
||||
* @param e The exception causing the error
|
||||
*/
|
||||
public void error(String message, Throwable e) {
|
||||
error(message);
|
||||
error(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param e The exception causing the error
|
||||
*/
|
||||
public void error(Throwable e) {
|
||||
out.println(new Date()+" ERROR:" +e.getMessage());
|
||||
e.printStackTrace(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param message The message describing the error
|
||||
*/
|
||||
public void error(String message) {
|
||||
out.println(new Date()+" ERROR:" +message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*
|
||||
* @param message The message describing the warning
|
||||
*/
|
||||
public void warn(String message) {
|
||||
out.println(new Date()+" WARN:" +message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an information message
|
||||
*
|
||||
* @param message The message describing the infomation
|
||||
*/
|
||||
public void info(String message) {
|
||||
out.println(new Date()+" INFO:" +message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a debug message
|
||||
*
|
||||
* @param message The message describing the debug
|
||||
*/
|
||||
public void debug(String message) {
|
||||
out.println(new Date()+" DEBUG:" +message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning with an exception that caused it
|
||||
*
|
||||
* @param message The message describing the warning
|
||||
* @param e The cause of the warning
|
||||
*/
|
||||
public void warn(String message, Throwable e) {
|
||||
warn(message);
|
||||
e.printStackTrace(out);
|
||||
}
|
||||
}
|
||||
56
lib/slick-source/org/newdawn/slick/util/FastTrig.java
Normal file
56
lib/slick-source/org/newdawn/slick/util/FastTrig.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
/**
|
||||
* Utility to handle Java's odd trig performance issues
|
||||
*
|
||||
* @author JeffK
|
||||
*/
|
||||
public class FastTrig {
|
||||
/**
|
||||
* Fast Trig functions for x86.
|
||||
* This forces the trig functiosn to stay within the safe area on the x86 processor (-45 degrees to +45 degrees)
|
||||
* The results may be very slightly off from what the Math and StrictMath trig functions give due to
|
||||
* rounding in the angle reduction but it will be very very close.
|
||||
*
|
||||
* @param radians The original angle
|
||||
* @return The reduced Sin angle
|
||||
*/
|
||||
private static double reduceSinAngle(double radians) {
|
||||
double orig = radians;
|
||||
radians %= Math.PI * 2.0; // put us in -2PI to +2PI space
|
||||
if (Math.abs(radians) > Math.PI) { // put us in -PI to +PI space
|
||||
radians = radians - (Math.PI * 2.0);
|
||||
}
|
||||
if (Math.abs(radians) > Math.PI / 2) {// put us in -PI/2 to +PI/2 space
|
||||
radians = Math.PI - radians;
|
||||
}
|
||||
|
||||
return radians;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sine of an angle
|
||||
*
|
||||
* @param radians The angle
|
||||
* @return The sine of the angle
|
||||
*/
|
||||
public static double sin(double radians) {
|
||||
radians = reduceSinAngle(radians); // limits angle to between -PI/2 and +PI/2
|
||||
if (Math.abs(radians) <= Math.PI / 4) {
|
||||
return Math.sin(radians);
|
||||
} else {
|
||||
return Math.cos(Math.PI / 2 - radians);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cosine of an angle
|
||||
*
|
||||
* @param radians The angle
|
||||
* @return The cosine of the angle
|
||||
*/
|
||||
public static double cos(double radians) {
|
||||
return sin(radians + Math.PI / 2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* A resource loading location that searches somewhere on the classpath
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class FileSystemLocation implements ResourceLocation {
|
||||
/** The root of the file system to search */
|
||||
private File root;
|
||||
|
||||
/**
|
||||
* Create a new resoruce location based on the file system
|
||||
*
|
||||
* @param root The root of the file system to search
|
||||
*/
|
||||
public FileSystemLocation(File root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ResourceLocation#getResource(String)
|
||||
*/
|
||||
public URL getResource(String ref) {
|
||||
try {
|
||||
File file = new File(root, ref);
|
||||
if (!file.exists()) {
|
||||
file = new File(ref);
|
||||
}
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return file.toURI().toURL();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ResourceLocation#getResourceAsStream(String)
|
||||
*/
|
||||
public InputStream getResourceAsStream(String ref) {
|
||||
try {
|
||||
File file = new File(root, ref);
|
||||
if (!file.exists()) {
|
||||
file = new File(ref);
|
||||
}
|
||||
return new FileInputStream(file);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
220
lib/slick-source/org/newdawn/slick/util/FontUtils.java
Normal file
220
lib/slick-source/org/newdawn/slick/util/FontUtils.java
Normal file
@@ -0,0 +1,220 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Font;
|
||||
|
||||
/**
|
||||
* Simple utility class to support justified text
|
||||
*
|
||||
* http://slick.javaunlimited.net/viewtopic.php?t=2640
|
||||
*
|
||||
* @author zenzei
|
||||
*/
|
||||
public class FontUtils {
|
||||
/**
|
||||
* Alignment indicators
|
||||
*/
|
||||
public class Alignment {
|
||||
/** Left alignment */
|
||||
public static final int LEFT = 1;
|
||||
/** Center alignment */
|
||||
public static final int CENTER = 2;
|
||||
/** Right alignment */
|
||||
public static final int RIGHT = 3;
|
||||
/** Justify alignment */
|
||||
public static final int JUSTIFY = 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text left justified
|
||||
*
|
||||
* @param font The font to draw with
|
||||
* @param s The string to draw
|
||||
* @param x The x location to draw at
|
||||
* @param y The y location to draw at
|
||||
*/
|
||||
public static void drawLeft(Font font, String s, int x, int y) {
|
||||
drawString(font, s, Alignment.LEFT, x, y, 0, Color.white);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text center justified
|
||||
*
|
||||
* @param font The font to draw with
|
||||
* @param s The string to draw
|
||||
* @param x The x location to draw at
|
||||
* @param y The y location to draw at
|
||||
* @param width The width to fill with the text
|
||||
*/
|
||||
public static void drawCenter(Font font, String s, int x, int y, int width) {
|
||||
drawString(font, s, Alignment.CENTER, x, y, width, Color.white);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text center justified
|
||||
*
|
||||
* @param font The font to draw with
|
||||
* @param s The string to draw
|
||||
* @param x The x location to draw at
|
||||
* @param y The y location to draw at
|
||||
* @param width The width to fill with the text
|
||||
* @param color The color to draw in
|
||||
*/
|
||||
public static void drawCenter(Font font, String s, int x, int y, int width,
|
||||
Color color) {
|
||||
drawString(font, s, Alignment.CENTER, x, y, width, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text right justified
|
||||
*
|
||||
* @param font The font to draw with
|
||||
* @param s The string to draw
|
||||
* @param x The x location to draw at
|
||||
* @param y The y location to draw at
|
||||
* @param width The width to fill with the text
|
||||
*/
|
||||
public static void drawRight(Font font, String s, int x, int y, int width) {
|
||||
drawString(font, s, Alignment.RIGHT, x, y, width, Color.white);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text right justified
|
||||
*
|
||||
* @param font The font to draw with
|
||||
* @param s The string to draw
|
||||
* @param x The x location to draw at
|
||||
* @param y The y location to draw at
|
||||
* @param width The width to fill with the text
|
||||
* @param color The color to draw in
|
||||
*/
|
||||
public static void drawRight(Font font, String s, int x, int y, int width,
|
||||
Color color) {
|
||||
drawString(font, s, Alignment.RIGHT, x, y, width, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a string
|
||||
*
|
||||
* @param font The font to draw with
|
||||
* @param s The text to draw
|
||||
* @param alignment The alignment to apply
|
||||
* @param x The x location to draw at
|
||||
* @param y The y location to draw at
|
||||
* @param width The width to fill with the string
|
||||
* @param color The color to draw in
|
||||
* @return The final x coordinate of the text
|
||||
*/
|
||||
public static final int drawString(Font font, final String s,
|
||||
final int alignment, final int x, final int y, final int width,
|
||||
Color color) {
|
||||
int resultingXCoordinate = 0;
|
||||
if (alignment == Alignment.LEFT) {
|
||||
font.drawString(x, y, s, color);
|
||||
} else if (alignment == Alignment.CENTER) {
|
||||
font.drawString(x + (width / 2) - (font.getWidth(s) / 2), y, s,
|
||||
color);
|
||||
} else if (alignment == Alignment.RIGHT) {
|
||||
font.drawString(x + width - font.getWidth(s), y, s, color);
|
||||
} else if (alignment == Alignment.JUSTIFY) {
|
||||
// calculate left width
|
||||
int leftWidth = width - font.getWidth(s);
|
||||
if (leftWidth <= 0) {
|
||||
// no width left, use standard draw string
|
||||
font.drawString(x, y, s, color);
|
||||
}
|
||||
|
||||
return FontUtils.drawJustifiedSpaceSeparatedSubstrings(font, s, x,
|
||||
y, FontUtils.calculateWidthOfJustifiedSpaceInPixels(font,
|
||||
s, leftWidth));
|
||||
}
|
||||
|
||||
return resultingXCoordinate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and returns the width of a single justified space for the
|
||||
* given {@link String}, in pixels.
|
||||
*
|
||||
* @param font The font to draw with
|
||||
* @param s
|
||||
* The given non-null {@link String} to use to calculate the
|
||||
* width of a space for.
|
||||
* @param leftWidth
|
||||
* The integer specifying the left width buffer to use to
|
||||
* calculate how much space a space should take up in
|
||||
* justification.
|
||||
* @return The width of a single justified space for the given
|
||||
* {@link String}, in pixels.
|
||||
*/
|
||||
private static int calculateWidthOfJustifiedSpaceInPixels(final Font font,
|
||||
final String s, final int leftWidth) {
|
||||
int space = 0; // hold total space; hold space width in pixel
|
||||
int curpos = 0; // current string position
|
||||
|
||||
// count total space
|
||||
while (curpos < s.length()) {
|
||||
if (s.charAt(curpos++) == ' ') {
|
||||
space++;
|
||||
}
|
||||
}
|
||||
|
||||
if (space > 0) {
|
||||
// width left plus with total space
|
||||
// space width (in pixel) = width left / total space
|
||||
space = (leftWidth + (font.getWidth(" ") * space)) / space;
|
||||
}
|
||||
return space;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws justified-space separated substrings based on the given
|
||||
* {@link String} and the given starting x and y coordinates to the given
|
||||
* {@link Graphics2D} instance.
|
||||
*
|
||||
* @param font
|
||||
* The font to draw with
|
||||
* @param s
|
||||
* The non-null {@link String} to draw as space-separated
|
||||
* substrings.
|
||||
* @param x
|
||||
* The given starting x-coordinate position to use to draw the
|
||||
* {@link String}.
|
||||
* @param y
|
||||
* The given starting y-coordinate position to use to draw the
|
||||
* {@link String}.
|
||||
* @param justifiedSpaceWidth
|
||||
* The integer specifying the width of a justified space
|
||||
* {@link String}, in pixels.
|
||||
* @return The resulting x-coordinate of the current cursor after the
|
||||
* drawing operation completes.
|
||||
* @throws NullPointerException
|
||||
* Throws a {@link NullPointerException} if any of the given
|
||||
* arguments are null.
|
||||
*/
|
||||
private static int drawJustifiedSpaceSeparatedSubstrings(Font font,
|
||||
final String s, final int x, final int y,
|
||||
final int justifiedSpaceWidth) {
|
||||
int curpos = 0;
|
||||
int endpos = 0;
|
||||
int resultingXCoordinate = x;
|
||||
while (curpos < s.length()) {
|
||||
endpos = s.indexOf(' ', curpos); // find space
|
||||
if (endpos == -1) {
|
||||
endpos = s.length(); // no space, draw all string directly
|
||||
}
|
||||
String substring = s.substring(curpos, endpos);
|
||||
|
||||
font.drawString(resultingXCoordinate, y, substring);
|
||||
|
||||
resultingXCoordinate += font.getWidth(substring)
|
||||
+ justifiedSpaceWidth; // increase
|
||||
// x-coordinate
|
||||
curpos = endpos + 1;
|
||||
}
|
||||
|
||||
return resultingXCoordinate;
|
||||
}
|
||||
}
|
||||
154
lib/slick-source/org/newdawn/slick/util/InputAdapter.java
Normal file
154
lib/slick-source/org/newdawn/slick/util/InputAdapter.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import org.newdawn.slick.Input;
|
||||
import org.newdawn.slick.InputListener;
|
||||
|
||||
/**
|
||||
* An implement implementation of the InputListener interface
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class InputAdapter implements InputListener {
|
||||
/** A flag to indicate if we're accepting input here */
|
||||
private boolean acceptingInput = true;
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerButtonPressed(int, int)
|
||||
*/
|
||||
public void controllerButtonPressed(int controller, int button) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerButtonReleased(int, int)
|
||||
*/
|
||||
public void controllerButtonReleased(int controller, int button) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerDownPressed(int)
|
||||
*/
|
||||
public void controllerDownPressed(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerDownReleased(int)
|
||||
*/
|
||||
public void controllerDownReleased(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerLeftPressed(int)
|
||||
*/
|
||||
public void controllerLeftPressed(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerLeftReleased(int)
|
||||
*/
|
||||
public void controllerLeftReleased(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerRightPressed(int)
|
||||
*/
|
||||
public void controllerRightPressed(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerRightReleased(int)
|
||||
*/
|
||||
public void controllerRightReleased(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerUpPressed(int)
|
||||
*/
|
||||
public void controllerUpPressed(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#controllerUpReleased(int)
|
||||
*/
|
||||
public void controllerUpReleased(int controller) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#inputEnded()
|
||||
*/
|
||||
public void inputEnded() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#isAcceptingInput()
|
||||
*/
|
||||
public boolean isAcceptingInput() {
|
||||
return acceptingInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if we should be accepting input of any sort
|
||||
*
|
||||
* @param acceptingInput True if we should accept input
|
||||
*/
|
||||
public void setAcceptingInput(boolean acceptingInput) {
|
||||
this.acceptingInput = acceptingInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#keyPressed(int, char)
|
||||
*/
|
||||
public void keyPressed(int key, char c) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#keyReleased(int, char)
|
||||
*/
|
||||
public void keyReleased(int key, char c) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#mouseMoved(int, int, int, int)
|
||||
*/
|
||||
public void mouseMoved(int oldx, int oldy, int newx, int newy) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#mousePressed(int, int, int)
|
||||
*/
|
||||
public void mousePressed(int button, int x, int y) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#mouseReleased(int, int, int)
|
||||
*/
|
||||
public void mouseReleased(int button, int x, int y) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#mouseWheelMoved(int)
|
||||
*/
|
||||
public void mouseWheelMoved(int change) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#setInput(org.newdawn.slick.Input)
|
||||
*/
|
||||
public void setInput(Input input) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.InputListener#mouseClicked(int, int, int, int)
|
||||
*/
|
||||
public void mouseClicked(int button, int x, int y, int clickCount) {
|
||||
}
|
||||
|
||||
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.ControlledInputReciever#inputStarted()
|
||||
*/
|
||||
public void inputStarted() {
|
||||
|
||||
}
|
||||
}
|
||||
137
lib/slick-source/org/newdawn/slick/util/LocatedImage.java
Normal file
137
lib/slick-source/org/newdawn/slick/util/LocatedImage.java
Normal file
@@ -0,0 +1,137 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
|
||||
/**
|
||||
* An image along with state information that allows it to be drawn without
|
||||
* specifing the state in which to render.
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class LocatedImage {
|
||||
/** The image to be rendered - prefer aggregation */
|
||||
private Image image;
|
||||
/** The x coordinate at which the image should be rendered */
|
||||
private int x;
|
||||
/** The y coordinate at which the image should be rendered */
|
||||
private int y;
|
||||
/** The filter to apply across the image */
|
||||
private Color filter = Color.white;
|
||||
/** The width to render the image */
|
||||
private float width;
|
||||
/** The height to render the image */
|
||||
private float height;
|
||||
|
||||
/**
|
||||
* Create a new located image
|
||||
*
|
||||
* @param image The image to be drawn
|
||||
* @param x The x location at which the image should be drawn
|
||||
* @param y The y location at which the image should be drawn
|
||||
*/
|
||||
public LocatedImage(Image image, int x, int y) {
|
||||
this.image = image;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = image.getWidth();
|
||||
this.height = image.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height the image will be drawn at
|
||||
*
|
||||
* @return The height
|
||||
*/
|
||||
public float getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width the image will be drawn at
|
||||
*
|
||||
* @return The width
|
||||
*/
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the height the image should be drawn at
|
||||
*
|
||||
* @param height The height the image should be drawn at
|
||||
*/
|
||||
public void setHeight(float height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width the image should be drawn at
|
||||
*
|
||||
* @param width The width the image should be drawn at
|
||||
*/
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the colour filter to apply to the image
|
||||
*
|
||||
* @param c The color filter to apply to the image
|
||||
*/
|
||||
public void setColor(Color c) {
|
||||
this.filter = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the colour filter being applied
|
||||
*
|
||||
* @return The color the being applied
|
||||
*/
|
||||
public Color getColor() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the x position at which the image should be drawn
|
||||
*
|
||||
* @param x The x coordinate of the position
|
||||
*/
|
||||
public void setX(int x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the y position at which the image should be drawn
|
||||
*
|
||||
* @param y The y coordinate of the position
|
||||
*/
|
||||
public void setY(int y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x position at which the image will be drawn
|
||||
*
|
||||
* @return The x position at which the image will be drawn
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y position at which the image will be drawn
|
||||
*
|
||||
* @return The y position at which the image will be drawn
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the image based on the current configured state
|
||||
*/
|
||||
public void draw() {
|
||||
image.draw(x,y,width,height,filter);
|
||||
}
|
||||
}
|
||||
161
lib/slick-source/org/newdawn/slick/util/Log.java
Normal file
161
lib/slick-source/org/newdawn/slick/util/Log.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
/**
|
||||
* A simple central logging system
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public final class Log {
|
||||
/** True if we're doing verbose logging INFO and DEBUG */
|
||||
private static boolean verbose = true;
|
||||
/** true if activated by the system property "org.newdawn.slick.forceVerboseLog" */
|
||||
private static boolean forcedVerbose = false;
|
||||
|
||||
/**
|
||||
* The debug property which can be set via JNLP or startup parameter to switch
|
||||
* logging mode to verbose for games that were released without verbose logging
|
||||
* value must be "true"
|
||||
*/
|
||||
private static final String forceVerboseProperty = "org.newdawn.slick.forceVerboseLog";
|
||||
|
||||
/**
|
||||
* the verbose property must be set to "true" to switch on verbose logging
|
||||
*/
|
||||
private static final String forceVerbosePropertyOnValue = "true";
|
||||
|
||||
/** The log system plugin in use */
|
||||
private static LogSystem logSystem = new DefaultLogSystem();
|
||||
|
||||
/**
|
||||
* The log is a simple static utility, no construction
|
||||
*/
|
||||
private Log() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the log system that will have all of the log info
|
||||
* sent to it.
|
||||
*
|
||||
* @param system The system to use for logging.
|
||||
*/
|
||||
public static void setLogSystem(LogSystem system) {
|
||||
logSystem = system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that we want verbose logging.
|
||||
* The call is ignored if verbose logging is forced by the system property
|
||||
* "org.newdawn.slick.forceVerboseLog"
|
||||
*
|
||||
* @param v True if we want verbose logging (INFO and DEBUG)
|
||||
*/
|
||||
public static void setVerbose(boolean v) {
|
||||
if (forcedVerbose)
|
||||
return;
|
||||
verbose = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the system property org.newdawn.slick.verboseLog is set to true.
|
||||
* If this is the case we activate the verbose logging mode
|
||||
*/
|
||||
public static void checkVerboseLogSetting() {
|
||||
try {
|
||||
AccessController.doPrivileged(new PrivilegedAction() {
|
||||
public Object run() {
|
||||
String val = System.getProperty(Log.forceVerboseProperty);
|
||||
if ((val != null) && (val.equalsIgnoreCase(Log.forceVerbosePropertyOnValue))) {
|
||||
Log.setForcedVerboseOn();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
// ignore, security failure - probably an applet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that we want verbose logging, even if switched off in game code.
|
||||
* Only be called when system property "org.newdawn.slick.forceVerboseLog" is set to true.
|
||||
* You must not call this method directly.
|
||||
*/
|
||||
public static void setForcedVerboseOn() {
|
||||
forcedVerbose = true;
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param message The message describing the error
|
||||
* @param e The exception causing the error
|
||||
*/
|
||||
public static void error(String message, Throwable e) {
|
||||
logSystem.error(message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param e The exception causing the error
|
||||
*/
|
||||
public static void error(Throwable e) {
|
||||
logSystem.error(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param message The message describing the error
|
||||
*/
|
||||
public static void error(String message) {
|
||||
logSystem.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*
|
||||
* @param message The message describing the warning
|
||||
*/
|
||||
public static void warn(String message) {
|
||||
logSystem.warn(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*
|
||||
* @param message The message describing the warning
|
||||
* @param e The issue causing the warning
|
||||
*/
|
||||
public static void warn(String message, Throwable e) {
|
||||
logSystem.warn(message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an information message
|
||||
*
|
||||
* @param message The message describing the infomation
|
||||
*/
|
||||
public static void info(String message) {
|
||||
if (verbose || forcedVerbose) {
|
||||
logSystem.info(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a debug message
|
||||
*
|
||||
* @param message The message describing the debug
|
||||
*/
|
||||
public static void debug(String message) {
|
||||
if (verbose || forcedVerbose) {
|
||||
logSystem.debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
lib/slick-source/org/newdawn/slick/util/LogSystem.java
Normal file
60
lib/slick-source/org/newdawn/slick/util/LogSystem.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
/**
|
||||
* Plugin in interface for the logging of Slick
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public interface LogSystem {
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param message The message describing the error
|
||||
* @param e The exception causing the error
|
||||
*/
|
||||
public void error(String message, Throwable e);
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param e The exception causing the error
|
||||
*/
|
||||
public void error(Throwable e);
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param message The message describing the error
|
||||
*/
|
||||
public void error(String message);
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*
|
||||
* @param message The message describing the warning
|
||||
*/
|
||||
public void warn(String message);
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*
|
||||
* @param message The message describing the warning
|
||||
* @param e The cause of the warning
|
||||
*/
|
||||
public void warn(String message, Throwable e);
|
||||
|
||||
/**
|
||||
* Log an information message
|
||||
*
|
||||
* @param message The message describing the infomation
|
||||
*/
|
||||
public void info(String message);
|
||||
|
||||
/**
|
||||
* Log a debug message
|
||||
*
|
||||
* @param message The message describing the debug
|
||||
*/
|
||||
public void debug(String message);
|
||||
}
|
||||
62
lib/slick-source/org/newdawn/slick/util/MaskUtil.java
Normal file
62
lib/slick-source/org/newdawn/slick/util/MaskUtil.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import org.newdawn.slick.opengl.renderer.Renderer;
|
||||
import org.newdawn.slick.opengl.renderer.SGL;
|
||||
|
||||
/**
|
||||
* A utility to provide full screen masking
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class MaskUtil {
|
||||
/** The renderer to use for all GL operations */
|
||||
protected static SGL GL = Renderer.get();
|
||||
|
||||
/**
|
||||
* Start defining the screen mask. After calling this use graphics functions to
|
||||
* mask out the area
|
||||
*/
|
||||
public static void defineMask() {
|
||||
GL.glDepthMask(true);
|
||||
GL.glClearDepth(1);
|
||||
GL.glClear(SGL.GL_DEPTH_BUFFER_BIT);
|
||||
GL.glDepthFunc(SGL.GL_ALWAYS);
|
||||
GL.glEnable(SGL.GL_DEPTH_TEST);
|
||||
GL.glDepthMask(true);
|
||||
GL.glColorMask(false, false, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish defining the screen mask
|
||||
*/
|
||||
public static void finishDefineMask() {
|
||||
GL.glDepthMask(false);
|
||||
GL.glColorMask(true, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start drawing only on the masked area
|
||||
*/
|
||||
public static void drawOnMask() {
|
||||
GL.glDepthFunc(SGL.GL_EQUAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start drawing only off the masked area
|
||||
*/
|
||||
public static void drawOffMask() {
|
||||
GL.glDepthFunc(SGL.GL_NOTEQUAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the masked area - should be done after you've finished rendering
|
||||
*/
|
||||
public static void resetMask() {
|
||||
GL.glDepthMask(true);
|
||||
GL.glClearDepth(0);
|
||||
GL.glClear(SGL.GL_DEPTH_BUFFER_BIT);
|
||||
GL.glDepthMask(false);
|
||||
|
||||
GL.glDisable(SGL.GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a limited implementation of a class can not
|
||||
* support the operation requested.
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class OperationNotSupportedException extends RuntimeException {
|
||||
/**
|
||||
* Create a new exception
|
||||
*
|
||||
* @param msg The message describing the limitation
|
||||
*/
|
||||
public OperationNotSupportedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
120
lib/slick-source/org/newdawn/slick/util/ResourceLoader.java
Normal file
120
lib/slick-source/org/newdawn/slick/util/ResourceLoader.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A simple wrapper around resource loading should anyone decide to change
|
||||
* their minds how this is meant to work in the future.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class ResourceLoader {
|
||||
/** The list of locations to be searched */
|
||||
private static ArrayList locations = new ArrayList();
|
||||
|
||||
static {
|
||||
locations.add(new ClasspathLocation());
|
||||
locations.add(new FileSystemLocation(new File(".")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a location that will be searched for resources
|
||||
*
|
||||
* @param location The location that will be searched for resoruces
|
||||
*/
|
||||
public static void addResourceLocation(ResourceLocation location) {
|
||||
locations.add(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a location that will be no longer be searched for resources
|
||||
*
|
||||
* @param location The location that will be removed from the search list
|
||||
*/
|
||||
public static void removeResourceLocation(ResourceLocation location) {
|
||||
locations.remove(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all the locations, no resources will be found until
|
||||
* new locations have been added
|
||||
*/
|
||||
public static void removeAllResourceLocations() {
|
||||
locations.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource
|
||||
*
|
||||
* @param ref The reference to the resource to retrieve
|
||||
* @return A stream from which the resource can be read
|
||||
*/
|
||||
public static InputStream getResourceAsStream(String ref) {
|
||||
InputStream in = null;
|
||||
|
||||
for (int i=0;i<locations.size();i++) {
|
||||
ResourceLocation location = (ResourceLocation) locations.get(i);
|
||||
in = location.getResourceAsStream(ref);
|
||||
if (in != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (in == null)
|
||||
{
|
||||
throw new RuntimeException("Resource not found: "+ref);
|
||||
}
|
||||
|
||||
return new BufferedInputStream(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a resource is available from any given resource loader
|
||||
*
|
||||
* @param ref A reference to the resource that should be checked
|
||||
* @return True if the resource can be located
|
||||
*/
|
||||
public static boolean resourceExists(String ref) {
|
||||
URL url = null;
|
||||
|
||||
for (int i=0;i<locations.size();i++) {
|
||||
ResourceLocation location = (ResourceLocation) locations.get(i);
|
||||
url = location.getResource(ref);
|
||||
if (url != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource as a URL
|
||||
*
|
||||
* @param ref The reference to the resource to retrieve
|
||||
* @return A URL from which the resource can be read
|
||||
*/
|
||||
public static URL getResource(String ref) {
|
||||
|
||||
URL url = null;
|
||||
|
||||
for (int i=0;i<locations.size();i++) {
|
||||
ResourceLocation location = (ResourceLocation) locations.get(i);
|
||||
url = location.getResource(ref);
|
||||
if (url != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (url == null)
|
||||
{
|
||||
throw new RuntimeException("Resource not found: "+ref);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.newdawn.slick.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* A location from which resources can be loaded
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public interface ResourceLocation {
|
||||
|
||||
/**
|
||||
* Get a resource as an input stream
|
||||
*
|
||||
* @param ref The reference to the resource to retrieve
|
||||
* @return A stream from which the resource can be read or
|
||||
* null if the resource can't be found in this location
|
||||
*/
|
||||
public InputStream getResourceAsStream(String ref);
|
||||
|
||||
/**
|
||||
* Get a resource as a URL
|
||||
*
|
||||
* @param ref The reference to the resource to retrieve
|
||||
* @return A URL from which the resource can be read
|
||||
*/
|
||||
public URL getResource(String ref);
|
||||
}
|
||||
3
lib/slick-source/org/newdawn/slick/util/package.html
Normal file
3
lib/slick-source/org/newdawn/slick/util/package.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<BODY>
|
||||
Utilities to support the library. Basically anything that didn't fit elsewhere.
|
||||
</BODY>
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* The description of a class providing a cost for a given tile based
|
||||
* on a target location and entity being moved. This heuristic controls
|
||||
* what priority is placed on different tiles during the search for a path
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface AStarHeuristic {
|
||||
|
||||
/**
|
||||
* Get the additional heuristic cost of the given tile. This controls the
|
||||
* order in which tiles are searched while attempting to find a path to the
|
||||
* target location. The lower the cost the more likely the tile will
|
||||
* be searched.
|
||||
*
|
||||
* @param map The map on which the path is being found
|
||||
* @param mover The entity that is moving along the path
|
||||
* @param x The x coordinate of the tile being evaluated
|
||||
* @param y The y coordinate of the tile being evaluated
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty Teh y coordinate of the target location
|
||||
* @return The cost associated with the given tile
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx, int ty);
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.heuristics.ClosestHeuristic;
|
||||
|
||||
/**
|
||||
* A path finder implementation that uses the AStar heuristic based algorithm
|
||||
* to determine a path.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class AStarPathFinder implements PathFinder, PathFindingContext {
|
||||
/** The set of nodes that have been searched through */
|
||||
private ArrayList closed = new ArrayList();
|
||||
/** The set of nodes that we do not yet consider fully searched */
|
||||
private PriorityList open = new PriorityList();
|
||||
|
||||
/** The map being searched */
|
||||
private TileBasedMap map;
|
||||
/** The maximum depth of search we're willing to accept before giving up */
|
||||
private int maxSearchDistance;
|
||||
|
||||
/** The complete set of nodes across the map */
|
||||
private Node[][] nodes;
|
||||
/** True if we allow diaganol movement */
|
||||
private boolean allowDiagMovement;
|
||||
/** The heuristic we're applying to determine which nodes to search first */
|
||||
private AStarHeuristic heuristic;
|
||||
/** The node we're currently searching from */
|
||||
private Node current;
|
||||
|
||||
/** The mover going through the path */
|
||||
private Mover mover;
|
||||
/** The x coordinate of the source tile we're moving from */
|
||||
private int sourceX;
|
||||
/** The y coordinate of the source tile we're moving from */
|
||||
private int sourceY;
|
||||
/** The distance searched so far */
|
||||
private int distance;
|
||||
|
||||
/**
|
||||
* Create a path finder with the default heuristic - closest to target.
|
||||
*
|
||||
* @param map The map to be searched
|
||||
* @param maxSearchDistance The maximum depth we'll search before giving up
|
||||
* @param allowDiagMovement True if the search should try diaganol movement
|
||||
*/
|
||||
public AStarPathFinder(TileBasedMap map, int maxSearchDistance, boolean allowDiagMovement) {
|
||||
this(map, maxSearchDistance, allowDiagMovement, new ClosestHeuristic());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path finder
|
||||
*
|
||||
* @param heuristic The heuristic used to determine the search order of the map
|
||||
* @param map The map to be searched
|
||||
* @param maxSearchDistance The maximum depth we'll search before giving up
|
||||
* @param allowDiagMovement True if the search should try diaganol movement
|
||||
*/
|
||||
public AStarPathFinder(TileBasedMap map, int maxSearchDistance,
|
||||
boolean allowDiagMovement, AStarHeuristic heuristic) {
|
||||
this.heuristic = heuristic;
|
||||
this.map = map;
|
||||
this.maxSearchDistance = maxSearchDistance;
|
||||
this.allowDiagMovement = allowDiagMovement;
|
||||
|
||||
nodes = new Node[map.getWidthInTiles()][map.getHeightInTiles()];
|
||||
for (int x=0;x<map.getWidthInTiles();x++) {
|
||||
for (int y=0;y<map.getHeightInTiles();y++) {
|
||||
nodes[x][y] = new Node(x,y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PathFinder#findPath(Mover, int, int, int, int)
|
||||
*/
|
||||
public Path findPath(Mover mover, int sx, int sy, int tx, int ty) {
|
||||
current = null;
|
||||
|
||||
// easy first check, if the destination is blocked, we can't get there
|
||||
this.mover = mover;
|
||||
this.sourceX = tx;
|
||||
this.sourceY = ty;
|
||||
this.distance = 0;
|
||||
|
||||
if (map.blocked(this, tx, ty)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int x=0;x<map.getWidthInTiles();x++) {
|
||||
for (int y=0;y<map.getHeightInTiles();y++) {
|
||||
nodes[x][y].reset();
|
||||
}
|
||||
}
|
||||
|
||||
// initial state for A*. The closed group is empty. Only the starting
|
||||
// tile is in the open list and it's cost is zero, i.e. we're already there
|
||||
nodes[sx][sy].cost = 0;
|
||||
nodes[sx][sy].depth = 0;
|
||||
closed.clear();
|
||||
open.clear();
|
||||
addToOpen(nodes[sx][sy]);
|
||||
|
||||
nodes[tx][ty].parent = null;
|
||||
|
||||
// while we haven't found the goal and haven't exceeded our max search depth
|
||||
int maxDepth = 0;
|
||||
while ((maxDepth < maxSearchDistance) && (open.size() != 0)) {
|
||||
// pull out the first node in our open list, this is determined to
|
||||
// be the most likely to be the next step based on our heuristic
|
||||
int lx = sx;
|
||||
int ly = sy;
|
||||
if (current != null) {
|
||||
lx = current.x;
|
||||
ly = current.y;
|
||||
}
|
||||
|
||||
current = getFirstInOpen();
|
||||
distance = current.depth;
|
||||
|
||||
if (current == nodes[tx][ty]) {
|
||||
if (isValidLocation(mover,lx,ly,tx,ty)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
removeFromOpen(current);
|
||||
addToClosed(current);
|
||||
|
||||
// search through all the neighbours of the current node evaluating
|
||||
// them as next steps
|
||||
for (int x=-1;x<2;x++) {
|
||||
for (int y=-1;y<2;y++) {
|
||||
// not a neighbour, its the current tile
|
||||
if ((x == 0) && (y == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we're not allowing diaganol movement then only
|
||||
// one of x or y can be set
|
||||
if (!allowDiagMovement) {
|
||||
if ((x != 0) && (y != 0)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// determine the location of the neighbour and evaluate it
|
||||
int xp = x + current.x;
|
||||
int yp = y + current.y;
|
||||
|
||||
if (isValidLocation(mover,current.x,current.y,xp,yp)) {
|
||||
// the cost to get to this node is cost the current plus the movement
|
||||
// cost to reach this node. Note that the heursitic value is only used
|
||||
// in the sorted open list
|
||||
float nextStepCost = current.cost + getMovementCost(mover, current.x, current.y, xp, yp);
|
||||
Node neighbour = nodes[xp][yp];
|
||||
map.pathFinderVisited(xp, yp);
|
||||
|
||||
// if the new cost we've determined for this node is lower than
|
||||
// it has been previously makes sure the node hasn't been discarded. We've
|
||||
// determined that there might have been a better path to get to
|
||||
// this node so it needs to be re-evaluated
|
||||
if (nextStepCost < neighbour.cost) {
|
||||
if (inOpenList(neighbour)) {
|
||||
removeFromOpen(neighbour);
|
||||
}
|
||||
if (inClosedList(neighbour)) {
|
||||
removeFromClosed(neighbour);
|
||||
}
|
||||
}
|
||||
|
||||
// if the node hasn't already been processed and discarded then
|
||||
// reset it's cost to our current cost and add it as a next possible
|
||||
// step (i.e. to the open list)
|
||||
if (!inOpenList(neighbour) && !(inClosedList(neighbour))) {
|
||||
neighbour.cost = nextStepCost;
|
||||
neighbour.heuristic = getHeuristicCost(mover, xp, yp, tx, ty);
|
||||
maxDepth = Math.max(maxDepth, neighbour.setParent(current));
|
||||
addToOpen(neighbour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// since we've got an empty open list or we've run out of search
|
||||
// there was no path. Just return null
|
||||
if (nodes[tx][ty].parent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// At this point we've definitely found a path so we can uses the parent
|
||||
// references of the nodes to find out way from the target location back
|
||||
// to the start recording the nodes on the way.
|
||||
Path path = new Path();
|
||||
Node target = nodes[tx][ty];
|
||||
while (target != nodes[sx][sy]) {
|
||||
path.prependStep(target.x, target.y);
|
||||
target = target.parent;
|
||||
}
|
||||
path.prependStep(sx,sy);
|
||||
|
||||
// thats it, we have our path
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the X coordinate of the node currently being evaluated
|
||||
*
|
||||
* @return The X coordinate of the node currently being evaluated
|
||||
*/
|
||||
public int getCurrentX() {
|
||||
if (current == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return current.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Y coordinate of the node currently being evaluated
|
||||
*
|
||||
* @return The Y coordinate of the node currently being evaluated
|
||||
*/
|
||||
public int getCurrentY() {
|
||||
if (current == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return current.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first element from the open list. This is the next
|
||||
* one to be searched.
|
||||
*
|
||||
* @return The first element in the open list
|
||||
*/
|
||||
protected Node getFirstInOpen() {
|
||||
return (Node) open.first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the open list
|
||||
*
|
||||
* @param node The node to be added to the open list
|
||||
*/
|
||||
protected void addToOpen(Node node) {
|
||||
node.setOpen(true);
|
||||
open.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node is in the open list
|
||||
*
|
||||
* @param node The node to check for
|
||||
* @return True if the node given is in the open list
|
||||
*/
|
||||
protected boolean inOpenList(Node node) {
|
||||
return node.isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node from the open list
|
||||
*
|
||||
* @param node The node to remove from the open list
|
||||
*/
|
||||
protected void removeFromOpen(Node node) {
|
||||
node.setOpen(false);
|
||||
open.remove(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the closed list
|
||||
*
|
||||
* @param node The node to add to the closed list
|
||||
*/
|
||||
protected void addToClosed(Node node) {
|
||||
node.setClosed(true);
|
||||
closed.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node supplied is in the closed list
|
||||
*
|
||||
* @param node The node to search for
|
||||
* @return True if the node specified is in the closed list
|
||||
*/
|
||||
protected boolean inClosedList(Node node) {
|
||||
return node.isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node from the closed list
|
||||
*
|
||||
* @param node The node to remove from the closed list
|
||||
*/
|
||||
protected void removeFromClosed(Node node) {
|
||||
node.setClosed(false);
|
||||
closed.remove(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given location is valid for the supplied mover
|
||||
*
|
||||
* @param mover The mover that would hold a given location
|
||||
* @param sx The starting x coordinate
|
||||
* @param sy The starting y coordinate
|
||||
* @param x The x coordinate of the location to check
|
||||
* @param y The y coordinate of the location to check
|
||||
* @return True if the location is valid for the given mover
|
||||
*/
|
||||
protected boolean isValidLocation(Mover mover, int sx, int sy, int x, int y) {
|
||||
boolean invalid = (x < 0) || (y < 0) || (x >= map.getWidthInTiles()) || (y >= map.getHeightInTiles());
|
||||
|
||||
if ((!invalid) && ((sx != x) || (sy != y))) {
|
||||
this.mover = mover;
|
||||
this.sourceX = sx;
|
||||
this.sourceY = sy;
|
||||
invalid = map.blocked(this, x, y);
|
||||
}
|
||||
|
||||
return !invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cost to move through a given location
|
||||
*
|
||||
* @param mover The entity that is being moved
|
||||
* @param sx The x coordinate of the tile whose cost is being determined
|
||||
* @param sy The y coordiante of the tile whose cost is being determined
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @return The cost of movement through the given tile
|
||||
*/
|
||||
public float getMovementCost(Mover mover, int sx, int sy, int tx, int ty) {
|
||||
this.mover = mover;
|
||||
this.sourceX = sx;
|
||||
this.sourceY = sy;
|
||||
|
||||
return map.getCost(this, tx, ty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the heuristic cost for the given location. This determines in which
|
||||
* order the locations are processed.
|
||||
*
|
||||
* @param mover The entity that is being moved
|
||||
* @param x The x coordinate of the tile whose cost is being determined
|
||||
* @param y The y coordiante of the tile whose cost is being determined
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @return The heuristic cost assigned to the tile
|
||||
*/
|
||||
public float getHeuristicCost(Mover mover, int x, int y, int tx, int ty) {
|
||||
return heuristic.getCost(map, mover, x, y, tx, ty);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list that sorts any element provided into the list
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
private class PriorityList {
|
||||
/** The list of elements */
|
||||
private List list = new LinkedList();
|
||||
|
||||
/**
|
||||
* Retrieve the first element from the list
|
||||
*
|
||||
* @return The first element from the list
|
||||
*/
|
||||
public Object first() {
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the list
|
||||
*/
|
||||
public void clear() {
|
||||
list.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to the list - causes sorting
|
||||
*
|
||||
* @param o The element to add
|
||||
*/
|
||||
public void add(Object o) {
|
||||
// float the new entry
|
||||
for (int i=0;i<list.size();i++) {
|
||||
if (((Comparable) list.get(i)).compareTo(o) > 0) {
|
||||
list.add(i, o);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!list.contains(o)) {
|
||||
list.add(o);
|
||||
}
|
||||
//Collections.sort(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the list
|
||||
*
|
||||
* @param o The element to remove
|
||||
*/
|
||||
public void remove(Object o) {
|
||||
list.remove(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of elements in the list
|
||||
*
|
||||
* @return The number of element in the list
|
||||
*/
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is in the list
|
||||
*
|
||||
* @param o The element to search for
|
||||
* @return True if the element is in the list
|
||||
*/
|
||||
public boolean contains(Object o) {
|
||||
return list.contains(o);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String temp = "{";
|
||||
for (int i=0;i<size();i++) {
|
||||
temp += list.get(i).toString()+",";
|
||||
}
|
||||
temp += "}";
|
||||
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single node in the search graph
|
||||
*/
|
||||
private class Node implements Comparable {
|
||||
/** The x coordinate of the node */
|
||||
private int x;
|
||||
/** The y coordinate of the node */
|
||||
private int y;
|
||||
/** The path cost for this node */
|
||||
private float cost;
|
||||
/** The parent of this node, how we reached it in the search */
|
||||
private Node parent;
|
||||
/** The heuristic cost of this node */
|
||||
private float heuristic;
|
||||
/** The search depth of this node */
|
||||
private int depth;
|
||||
/** In the open list */
|
||||
private boolean open;
|
||||
/** In the closed list */
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
* Create a new node
|
||||
*
|
||||
* @param x The x coordinate of the node
|
||||
* @param y The y coordinate of the node
|
||||
*/
|
||||
public Node(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent of this node
|
||||
*
|
||||
* @param parent The parent node which lead us to this node
|
||||
* @return The depth we have no reached in searching
|
||||
*/
|
||||
public int setParent(Node parent) {
|
||||
depth = parent.depth + 1;
|
||||
this.parent = parent;
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Comparable#compareTo(Object)
|
||||
*/
|
||||
public int compareTo(Object other) {
|
||||
Node o = (Node) other;
|
||||
|
||||
float f = heuristic + cost;
|
||||
float of = o.heuristic + o.cost;
|
||||
|
||||
if (f < of) {
|
||||
return -1;
|
||||
} else if (f > of) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the node is in the open list
|
||||
*
|
||||
* @param open True if the node is in the open list
|
||||
*/
|
||||
public void setOpen(boolean open) {
|
||||
this.open = open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is in the open list
|
||||
*
|
||||
* @return True if the node is in the open list
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the node is in the closed list
|
||||
*
|
||||
* @param closed True if the node is in the closed list
|
||||
*/
|
||||
public void setClosed(boolean closed) {
|
||||
this.closed = closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is in the closed list
|
||||
*
|
||||
* @return True if the node is in the closed list
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of this node
|
||||
*/
|
||||
public void reset() {
|
||||
closed = false;
|
||||
open = false;
|
||||
cost = 0;
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Node "+x+","+y+"]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getMover()
|
||||
*/
|
||||
public Mover getMover() {
|
||||
return mover;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getSearchDistance()
|
||||
*/
|
||||
public int getSearchDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getSourceX()
|
||||
*/
|
||||
public int getSourceX() {
|
||||
return sourceX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.util.pathfinding.PathFindingContext#getSourceY()
|
||||
*/
|
||||
public int getSourceY() {
|
||||
return sourceY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* A tagging interface for an object representing the entity in the game that
|
||||
* is going to moving along the path. This allows us to pass around entity/state
|
||||
* information to determine whether a particular tile is blocked, or how much
|
||||
* cost to apply on a particular tile.
|
||||
*
|
||||
* For instance, a Mover might represent a tank or plane on a game map. Passing round
|
||||
* this entity allows us to determine whether rough ground on a map should effect
|
||||
* the unit's cost for moving through the tile.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface Mover {
|
||||
|
||||
}
|
||||
158
lib/slick-source/org/newdawn/slick/util/pathfinding/Path.java
Normal file
158
lib/slick-source/org/newdawn/slick/util/pathfinding/Path.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A path determined by some path finding algorithm. A series of steps from
|
||||
* the starting location to the target location. This includes a step for the
|
||||
* initial location.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class Path implements Serializable {
|
||||
/** The serial identifier for this class */
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** The list of steps building up this path */
|
||||
private ArrayList steps = new ArrayList();
|
||||
|
||||
/**
|
||||
* Create an empty path
|
||||
*/
|
||||
public Path() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the path, i.e. the number of steps
|
||||
*
|
||||
* @return The number of steps in this path
|
||||
*/
|
||||
public int getLength() {
|
||||
return steps.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the step at a given index in the path
|
||||
*
|
||||
* @param index The index of the step to retrieve. Note this should
|
||||
* be >= 0 and < getLength();
|
||||
* @return The step information, the position on the map.
|
||||
*/
|
||||
public Step getStep(int index) {
|
||||
return (Step) steps.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate for the step at the given index
|
||||
*
|
||||
* @param index The index of the step whose x coordinate should be retrieved
|
||||
* @return The x coordinate at the step
|
||||
*/
|
||||
public int getX(int index) {
|
||||
return getStep(index).x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate for the step at the given index
|
||||
*
|
||||
* @param index The index of the step whose y coordinate should be retrieved
|
||||
* @return The y coordinate at the step
|
||||
*/
|
||||
public int getY(int index) {
|
||||
return getStep(index).y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a step to the path.
|
||||
*
|
||||
* @param x The x coordinate of the new step
|
||||
* @param y The y coordinate of the new step
|
||||
*/
|
||||
public void appendStep(int x, int y) {
|
||||
steps.add(new Step(x,y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a step to the path.
|
||||
*
|
||||
* @param x The x coordinate of the new step
|
||||
* @param y The y coordinate of the new step
|
||||
*/
|
||||
public void prependStep(int x, int y) {
|
||||
steps.add(0, new Step(x, y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this path contains the given step
|
||||
*
|
||||
* @param x The x coordinate of the step to check for
|
||||
* @param y The y coordinate of the step to check for
|
||||
* @return True if the path contains the given step
|
||||
*/
|
||||
public boolean contains(int x, int y) {
|
||||
return steps.contains(new Step(x,y));
|
||||
}
|
||||
|
||||
/**
|
||||
* A single step within the path
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class Step implements Serializable {
|
||||
/** The x coordinate at the given step */
|
||||
private int x;
|
||||
/** The y coordinate at the given step */
|
||||
private int y;
|
||||
|
||||
/**
|
||||
* Create a new step
|
||||
*
|
||||
* @param x The x coordinate of the new step
|
||||
* @param y The y coordinate of the new step
|
||||
*/
|
||||
public Step(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the new step
|
||||
*
|
||||
* @return The x coodindate of the new step
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the new step
|
||||
*
|
||||
* @return The y coodindate of the new step
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Object#hashCode()
|
||||
*/
|
||||
public int hashCode() {
|
||||
return x*y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Object#equals(Object)
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof Step) {
|
||||
Step o = (Step) other;
|
||||
|
||||
return (o.x == x) && (o.y == y);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* A description of an implementation that can find a path from one
|
||||
* location on a tile map to another based on information provided
|
||||
* by that tile map.
|
||||
*
|
||||
* @see TileBasedMap
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface PathFinder {
|
||||
|
||||
/**
|
||||
* Find a path from the starting location provided (sx,sy) to the target
|
||||
* location (tx,ty) avoiding blockages and attempting to honour costs
|
||||
* provided by the tile map.
|
||||
*
|
||||
* @param mover The entity that will be moving along the path. This provides
|
||||
* a place to pass context information about the game entity doing the moving, e.g.
|
||||
* can it fly? can it swim etc.
|
||||
*
|
||||
* @param sx The x coordinate of the start location
|
||||
* @param sy The y coordinate of the start location
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty Teh y coordinate of the target location
|
||||
* @return The path found from start to end, or null if no path can be found.
|
||||
*/
|
||||
public Path findPath(Mover mover, int sx, int sy, int tx, int ty);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* The context describing the current path finding state
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public interface PathFindingContext {
|
||||
/**
|
||||
* Get the object being moved along the path if any
|
||||
*
|
||||
* @return The object being moved along the path
|
||||
*/
|
||||
public Mover getMover();
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the source location
|
||||
*
|
||||
* @return The x coordinate of the source location
|
||||
*/
|
||||
public int getSourceX();
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the source location
|
||||
*
|
||||
* @return The y coordinate of the source location
|
||||
*/
|
||||
public int getSourceY();
|
||||
|
||||
/**
|
||||
* Get the distance that has been searched to reach this point
|
||||
*
|
||||
* @return The distance that has been search to reach this point
|
||||
*/
|
||||
public int getSearchDistance();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.newdawn.slick.util.pathfinding;
|
||||
|
||||
/**
|
||||
* The description for the data we're pathfinding over. This provides the contract
|
||||
* between the data being searched (i.e. the in game map) and the path finding
|
||||
* generic tools
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public interface TileBasedMap {
|
||||
/**
|
||||
* Get the width of the tile map. The slightly odd name is used
|
||||
* to distiguish this method from commonly used names in game maps.
|
||||
*
|
||||
* @return The number of tiles across the map
|
||||
*/
|
||||
public int getWidthInTiles();
|
||||
|
||||
/**
|
||||
* Get the height of the tile map. The slightly odd name is used
|
||||
* to distiguish this method from commonly used names in game maps.
|
||||
*
|
||||
* @return The number of tiles down the map
|
||||
*/
|
||||
public int getHeightInTiles();
|
||||
|
||||
/**
|
||||
* Notification that the path finder visited a given tile. This is
|
||||
* used for debugging new heuristics.
|
||||
*
|
||||
* @param x The x coordinate of the tile that was visited
|
||||
* @param y The y coordinate of the tile that was visited
|
||||
*/
|
||||
public void pathFinderVisited(int x, int y);
|
||||
|
||||
/**
|
||||
* Check if the given location is blocked, i.e. blocks movement of
|
||||
* the supplied mover.
|
||||
*
|
||||
* @param context The context describing the pathfinding at the time of this request
|
||||
* @param tx The x coordinate of the tile we're moving to
|
||||
* @param ty The y coordinate of the tile we're moving to
|
||||
* @return True if the location is blocked
|
||||
*/
|
||||
public boolean blocked(PathFindingContext context, int tx, int ty);
|
||||
|
||||
/**
|
||||
* Get the cost of moving through the given tile. This can be used to
|
||||
* make certain areas more desirable. A simple and valid implementation
|
||||
* of this method would be to return 1 in all cases.
|
||||
*
|
||||
* @param context The context describing the pathfinding at the time of this request
|
||||
* @param tx The x coordinate of the tile we're moving to
|
||||
* @param ty The y coordinate of the tile we're moving to
|
||||
* @return The relative cost of moving across the given tile
|
||||
*/
|
||||
public float getCost(PathFindingContext context, int tx, int ty);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.newdawn.slick.util.pathfinding.heuristics;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.AStarHeuristic;
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* A heuristic that uses the tile that is closest to the target
|
||||
* as the next best tile.
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class ClosestHeuristic implements AStarHeuristic {
|
||||
/**
|
||||
* @see AStarHeuristic#getCost(TileBasedMap, Mover, int, int, int, int)
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx, int ty) {
|
||||
float dx = tx - x;
|
||||
float dy = ty - y;
|
||||
|
||||
float result = (float) (Math.sqrt((dx*dx)+(dy*dy)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.newdawn.slick.util.pathfinding.heuristics;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.AStarHeuristic;
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* A heuristic that uses the tile that is closest to the target
|
||||
* as the next best tile. In this case the sqrt is removed
|
||||
* and the distance squared is used instead
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class ClosestSquaredHeuristic implements AStarHeuristic {
|
||||
|
||||
/**
|
||||
* @see AStarHeuristic#getCost(TileBasedMap, Mover, int, int, int, int)
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx, int ty) {
|
||||
float dx = tx - x;
|
||||
float dy = ty - y;
|
||||
|
||||
return ((dx*dx)+(dy*dy));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.newdawn.slick.util.pathfinding.heuristics;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.AStarHeuristic;
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* A heuristic that drives the search based on the Manhattan distance
|
||||
* between the current location and the target
|
||||
*
|
||||
* @author Kevin Glass
|
||||
*/
|
||||
public class ManhattanHeuristic implements AStarHeuristic {
|
||||
/** The minimum movement cost from any one square to the next */
|
||||
private int minimumCost;
|
||||
|
||||
/**
|
||||
* Create a new heuristic
|
||||
*
|
||||
* @param minimumCost The minimum movement cost from any one square to the next
|
||||
*/
|
||||
public ManhattanHeuristic(int minimumCost) {
|
||||
this.minimumCost = minimumCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AStarHeuristic#getCost(TileBasedMap, Mover, int, int, int, int)
|
||||
*/
|
||||
public float getCost(TileBasedMap map, Mover mover, int x, int y, int tx,
|
||||
int ty) {
|
||||
return minimumCost * (Math.abs(x-tx) + Math.abs(y-ty));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
/**
|
||||
* A link between this space and another
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class Link {
|
||||
/** The x coordinate of the joining point */
|
||||
private float px;
|
||||
/** The y coordinate of the joining point */
|
||||
private float py;
|
||||
/** The target space we'd be linking to */
|
||||
private Space target;
|
||||
|
||||
/**
|
||||
* Create a new link
|
||||
*
|
||||
* @param px The x coordinate of the linking point
|
||||
* @param py The y coordinate of the linking point
|
||||
* @param target The target space we're linking to
|
||||
*/
|
||||
public Link(float px, float py, Space target) {
|
||||
this.px = px;
|
||||
this.py = py;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance squared from this link to the given position
|
||||
*
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @return The distance squared from this link to the target
|
||||
*/
|
||||
public float distance2(float tx, float ty) {
|
||||
float dx = tx - px;
|
||||
float dy = ty - py;
|
||||
|
||||
return ((dx*dx) + (dy*dy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the link
|
||||
*
|
||||
* @return The x coordinate of the link
|
||||
*/
|
||||
public float getX() {
|
||||
return px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the link
|
||||
*
|
||||
* @return The y coordinate of the link
|
||||
*/
|
||||
public float getY() {
|
||||
return py;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the space this object links to
|
||||
*
|
||||
* @return The space this object links to
|
||||
*/
|
||||
public Space getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A nav-mesh is a set of shapes that describe the navigation of a map. These
|
||||
* shapes are linked together allow path finding but without the high
|
||||
* resolution that tile maps require. This leads to fast path finding and
|
||||
* potentially much more accurate map definition.
|
||||
*
|
||||
* @author kevin
|
||||
*
|
||||
*/
|
||||
public class NavMesh {
|
||||
/** The list of spaces that build up this navigation mesh */
|
||||
private ArrayList spaces = new ArrayList();
|
||||
|
||||
/**
|
||||
* Create a new empty mesh
|
||||
*/
|
||||
public NavMesh() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new mesh with a set of spaces
|
||||
*
|
||||
* @param spaces The spaces included in the mesh
|
||||
*/
|
||||
public NavMesh(ArrayList spaces) {
|
||||
this.spaces.addAll(spaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of spaces that are in the mesh
|
||||
*
|
||||
* @return The spaces in the mesh
|
||||
*/
|
||||
public int getSpaceCount() {
|
||||
return spaces.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the space at a given index
|
||||
*
|
||||
* @param index The index of the space to retrieve
|
||||
* @return The space at the given index
|
||||
*/
|
||||
public Space getSpace(int index) {
|
||||
return (Space) spaces.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single space to the mesh
|
||||
*
|
||||
* @param space The space to be added
|
||||
*/
|
||||
public void addSpace(Space space) {
|
||||
spaces.add(space);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the space at a given location
|
||||
*
|
||||
* @param x The x coordinate at which to find the space
|
||||
* @param y The y coordinate at which to find the space
|
||||
* @return The space at the given location
|
||||
*/
|
||||
public Space findSpace(float x, float y) {
|
||||
for (int i=0;i<spaces.size();i++) {
|
||||
Space space = getSpace(i);
|
||||
if (space.contains(x,y)) {
|
||||
return space;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a path from the source to the target coordinates
|
||||
*
|
||||
* @param sx The x coordinate of the source location
|
||||
* @param sy The y coordinate of the source location
|
||||
* @param tx The x coordinate of the target location
|
||||
* @param ty The y coordinate of the target location
|
||||
* @param optimize True if paths should be optimized
|
||||
* @return The path between the two spaces
|
||||
*/
|
||||
public NavPath findPath(float sx, float sy, float tx, float ty, boolean optimize) {
|
||||
Space source = findSpace(sx,sy);
|
||||
Space target = findSpace(tx,ty);
|
||||
|
||||
if ((source == null) || (target == null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i=0;i<spaces.size();i++) {
|
||||
((Space) spaces.get(i)).clearCost();
|
||||
}
|
||||
target.fill(source,tx, ty, 0);
|
||||
if (target.getCost() == Float.MAX_VALUE) {
|
||||
return null;
|
||||
}
|
||||
if (source.getCost() == Float.MAX_VALUE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NavPath path = new NavPath();
|
||||
path.push(new Link(sx, sy, null));
|
||||
if (source.pickLowestCost(target, path)) {
|
||||
path.push(new Link(tx, ty, null));
|
||||
if (optimize) {
|
||||
optimize(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a particular path is clear
|
||||
*
|
||||
* @param x1 The x coordinate of the starting point
|
||||
* @param y1 The y coordinate of the starting point
|
||||
* @param x2 The x coordinate of the ending point
|
||||
* @param y2 The y coordinate of the ending point
|
||||
* @param step The size of the step between points
|
||||
* @return True if there are no blockages along the path
|
||||
*/
|
||||
private boolean isClear(float x1, float y1, float x2, float y2, float step) {
|
||||
float dx = (x2 - x1);
|
||||
float dy = (y2 - y1);
|
||||
float len = (float) Math.sqrt((dx*dx)+(dy*dy));
|
||||
dx *= step;
|
||||
dx /= len;
|
||||
dy *= step;
|
||||
dy /= len;
|
||||
int steps = (int) (len / step);
|
||||
|
||||
for (int i=0;i<steps;i++) {
|
||||
float x = x1 + (dx*i);
|
||||
float y = y1 + (dy*i);
|
||||
|
||||
if (findSpace(x,y) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize a path by removing segments that arn't required
|
||||
* to reach the end point
|
||||
*
|
||||
* @param path The path to optimize. Redundant segments will be removed
|
||||
*/
|
||||
private void optimize(NavPath path) {
|
||||
int pt = 0;
|
||||
|
||||
while (pt < path.length()-2) {
|
||||
float sx = path.getX(pt);
|
||||
float sy = path.getY(pt);
|
||||
float nx = path.getX(pt+2);
|
||||
float ny = path.getY(pt+2);
|
||||
|
||||
if (isClear(sx,sy,nx,ny,0.1f)) {
|
||||
path.remove(pt+1);
|
||||
} else {
|
||||
pt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.newdawn.slick.util.pathfinding.Mover;
|
||||
import org.newdawn.slick.util.pathfinding.PathFindingContext;
|
||||
import org.newdawn.slick.util.pathfinding.TileBasedMap;
|
||||
|
||||
/**
|
||||
* The builder responsible for converting a tile based map into
|
||||
* a navigation mesh
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class NavMeshBuilder implements PathFindingContext {
|
||||
/** The current x position we're searching */
|
||||
private int sx;
|
||||
/** The current y position we've searching */
|
||||
private int sy;
|
||||
/** The smallest space allowed */
|
||||
private float smallestSpace = 0.2f;
|
||||
/** True if we're working tile based */
|
||||
private boolean tileBased;
|
||||
|
||||
/**
|
||||
* Build a navigation mesh based on a tile map
|
||||
*
|
||||
* @param map The map to build the navigation mesh from
|
||||
*
|
||||
* @return The newly created navigation mesh
|
||||
*/
|
||||
public NavMesh build(TileBasedMap map) {
|
||||
return build(map, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a navigation mesh based on a tile map
|
||||
*
|
||||
* @param map The map to build the navigation mesh from
|
||||
* @param tileBased True if we'll use the tiles for the mesh initially
|
||||
* rather than quad spacing
|
||||
* @return The newly created navigation mesh
|
||||
*/
|
||||
public NavMesh build(TileBasedMap map, boolean tileBased) {
|
||||
this.tileBased = tileBased;
|
||||
|
||||
ArrayList spaces = new ArrayList();
|
||||
|
||||
if (tileBased) {
|
||||
for (int x=0;x<map.getWidthInTiles();x++) {
|
||||
for (int y=0;y<map.getHeightInTiles();y++) {
|
||||
if (!map.blocked(this, x, y)) {
|
||||
spaces.add(new Space(x,y,1,1));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Space space = new Space(0,0,map.getWidthInTiles(),map.getHeightInTiles());
|
||||
|
||||
subsection(map, space, spaces);
|
||||
}
|
||||
|
||||
while (mergeSpaces(spaces)) {}
|
||||
linkSpaces(spaces);
|
||||
|
||||
return new NavMesh(spaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the spaces that have been created to optimize out anywhere
|
||||
* we can.
|
||||
*
|
||||
* @param spaces The list of spaces to be merged
|
||||
* @return True if a merge occured and we'll have to start the merge
|
||||
* process again
|
||||
*/
|
||||
private boolean mergeSpaces(ArrayList spaces) {
|
||||
for (int source=0;source<spaces.size();source++) {
|
||||
Space a = (Space) spaces.get(source);
|
||||
|
||||
for (int target=source+1;target<spaces.size();target++) {
|
||||
Space b = (Space) spaces.get(target);
|
||||
|
||||
if (a.canMerge(b)) {
|
||||
spaces.remove(a);
|
||||
spaces.remove(b);
|
||||
spaces.add(a.merge(b));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the links between spaces
|
||||
*
|
||||
* @param spaces The spaces to link up
|
||||
*/
|
||||
private void linkSpaces(ArrayList spaces) {
|
||||
for (int source=0;source<spaces.size();source++) {
|
||||
Space a = (Space) spaces.get(source);
|
||||
|
||||
for (int target=source+1;target<spaces.size();target++) {
|
||||
Space b = (Space) spaces.get(target);
|
||||
|
||||
if (a.hasJoinedEdge(b)) {
|
||||
a.link(b);
|
||||
b.link(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a particular space is clear of blockages
|
||||
*
|
||||
* @param map The map the spaces are being built from
|
||||
* @param space The space to check
|
||||
* @return True if there are no blockages in the space
|
||||
*/
|
||||
public boolean clear(TileBasedMap map, Space space) {
|
||||
if (tileBased) {
|
||||
return true;
|
||||
}
|
||||
|
||||
float x = 0;
|
||||
boolean donex = false;
|
||||
|
||||
while (x < space.getWidth()) {
|
||||
float y = 0;
|
||||
boolean doney = false;
|
||||
|
||||
while (y < space.getHeight()) {
|
||||
sx = (int) (space.getX()+x);
|
||||
sy = (int) (space.getY()+y);
|
||||
|
||||
if (map.blocked(this, sx, sy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
y += 0.1f;
|
||||
if ((y > space.getHeight()) && (!doney)) {
|
||||
y = space.getHeight();
|
||||
doney = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
x += 0.1f;
|
||||
if ((x > space.getWidth()) && (!donex)) {
|
||||
x = space.getWidth();
|
||||
donex = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subsection a space into smaller spaces if required to find a non-blocked
|
||||
* area.
|
||||
*
|
||||
* @param map The map being processed
|
||||
* @param space The space being sections
|
||||
* @param spaces The list of spaces that have been created
|
||||
*/
|
||||
private void subsection(TileBasedMap map, Space space, ArrayList spaces) {
|
||||
if (!clear(map, space)) {
|
||||
float width2 = space.getWidth()/2;
|
||||
float height2 = space.getHeight()/2;
|
||||
|
||||
if ((width2 < smallestSpace) && (height2 < smallestSpace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
subsection(map, new Space(space.getX(), space.getY(), width2, height2), spaces);
|
||||
subsection(map, new Space(space.getX(), space.getY()+height2, width2, height2), spaces);
|
||||
subsection(map, new Space(space.getX()+width2, space.getY(), width2, height2), spaces);
|
||||
subsection(map, new Space(space.getX()+width2, space.getY()+height2, width2, height2), spaces);
|
||||
} else {
|
||||
spaces.add(space);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current mover
|
||||
*/
|
||||
public Mover getMover() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current search distance
|
||||
*/
|
||||
public int getSearchDistance() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current x location
|
||||
*/
|
||||
public int getSourceX() {
|
||||
return sx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path finding context implementation
|
||||
*
|
||||
* @return The current y location
|
||||
*/
|
||||
public int getSourceY() {
|
||||
return sy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A path across a navigation mesh
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class NavPath {
|
||||
/** The list of links that form this path */
|
||||
private ArrayList links = new ArrayList();
|
||||
|
||||
/**
|
||||
* Create a new path
|
||||
*/
|
||||
public NavPath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a link to the end of the path
|
||||
*
|
||||
* @param link The link to the end of the path
|
||||
*/
|
||||
public void push(Link link) {
|
||||
links.add(link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the path
|
||||
*
|
||||
* @return The number of steps in the path
|
||||
*/
|
||||
public int length() {
|
||||
return links.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the given step
|
||||
*
|
||||
* @param step The index of the step to retrieve
|
||||
* @return The x coordinate at the given step index
|
||||
*/
|
||||
public float getX(int step) {
|
||||
return ((Link) links.get(step)).getX();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the given step
|
||||
*
|
||||
* @param step The index of the step to retrieve
|
||||
* @return The y coordinate at the given step index
|
||||
*/
|
||||
public float getY(int step) {
|
||||
return ((Link) links.get(step)).getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of this instance
|
||||
*
|
||||
* @return The string representation of this instance
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Path length="+length()+"]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a step in the path
|
||||
*
|
||||
* @param i
|
||||
*/
|
||||
public void remove(int i) {
|
||||
links.remove(i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package org.newdawn.slick.util.pathfinding.navmesh;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A quad space within a navigation mesh
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class Space {
|
||||
/** The x coordinate of the top corner of the space */
|
||||
private float x;
|
||||
/** The y coordinate of the top corner of the space */
|
||||
private float y;
|
||||
/** The width of the space */
|
||||
private float width;
|
||||
/** The height of the space */
|
||||
private float height;
|
||||
|
||||
/** A map from spaces to the links that connect them to this space */
|
||||
private HashMap links = new HashMap();
|
||||
/** A list of the links from this space to others */
|
||||
private ArrayList linksList = new ArrayList();
|
||||
/** The cost to get to this node */
|
||||
private float cost;
|
||||
|
||||
/**
|
||||
* Create a new space
|
||||
*
|
||||
* @param x The x coordinate of the top corner of the space
|
||||
* @param y The y coordinate of the top corner of the space
|
||||
* @param width The width of the space
|
||||
* @param height The height of the space
|
||||
*/
|
||||
public Space(float x, float y, float width, float height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the space
|
||||
*
|
||||
* @return The width of the space
|
||||
*/
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the space
|
||||
*
|
||||
* @return The height of the space
|
||||
*/
|
||||
public float getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordinate of the top corner of the space
|
||||
*
|
||||
* @return The x coordinate of the top corner of the space
|
||||
*/
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordinate of the top corner of the space
|
||||
*
|
||||
* @return The y coordinate of the top corner of the space
|
||||
*/
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link this space to another by creating a link and finding the point
|
||||
* at which the spaces link up
|
||||
*
|
||||
* @param other The other space to link to
|
||||
*/
|
||||
public void link(Space other) {
|
||||
// aligned vertical edges
|
||||
if (inTolerance(x,other.x+other.width) || inTolerance(x+width, other.x)) {
|
||||
float linkx = x;
|
||||
if (x+width == other.x) {
|
||||
linkx = x+width;
|
||||
}
|
||||
|
||||
float top = Math.max(y, other.y);
|
||||
float bottom = Math.min(y+height, other.y+other.height);
|
||||
float linky = top + ((bottom-top)/2);
|
||||
|
||||
Link link = new Link(linkx, linky, other);
|
||||
links.put(other,link);
|
||||
linksList.add(link);
|
||||
}
|
||||
// aligned horizontal edges
|
||||
if (inTolerance(y, other.y+other.height) || inTolerance(y+height, other.y)) {
|
||||
float linky = y;
|
||||
if (y+height == other.y) {
|
||||
linky = y+height;
|
||||
}
|
||||
|
||||
float left = Math.max(x, other.x);
|
||||
float right = Math.min(x+width, other.x+other.width);
|
||||
float linkx = left + ((right-left)/2);
|
||||
|
||||
Link link = new Link(linkx, linky, other);
|
||||
links.put(other, link);
|
||||
linksList.add(link);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether two locations are within tolerance distance. This is
|
||||
* used when finding aligned edges to remove float rounding errors
|
||||
*
|
||||
* @param a The first value
|
||||
* @param b The second value
|
||||
* @return True if the edges are close enough (tm)
|
||||
*/
|
||||
private boolean inTolerance(float a, float b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this space has an edge that is joined with another
|
||||
*
|
||||
* @param other The other space to check against
|
||||
* @return True if the spaces have a shared edge
|
||||
*/
|
||||
public boolean hasJoinedEdge(Space other) {
|
||||
// aligned vertical edges
|
||||
if (inTolerance(x,other.x+other.width) || inTolerance(x+width,other.x)) {
|
||||
if ((y >= other.y) && (y <= other.y + other.height)) {
|
||||
return true;
|
||||
}
|
||||
if ((y+height >= other.y) && (y+height <= other.y + other.height)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.y >= y) && (other.y <= y + height)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.y+other.height >= y) && (other.y+other.height <= y + height)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// aligned horizontal edges
|
||||
if (inTolerance(y, other.y+other.height) || inTolerance(y+height, other.y)) {
|
||||
if ((x >= other.x) && (x <= other.x + other.width)) {
|
||||
return true;
|
||||
}
|
||||
if ((x+width >= other.x) && (x+width <= other.x + other.width)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.x >= x) && (other.x <= x + width)) {
|
||||
return true;
|
||||
}
|
||||
if ((other.x+other.width >= x) && (other.x+other.width <= x + width)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this space with another
|
||||
*
|
||||
* @param other The other space to merge with
|
||||
* @return The result space created by joining the two
|
||||
*/
|
||||
public Space merge(Space other) {
|
||||
float minx = Math.min(x, other.x);
|
||||
float miny = Math.min(y, other.y);
|
||||
|
||||
float newwidth = width+other.width;
|
||||
float newheight = height+other.height;
|
||||
if (x == other.x) {
|
||||
newwidth = width;
|
||||
} else {
|
||||
newheight = height;
|
||||
}
|
||||
return new Space(minx, miny, newwidth, newheight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given space can be merged with this one. It must have
|
||||
* an adjacent edge and have the same height or width as this space.
|
||||
*
|
||||
* @param other The other space to be considered
|
||||
* @return True if the spaces can be joined together
|
||||
*/
|
||||
public boolean canMerge(Space other) {
|
||||
if (!hasJoinedEdge(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((x == other.x) && (width == other.width)) {
|
||||
return true;
|
||||
}
|
||||
if ((y == other.y) && (height == other.height)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of links
|
||||
*
|
||||
* @return The number of links from the space to others
|
||||
*/
|
||||
public int getLinkCount() {
|
||||
return linksList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link from this space to another at a particular index
|
||||
*
|
||||
* @param index The index of the link to retrieve
|
||||
* @return The link from this space to another
|
||||
*/
|
||||
public Link getLink(int index) {
|
||||
return (Link) linksList.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this space contains a given point
|
||||
*
|
||||
* @param xp The x coordinate to check
|
||||
* @param yp The y coordinate to check
|
||||
* @return True if this space container the coordinate given
|
||||
*/
|
||||
public boolean contains(float xp, float yp) {
|
||||
return (xp >= x) && (xp < x+width) && (yp >= y) && (yp < y+height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the spaces based on the cost from a given starting point
|
||||
*
|
||||
* @param target The target space we're heading for
|
||||
* @param sx The x coordinate of the starting point
|
||||
* @param sy The y coordinate of the starting point
|
||||
* @param cost The cost up to this point
|
||||
*/
|
||||
public void fill(Space target, float sx, float sy, float cost) {
|
||||
if (cost >= this.cost) {
|
||||
return;
|
||||
}
|
||||
this.cost = cost;
|
||||
if (target == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0;i<getLinkCount();i++) {
|
||||
Link link = getLink(i);
|
||||
float extraCost = link.distance2(sx,sy);
|
||||
float nextCost = cost + extraCost;
|
||||
link.getTarget().fill(target, link.getX(), link.getY(), nextCost);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the costing values across the whole map
|
||||
*/
|
||||
public void clearCost() {
|
||||
cost = Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cost to get to this node at the moment
|
||||
*
|
||||
* @return The cost to get to this node
|
||||
*/
|
||||
public float getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the lowest cost route from this space to another on the path
|
||||
*
|
||||
* @param target The target space we're looking for
|
||||
* @param path The path to add the steps to
|
||||
* @return True if the path was found
|
||||
*/
|
||||
public boolean pickLowestCost(Space target, NavPath path) {
|
||||
if (target == this) {
|
||||
return true;
|
||||
}
|
||||
if (links.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Link bestLink = null;
|
||||
for (int i=0;i<getLinkCount();i++) {
|
||||
Link link = getLink(i);
|
||||
if ((bestLink == null) || (link.getTarget().getCost() < bestLink.getTarget().getCost())) {
|
||||
bestLink = link;
|
||||
}
|
||||
}
|
||||
|
||||
path.push(bestLink);
|
||||
return bestLink.getTarget().pickLowestCost(target, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string representation of this instance
|
||||
*
|
||||
* @return The string representation of this instance
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Space "+x+","+y+" "+width+","+height+"]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<BODY>
|
||||
A set of classes to provide configurable A* path finding on tilebased maps
|
||||
</BODY>
|
||||
@@ -0,0 +1,483 @@
|
||||
package org.newdawn.slick.util.xml;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.newdawn.slick.util.Log;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
|
||||
/**
|
||||
* Provides a method of parsing XML into an existing data model. This does not
|
||||
* provide the same functionality as JAXB or the variety of XML bindings out there. This
|
||||
* is a utility to map XML onto an existing data model. The idea being that the design level
|
||||
* model should not be driven by the XML schema thats defined. The two arn't always equal
|
||||
* and often you end up with a set of class that represent your XML that you then have
|
||||
* to traverse to extract into your normal data model.
|
||||
*
|
||||
* This utility hopes to take a piece of XML and map it onto a previously designed data
|
||||
* model. At the moment it's way to tied to the structure of the XML but this will
|
||||
* hopefully change with time.
|
||||
*
|
||||
* XML element names must be mapped to class names. This can be done in two ways either:
|
||||
*
|
||||
* - Specify an explict mapping with addElementMapping()
|
||||
* - Specify the default package name and use the element name as the class name
|
||||
*
|
||||
* Each attribute in an element is mapped into a property of the element class, preferably
|
||||
* through a set<AttrName> bean method, but alternatively by direct injection into private
|
||||
* fields.
|
||||
*
|
||||
* Each child element is added to the target class by call the method add() on it with a single
|
||||
* parameter of the type generated for the child element.
|
||||
*
|
||||
* Classes can optionally implement setXMLElementName(String) and setXMLElementContent(String) to
|
||||
* recieve the name and content respectively of the XMLElement they were parsed from. This can
|
||||
* help when mapping two elements to a single class.
|
||||
*
|
||||
* To reiterate, I'm not sure this is a good idea yet. It helps me as a utility since I've done
|
||||
* this several times in the past but in the general case it may not be perfect. Consider a custom
|
||||
* parser using XMLParser or JAXB (et al) seriously instead.
|
||||
*
|
||||
* @author kevin
|
||||
*
|
||||
*/
|
||||
public class ObjectTreeParser {
|
||||
/** The mapping of XML element names to class names */
|
||||
private HashMap nameToClass = new HashMap();
|
||||
/** The default package where classes will be searched for */
|
||||
private String defaultPackage;
|
||||
/** The list of elements to ignore */
|
||||
private ArrayList ignored = new ArrayList();
|
||||
/** The name of the method to add an child object to it's parent */
|
||||
private String addMethod = "add";
|
||||
|
||||
/**
|
||||
* Create an object tree parser with no default package
|
||||
*/
|
||||
public ObjectTreeParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an object tree parser specifing the default package
|
||||
* where classes will be search for using the XML element name
|
||||
*
|
||||
* @param defaultPackage The default package to be searched
|
||||
*/
|
||||
public ObjectTreeParser(String defaultPackage) {
|
||||
this.defaultPackage = defaultPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mapping between XML element name and class name
|
||||
*
|
||||
* @param elementName The name of the XML element
|
||||
* @param elementClass The class to be created for the given element
|
||||
*/
|
||||
public void addElementMapping(String elementName, Class elementClass) {
|
||||
nameToClass.put(elementName, elementClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a name to the list of elements ignored
|
||||
*
|
||||
* @param elementName The name to ignore
|
||||
*/
|
||||
public void addIgnoredElement(String elementName) {
|
||||
ignored.add(elementName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the method to use to add child objects to their
|
||||
* parents. This is sometimes useful to not clash with the existing
|
||||
* data model methods.
|
||||
*
|
||||
* @param methodName The name of the method to call
|
||||
*/
|
||||
public void setAddMethodName(String methodName) {
|
||||
addMethod = methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default package which will be search for classes by their XML
|
||||
* element name.
|
||||
*
|
||||
* @param defaultPackage The default package to be searched
|
||||
*/
|
||||
public void setDefaultPackage(String defaultPackage) {
|
||||
this.defaultPackage = defaultPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML document located by the slick resource loader using the
|
||||
* reference given.
|
||||
*
|
||||
* @param ref The reference to the XML document
|
||||
* @return The root element of the newly parse document
|
||||
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
|
||||
* XML is malformed in some way.
|
||||
*/
|
||||
public Object parse(String ref) throws SlickXMLException {
|
||||
return parse(ref, ResourceLoader.getResourceAsStream(ref));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML document that can be read from the given input stream
|
||||
*
|
||||
* @param name The name of the document
|
||||
* @param in The input stream from which the document can be read
|
||||
* @return The root element of the newly parse document
|
||||
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
|
||||
* XML is malformed in some way.
|
||||
*/
|
||||
public Object parse(String name, InputStream in) throws SlickXMLException {
|
||||
XMLParser parser = new XMLParser();
|
||||
XMLElement root = parser.parse(name, in);
|
||||
|
||||
return traverse(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML document located by the slick resource loader using the
|
||||
* reference given.
|
||||
*
|
||||
* @param ref The reference to the XML document
|
||||
* @param target The top level object that represents the root node
|
||||
* @return The root element of the newly parse document
|
||||
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
|
||||
* XML is malformed in some way.
|
||||
*/
|
||||
public Object parseOnto(String ref, Object target) throws SlickXMLException {
|
||||
return parseOnto(ref, ResourceLoader.getResourceAsStream(ref), target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML document that can be read from the given input stream
|
||||
*
|
||||
* @param name The name of the document
|
||||
* @param in The input stream from which the document can be read
|
||||
* @param target The top level object that represents the root node
|
||||
* @return The root element of the newly parse document
|
||||
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
|
||||
* XML is malformed in some way.
|
||||
*/
|
||||
public Object parseOnto(String name, InputStream in, Object target) throws SlickXMLException {
|
||||
XMLParser parser = new XMLParser();
|
||||
XMLElement root = parser.parse(name, in);
|
||||
|
||||
return traverse(root, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterine the name of the class that should be used for a given
|
||||
* XML element name.
|
||||
*
|
||||
* @param name The name of the XML element
|
||||
* @return The class to be used or null if none can be found
|
||||
*/
|
||||
private Class getClassForElementName(String name) {
|
||||
Class clazz = (Class) nameToClass.get(name);
|
||||
if (clazz != null) {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
if (defaultPackage != null) {
|
||||
try {
|
||||
return Class.forName(defaultPackage+"."+name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore, it's just not there
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the XML element specified generating the appropriate object structure
|
||||
* for it and it's children
|
||||
*
|
||||
* @param current The XML element to process
|
||||
* @return The object created for the given element
|
||||
* @throws SlickXMLException
|
||||
*/
|
||||
private Object traverse(XMLElement current) throws SlickXMLException {
|
||||
return traverse(current, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the XML element specified generating the appropriate object structure
|
||||
* for it and it's children
|
||||
*
|
||||
* @param current The XML element to process
|
||||
* @param instance The instance to parse onto, normally null
|
||||
* @return The object created for the given element
|
||||
* @throws SlickXMLException
|
||||
*/
|
||||
private Object traverse(XMLElement current, Object instance) throws SlickXMLException {
|
||||
String name = current.getName();
|
||||
if (ignored.contains(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class clazz;
|
||||
|
||||
if (instance == null) {
|
||||
clazz = getClassForElementName(name);
|
||||
} else {
|
||||
clazz = instance.getClass();
|
||||
}
|
||||
|
||||
if (clazz == null) {
|
||||
throw new SlickXMLException("Unable to map element "+name+" to a class, define the mapping");
|
||||
}
|
||||
|
||||
try {
|
||||
if (instance == null) {
|
||||
instance = clazz.newInstance();
|
||||
|
||||
Method elementNameMethod = getMethod(clazz, "setXMLElementName", new Class[] {String.class});
|
||||
if (elementNameMethod != null) {
|
||||
invoke(elementNameMethod, instance, new Object[] {name});
|
||||
}
|
||||
Method contentMethod = getMethod(clazz, "setXMLElementContent", new Class[] {String.class});
|
||||
if (contentMethod != null) {
|
||||
invoke(contentMethod, instance, new Object[] {current.getContent()});
|
||||
}
|
||||
}
|
||||
|
||||
String[] attrs = current.getAttributeNames();
|
||||
for (int i=0;i<attrs.length;i++) {
|
||||
String methodName = "set"+attrs[i];
|
||||
|
||||
Method method = findMethod(clazz, methodName);
|
||||
|
||||
if (method == null) {
|
||||
Field field = findField(clazz, attrs[i]);
|
||||
if (field != null) {
|
||||
String value = current.getAttribute(attrs[i]);
|
||||
Object typedValue = typeValue(value, field.getType());
|
||||
setField(field, instance, typedValue);
|
||||
} else {
|
||||
Log.info("Unable to find property on: "+clazz+" for attribute: "+attrs[i]);
|
||||
}
|
||||
} else {
|
||||
String value = current.getAttribute(attrs[i]);
|
||||
Object typedValue = typeValue(value, method.getParameterTypes()[0]);
|
||||
invoke(method, instance, new Object[] {typedValue});
|
||||
}
|
||||
}
|
||||
|
||||
XMLElementList children = current.getChildren();
|
||||
for (int i=0;i<children.size();i++) {
|
||||
XMLElement element = children.get(i);
|
||||
|
||||
Object child = traverse(element);
|
||||
if (child != null) {
|
||||
String methodName = addMethod;
|
||||
|
||||
Method method = findMethod(clazz, methodName, child.getClass());
|
||||
if (method == null) {
|
||||
Log.info("Unable to find method to add: "+child+" to "+clazz);
|
||||
} else {
|
||||
invoke(method, instance, new Object[] {child});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
} catch (InstantiationException e) {
|
||||
throw new SlickXMLException("Unable to instance "+clazz+" for element "+name+", no zero parameter constructor?", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new SlickXMLException("Unable to instance "+clazz+" for element "+name+", no zero parameter constructor?", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given value to a given type
|
||||
*
|
||||
* @param value The value to convert
|
||||
* @param clazz The class that the returned object must be
|
||||
* @return The value as the given type
|
||||
* @throws SlickXMLException Indicates there is no automatic way of converting the value to the type
|
||||
*/
|
||||
private Object typeValue(String value, Class clazz) throws SlickXMLException {
|
||||
if (clazz == String.class) {
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
clazz = mapPrimitive(clazz);
|
||||
return clazz.getConstructor(new Class[] {String.class}).newInstance(new Object[] {value});
|
||||
} catch (Exception e) {
|
||||
throw new SlickXMLException("Failed to convert: "+value+" to the expected primitive type: "+clazz, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a primitive class type to it's real object wrapper
|
||||
*
|
||||
* @param clazz The primitive type class
|
||||
* @return The object wrapper class
|
||||
*/
|
||||
private Class mapPrimitive(Class clazz) {
|
||||
if (clazz == Integer.TYPE) {
|
||||
return Integer.class;
|
||||
}
|
||||
if (clazz == Double.TYPE) {
|
||||
return Double.class;
|
||||
}
|
||||
if (clazz == Float.TYPE) {
|
||||
return Float.class;
|
||||
}
|
||||
if (clazz == Boolean.TYPE) {
|
||||
return Boolean.class;
|
||||
}
|
||||
if (clazz == Long.TYPE) {
|
||||
return Long.class;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unsupported primitive: "+clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a field in a class by it's name. Note that this method is
|
||||
* only needed because the general reflection method is case
|
||||
* sensitive
|
||||
*
|
||||
* @param clazz The clazz to search
|
||||
* @param name The name of the field to search for
|
||||
* @return The field or null if none could be located
|
||||
*/
|
||||
private Field findField(Class clazz, String name) {
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for (int i=0;i<fields.length;i++) {
|
||||
if (fields[i].getName().equalsIgnoreCase(name)) {
|
||||
if (fields[i].getType().isPrimitive()) {
|
||||
return fields[i];
|
||||
}
|
||||
if (fields[i].getType() == String.class) {
|
||||
return fields[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a method in a class by it's name. Note that this method is
|
||||
* only needed because the general reflection method is case
|
||||
* sensitive
|
||||
*
|
||||
* @param clazz The clazz to search
|
||||
* @param name The name of the method to search for
|
||||
* @return The method or null if none could be located
|
||||
*/
|
||||
private Method findMethod(Class clazz, String name) {
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (int i=0;i<methods.length;i++) {
|
||||
if (methods[i].getName().equalsIgnoreCase(name)) {
|
||||
Method method = methods[i];
|
||||
Class[] params = method.getParameterTypes();
|
||||
|
||||
if (params.length == 1) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a method on a class with a single given parameter.
|
||||
*
|
||||
* @param clazz The clazz to search through
|
||||
* @param name The name of the method to locate
|
||||
* @param parameter The type the single parameter must have
|
||||
* @return The method or null if none could be located
|
||||
*/
|
||||
private Method findMethod(Class clazz, String name, Class parameter) {
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (int i=0;i<methods.length;i++) {
|
||||
if (methods[i].getName().equalsIgnoreCase(name)) {
|
||||
Method method = methods[i];
|
||||
Class[] params = method.getParameterTypes();
|
||||
|
||||
if (params.length == 1) {
|
||||
if (method.getParameterTypes()[0].isAssignableFrom(parameter)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a field value on a object instance
|
||||
*
|
||||
* @param field The field to be set
|
||||
* @param instance The instance of the object to set it on
|
||||
* @param value The value to set
|
||||
* @throws SlickXMLException Indicates a failure to set or access the field
|
||||
*/
|
||||
private void setField(Field field, Object instance, Object value) throws SlickXMLException {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
field.set(instance, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SlickXMLException("Failed to set: "+field+" for an XML attribute, is it valid?", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new SlickXMLException("Failed to set: "+field+" for an XML attribute, is it valid?", e);
|
||||
} finally {
|
||||
field.setAccessible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a method on a object
|
||||
*
|
||||
* @param method The method to call
|
||||
* @param instance The objet to call the method on
|
||||
* @param params The parameters to pass
|
||||
* @throws SlickXMLException Indicates a failure to call or access the method
|
||||
*/
|
||||
private void invoke(Method method, Object instance, Object[] params) throws SlickXMLException {
|
||||
try {
|
||||
method.setAccessible(true);
|
||||
method.invoke(instance, params);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SlickXMLException("Failed to invoke: "+method+" for an XML attribute, is it valid?", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new SlickXMLException("Failed to invoke: "+method+" for an XML attribute, is it valid?", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new SlickXMLException("Failed to invoke: "+method+" for an XML attribute, is it valid?", e);
|
||||
} finally {
|
||||
method.setAccessible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a method on a given class. Only here for tidy purposes,
|
||||
* hides the the big exceptions.
|
||||
*
|
||||
* @param clazz The class to search
|
||||
* @param name The name of the method
|
||||
* @param params The parameters for the method
|
||||
* @return The method or null if none can be found
|
||||
*/
|
||||
private Method getMethod(Class clazz, String name, Class[] params) {
|
||||
try {
|
||||
return clazz.getMethod(name, params);
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.newdawn.slick.util.xml;
|
||||
|
||||
import org.newdawn.slick.SlickException;
|
||||
|
||||
/**
|
||||
* An exception to describe failures in XML. Made a special case because with XML
|
||||
* to object parsing you might want to handle it differently
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class SlickXMLException extends SlickException {
|
||||
|
||||
/**
|
||||
* Create a new exception
|
||||
*
|
||||
* @param message The message describing the failure
|
||||
*/
|
||||
public SlickXMLException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new exception
|
||||
*
|
||||
* @param message The message describing the failure
|
||||
* @param e The exception causing this failure
|
||||
*/
|
||||
public SlickXMLException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
}
|
||||
260
lib/slick-source/org/newdawn/slick/util/xml/XMLElement.java
Normal file
260
lib/slick-source/org/newdawn/slick/util/xml/XMLElement.java
Normal file
@@ -0,0 +1,260 @@
|
||||
package org.newdawn.slick.util.xml;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.w3c.dom.Text;
|
||||
|
||||
/**
|
||||
* A utility wrapper round the standard DOM XML element. This provides a more simple API
|
||||
* for accessing attributes, children and providing defaults when schemas arn't used - which
|
||||
* is generally a little simpler for most of us.
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class XMLElement {
|
||||
/** The Java DOM implementation XML element */
|
||||
private Element dom;
|
||||
/** The list of children initialised on first access */
|
||||
private XMLElementList children;
|
||||
/** The name of the element */
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Create a new element wrapped round a DOM element
|
||||
*
|
||||
* @param xmlElement The DOM element to present
|
||||
*/
|
||||
XMLElement(Element xmlElement) {
|
||||
dom = xmlElement;
|
||||
name = dom.getTagName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of the attributes specified on this element
|
||||
*
|
||||
* @return The names of the elements specified
|
||||
*/
|
||||
public String[] getAttributeNames() {
|
||||
NamedNodeMap map = dom.getAttributes();
|
||||
String[] names = new String[map.getLength()];
|
||||
|
||||
for (int i=0;i<names.length;i++) {
|
||||
names[i] = map.item(i).getNodeName();
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this element
|
||||
*
|
||||
* @return The name of this element
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @return The value given for the attribute
|
||||
*/
|
||||
public String getAttribute(String name) {
|
||||
return dom.getAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @param def The default value to return if the attribute is specified
|
||||
* @return The value given for the attribute
|
||||
*/
|
||||
public String getAttribute(String name, String def) {
|
||||
String value = dom.getAttribute(name);
|
||||
if ((value == null) || (value.length() == 0)) {
|
||||
return def;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element as an integer.
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @return The value given for the attribute
|
||||
* @throws SlickXMLException Indicates a failure to convert the value into an integer
|
||||
*/
|
||||
public int getIntAttribute(String name) throws SlickXMLException {
|
||||
try {
|
||||
return Integer.parseInt(getAttribute(name));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SlickXMLException("Value read: '"+getAttribute(name)+"' is not an integer",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element as an integer.
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @param def The default value to return if the attribute is specified
|
||||
* @return The value given for the attribute
|
||||
* @throws SlickXMLException Indicates a failure to convert the value into an integer
|
||||
*/
|
||||
public int getIntAttribute(String name, int def) throws SlickXMLException {
|
||||
try {
|
||||
return Integer.parseInt(getAttribute(name,""+def));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SlickXMLException("Value read: '"+getAttribute(name, ""+def)+"' is not an integer",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element as an double.
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @return The value given for the attribute
|
||||
* @throws SlickXMLException Indicates a failure to convert the value into an double
|
||||
*/
|
||||
public double getDoubleAttribute(String name) throws SlickXMLException {
|
||||
try {
|
||||
return Double.parseDouble(getAttribute(name));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SlickXMLException("Value read: '"+getAttribute(name)+"' is not a double",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element as an double.
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @param def The default value to return if the attribute is specified
|
||||
* @return The value given for the attribute
|
||||
* @throws SlickXMLException Indicates a failure to convert the value into an double
|
||||
*/
|
||||
public double getDoubleAttribute(String name, double def) throws SlickXMLException {
|
||||
try {
|
||||
return Double.parseDouble(getAttribute(name,""+def));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SlickXMLException("Value read: '"+getAttribute(name, ""+def)+"' is not a double",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element as a boolean.
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @return The value given for the attribute
|
||||
* @throws SlickXMLException Indicates a failure to convert the value into an boolean
|
||||
*/
|
||||
public boolean getBooleanAttribute(String name) throws SlickXMLException {
|
||||
String value = getAttribute(name);
|
||||
if (value.equalsIgnoreCase("true")) {
|
||||
return true;
|
||||
}
|
||||
if (value.equalsIgnoreCase("false")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new SlickXMLException("Value read: '"+getAttribute(name)+"' is not a boolean");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the value specified for a given attribute on this element as a boolean.
|
||||
*
|
||||
* @param name The name of the attribute whose value should be retrieved
|
||||
* @param def The default value to return if the attribute is specified
|
||||
* @return The value given for the attribute
|
||||
* @throws SlickXMLException Indicates a failure to convert the value into an boolean
|
||||
*/
|
||||
public boolean getBooleanAttribute(String name, boolean def) throws SlickXMLException {
|
||||
String value = getAttribute(name,""+def);
|
||||
if (value.equalsIgnoreCase("true")) {
|
||||
return true;
|
||||
}
|
||||
if (value.equalsIgnoreCase("false")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new SlickXMLException("Value read: '"+getAttribute(name, ""+def)+"' is not a boolean");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of the element, i.e. the bit between the tags
|
||||
*
|
||||
* @return The text content of the node
|
||||
*/
|
||||
public String getContent() {
|
||||
String content = "";
|
||||
|
||||
NodeList list = dom.getChildNodes();
|
||||
for (int i=0;i<list.getLength();i++) {
|
||||
if (list.item(i) instanceof Text) {
|
||||
content += (list.item(i).getNodeValue());
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete list of children for this node
|
||||
*
|
||||
* @return The list of children for this node
|
||||
*/
|
||||
public XMLElementList getChildren() {
|
||||
if (children != null) {
|
||||
return children;
|
||||
}
|
||||
|
||||
NodeList list = dom.getChildNodes();
|
||||
children = new XMLElementList();
|
||||
|
||||
for (int i=0;i<list.getLength();i++) {
|
||||
if (list.item(i) instanceof Element) {
|
||||
children.add(new XMLElement((Element) list.item(i)));
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of children with a given element name
|
||||
*
|
||||
* @param name The name of the element type that should be retrieved
|
||||
* @return A list of elements
|
||||
*/
|
||||
public XMLElementList getChildrenByName(String name) {
|
||||
XMLElementList selected = new XMLElementList();
|
||||
XMLElementList children = getChildren();
|
||||
|
||||
for (int i=0;i<children.size();i++) {
|
||||
if (children.get(i).getName().equals(name)) {
|
||||
selected.add(children.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
String value = "[XML "+getName();
|
||||
String[] attrs = getAttributeNames();
|
||||
|
||||
for (int i=0;i<attrs.length;i++) {
|
||||
value += " "+attrs[i]+"="+getAttribute(attrs[i]);
|
||||
}
|
||||
|
||||
value += "]";
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.newdawn.slick.util.xml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A simple typed list.
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class XMLElementList {
|
||||
/** The list of elements */
|
||||
private ArrayList list = new ArrayList();
|
||||
|
||||
/**
|
||||
* Create a new list
|
||||
*/
|
||||
public XMLElementList() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to the list
|
||||
*
|
||||
* @param element The element to be added
|
||||
*/
|
||||
public void add(XMLElement element) {
|
||||
list.add(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of elements in the list
|
||||
*
|
||||
* @return The number of elements in the list
|
||||
*/
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element at a specified index
|
||||
*
|
||||
* @param i The index of the element
|
||||
* @return The element at the specified index
|
||||
*/
|
||||
public XMLElement get(int i) {
|
||||
return (XMLElement) list.get(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this list contains the given element
|
||||
*
|
||||
* @param element The element to check for
|
||||
* @return True if the element is in the list
|
||||
*/
|
||||
public boolean contains(XMLElement element) {
|
||||
return list.contains(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the elements in this list to another collection
|
||||
*
|
||||
* @param collection The collection the elements should be added to
|
||||
*/
|
||||
public void addAllTo(Collection collection) {
|
||||
collection.addAll(list);
|
||||
}
|
||||
}
|
||||
63
lib/slick-source/org/newdawn/slick/util/xml/XMLParser.java
Normal file
63
lib/slick-source/org/newdawn/slick/util/xml/XMLParser.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package org.newdawn.slick.util.xml;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
/**
|
||||
* A simple utility wrapper around the Java DOM implementation to hopefully
|
||||
* make XML parsing that bit easier without requiring YAL.
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class XMLParser {
|
||||
/** The factory used to to create document builders that parse XML into the DOM */
|
||||
private static DocumentBuilderFactory factory;
|
||||
|
||||
/**
|
||||
* Create a new parser
|
||||
*/
|
||||
public XMLParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML document located by the slick resource loader using the
|
||||
* reference given.
|
||||
*
|
||||
* @param ref The reference to the XML document
|
||||
* @return The root element of the newly parse document
|
||||
* @throws SlickException Indicates a failure to parse the XML, most likely the
|
||||
* XML is malformed in some way.
|
||||
*/
|
||||
public XMLElement parse(String ref) throws SlickException {
|
||||
return parse(ref, ResourceLoader.getResourceAsStream(ref));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML document that can be read from the given input stream
|
||||
*
|
||||
* @param name The name of the document
|
||||
* @param in The input stream from which the document can be read
|
||||
* @return The root element of the newly parse document
|
||||
* @throws SlickXMLException Indicates a failure to parse the XML, most likely the
|
||||
* XML is malformed in some way.
|
||||
*/
|
||||
public XMLElement parse(String name, InputStream in) throws SlickXMLException {
|
||||
try {
|
||||
if (factory == null) {
|
||||
factory = DocumentBuilderFactory.newInstance();
|
||||
}
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document doc = builder.parse(in);
|
||||
|
||||
return new XMLElement(doc.getDocumentElement());
|
||||
} catch (Exception e) {
|
||||
throw new SlickXMLException("Failed to parse document: "+name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
lib/slick-source/org/newdawn/slick/util/xml/package.html
Normal file
3
lib/slick-source/org/newdawn/slick/util/xml/package.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<BODY>
|
||||
Some utilities for reading XML using Java DOM and for mapping XML onto existing data models
|
||||
</BODY>
|
||||
Reference in New Issue
Block a user