Compare commits
1 Commits
main
...
view_asset
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
def68f30be |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,3 @@
|
||||
|
||||
.run/
|
||||
.gradle
|
||||
build/
|
||||
#!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
Binary file not shown.
18
Projekte/.run/MdgaApp.run.xml
Normal file
18
Projekte/.run/MdgaApp.run.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="MdgaApp" type="Application" factoryName="Application" singleton="false"
|
||||
nameIsGenerated="true">
|
||||
<option name="MAIN_CLASS_NAME" value="pp.mdga.client.MdgaApp"/>
|
||||
<module name="Projekte.mdga.client.main"/>
|
||||
<option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/>
|
||||
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="pp.mdga.client.*"/>
|
||||
<option name="ENABLED" value="true"/>
|
||||
</pattern>
|
||||
</extension>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true"/>
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
5
Projekte/common/build.gradle
Normal file
5
Projekte/common/build.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
plugins {
|
||||
id 'buildlogic.java-library-conventions'
|
||||
}
|
||||
|
||||
description = 'Common classes'
|
||||
7
Projekte/common/logging.properties
Normal file
7
Projekte/common/logging.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
handlers=java.util.logging.ConsoleHandler
|
||||
.level=INFO
|
||||
;pp.level=FINE
|
||||
pp.util.triangulation.Polygon.level=FINE
|
||||
java.util.logging.ConsoleHandler.level=FINER
|
||||
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
|
||||
java.util.logging.SimpleFormatter.format=[%4$s %2$s] %5$s%6$s%n
|
||||
257
Projekte/common/src/main/java/pp/util/Angle.java
Normal file
257
Projekte/common/src/main/java/pp/util/Angle.java
Normal file
@@ -0,0 +1,257 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static pp.util.FloatMath.DEG_TO_RAD;
|
||||
import static pp.util.FloatMath.FLT_EPSILON;
|
||||
import static pp.util.FloatMath.RAD_TO_DEG;
|
||||
import static pp.util.FloatMath.abs;
|
||||
import static pp.util.FloatMath.atan2;
|
||||
import static pp.util.FloatMath.cos;
|
||||
import static pp.util.FloatMath.sin;
|
||||
import static pp.util.FloatMath.sqrt;
|
||||
|
||||
/**
|
||||
* A class for representing angles by unit vectors where angles define
|
||||
* polar coordinates.
|
||||
*/
|
||||
public class Angle implements Comparable<Angle> {
|
||||
/**
|
||||
* 0 degrees, i.e., along the x-axis
|
||||
*/
|
||||
public static final Angle ZERO = new Angle(1f, 0f);
|
||||
/**
|
||||
* 180 degrees, i.e., along the negative x-axis
|
||||
*/
|
||||
public static final Angle PI = new Angle(-1f, 0f);
|
||||
|
||||
/**
|
||||
* Returns the minimum of the specified angles with respect to
|
||||
* {@linkplain #compareTo(Angle)}.
|
||||
*
|
||||
* @param u first angle to compare
|
||||
* @param v second angle to compare
|
||||
*/
|
||||
public static Angle min(Angle u, Angle v) {
|
||||
return u.compareTo(v) <= 0 ? u : v;
|
||||
}
|
||||
|
||||
/**
|
||||
* The x-coordinate of the unit vector.
|
||||
*/
|
||||
public final float x;
|
||||
/**
|
||||
* The y-coordinate of the unit vector.
|
||||
*/
|
||||
public final float y;
|
||||
|
||||
/**
|
||||
* Creates a new angle represented by the vector with the specified
|
||||
* coordinates. Note that the vector must have length 1.
|
||||
*/
|
||||
private Angle(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the polar coordinates angle of the specified vector.
|
||||
*
|
||||
* @param x the vectors x value
|
||||
* @param y the vectors y value
|
||||
*/
|
||||
public static Angle fromVector(float x, float y) {
|
||||
final float len = sqrt(x * x + y * y);
|
||||
if (len < FLT_EPSILON)
|
||||
throw new IllegalArgumentException("null vector");
|
||||
return new Angle(x / len, y / len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the polar coordinates angle when looking from the first to the second
|
||||
* specified position.
|
||||
*
|
||||
* @param from first position
|
||||
* @param to second position
|
||||
*/
|
||||
public static Angle direction(Position from, Position to) {
|
||||
return fromVector(to.getX() - from.getX(),
|
||||
to.getY() - from.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Angle object for the specified angle in radians.
|
||||
*
|
||||
* @param radians the specified radian value
|
||||
*/
|
||||
public static Angle fromRadians(float radians) {
|
||||
return new Angle(cos(radians), sin(radians));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Angle object for the specified angle in degrees.
|
||||
*
|
||||
* @param degrees the specified degrees value
|
||||
*/
|
||||
public static Angle fromDegrees(float degrees) {
|
||||
return fromRadians(degrees * DEG_TO_RAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this angle in radians in the range (-pi,pi].
|
||||
*/
|
||||
public float radians() {
|
||||
return atan2(y, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this angle in degrees in the range (-180,180].
|
||||
*/
|
||||
public float degrees() {
|
||||
return radians() * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x-coordinate of the unit vector, that is the cosine of the angle.
|
||||
*/
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate of the unit vector, that is the sinus of the angle.
|
||||
*/
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the angle obtained by adding the specified angle to this angle.
|
||||
*
|
||||
* @param o the other angle
|
||||
*/
|
||||
public Angle plus(Angle o) {
|
||||
return new Angle(x * o.x - y * o.y,
|
||||
x * o.y + y * o.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the angle obtained by subtracting the specified angle from this angle.
|
||||
*
|
||||
* @param o the other angle
|
||||
*/
|
||||
public Angle minus(Angle o) {
|
||||
return new Angle(y * o.y + x * o.x,
|
||||
y * o.x - x * o.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bisector angle between this angle as left angle and the
|
||||
* specified right angle, that is, the angle halfway from the right to this
|
||||
* angle when turning from right to left.
|
||||
*
|
||||
* @param right right angle
|
||||
* @return the bisector angle between this as left and the specified right angle
|
||||
*/
|
||||
public Angle bisector(Angle right) {
|
||||
// compute vector of this.minus(right)
|
||||
final float dx = y * right.y + x * right.x;
|
||||
final float dy = y * right.x - x * right.y;
|
||||
if (abs(dy) < FLT_EPSILON) {
|
||||
// the difference is either 0° or 180°
|
||||
if (dx > 0f)
|
||||
return this;
|
||||
else
|
||||
return new Angle(-right.y, right.x);
|
||||
}
|
||||
final float mid = 0.5f * atan2(dy, dx);
|
||||
final float sum = right.radians() + mid;
|
||||
if (mid > 0f)
|
||||
return new Angle(cos(sum), sin(sum));
|
||||
else
|
||||
return new Angle(-cos(sum), -sin(sum));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if turning left from the specified angle towards this angle is
|
||||
* shorter than turning right towards this angle.
|
||||
*
|
||||
* @param o another angle
|
||||
*/
|
||||
public boolean leftOf(Angle o) {
|
||||
return y * o.x - x * o.y > 0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if turning right from the specified angle towards this angle is
|
||||
* shorter than turning left towards this angle.
|
||||
*
|
||||
* @param o another angle
|
||||
*/
|
||||
public boolean rightOf(Angle o) {
|
||||
return y * o.x - x * o.y < 0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this angle with the specified one and returns -1, 0, or 1 if
|
||||
* the value of this angle is less than, equal to, or greater than the value
|
||||
* of the specified angle, respectively, where angle values are in the
|
||||
* range [0,2*pi) and 0 means along the x-axis.
|
||||
*
|
||||
* @param o the other angle
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Angle o) {
|
||||
if (y == 0f) {
|
||||
if (o.y < 0f)
|
||||
return -1;
|
||||
if (o.y > 0f)
|
||||
return x > 0f ? -1 : 1;
|
||||
if (x > 0f)
|
||||
return o.x > 0f ? 0 : 1;
|
||||
return o.x > 0f ? -1 : 0;
|
||||
}
|
||||
if (y > 0f) {
|
||||
if (o.y < 0f)
|
||||
return -1;
|
||||
if (o.y == 0f)
|
||||
return o.x > 0f ? 1 : -1;
|
||||
}
|
||||
else if (o.y >= 0f)
|
||||
return 1;
|
||||
final float det = x * o.y - y * o.x;
|
||||
if (det == 0f)
|
||||
return 0;
|
||||
return det > 0f ? -1 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof Angle angle)
|
||||
return compareTo(angle) == 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this angle in degrees in the range [0,2*pi).
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (degrees() < 0f)
|
||||
return (degrees() + 360f) + "°";
|
||||
return degrees() + "°";
|
||||
}
|
||||
}
|
||||
460
Projekte/common/src/main/java/pp/util/FloatMath.java
Normal file
460
Projekte/common/src/main/java/pp/util/FloatMath.java
Normal file
@@ -0,0 +1,460 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
/**
|
||||
* Provides mathematical functions using float precision.
|
||||
*/
|
||||
public class FloatMath {
|
||||
private FloatMath() { /* don't instantiate */ }
|
||||
|
||||
public static final double DBL_EPSILON = 2.220446049250313E-16d;
|
||||
/**
|
||||
* A "close to zero" float epsilon value for use
|
||||
*/
|
||||
public static final float FLT_EPSILON = 1.1920928955078125E-7f;
|
||||
/**
|
||||
* A "close to zero" float epsilon value for use
|
||||
*/
|
||||
public static final float ZERO_TOLERANCE = 0.0001f;
|
||||
/**
|
||||
* The value 1/3, as a float.
|
||||
*/
|
||||
public static final float ONE_THIRD = 1f / 3f;
|
||||
/**
|
||||
* The value PI as a float. (180 degrees)
|
||||
*/
|
||||
public static final float PI = (float) Math.PI;
|
||||
/**
|
||||
* The value 2PI as a float. (360 degrees)
|
||||
*/
|
||||
public static final float TWO_PI = 2.0f * PI;
|
||||
/**
|
||||
* The value PI/2 as a float. (90 degrees)
|
||||
*/
|
||||
public static final float HALF_PI = 0.5f * PI;
|
||||
/**
|
||||
* The value PI/4 as a float. (45 degrees)
|
||||
*/
|
||||
public static final float QUARTER_PI = 0.25f * PI;
|
||||
/**
|
||||
* The value 1/PI as a float.
|
||||
*/
|
||||
public static final float INV_PI = 1.0f / PI;
|
||||
/**
|
||||
* The value 1/(2PI) as a float.
|
||||
*/
|
||||
public static final float INV_TWO_PI = 1.0f / TWO_PI;
|
||||
/**
|
||||
* A value to multiply a degree value by, to convert it to radians.
|
||||
*/
|
||||
public static final float DEG_TO_RAD = PI / 180.0f;
|
||||
/**
|
||||
* A value to multiply a radian value by, to convert it to degrees.
|
||||
*/
|
||||
public static final float RAD_TO_DEG = 180.0f / PI;
|
||||
|
||||
/**
|
||||
* Linear interpolation from startValue to endValue by the given percent.
|
||||
* Basically: ((1 - percent) * startValue) + (percent * endValue)
|
||||
*
|
||||
* @param scale scale value to use. if 1, use endValue, if 0, use startValue.
|
||||
* @param startValue Beginning value. 0% of f
|
||||
* @param endValue ending value. 100% of f
|
||||
* @return The interpolated value between startValue and endValue.
|
||||
*/
|
||||
public static float interpolateLinear(float scale, float startValue, float endValue) {
|
||||
if (startValue == endValue) {
|
||||
return startValue;
|
||||
}
|
||||
if (scale <= 0f) {
|
||||
return startValue;
|
||||
}
|
||||
if (scale >= 1f) {
|
||||
return endValue;
|
||||
}
|
||||
return ((1f - scale) * startValue) + (scale * endValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear extrapolation from startValue to endValue by the given scale.
|
||||
* if scale is between 0 and 1 this method returns the same result as interpolateLinear
|
||||
* if the scale is over 1 the value is linearly extrapolated.
|
||||
* Note that the end value is the value for a scale of 1.
|
||||
*
|
||||
* @param scale the scale for extrapolation
|
||||
* @param startValue the starting value (scale = 0)
|
||||
* @param endValue the end value (scale = 1)
|
||||
* @return an extrapolation for the given parameters
|
||||
*/
|
||||
public static float extrapolateLinear(float scale, float startValue, float endValue) {
|
||||
return ((1f - scale) * startValue) + (scale * endValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arc cosine of a value.<br>
|
||||
* Special cases:
|
||||
* <ul><li>If fValue is smaller than -1, then the result is PI.
|
||||
* <li>If the argument is greater than 1, then the result is 0.</ul>
|
||||
*
|
||||
* @param fValue The value to arc cosine.
|
||||
* @return The angle, in radians.
|
||||
* @see Math#acos(double)
|
||||
*/
|
||||
public static float acos(float fValue) {
|
||||
if (-1.0f < fValue) {
|
||||
if (fValue < 1.0f) {
|
||||
return (float) Math.acos(fValue);
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arc sine of a value.<br>
|
||||
* Special cases:
|
||||
* <ul><li>If fValue is smaller than -1, then the result is -HALF_PI.
|
||||
* <li>If the argument is greater than 1, then the result is HALF_PI.</ul>
|
||||
*
|
||||
* @param fValue The value to arc sine.
|
||||
* @return the angle in radians.
|
||||
* @see Math#asin(double)
|
||||
*/
|
||||
public static float asin(float fValue) {
|
||||
if (-1.0f < fValue) {
|
||||
if (fValue < 1.0f) {
|
||||
return (float) Math.asin(fValue);
|
||||
}
|
||||
|
||||
return HALF_PI;
|
||||
}
|
||||
|
||||
return -HALF_PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arc tangent of an angle given in radians.<br>
|
||||
*
|
||||
* @param fValue The angle, in radians.
|
||||
* @return fValue's atan
|
||||
* @see Math#atan(double)
|
||||
*/
|
||||
public static float atan(float fValue) {
|
||||
return (float) Math.atan(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* A direct call to Math.atan2.
|
||||
*
|
||||
* @param fY ordinate
|
||||
* @param fX abscissa
|
||||
* @return Math.atan2(fY, fX)
|
||||
* @see Math#atan2(double, double)
|
||||
*/
|
||||
public static float atan2(float fY, float fX) {
|
||||
return (float) Math.atan2(fY, fX);
|
||||
}
|
||||
|
||||
/**
|
||||
* A direct call to Math.sinh.
|
||||
*
|
||||
* @param x The value for which to compute the hyperbolic sine
|
||||
* @return Math.sinh(x)
|
||||
* @see Math#sinh(double)
|
||||
*/
|
||||
public static float sinh(float x) {
|
||||
return (float) Math.sinh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* A direct call to Math.cosh.
|
||||
*
|
||||
* @param x The value for which to compute the hyperbolic cosine
|
||||
* @return Math.cosh(x)
|
||||
* @see Math#cosh(double)
|
||||
*/
|
||||
public static float cosh(float x) {
|
||||
return (float) Math.cosh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* A direct call to Math.tanh.
|
||||
*
|
||||
* @param x The value for which to compute the hyperbolic tangent
|
||||
* @return Math.tanh(x)
|
||||
* @see Math#tanh(double)
|
||||
*/
|
||||
public static float tanh(float x) {
|
||||
return (float) Math.tanh(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hyperbolic cotangent of a value.
|
||||
* @param x The value for which to compute the hyperbolic cotangent.
|
||||
* @return The hyperbolic cotangent of x.
|
||||
* @see Math#tanh(double)
|
||||
*/
|
||||
public static float coth(float x) {
|
||||
return (float) (1d / Math.tanh(x));
|
||||
}
|
||||
|
||||
public static float arsinh(float x) {
|
||||
return log(x + sqrt(x * x + 1f));
|
||||
}
|
||||
|
||||
public static float arcosh(float x) {
|
||||
return log(x + sqrt(x * x - 1f));
|
||||
}
|
||||
|
||||
public static float artanh(float x) {
|
||||
return 0.5f * log((1f + x) / (1f - x));
|
||||
}
|
||||
|
||||
public static float arcoth(float x) {
|
||||
return 0.5f * log((x + 1f) / (x - 1f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a fValue up. A call to Math.ceil
|
||||
*
|
||||
* @param fValue The value.
|
||||
* @return The fValue rounded up
|
||||
* @see Math#ceil(double)
|
||||
*/
|
||||
public static float ceil(float fValue) {
|
||||
return (float) Math.ceil(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cosine of an angle. Direct call to Math
|
||||
*
|
||||
* @param v The angle to cosine.
|
||||
* @return the cosine of the angle.
|
||||
* @see Math#cos(double)
|
||||
*/
|
||||
public static float cos(float v) {
|
||||
return (float) Math.cos(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sine of an angle. Direct call to Math
|
||||
*
|
||||
* @param v The angle to sine.
|
||||
* @return the sine of the angle.
|
||||
* @see Math#sin(double)
|
||||
*/
|
||||
public static float sin(float v) {
|
||||
return (float) Math.sin(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns E^fValue
|
||||
*
|
||||
* @param fValue Value to raise to a power.
|
||||
* @return The value E^fValue
|
||||
* @see Math#exp(double)
|
||||
*/
|
||||
public static float exp(float fValue) {
|
||||
return (float) Math.exp(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns e^fValue - 1.
|
||||
* This is equivalent to calling Math.expm1.
|
||||
*
|
||||
* @param fValue The exponent to raise e to, minus 1.
|
||||
* @return The result of e^fValue - 1.
|
||||
* @see Math#expm1(double)
|
||||
*/
|
||||
public static float expm1(float fValue) {
|
||||
return (float) Math.expm1(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Absolute value of a float.
|
||||
*
|
||||
* @param fValue The value to abs.
|
||||
* @return The abs of the value.
|
||||
* @see Math#abs(float)
|
||||
*/
|
||||
public static float abs(float fValue) {
|
||||
if (fValue < 0) {
|
||||
return -fValue;
|
||||
}
|
||||
return fValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number rounded down.
|
||||
*
|
||||
* @param fValue The value to round
|
||||
* @return The given number rounded down
|
||||
* @see Math#floor(double)
|
||||
*/
|
||||
public static float floor(float fValue) {
|
||||
return (float) Math.floor(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1/sqrt(fValue)
|
||||
*
|
||||
* @param fValue The value to process.
|
||||
* @return 1/sqrt(fValue)
|
||||
* @see Math#sqrt(double)
|
||||
*/
|
||||
public static float invSqrt(float fValue) {
|
||||
return (float) (1.0f / Math.sqrt(fValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly estimate 1/sqrt(fValue).
|
||||
*
|
||||
* @param x the input value (≥0)
|
||||
* @return an approximate value for 1/sqrt(x)
|
||||
*/
|
||||
public static float fastInvSqrt(float x) {
|
||||
float halfX = 0.5f * x;
|
||||
int i = Float.floatToIntBits(x); // get bits for floating value
|
||||
i = 0x5f375a86 - (i >> 1); // gives initial guess y0
|
||||
x = Float.intBitsToFloat(i); // convert bits back to float
|
||||
x = x * (1.5f - halfX * x * x); // Newton step, repeating increases accuracy
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the log base E of a value.
|
||||
*
|
||||
* @param fValue The value to log.
|
||||
* @return The log of fValue base E
|
||||
* @see Math#log(double)
|
||||
*/
|
||||
public static float log(float fValue) {
|
||||
return (float) Math.log(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number raised to an exponent power. fBase^fExponent
|
||||
*
|
||||
* @param fBase The base value (IE 2)
|
||||
* @param fExponent The exponent value (IE 3)
|
||||
* @return base raised to exponent (IE 8)
|
||||
* @see Math#pow(double, double)
|
||||
*/
|
||||
public static float pow(float fBase, float fExponent) {
|
||||
return (float) Math.pow(fBase, fExponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value squared. fValue ^ 2
|
||||
*
|
||||
* @param fValue The value to square.
|
||||
* @return The square of the given value.
|
||||
*/
|
||||
public static float sqr(float fValue) {
|
||||
return fValue * fValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square root of a given value.
|
||||
*
|
||||
* @param fValue The value to sqrt.
|
||||
* @return The square root of the given value.
|
||||
* @see Math#sqrt(double)
|
||||
*/
|
||||
public static float sqrt(float fValue) {
|
||||
return (float) Math.sqrt(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tangent of the specified angle.
|
||||
*
|
||||
* @param fValue The value to tangent, in radians.
|
||||
* @return The tangent of fValue.
|
||||
* @see Math#tan(double)
|
||||
*/
|
||||
public static float tan(float fValue) {
|
||||
return (float) Math.tan(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise
|
||||
*
|
||||
* @param iValue The integer to examine.
|
||||
* @return The integer's sign.
|
||||
*/
|
||||
public static int sign(int iValue) {
|
||||
return Integer.compare(iValue, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise
|
||||
*
|
||||
* @param fValue The float to examine.
|
||||
* @return The float's sign.
|
||||
*/
|
||||
public static float sign(float fValue) {
|
||||
return Math.signum(fValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a float input and clamp it between min and max.
|
||||
*
|
||||
* @param input the value to be clamped
|
||||
* @param min the minimum output value
|
||||
* @param max the maximum output value
|
||||
* @return clamped input
|
||||
*/
|
||||
public static float clamp(float input, float min, float max) {
|
||||
return Math.max(min, Math.min(input, max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps the given float to be between 0 and 1.
|
||||
*
|
||||
* @param input the value to be clamped
|
||||
* @return input clamped between 0 and 1.
|
||||
*/
|
||||
public static float saturate(float input) {
|
||||
return clamp(input, 0f, 1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two floats are approximately equal.
|
||||
* This takes into account the magnitude of the floats, since
|
||||
* large numbers will have larger differences be close to each other.
|
||||
* <p>
|
||||
* Should return true for a=100000, b=100001, but false for a=10000, b=10001.
|
||||
*
|
||||
* @param a The first float to compare
|
||||
* @param b The second float to compare
|
||||
* @return True if a and b are approximately equal, false otherwise.
|
||||
*/
|
||||
public static boolean approximateEquals(float a, float b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return (abs(a - b) / Math.max(abs(a), abs(b))) <= 0.00001f;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the specified angle to lie in the interval (-pi,pi] and returns the normalized value in radians.
|
||||
*
|
||||
* @param angle the specified angle in radians
|
||||
*/
|
||||
public static float normalizeAngle(float angle) {
|
||||
final float res = angle % TWO_PI;
|
||||
if (res <= -FloatMath.PI) return res + TWO_PI;
|
||||
else if (res > FloatMath.PI) return res - TWO_PI;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
47
Projekte/common/src/main/java/pp/util/FloatPoint.java
Normal file
47
Projekte/common/src/main/java/pp/util/FloatPoint.java
Normal file
@@ -0,0 +1,47 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
/**
|
||||
* A trivial implementation of points in the plane with float coordinates.
|
||||
*
|
||||
* @param x x-coordinate
|
||||
* @param y y-coordinate
|
||||
*/
|
||||
public record FloatPoint(float x, float y) implements Position {
|
||||
public static final FloatPoint ZERO = new FloatPoint(0f, 0f);
|
||||
|
||||
/**
|
||||
* Create a new FloatPoint object for the given position.
|
||||
*
|
||||
* @param p a position
|
||||
*/
|
||||
public FloatPoint(Position p) {
|
||||
this(p.getX(), p.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x-coordinate.
|
||||
*/
|
||||
@Override
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate.
|
||||
*/
|
||||
@Override
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public static Position p(float x, float y) {
|
||||
return new FloatPoint(x, y);
|
||||
}
|
||||
}
|
||||
77
Projekte/common/src/main/java/pp/util/Interval.java
Normal file
77
Projekte/common/src/main/java/pp/util/Interval.java
Normal file
@@ -0,0 +1,77 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import static pp.util.FloatMath.ZERO_TOLERANCE;
|
||||
import static pp.util.FloatMath.abs;
|
||||
|
||||
/**
|
||||
* Represents an interval. The start value must not be greater than the end value.
|
||||
*
|
||||
* @param from the start value of the interval
|
||||
* @param to the end value of the interval
|
||||
*/
|
||||
public record Interval(float from, float to) {
|
||||
|
||||
/**
|
||||
* Creates a new interval between two specified values.
|
||||
*
|
||||
* @param from the specified start value
|
||||
* @param to the specified end value
|
||||
*/
|
||||
public Interval {
|
||||
if (from > to)
|
||||
throw new IllegalArgumentException(from + " > " + to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this interval has length 0, i.e., from and to are equal.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return to == from;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified value is contained in this interval. Note that an empty interval may
|
||||
* contain the value if the value is the start and the end value of the interval.
|
||||
*
|
||||
* @param value the specified value to check
|
||||
*/
|
||||
public boolean contains(float value) {
|
||||
return from - value <= ZERO_TOLERANCE && value - to <= ZERO_TOLERANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified interval is contained as a sub-interval.
|
||||
*
|
||||
* @param other the potential sub-interval
|
||||
*/
|
||||
public boolean contains(Interval other) {
|
||||
return from - other.from < ZERO_TOLERANCE && other.to - to < ZERO_TOLERANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this interval.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + from + "; " + to + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified interval is almost equal to this
|
||||
* interval up to the specified epsilon value.
|
||||
*
|
||||
* @param other the other interval to check
|
||||
* @param eps the allowed epsilon value
|
||||
*/
|
||||
public boolean matches(Interval other, float eps) {
|
||||
return abs(from - other.from) < eps &&
|
||||
abs(to - other.to) < eps;
|
||||
}
|
||||
}
|
||||
84
Projekte/common/src/main/java/pp/util/Position.java
Normal file
84
Projekte/common/src/main/java/pp/util/Position.java
Normal file
@@ -0,0 +1,84 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import static pp.util.FloatMath.sqrt;
|
||||
|
||||
/**
|
||||
* Interface for all objects that provide a position in the plane.
|
||||
*/
|
||||
public interface Position extends Comparable<Position> {
|
||||
/**
|
||||
* Returns the x-coordinate of the position
|
||||
*
|
||||
* @return x-coordinate as float
|
||||
*/
|
||||
float getX();
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate of the position
|
||||
*
|
||||
* @return y-coordinate as float
|
||||
*/
|
||||
float getY();
|
||||
|
||||
/**
|
||||
* Returns the distance of this position from the specified position.
|
||||
*
|
||||
* @param x x-coordinate of the position
|
||||
* @param y y-coordinate of the position
|
||||
* @return distance
|
||||
*/
|
||||
default float distanceTo(float x, float y) {
|
||||
return sqrt(distanceSquaredTo(x, y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance of this position from the specified position.
|
||||
* This is just a convenience method for {@linkplain #distanceTo(float, float)}.
|
||||
*
|
||||
* @param other the other position
|
||||
* @return distance
|
||||
*/
|
||||
default float distanceTo(Position other) {
|
||||
return distanceTo(other.getX(), other.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the squared distance of this position from the specified position.
|
||||
*
|
||||
* @param x x-coordinate of the position
|
||||
* @param y y-coordinate of the position
|
||||
* @return squared distance
|
||||
*/
|
||||
default float distanceSquaredTo(float x, float y) {
|
||||
final float dx = getX() - x;
|
||||
final float dy = getY() - y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the squared distance of this position from the specified position.
|
||||
*
|
||||
* @param p the other position
|
||||
* @return squared distance
|
||||
*/
|
||||
default float distanceSquaredTo(Position p) {
|
||||
return distanceSquaredTo(p.getX(), p.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares positions in the plane from top to bottom
|
||||
* (y coordinates grow downwards) and then from left ro right.
|
||||
*/
|
||||
@Override
|
||||
default int compareTo(Position other) {
|
||||
final int c = Float.compare(getY(), other.getY());
|
||||
return c != 0 ? c : Float.compare(getX(), other.getX());
|
||||
}
|
||||
}
|
||||
30
Projekte/common/src/main/java/pp/util/PreferencesUtils.java
Normal file
30
Projekte/common/src/main/java/pp/util/PreferencesUtils.java
Normal file
@@ -0,0 +1,30 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* A class with convenience methods for preferences.
|
||||
*/
|
||||
public class PreferencesUtils {
|
||||
private PreferencesUtils() {
|
||||
// don't instantiate
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a preferences node for the specified class object. The path of the
|
||||
* preference node corresponds to the fully qualified name of the class.
|
||||
*
|
||||
* @param clazz a class object
|
||||
* @return a preference node for the specified class
|
||||
*/
|
||||
public static Preferences getPreferences(Class<?> clazz) {
|
||||
return Preferences.userNodeForPackage(clazz).node(clazz.getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* An iterator that creates a random permutation of positions (x, y) where x and y are integer values
|
||||
* in the range {0, ..., width-1} x {0, ..., height-1}. All permutations are uniformly distributed
|
||||
* using the Fisher–Yates shuffle (also known as Knuth shuffle).
|
||||
*
|
||||
* @param <T> the type of elements returned by this iterator
|
||||
*/
|
||||
public class RandomPositionIterator<T> implements Iterator<T> {
|
||||
private final Random random = new Random();
|
||||
private final int height;
|
||||
private final Map<Integer, T> movedMap = new HashMap<>();
|
||||
private int remaining;
|
||||
private final Creator<T> creator;
|
||||
|
||||
/**
|
||||
* Functional interface to create instances of type T.
|
||||
*
|
||||
* @param <T> the type of elements created by this creator
|
||||
*/
|
||||
public interface Creator<T> {
|
||||
T create(int x, int y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a RandomPositionIterator for Position instances with float coordinates.
|
||||
*
|
||||
* @param width the width of the rectangle
|
||||
* @param height the height of the rectangle
|
||||
* @return a RandomPositionIterator for Position instances
|
||||
*/
|
||||
public static RandomPositionIterator<Position> floatPoints(int width, int height) {
|
||||
return new RandomPositionIterator<>(FloatPoint::new, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new permutation iterator generating a random permutation of positions (x, y)
|
||||
* where x and y are integer values in the range {0, ..., width-1} x {0, ..., height-1}.
|
||||
*
|
||||
* @param creator the creator to create instances of type T
|
||||
* @param width the width of the rectangle
|
||||
* @param height the height of the rectangle
|
||||
*/
|
||||
public RandomPositionIterator(Creator<T> creator, int width, int height) {
|
||||
this.height = height;
|
||||
this.remaining = width * height;
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return remaining > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (hasNext()) {
|
||||
final int idx = random.nextInt(remaining--); // note that remaining is decremented
|
||||
final T result = getWhere(idx);
|
||||
if (idx < remaining)
|
||||
movedMap.put(idx, getWhere(remaining));
|
||||
movedMap.remove(remaining);
|
||||
return result;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
private T getWhere(int idx) {
|
||||
final T movedWhere = movedMap.get(idx);
|
||||
if (movedWhere != null)
|
||||
return movedWhere;
|
||||
final int x = idx / height;
|
||||
final int y = idx % height;
|
||||
return creator.create(x, y);
|
||||
}
|
||||
}
|
||||
16
Projekte/common/src/main/java/pp/util/Segment.java
Normal file
16
Projekte/common/src/main/java/pp/util/Segment.java
Normal file
@@ -0,0 +1,16 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
/**
|
||||
* A directed straight line segment between two positions in the plane.
|
||||
*
|
||||
* @param from the start position of the segment
|
||||
* @param to the end position of the segment
|
||||
*/
|
||||
public record Segment(Position from, Position to) implements SegmentLike {}
|
||||
396
Projekte/common/src/main/java/pp/util/SegmentLike.java
Normal file
396
Projekte/common/src/main/java/pp/util/SegmentLike.java
Normal file
@@ -0,0 +1,396 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import static java.lang.Float.max;
|
||||
import static java.lang.Float.min;
|
||||
import static pp.util.FloatMath.FLT_EPSILON;
|
||||
import static pp.util.FloatMath.ZERO_TOLERANCE;
|
||||
import static pp.util.FloatMath.abs;
|
||||
import static pp.util.FloatMath.atan2;
|
||||
import static pp.util.FloatMath.cos;
|
||||
import static pp.util.FloatMath.sin;
|
||||
import static pp.util.FloatMath.sqr;
|
||||
import static pp.util.FloatMath.sqrt;
|
||||
|
||||
/**
|
||||
* Interface of geometrical objects like segments, i.e., having a start and an end position.
|
||||
*/
|
||||
public interface SegmentLike {
|
||||
/**
|
||||
* Returns the start position of the segment.
|
||||
*/
|
||||
Position from();
|
||||
|
||||
/**
|
||||
* Returns the end position of the segment.
|
||||
*/
|
||||
Position to();
|
||||
|
||||
/**
|
||||
* Returns the length, i.e., the distance between the start and the end point of this segment.
|
||||
*
|
||||
* @return length of the line segment
|
||||
*/
|
||||
default float length() {
|
||||
return sqrt(lengthSquared());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length squared where the length is the distance between the start and the
|
||||
* end point of this segment.
|
||||
*
|
||||
* @return length squared of the line segment
|
||||
*/
|
||||
default float lengthSquared() {
|
||||
return lengthSquared(from(), to());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the squared length of the vector between the specified points.
|
||||
*/
|
||||
static float lengthSquared(Position from, Position to) {
|
||||
final float dx = to.getX() - from.getX();
|
||||
final float dy = to.getY() - from.getY();
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the vector between the specified points.
|
||||
*/
|
||||
static float length(Position from, Position to) {
|
||||
return sqrt(lengthSquared(from, to));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the angle of this line segment with the x-axis.
|
||||
*
|
||||
* @return angle with the x-axis
|
||||
*/
|
||||
default float angle() {
|
||||
final float dx = to().getX() - from().getX();
|
||||
final float dy = to().getY() - from().getY();
|
||||
return atan2(dy, dx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance of the specified point from this segment.
|
||||
* The distance is the length of the shortest connection between the specified
|
||||
* point and any point of the line segment.
|
||||
*
|
||||
* @param x x-coordinate of the point
|
||||
* @param y y-coordinate of the point
|
||||
* @return the distance
|
||||
*/
|
||||
default float distanceTo(float x, float y) {
|
||||
return distance(from().getX(), from().getY(), to().getX(), to().getY(), x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance of the point from the segment between the specified points.
|
||||
* The distance is the length of the shortest connection between the specified
|
||||
* point and any point of the line segment.
|
||||
*
|
||||
* @param from the segment start point
|
||||
* @param to the segment end point
|
||||
* @param p the point
|
||||
* @return the distance
|
||||
*/
|
||||
static float distance(Position from, Position to, Position p) {
|
||||
return distance(from.getX(), from.getY(), to.getX(), to.getY(), p.getX(), p.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance of the point (x,y) from the segment from (x1,y1) to (x2,y2)
|
||||
* The distance is the length of the shortest connection between the specified
|
||||
* point and any point of the line segment.
|
||||
*
|
||||
* @param x1 x-coordinate of the segment start point
|
||||
* @param y1 y-coordinate of the segment start point
|
||||
* @param x2 x-coordinate of the segment end point
|
||||
* @param y2 y-coordinate of the segment end point
|
||||
* @param x x-coordinate of the point
|
||||
* @param y y-coordinate of the point
|
||||
* @return the distance
|
||||
*/
|
||||
static float distance(float x1, float y1, float x2, float y2, float x, float y) {
|
||||
final float dx = x2 - x1;
|
||||
final float dy = y2 - y1;
|
||||
final float dx1 = x - x1;
|
||||
final float dy1 = y - y1;
|
||||
if (dx * dx1 + dy * dy1 <= 0f)
|
||||
return sqrt(dx1 * dx1 + dy1 * dy1);
|
||||
final float dx2 = x - x2;
|
||||
final float dy2 = y - y2;
|
||||
if (dx * dx2 + dy * dy2 >= 0f)
|
||||
return sqrt(dx2 * dx2 + dy2 * dy2);
|
||||
final float len = sqrt(dx * dx + dy * dy);
|
||||
return FloatMath.abs(dx1 * dy - dy1 * dx) / len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance of the specified position from this segment.
|
||||
* This is just a convenience method for {@linkplain #distanceTo(float, float)}.
|
||||
*
|
||||
* @param pos a position
|
||||
* @return the distance
|
||||
*/
|
||||
default float distanceTo(Position pos) {
|
||||
return distanceTo(pos.getX(), pos.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the point of this segment with the specified quotient, i.e., q*from()+(1-q)*to().
|
||||
*
|
||||
* @param q the quotient
|
||||
*/
|
||||
default Position pointAt(float q) {
|
||||
if (q == 0f) return from();
|
||||
if (q == 1f) return to();
|
||||
return new FloatPoint((1f - q) * from().getX() + q * to().getX(),
|
||||
(1f - q) * from().getY() + q * to().getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shoots a ray from the specified position in the direction of the specified angle and returns the distance
|
||||
* of the specified position from the intersection point of the ray with the straight line determined by the
|
||||
* end points of this segment. Returns {@linkplain Float#NaN} if there is no intersection.
|
||||
*
|
||||
* @param pos the specified position
|
||||
* @param angle the specified angle
|
||||
*/
|
||||
default float dist(Position pos, float angle) {
|
||||
return quotientDist(pos, quotient(pos, angle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shoots a ray from the specified position in the direction of the specified angle and returns the distance
|
||||
* of the specified position from the intersection point of the ray with the straight line determined by the
|
||||
* end points of this segment. Returns {@linkplain Float#NaN} if there is no intersection.
|
||||
*
|
||||
* @param pos the specified position
|
||||
* @param angle the specified angle
|
||||
*/
|
||||
default float dist(Position pos, Angle angle) {
|
||||
return quotientDist(pos, quotient(pos, angle.x, angle.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shoots a ray from the specified position in the direction of the point of this segment with the specified
|
||||
* quotient, i.e., the point at q*from()+(1-q)*to(), and returns the distance
|
||||
* of the specified position from the intersection point of the ray with the straight line determined by the
|
||||
* end points of this segment. Returns {@linkplain Float#NaN} if there is no intersection.
|
||||
*
|
||||
* @param pos the specified position
|
||||
* @param q the specified quotient
|
||||
*/
|
||||
default float quotientDist(Position pos, float q) {
|
||||
final float dx = (1f - q) * from().getX() + q * to().getX() - pos.getX();
|
||||
final float dy = (1f - q) * from().getY() + q * to().getY() - pos.getY();
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shoots a ray from the specified position in the direction of the specified angle and returns the
|
||||
* quotient q such that the intersection point of the ray with the straight line determined by the
|
||||
* end points of this segment is at q*from()+(1-q)*to().
|
||||
*
|
||||
* @param pos the specified position
|
||||
* @param angle the specified angle
|
||||
*/
|
||||
default float quotient(Position pos, float angle) {
|
||||
final float ux = cos(angle);
|
||||
final float uy = sin(angle);
|
||||
return quotient(pos, ux, uy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shoots a ray from the specified position in the direction of the specified vector and returns the
|
||||
* quotient q such that the intersection point of the ray with the straight line determined by the
|
||||
* end points of this segment is at q*from()+(1-q)*to().
|
||||
*
|
||||
* @param pos the specified position
|
||||
* @param ux the vectors x value
|
||||
* @param uy the vectors y value
|
||||
*/
|
||||
private float quotient(Position pos, float ux, float uy) {
|
||||
final float nom = nominator(pos, ux, uy);
|
||||
final float det = determinant(ux, uy);
|
||||
// the following is for dealing with floating point imprecision
|
||||
if (abs(det) > FLT_EPSILON)
|
||||
return nom / det;
|
||||
if (abs(nom) > FLT_EPSILON)
|
||||
return Float.NaN;
|
||||
final float q = project(pos);
|
||||
if (q > -FLT_EPSILON && q - 1f < FLT_EPSILON)
|
||||
// pos lies (almost) within the segment
|
||||
return q;
|
||||
final float distFrom = isCandidate(pos, ux, uy, from());
|
||||
final float distTo = isCandidate(pos, ux, uy, to());
|
||||
if (distFrom >= 0f) {
|
||||
if (distTo >= 0f)
|
||||
return distFrom < distTo ? 0f : 1f;
|
||||
else
|
||||
return 0f;
|
||||
}
|
||||
if (distTo >= 0f)
|
||||
return 1f;
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the determinant of a specified vector.
|
||||
*
|
||||
* @param ux the vectors x value
|
||||
* @param uy the vectors y value
|
||||
*/
|
||||
private float determinant(float ux, float uy) {
|
||||
return diffX() * uy - diffY() * ux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nominator of the specified vector starting at a specified position.
|
||||
*
|
||||
* @param pos the specified position
|
||||
* @param ux the vectors x value
|
||||
* @param uy the vectors y value
|
||||
*/
|
||||
private float nominator(Position pos, float ux, float uy) {
|
||||
final float dx = pos.getX() - from().getX();
|
||||
final float dy = pos.getY() - from().getY();
|
||||
return dx * uy - dy * ux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the (ux,uy) ray starting at pos hits (or almost hits) target.
|
||||
*
|
||||
* @param pos the specified start position
|
||||
* @param ux the rays x value
|
||||
* @param uy the rays y value
|
||||
* @param target the specified target position
|
||||
*/
|
||||
private float isCandidate(Position pos, float ux, float uy, Position target) {
|
||||
final float lambda = lambda(pos, target, ux, uy);
|
||||
if (lambda < -FLT_EPSILON) return -1f;
|
||||
final float dx = target.getX() - pos.getX() - lambda * ux;
|
||||
final float dy = target.getY() - pos.getY() - lambda * uy;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
private float lambda(Position p1, Position p2, float ux, float uy) {
|
||||
return ux * (p2.getX() - p1.getX()) + uy * (p2.getY() - p1.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient q such that the specified point projected onto this
|
||||
* segment is at q*from()+(1-q)*to().
|
||||
*
|
||||
* @param pos the specified points position
|
||||
*/
|
||||
default float project(Position pos) {
|
||||
return ((pos.getX() - from().getX()) * diffX() + (pos.getY() - from().getY()) * diffY()) / lengthSquared();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interval of quotients between leftAngle and rightAngle
|
||||
* looking from the specified position. The starting point of the
|
||||
* segment must be left of its end point when looking from the
|
||||
* specified position.
|
||||
*
|
||||
* @param pos the specified position to look from
|
||||
* @param leftAngle the specified left angle
|
||||
* @param rightAngle the specified right angle
|
||||
*/
|
||||
default Interval interval(Position pos, Angle leftAngle, Angle rightAngle) {
|
||||
final float nomLeft = nominator(pos, leftAngle.x, leftAngle.y);
|
||||
final float detLeft = determinant(leftAngle.x, leftAngle.y);
|
||||
final float nomRight = nominator(pos, rightAngle.x, rightAngle.y);
|
||||
final float detRight = determinant(rightAngle.x, rightAngle.y);
|
||||
if (abs(detLeft) <= FLT_EPSILON || abs(detRight) <= FLT_EPSILON)
|
||||
return new Interval(0f, 0f);
|
||||
final float q1 = nomLeft / detLeft;
|
||||
final float q2 = nomRight / detRight;
|
||||
if (q1 > q2)
|
||||
return new Interval(0f, 0f);
|
||||
final float lower = q1 < ZERO_TOLERANCE ? 0f : min(1f, q1);
|
||||
final float upper = q2 > 1f - ZERO_TOLERANCE ? 1f : max(0f, q2);
|
||||
return new Interval(lower, upper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x-coordinate of the vector from the start to the end point.
|
||||
*/
|
||||
default float diffX() {
|
||||
return to().getX() - from().getX();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate of the vector from the start to the end point.
|
||||
*/
|
||||
default float diffY() {
|
||||
return to().getY() - from().getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the determinant of the matrix whose first column vector is this segment and the
|
||||
* second column vector is the specified segment.
|
||||
*
|
||||
* @param other the specified segment
|
||||
*/
|
||||
default float determinantWith(SegmentLike other) {
|
||||
return diffX() * other.diffY() - diffY() * other.diffX();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the square of the minimal distance between this and the specified segment.
|
||||
*
|
||||
* @param other other segment
|
||||
* @return squared distance
|
||||
*/
|
||||
default float minDistanceSquared(SegmentLike other) {
|
||||
final float rx = other.from().getX() - from().getX();
|
||||
final float ry = other.from().getY() - from().getY();
|
||||
final float ux = diffX();
|
||||
final float uy = diffY();
|
||||
final float vx = other.diffX();
|
||||
final float vy = other.diffY();
|
||||
|
||||
final float ru = rx * ux + ry * uy;
|
||||
final float rv = rx * vx + ry * vy;
|
||||
final float uu = ux * ux + uy * uy;
|
||||
final float uv = ux * vx + uy * vy;
|
||||
final float vv = vx * vx + vy * vy;
|
||||
|
||||
if (uu < ZERO_TOLERANCE) { // this segment is in fact a single point
|
||||
if (vv < ZERO_TOLERANCE) // other is a point, too
|
||||
return rx * rx + ry * ry;
|
||||
else
|
||||
return sqr(other.distanceTo(from()));
|
||||
}
|
||||
if (vv < ZERO_TOLERANCE) // other is in fact a point
|
||||
return sqr(distanceTo(other.from()));
|
||||
|
||||
final float det = uu * vv - uv * uv;
|
||||
final float s;
|
||||
final float t;
|
||||
|
||||
if (det < ZERO_TOLERANCE * uu * vv) {
|
||||
s = min(max(ru / uu, 0f), 1f);
|
||||
t = 0f;
|
||||
}
|
||||
else {
|
||||
s = min(max((ru * vv - rv * uv) / det, 0f), 1f);
|
||||
t = min(max((ru * uv - rv * uu) / det, 0f), 1f);
|
||||
}
|
||||
|
||||
final float mu1 = min(max((t * uv + ru) / uu, 0f), 1f);
|
||||
final float mu2 = min(max((s * uv - rv) / vv, 0f), 1f);
|
||||
|
||||
return pointAt(mu1).distanceSquaredTo(other.pointAt(mu2));
|
||||
}
|
||||
}
|
||||
90
Projekte/common/src/main/java/pp/util/Util.java
Normal file
90
Projekte/common/src/main/java/pp/util/Util.java
Normal file
@@ -0,0 +1,90 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A class with auxiliary functions.
|
||||
*/
|
||||
public class Util {
|
||||
private Util() { /* do not instantiate */ }
|
||||
|
||||
/**
|
||||
* Calculates and returns the area of a polygon defined by a list of points, using the shoelace formula.
|
||||
* The result is positive if the sequence of vertices is in clockwise order, and negative
|
||||
* otherwise.
|
||||
*
|
||||
* @param points A list of positions that define the vertices of the polygon in planar coordinates.
|
||||
* @return The calculated area of the polygon.
|
||||
*/
|
||||
public static float getArea(List<? extends Position> points) {
|
||||
float sum = 0;
|
||||
Position prev = getLast(points);
|
||||
for (var next : points) {
|
||||
sum += prev.getX() * next.getY() - next.getX() * prev.getY();
|
||||
prev = next;
|
||||
}
|
||||
return 0.5f * sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last element of the given list.
|
||||
*
|
||||
* @param list the list from which to retrieve the last element
|
||||
* @param <T> the type of elements in the list
|
||||
* @return the last element of the list
|
||||
* @throws IndexOutOfBoundsException if the list is empty
|
||||
*/
|
||||
public static <T> T getLast(List<T> list) {
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the order of elements in the given list.
|
||||
*
|
||||
* @param list the list to be reversed
|
||||
* @param <T> the type of elements in the list
|
||||
* @return a new list with elements in reversed order
|
||||
*/
|
||||
public static <T> List<T> reverse(List<T> list) {
|
||||
final List<T> reversed = new ArrayList<>(list);
|
||||
Collections.reverse(reversed);
|
||||
return reversed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the given list.
|
||||
*
|
||||
* @param list the list to be copied, may be null
|
||||
* @param <T> the type of elements in the list
|
||||
* @return a new list containing the elements of the original list, or null if the original list is null
|
||||
*/
|
||||
public static <T> List<T> copy(List<T> list) {
|
||||
return list == null ? null : new ArrayList<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element to a set and returns a new set containing the original elements and the new element.
|
||||
*
|
||||
* @param set the original set, must not be null
|
||||
* @param element the element to be added to the set
|
||||
* @param <T> the type of elements in the set
|
||||
* @param <E> the type of the element being added, must extend T
|
||||
* @return a new set containing the original elements and the new element
|
||||
*/
|
||||
public static <T, E extends T> Set<T> add(Set<T> set, E element) {
|
||||
final Set<T> newSet = new HashSet<>(set);
|
||||
newSet.add(element);
|
||||
return newSet;
|
||||
}
|
||||
}
|
||||
286
Projekte/common/src/main/java/pp/util/config/Config.java
Normal file
286
Projekte/common/src/main/java/pp/util/config/Config.java
Normal file
@@ -0,0 +1,286 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Abstract base class for representing configurations that can be read from properties files.
|
||||
* Subclasses can define fields annotated with {@link Property} to specify the configuration keys
|
||||
* and optionally {@link Separator} to specify custom separators for array values.
|
||||
*/
|
||||
public abstract class Config {
|
||||
private static final String BLANK_SEQ = " *";
|
||||
|
||||
/**
|
||||
* Annotation for specifying the property key for a field.
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@Target(FIELD)
|
||||
@Documented
|
||||
protected @interface Property {
|
||||
/**
|
||||
* The key of the property.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation for specifying a custom separator for array values.
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@Target(FIELD)
|
||||
@Documented
|
||||
protected @interface Separator {
|
||||
/**
|
||||
* The separator for array values.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = System.getLogger(Config.class.getName());
|
||||
private static final Map<Class<?>, Function<String, ?>> CONVERTER_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
CONVERTER_MAP.put(String.class, x -> x);
|
||||
CONVERTER_MAP.put(byte.class, Byte::parseByte);
|
||||
CONVERTER_MAP.put(short.class, Short::parseShort);
|
||||
CONVERTER_MAP.put(int.class, Integer::parseInt);
|
||||
CONVERTER_MAP.put(long.class, Long::parseLong);
|
||||
CONVERTER_MAP.put(boolean.class, Boolean::parseBoolean);
|
||||
CONVERTER_MAP.put(float.class, Float::parseFloat);
|
||||
CONVERTER_MAP.put(double.class, Double::parseDouble);
|
||||
CONVERTER_MAP.put(Byte.class, Byte::parseByte);
|
||||
CONVERTER_MAP.put(Short.class, Short::parseShort);
|
||||
CONVERTER_MAP.put(Integer.class, Integer::parseInt);
|
||||
CONVERTER_MAP.put(Long.class, Long::parseLong);
|
||||
CONVERTER_MAP.put(Boolean.class, Boolean::parseBoolean);
|
||||
CONVERTER_MAP.put(Float.class, Float::parseFloat);
|
||||
CONVERTER_MAP.put(Double.class, Double::parseDouble);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified properties file and sets the values of this config using {@link #readFrom(Properties)}.
|
||||
*
|
||||
* @param file the properties file to read
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @see #readFrom(Properties)
|
||||
*/
|
||||
public void readFrom(File file) throws IOException {
|
||||
try (Reader reader = new FileReader(file)) {
|
||||
final Properties properties = new Properties();
|
||||
properties.load(reader);
|
||||
readFrom(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values of fields annotated with {@link Property} using the specified properties.
|
||||
* Array fields can be split into components using the default separator (",") or a custom separator
|
||||
* specified with {@link Separator}.
|
||||
*
|
||||
* @param props the properties to read from
|
||||
*/
|
||||
public void readFrom(Properties props) {
|
||||
for (Class<?> clazz = getClass(); Config.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
final Property keyAnnot = field.getAnnotation(Property.class);
|
||||
if (keyAnnot != null && props.containsKey(keyAnnot.value())) {
|
||||
try {
|
||||
final String text = props.getProperty(keyAnnot.value());
|
||||
final Object value = createValue(text, field);
|
||||
setField(field, value);
|
||||
}
|
||||
catch (IllegalAccessException ex) {
|
||||
LOGGER.log(Level.ERROR, "Cannot access " + field, ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified properties file and sets the values of this config if the file exists,
|
||||
* otherwise uses default values. This method is a convenience version of {@link #readFrom(File)}
|
||||
* that checks the existence of the specified file and does nothing if the file does not exist.
|
||||
*
|
||||
* @param file the properties file to read, if it exists
|
||||
*/
|
||||
public void readFromIfExists(File file) {
|
||||
if (!file.exists()) {
|
||||
LOGGER.log(Level.INFO, "There is no config file {0}; using default configuration", //NON-NLS
|
||||
file.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
readFrom(file);
|
||||
LOGGER.log(Level.INFO, "Successfully read config from {0}", file.getAbsolutePath()); //NON-NLS
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot read config file " + file.getAbsolutePath(), e); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string value from the properties file into an object that can be assigned to the specified field.
|
||||
* For array fields, the string value is first split into components.
|
||||
*
|
||||
* @param value the string value to convert
|
||||
* @param field the field to set with the converted value
|
||||
* @return an object of the appropriate type for the field
|
||||
*/
|
||||
private Object createValue(String value, Field field) {
|
||||
if (!field.getType().isArray())
|
||||
return convertToType(value, field.getType());
|
||||
// the field is an array
|
||||
final Separator sepAnn = field.getDeclaredAnnotation(Separator.class);
|
||||
final String sep = sepAnn == null ? "," : sepAnn.value();
|
||||
final String[] split = value.split(BLANK_SEQ + sep + BLANK_SEQ, -1);
|
||||
final Object array = Array.newInstance(field.getType().componentType(), split.length);
|
||||
for (int i = 0; i < split.length; i++)
|
||||
Array.set(array, i, convertToType(split[i], field.getType().componentType()));
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string value into an object of the specified type.
|
||||
*
|
||||
* @param value the string value to convert
|
||||
* @param targetType the target type to convert to
|
||||
* @return an object of the specified type
|
||||
*/
|
||||
protected Object convertToType(String value, Class<?> targetType) {
|
||||
Function<String, ?> handler = CONVERTER_MAP.get(targetType);
|
||||
if (handler != null)
|
||||
return handler.apply(value);
|
||||
throw new IllegalArgumentException("Cannot translate " + value + " to " + targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the configuration object, including all properties and their values.
|
||||
*
|
||||
* @return a string representation of the configuration object
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final List<String> propertyStrings = getPropertyStrings();
|
||||
propertyStrings.sort(String.CASE_INSENSITIVE_ORDER);
|
||||
return "[\n" + String.join(",\n", propertyStrings) + "\n]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all property strings of the configuration object.
|
||||
*
|
||||
* @return a list of property strings
|
||||
*/
|
||||
private List<String> getPropertyStrings() {
|
||||
final List<String> propertyStrings = new ArrayList<>();
|
||||
for (Class<?> clazz = getClass(); Config.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
final String stringRepresentation = getStringRepresentation(field);
|
||||
if (stringRepresentation != null)
|
||||
propertyStrings.add(stringRepresentation);
|
||||
}
|
||||
}
|
||||
return propertyStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the string representation of a field annotated with {@link Property}.
|
||||
*
|
||||
* @param field the field to retrieve the string representation for
|
||||
* @return the string representation of the field, or null if the field is not annotated with {@link Property}
|
||||
*/
|
||||
private String getStringRepresentation(Field field) {
|
||||
final Property keyAnnotation = field.getAnnotation(Property.class);
|
||||
if (keyAnnotation != null) {
|
||||
try {
|
||||
final Object fieldValue = getField(field);
|
||||
final String valueString = asString(fieldValue);
|
||||
return keyAnnotation.value() + " -> " + field.getName() + " = " + valueString;
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
LOGGER.log(Level.ERROR, "Cannot access " + field, e); //NON-NLS
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to its string representation. For arrays, string representations of their components are produced.
|
||||
*
|
||||
* @param value the object to convert
|
||||
* @return the string representation of the object
|
||||
*/
|
||||
private String asString(Object value) {
|
||||
if (value == null)
|
||||
return "null"; //NON-NLS
|
||||
if (!value.getClass().isArray())
|
||||
return value.toString();
|
||||
final int length = Array.getLength(value);
|
||||
final List<String> components = new ArrayList<>(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
final Object component = Array.get(value, i);
|
||||
components.add(asString(component));
|
||||
}
|
||||
return "{" + String.join(", ", components) + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a field, making it accessible if necessary.
|
||||
*
|
||||
* @param field the field to set
|
||||
* @param value the value to set
|
||||
* @throws IllegalAccessException if the field cannot be accessed
|
||||
*/
|
||||
private void setField(Field field, Object value) throws IllegalAccessException {
|
||||
boolean inaccessible = !field.canAccess(this);
|
||||
if (inaccessible)
|
||||
field.setAccessible(true);
|
||||
field.set(this, value);
|
||||
if (inaccessible)
|
||||
field.setAccessible(false);
|
||||
LOGGER.log(Level.TRACE, "Set {0} to {1}", field, value); //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a field, making it accessible if necessary.
|
||||
*
|
||||
* @param field the field to retrieve the value from
|
||||
* @return the value of the field
|
||||
* @throws IllegalAccessException if the field cannot be accessed
|
||||
*/
|
||||
private Object getField(Field field) throws IllegalAccessException {
|
||||
boolean inaccessible = !field.canAccess(this);
|
||||
if (inaccessible)
|
||||
field.setAccessible(true);
|
||||
final Object value = field.get(this);
|
||||
if (inaccessible)
|
||||
field.setAccessible(false);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
109
Projekte/common/src/test/java/pp/util/AngleTest.java
Normal file
109
Projekte/common/src/test/java/pp/util/AngleTest.java
Normal file
@@ -0,0 +1,109 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static pp.util.FloatMath.DEG_TO_RAD;
|
||||
import static pp.util.FloatMath.ZERO_TOLERANCE;
|
||||
import static pp.util.FloatMath.cos;
|
||||
import static pp.util.FloatMath.sin;
|
||||
|
||||
public class AngleTest {
|
||||
@Test
|
||||
public void compareAngles() {
|
||||
for (int i = 0; i < 360; i++) {
|
||||
final Angle u = Angle.fromDegrees(i);
|
||||
for (int j = 0; j < 360; j++) {
|
||||
final Angle v = Angle.fromDegrees(j);
|
||||
assertEquals("compare " + i + "° and " + j + "°", Integer.compare(i, j), (Object) u.compareTo(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAngles() {
|
||||
for (int i = 0; i < 360; i++) {
|
||||
final Angle u = Angle.fromDegrees(i);
|
||||
for (int j = 0; j < 360; j++) {
|
||||
final Angle v = Angle.fromDegrees(j);
|
||||
final Angle sum = u.plus(v);
|
||||
assertEquals(i + "° + " + j + "°, x coordinate", cos((i + j) * DEG_TO_RAD), sum.x, ZERO_TOLERANCE);
|
||||
assertEquals(i + "° + " + j + "°, y coordinate", sin((i + j) * DEG_TO_RAD), sum.y, ZERO_TOLERANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subtractAngles() {
|
||||
for (int i = 0; i < 360; i++) {
|
||||
final Angle u = Angle.fromDegrees(i);
|
||||
for (int j = 0; j < 360; j++) {
|
||||
final Angle v = Angle.fromDegrees(j);
|
||||
final Angle diff = u.minus(v);
|
||||
assertEquals(i + "° - " + j + "°, x coordinate", cos((i - j) * DEG_TO_RAD), diff.x, ZERO_TOLERANCE);
|
||||
assertEquals(i + "° - " + j + "°, y coordinate", sin((i - j) * DEG_TO_RAD), diff.y, ZERO_TOLERANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void minAngle() {
|
||||
for (int i = 0; i < 360; i++) {
|
||||
final Angle u = Angle.fromDegrees(i);
|
||||
for (int j = 0; j < 360; j++) {
|
||||
final Angle v = Angle.fromDegrees(j);
|
||||
final Angle diff = Angle.min(u, v);
|
||||
assertEquals(i + "° - " + j + "°, x coordinate", cos(min(i, j) * DEG_TO_RAD), diff.x, ZERO_TOLERANCE);
|
||||
assertEquals(i + "° - " + j + "°, y coordinate", sin(min(i, j) * DEG_TO_RAD), diff.y, ZERO_TOLERANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bisector() {
|
||||
for (int right = 0; right < 360; right++) {
|
||||
final Angle rightAngle = Angle.fromDegrees(right);
|
||||
for (int add = 0; add < 360; add++) {
|
||||
final int left = right + add;
|
||||
final Angle bisector = Angle.fromDegrees(left).bisector(rightAngle);
|
||||
final float exp = (right + 0.5f * add) * DEG_TO_RAD;
|
||||
assertEquals("left=" + left + "° / right=" + right + "°, x coordinate", cos(exp), bisector.x, ZERO_TOLERANCE);
|
||||
assertEquals("left=" + left + "° / right=" + right + "°, y coordinate", sin(exp), bisector.y, ZERO_TOLERANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void leftOf() {
|
||||
for (int right = 0; right < 360; right++) {
|
||||
final Angle rightAngle = Angle.fromDegrees(right);
|
||||
for (int add = 1; add < 360; add++)
|
||||
if (add != 180) {
|
||||
final int left = right + add;
|
||||
final Angle leftAngle = Angle.fromDegrees(left);
|
||||
assertEquals(left + "° left of " + right + "°", add < 180, leftAngle.leftOf(rightAngle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rightOf() {
|
||||
for (int right = 0; right < 360; right++) {
|
||||
final Angle rightAngle = Angle.fromDegrees(right);
|
||||
for (int add = 1; add < 360; add++)
|
||||
if (add != 180) {
|
||||
final int left = right + add;
|
||||
final Angle leftAngle = Angle.fromDegrees(left);
|
||||
assertEquals(left + "° right of " + right + "°", add > 180, leftAngle.rightOf(rightAngle));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Projekte/common/src/test/java/pp/util/IntervalTest.java
Normal file
39
Projekte/common/src/test/java/pp/util/IntervalTest.java
Normal file
@@ -0,0 +1,39 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static pp.util.FloatMath.ZERO_TOLERANCE;
|
||||
|
||||
public class IntervalTest {
|
||||
private Interval interval;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
interval = new Interval(0f, 1f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contains() {
|
||||
assertTrue(interval.contains(0.5f));
|
||||
assertTrue(interval.contains(0f));
|
||||
assertTrue(interval.contains(1f));
|
||||
assertFalse(interval.contains(1.5f));
|
||||
assertFalse(interval.contains(-0.5f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matches() {
|
||||
assertTrue(interval.matches(new Interval(0f, 1f), ZERO_TOLERANCE));
|
||||
assertFalse(interval.matches(new Interval(0f, 0.99f), ZERO_TOLERANCE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RandomPositionIteratorTest {
|
||||
public static final int WIDTH = 15;
|
||||
public static final int HEIGHT = 25;
|
||||
|
||||
@Test
|
||||
public void permutation() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final List<Position> permutation = new ArrayList<>();
|
||||
RandomPositionIterator.floatPoints(WIDTH, HEIGHT).forEachRemaining(permutation::add);
|
||||
assertEquals(WIDTH * HEIGHT, permutation.size());
|
||||
assertEquals(permutation.size(), new HashSet<>(permutation).size());
|
||||
for (Position w : permutation) {
|
||||
assertTrue(w.toString(), 0 <= w.getX() && w.getX() < WIDTH);
|
||||
assertTrue(w.toString(), 0 <= w.getY() && w.getY() < HEIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
201
Projekte/common/src/test/java/pp/util/SegmentTest.java
Normal file
201
Projekte/common/src/test/java/pp/util/SegmentTest.java
Normal file
@@ -0,0 +1,201 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static pp.util.FloatMath.FLT_EPSILON;
|
||||
import static pp.util.FloatMath.PI;
|
||||
import static pp.util.FloatMath.ZERO_TOLERANCE;
|
||||
import static pp.util.FloatMath.cos;
|
||||
import static pp.util.FloatMath.sqr;
|
||||
import static pp.util.FloatMath.sqrt;
|
||||
import static pp.util.FloatMath.tan;
|
||||
|
||||
public class SegmentTest {
|
||||
private static final float SQRT2 = sqrt(2f);
|
||||
private static final Segment segment1 = new Segment(new FloatPoint(1f, -1f), new FloatPoint(1f, 1f));
|
||||
private static final Segment segment2 = new Segment(new FloatPoint(SQRT2, 0f), new FloatPoint(0f, SQRT2));
|
||||
private static final Segment segment3 = new Segment(new FloatPoint(2f, -1f), new FloatPoint(2f, 1f));
|
||||
private static final Segment segment4 = new Segment(new FloatPoint(SQRT2, -1f), new FloatPoint(SQRT2, 1f));
|
||||
private static final Segment segment5 = new Segment(new FloatPoint(-SQRT2, 2f * SQRT2), new FloatPoint(2f * SQRT2, -SQRT2));
|
||||
private static final Segment segment6 = new Segment(new FloatPoint(0f, 0f), new FloatPoint(SQRT2, 1f));
|
||||
private static final FloatPoint ZERO = new FloatPoint(0f, 0f);
|
||||
public static final FloatPoint ONE_UP = new FloatPoint(0f, 1f);
|
||||
public static final FloatPoint ONE_DOWN = new FloatPoint(0f, -1f);
|
||||
|
||||
@Test
|
||||
public void dist1() {
|
||||
assertEquals(1f, segment1.dist(ZERO, 0f), FLT_EPSILON);
|
||||
assertEquals(1f, segment1.dist(ONE_UP, 0f), FLT_EPSILON);
|
||||
assertEquals(1f, segment1.dist(ONE_DOWN, 0f), FLT_EPSILON);
|
||||
assertEquals(1f / cos(0.125f * PI), segment1.dist(ZERO, 0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(1f / cos(0.125f * PI), segment1.dist(ONE_UP, 0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(1f / cos(0.125f * PI), segment1.dist(ONE_DOWN, 0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(1f / cos(0.125f * PI), segment1.dist(ZERO, -0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(1f / cos(0.125f * PI), segment1.dist(ONE_UP, -0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(1f / cos(0.125f * PI), segment1.dist(ONE_DOWN, -0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(SQRT2, segment1.dist(ZERO, 0.25f * PI), FLT_EPSILON);
|
||||
assertEquals(SQRT2, segment1.dist(ZERO, -0.25f * PI), FLT_EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dist2() {
|
||||
assertEquals(1f, segment2.dist(ZERO, 0.25f * PI), FLT_EPSILON);
|
||||
assertEquals(SQRT2, segment2.dist(ZERO, 0f), FLT_EPSILON);
|
||||
assertEquals(SQRT2, segment2.dist(ZERO, 0.5f * PI), FLT_EPSILON);
|
||||
assertEquals(1f / cos(0.125f * PI), segment2.dist(ZERO, 0.375f * PI), FLT_EPSILON);
|
||||
assertEquals(SQRT2, segment2.dist(ZERO, PI / 2f), FLT_EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quotient1() {
|
||||
assertEquals(0.5f, segment1.quotient(ZERO, 0f), FLT_EPSILON);
|
||||
assertEquals(1f, segment1.quotient(ONE_UP, 0f), FLT_EPSILON);
|
||||
assertEquals(0f, segment1.quotient(ONE_DOWN, 0f), FLT_EPSILON);
|
||||
assertEquals(0.5f * tan(0.125f * PI) + 0.5f, segment1.quotient(ZERO, 0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(0.5f * tan(0.125f * PI) + 1f, segment1.quotient(ONE_UP, 0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(0.5f * tan(0.125f * PI), segment1.quotient(ONE_DOWN, 0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(0.5f - 0.5f * tan(0.125f * PI), segment1.quotient(ZERO, -0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(1f - 0.5f * tan(0.125f * PI), segment1.quotient(ONE_UP, -0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(-0.5f * tan(0.125f * PI), segment1.quotient(ONE_DOWN, -0.125f * PI), FLT_EPSILON);
|
||||
assertEquals(1f, segment1.quotient(ZERO, 0.25f * PI), FLT_EPSILON);
|
||||
assertEquals(0f, segment1.quotient(ZERO, -0.25f * PI), FLT_EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quotient2() {
|
||||
assertEquals(0.5f, segment2.quotient(ZERO, 0.25f * PI), FLT_EPSILON);
|
||||
assertEquals(0f, segment2.quotient(ZERO, 0f), FLT_EPSILON);
|
||||
assertEquals(1f, segment2.quotient(ZERO, 0.5f * PI), FLT_EPSILON);
|
||||
assertEquals(0.5f * SQRT2, segment2.quotient(ZERO, 0.375f * PI), FLT_EPSILON);
|
||||
assertEquals(1f - 0.5f * SQRT2, segment2.quotient(ZERO, 0.125f * PI), FLT_EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void project() {
|
||||
assertEquals(0.5f, segment1.project(ZERO), FLT_EPSILON);
|
||||
assertEquals(0.5f, segment2.project(ZERO), FLT_EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void minDistanceSquared1() {
|
||||
assertEquals(0f, segment1.minDistanceSquared(segment1), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment1.minDistanceSquared(segment2), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment2.minDistanceSquared(segment1), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(1f, segment1.minDistanceSquared(segment3), ZERO_TOLERANCE);
|
||||
assertEquals(1f, segment3.minDistanceSquared(segment1), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(sqr(SQRT2 - 1f), segment1.minDistanceSquared(segment4), ZERO_TOLERANCE);
|
||||
assertEquals(sqr(SQRT2 - 1f), segment4.minDistanceSquared(segment1), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment1.minDistanceSquared(segment5), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment5.minDistanceSquared(segment1), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment1.minDistanceSquared(segment6), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment6.minDistanceSquared(segment1), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment2.minDistanceSquared(segment2), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(sqr(2f - SQRT2), segment2.minDistanceSquared(segment3), ZERO_TOLERANCE);
|
||||
assertEquals(sqr(2f - SQRT2), segment3.minDistanceSquared(segment2), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment2.minDistanceSquared(segment4), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment4.minDistanceSquared(segment2), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment2.minDistanceSquared(segment5), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment5.minDistanceSquared(segment2), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment2.minDistanceSquared(segment6), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment6.minDistanceSquared(segment2), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment3.minDistanceSquared(segment3), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(sqr(2f - SQRT2), segment3.minDistanceSquared(segment4), ZERO_TOLERANCE);
|
||||
assertEquals(sqr(2f - SQRT2), segment4.minDistanceSquared(segment3), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment3.minDistanceSquared(segment5), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment5.minDistanceSquared(segment3), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(sqr(2f - SQRT2), segment3.minDistanceSquared(segment6), ZERO_TOLERANCE);
|
||||
assertEquals(sqr(2f - SQRT2), segment6.minDistanceSquared(segment3), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment4.minDistanceSquared(segment4), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment4.minDistanceSquared(segment5), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment5.minDistanceSquared(segment4), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment4.minDistanceSquared(segment6), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment6.minDistanceSquared(segment4), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment5.minDistanceSquared(segment5), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment5.minDistanceSquared(segment6), ZERO_TOLERANCE);
|
||||
assertEquals(0f, segment6.minDistanceSquared(segment5), ZERO_TOLERANCE);
|
||||
|
||||
assertEquals(0f, segment6.minDistanceSquared(segment6), ZERO_TOLERANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void minDistanceSquared2() {
|
||||
final Segment s1 = new Segment(new FloatPoint(0f, 0f), new FloatPoint(2f, 1f));
|
||||
for (int i = -20; i <= 40; i++) {
|
||||
final float x = i * 0.1f;
|
||||
final Segment s2 = new Segment(new FloatPoint(x, 2f), new FloatPoint(x + 2f, -2f));
|
||||
final float dist;
|
||||
if (i <= -10)
|
||||
dist = 0.8f * sqr(1f + x);
|
||||
else if (i <= 15)
|
||||
dist = 0f;
|
||||
else
|
||||
dist = 0.8f * sqr(x - 1.5f);
|
||||
assertEquals("x = " + x, dist, s1.minDistanceSquared(s2), ZERO_TOLERANCE);
|
||||
assertEquals("x = " + x, dist, s2.minDistanceSquared(s1), ZERO_TOLERANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void minDistanceSquared3() {
|
||||
final Segment s1 = new Segment(new FloatPoint(0f, 0f), new FloatPoint(2f, 1f));
|
||||
for (float i = -30; i <= 30; i++) {
|
||||
final float x = i * 0.1f;
|
||||
final Segment s2 = new Segment(new FloatPoint(x, 0.5f * x), new FloatPoint(x + 2f, 0.5f * x + 1f));
|
||||
final float dist;
|
||||
if (i <= -20)
|
||||
dist = 1.25f * sqr(2f + x);
|
||||
else if (i <= 20)
|
||||
dist = 0f;
|
||||
else
|
||||
dist = 1.25f * sqr(x - 2f);
|
||||
assertEquals("x = " + x, dist, s1.minDistanceSquared(s2), ZERO_TOLERANCE);
|
||||
assertEquals("x = " + x, dist, s2.minDistanceSquared(s1), ZERO_TOLERANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void minDistanceSquared4() {
|
||||
final Segment s1 = new Segment(new FloatPoint(0f, 0f), new FloatPoint(3f, 1.5f));
|
||||
for (float i = -30; i <= 50; i++) {
|
||||
final float x = i * 0.1f;
|
||||
final float y = 1f - 0.5f * x;
|
||||
final Segment s2 = new Segment(new FloatPoint(x, 1f), new FloatPoint(x, 1f));
|
||||
final float dist;
|
||||
if (i <= -5)
|
||||
dist = sqr(x) + 1f;
|
||||
else if (i <= 32)
|
||||
dist = 0.2f * sqr(x - 2f);
|
||||
else
|
||||
dist = sqr(x - 3f) + 0.25f;
|
||||
assertEquals("x = " + x, dist, s1.minDistanceSquared(s2), ZERO_TOLERANCE);
|
||||
assertEquals("x = " + x, dist, s2.minDistanceSquared(s1), ZERO_TOLERANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Projekte/common/src/test/java/pp/util/config/ConfigTest.java
Normal file
101
Projekte/common/src/test/java/pp/util/config/ConfigTest.java
Normal file
@@ -0,0 +1,101 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util.config;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ConfigTest {
|
||||
|
||||
private TestConfig config;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
config = new TestConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromProperties() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("test.string", "hello"); //NON-NLS
|
||||
properties.setProperty("test.int", "42");
|
||||
properties.setProperty("test.boolean", "true"); //NON-NLS
|
||||
properties.setProperty("test.intArray", "1; 2 ;3;4");
|
||||
|
||||
config.readFrom(properties);
|
||||
|
||||
assertEquals("hello", config.getTestString());
|
||||
assertEquals(42, config.getTestInt());
|
||||
assertTrue(config.isTestBoolean());
|
||||
assertArrayEquals(new int[]{1, 2, 3, 4}, config.getTestIntArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromFile() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("test.string", "fileTest");
|
||||
properties.setProperty("test.int", "24");
|
||||
properties.setProperty("test.boolean", "false"); //NON-NLS
|
||||
properties.setProperty("test.intArray", "10;20;30");
|
||||
|
||||
File tempFile = File.createTempFile("testConfig", ".properties");
|
||||
try (FileWriter writer = new FileWriter(tempFile)) {
|
||||
properties.store(writer, null);
|
||||
}
|
||||
|
||||
config.readFrom(tempFile);
|
||||
|
||||
assertEquals("fileTest", config.getTestString());
|
||||
assertEquals(24, config.getTestInt());
|
||||
assertFalse(config.isTestBoolean());
|
||||
assertArrayEquals(new int[]{10, 20, 30}, config.getTestIntArray());
|
||||
|
||||
// Clean up
|
||||
tempFile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToType() {
|
||||
assertEquals(42, config.convertToType("42", int.class));
|
||||
assertEquals(true, config.convertToType("true", boolean.class)); //NON-NLS
|
||||
assertEquals(3.14, config.convertToType("3.14", double.class));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testConvertToTypeWithUnsupportedType() {
|
||||
config.convertToType("unsupported", Object.class); //NON-NLS
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("test.string", "stringValue");
|
||||
properties.setProperty("test.int", "123");
|
||||
properties.setProperty("test.boolean", "true"); //NON-NLS
|
||||
properties.setProperty("test.intArray", "5;6;7");
|
||||
|
||||
config.readFrom(properties);
|
||||
|
||||
String expected = "[\ntest.boolean -> testBoolean = true,\n" +
|
||||
"test.int -> testInt = 123,\n" +
|
||||
"test.intArray -> testIntArray = {5, 6, 7},\n" +
|
||||
"test.string -> testString = stringValue\n" +
|
||||
"]";
|
||||
assertEquals(expected, config.toString());
|
||||
}
|
||||
}
|
||||
40
Projekte/common/src/test/java/pp/util/config/TestConfig.java
Normal file
40
Projekte/common/src/test/java/pp/util/config/TestConfig.java
Normal file
@@ -0,0 +1,40 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util.config;
|
||||
|
||||
class TestConfig extends Config {
|
||||
@Property("test.string")
|
||||
private String testString;
|
||||
|
||||
@Property("test.int")
|
||||
private int testInt;
|
||||
|
||||
@Property("test.boolean")
|
||||
private boolean testBoolean;
|
||||
|
||||
@Property("test.intArray")
|
||||
@Separator(";")
|
||||
private int[] testIntArray;
|
||||
|
||||
// Getters for testing
|
||||
public String getTestString() {
|
||||
return testString;
|
||||
}
|
||||
|
||||
public int getTestInt() {
|
||||
return testInt;
|
||||
}
|
||||
|
||||
public boolean isTestBoolean() {
|
||||
return testBoolean;
|
||||
}
|
||||
|
||||
public int[] getTestIntArray() {
|
||||
return testIntArray;
|
||||
}
|
||||
}
|
||||
14
Projekte/jme-common/build.gradle
Normal file
14
Projekte/jme-common/build.gradle
Normal file
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
id 'buildlogic.java-library-conventions'
|
||||
}
|
||||
|
||||
description = 'Common classes used in jME applications'
|
||||
|
||||
dependencies {
|
||||
implementation libs.jme3.core
|
||||
api libs.lemur
|
||||
api project(':common')
|
||||
|
||||
runtimeOnly libs.groovy.jsr223
|
||||
runtimeOnly libs.slf4j.nop
|
||||
}
|
||||
102
Projekte/jme-common/src/main/java/pp/dialog/Dialog.java
Normal file
102
Projekte/jme-common/src/main/java/pp/dialog/Dialog.java
Normal file
@@ -0,0 +1,102 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.simsilica.lemur.Container;
|
||||
|
||||
/**
|
||||
* Represents a dialog within a dialog manager system.
|
||||
* Extends the Container class from the Lemur GUI library.
|
||||
*/
|
||||
public class Dialog extends Container {
|
||||
/**
|
||||
* The depth of the dialog within the dialog stack.
|
||||
* Dialogs with lower depth values are considered to be "on top" of dialogs with higher values.
|
||||
*/
|
||||
protected final int depth;
|
||||
|
||||
/**
|
||||
* The manager responsible for handling this dialog.
|
||||
*/
|
||||
protected final DialogManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a new Dialog with a depth automatically assigned by the DialogManager.
|
||||
*
|
||||
* @param manager The DialogManager that manages this dialog.
|
||||
*/
|
||||
public Dialog(DialogManager manager) {
|
||||
this(manager, manager.nextDepth());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Dialog with the specified depth.
|
||||
*
|
||||
* @param manager The DialogManager that manages this dialog.
|
||||
* @param depth The depth of this dialog within the dialog stack.
|
||||
* @throws IllegalArgumentException if the specified depth is invalid (i.e., it is not greater than the depth of the top dialog in the manager's stack).
|
||||
*/
|
||||
public Dialog(DialogManager manager, int depth) {
|
||||
this.manager = manager;
|
||||
this.depth = depth;
|
||||
// Ensure the dialog depth is greater than the depth of the current top dialog in the stack
|
||||
if (!manager.getDialogStack().isEmpty() && manager.getDialogStack().getLast().depth >= depth)
|
||||
throw new IllegalArgumentException("Invalid dialog depth " + depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this dialog is the topmost dialog in the dialog stack.
|
||||
*
|
||||
* @return true if this dialog is the topmost dialog, false otherwise.
|
||||
*/
|
||||
public boolean isTopDialog() {
|
||||
return manager.isTop(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified runnable if this dialog is the topmost dialog in the dialog stack.
|
||||
*
|
||||
* @param runnable the runnable.
|
||||
* @see Dialog#isTopDialog()
|
||||
*/
|
||||
public void ifTopDialog(Runnable runnable) {
|
||||
if (isTopDialog()) runnable.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this dialog, centers it, and notifies the DialogManager to manage it.
|
||||
*/
|
||||
public void open() {
|
||||
manager.centering(this, depth);
|
||||
manager.open(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this dialog and notifies the DialogManager to stop managing it.
|
||||
*/
|
||||
public void close() {
|
||||
manager.close(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called whenever the {@linkplain pp.dialog.DialogManager} would
|
||||
* like to update this dialog.
|
||||
*/
|
||||
public void update() { /* empty */ }
|
||||
|
||||
/**
|
||||
* This method is called by {@linkplain DialogManager#update(float)} for periodically
|
||||
* updating this dialog. The default implementation does nothing.
|
||||
*/
|
||||
public void update(float delta) { /* empty */ }
|
||||
|
||||
/**
|
||||
* This method calls the escape action if this dialog is the top dialog.
|
||||
*/
|
||||
public void escape() { /* empty */ }
|
||||
}
|
||||
319
Projekte/jme-common/src/main/java/pp/dialog/DialogBuilder.java
Normal file
319
Projekte/jme-common/src/main/java/pp/dialog/DialogBuilder.java
Normal file
@@ -0,0 +1,319 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.simsilica.lemur.Button;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.Label;
|
||||
import com.simsilica.lemur.component.BorderLayout;
|
||||
import com.simsilica.lemur.style.ElementId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.simsilica.lemur.component.BorderLayout.Position.East;
|
||||
import static com.simsilica.lemur.component.BorderLayout.Position.West;
|
||||
|
||||
/**
|
||||
* A builder class for creating and customizing dialog boxes of type {@link SimpleDialog} or its subclasses.
|
||||
* This builder pattern facilitates the construction of dialog boxes with various configurations,
|
||||
* such as titles, text content, buttons, and additional custom behaviors.
|
||||
*
|
||||
* @param <D> the type of dialog to be built, typically extending {@link SimpleDialog}
|
||||
*/
|
||||
public class DialogBuilder<D extends SimpleDialog> {
|
||||
|
||||
/**
|
||||
* Creates a {@link DialogBuilder} for a simple dialog with default settings.
|
||||
*
|
||||
* @param manager the dialog manager responsible for managing the dialog's lifecycle
|
||||
* @return a {@link DialogBuilder} instance for creating simple dialogs
|
||||
*/
|
||||
public static DialogBuilder<SimpleDialog> simple(DialogManager manager) {
|
||||
return new DialogBuilder<>(manager, SimpleDialog::new);
|
||||
}
|
||||
|
||||
protected final DialogManager manager;
|
||||
private final Function<DialogManager, D> dialogFactory;
|
||||
private final List<Consumer<D>> extensionList = new ArrayList<>();
|
||||
private String title;
|
||||
private String text;
|
||||
private String okLabel;
|
||||
private String noLabel;
|
||||
private Consumer<D> okAction = d -> {};
|
||||
private Consumer<D> noAction = d -> {};
|
||||
private boolean okClose = true;
|
||||
private boolean noClose = true;
|
||||
private Function<D, Spatial> focus;
|
||||
|
||||
/**
|
||||
* Constructs a dialog builder with the specified dialog manager and dialog factory.
|
||||
*
|
||||
* @param manager the dialog manager responsible for managing the dialog's lifecycle
|
||||
* @param dialogFactory a factory function to create instances of the dialog
|
||||
*/
|
||||
public DialogBuilder(DialogManager manager, Function<DialogManager, D> dialogFactory) {
|
||||
this.manager = manager;
|
||||
this.dialogFactory = dialogFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all registered extensions to the given dialog.
|
||||
* Extensions allow for additional customizations beyond the standard configurations.
|
||||
*
|
||||
* @param dialog the dialog object to which the extensions will be applied
|
||||
* @see #setExtension(java.util.function.Consumer)
|
||||
*/
|
||||
protected void extendDialog(D dialog) {
|
||||
for (Consumer<D> extension : extensionList)
|
||||
extension.accept(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the dialog with the specified configurations.
|
||||
* This method creates a new dialog object and applies all the configured settings.
|
||||
*
|
||||
* @return the fully configured dialog object
|
||||
*/
|
||||
public D build() {
|
||||
return build(dialogFactory.apply(manager));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the dialog by configuring an existing dialog object with the specified settings.
|
||||
* This method allows for further customization of a pre-existing dialog object.
|
||||
*
|
||||
* @param dialog the dialog object to configure
|
||||
* @return the configured dialog object for chaining
|
||||
*/
|
||||
public D build(D dialog) {
|
||||
configureTitle(dialog);
|
||||
configureText(dialog);
|
||||
extendDialog(dialog);
|
||||
configureButtons(dialog);
|
||||
configureFocus(dialog);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the title of the dialog if a title has been set.
|
||||
*
|
||||
* @param dialog the dialog to which the title will be added
|
||||
*/
|
||||
private void configureTitle(D dialog) {
|
||||
if (title != null)
|
||||
dialog.addChild(new Label(title, new ElementId("header"))); // NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the main text content of the dialog if text has been set.
|
||||
*
|
||||
* @param dialog the dialog to which the text content will be added
|
||||
*/
|
||||
private void configureText(D dialog) {
|
||||
if (text != null)
|
||||
dialog.addChild(new Label(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the OK and NO buttons for the dialog if labels for them have been set.
|
||||
*
|
||||
* @param dialog the dialog to which the buttons will be added
|
||||
*/
|
||||
private void configureButtons(D dialog) {
|
||||
if (okLabel != null || noLabel != null) {
|
||||
final Container buttons = dialog.addChild(new Container(new BorderLayout()));
|
||||
if (okLabel != null) {
|
||||
final Button okButton = buttons.addChild(new Button(okLabel), West);
|
||||
dialog.setOkButton(okButton);
|
||||
configureButton(okButton, okAction, okClose, dialog);
|
||||
}
|
||||
if (noLabel != null) {
|
||||
final Button noButton = buttons.addChild(new Button(noLabel), East);
|
||||
configureButton(noButton, noAction, noClose, dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a button with its action and whether the dialog should close after the action is performed.
|
||||
*
|
||||
* @param button the button to configure
|
||||
* @param action the action to perform when the button is clicked
|
||||
* @param close whether the dialog should close after the action is performed
|
||||
* @param dialog the dialog that contains the button
|
||||
*/
|
||||
private void configureButton(Button button, Consumer<D> action, boolean close, D dialog) {
|
||||
button.addClickCommands(s -> {
|
||||
if (dialog.isTopDialog()) {
|
||||
action.accept(dialog);
|
||||
if (close) {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the initial focus for the dialog when it is displayed.
|
||||
* The focus will be set to either a specified component or the OK button if available.
|
||||
*
|
||||
* @param dialog the dialog to configure focus for
|
||||
*/
|
||||
private void configureFocus(D dialog) {
|
||||
final Spatial focusComponent = focus == null ? null : focus.apply(dialog);
|
||||
if (focusComponent != null || dialog.getOkButton() != null)
|
||||
manager.setFocus(focusComponent != null ? focusComponent : dialog.getOkButton());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of the dialog.
|
||||
*
|
||||
* @param title the title text to be displayed at the top of the dialog
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main text content of the dialog.
|
||||
*
|
||||
* @param text the main content text to be displayed in the dialog
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setText(String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for the OK button.
|
||||
*
|
||||
* @param okLabel the text label to display on the OK button
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setOkButton(String okLabel) {
|
||||
this.okLabel = okLabel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label and action for the OK button.
|
||||
* When the OK button is clicked, the specified action will be executed.
|
||||
*
|
||||
* @param okLabel the text label to display on the OK button
|
||||
* @param okAction the action to perform when the OK button is clicked
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setOkButton(String okLabel, Consumer<D> okAction) {
|
||||
this.okAction = okAction;
|
||||
return setOkButton(okLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label and action for the OK button.
|
||||
* When the OK button is clicked, the specified runnable action will be executed.
|
||||
*
|
||||
* @param okLabel the text label to display on the OK button
|
||||
* @param okAction the runnable action to perform when the OK button is clicked
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setOkButton(String okLabel, Runnable okAction) {
|
||||
this.okAction = d -> okAction.run();
|
||||
return setOkButton(okLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for the NO button.
|
||||
*
|
||||
* @param noLabel the text label to display on the NO button
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setNoButton(String noLabel) {
|
||||
this.noLabel = noLabel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label and action for the NO button.
|
||||
* When the NO button is clicked, the specified action will be executed.
|
||||
*
|
||||
* @param noLabel the text label to display on the NO button
|
||||
* @param noAction the action to perform when the NO button is clicked
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setNoButton(String noLabel, Consumer<D> noAction) {
|
||||
this.noAction = noAction;
|
||||
return setNoButton(noLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label and action for the NO button.
|
||||
* When the NO button is clicked, the specified runnable action will be executed.
|
||||
*
|
||||
* @param noLabel the text label to display on the NO button
|
||||
* @param noAction the runnable action to perform when the NO button is clicked
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setNoButton(String noLabel, Runnable noAction) {
|
||||
this.noAction = d -> noAction.run();
|
||||
return setNoButton(noLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the dialog should automatically close when the OK button is clicked.
|
||||
*
|
||||
* @param okClose true to close the dialog when the OK button is clicked, false otherwise
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setOkClose(boolean okClose) {
|
||||
this.okClose = okClose;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the dialog should automatically close when the NO button is clicked.
|
||||
*
|
||||
* @param noClose true to close the dialog when the NO button is clicked, false otherwise
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setNoClose(boolean noClose) {
|
||||
this.noClose = noClose;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the component that should initially receive focus when the dialog is displayed.
|
||||
* If a focus function is not provided, the focus defaults to the OK button.
|
||||
*
|
||||
* @param focus a function specifying which component of the dialog should receive focus
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setFocus(Function<D, Spatial> focus) {
|
||||
this.focus = focus;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extension to the dialog.
|
||||
* Extensions allow for additional customizations and behaviors beyond the basic configuration.
|
||||
*
|
||||
* @param extender a consumer that applies the extension to the dialog
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public DialogBuilder<D> setExtension(Consumer<D> extender) {
|
||||
extensionList.add(extender);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
157
Projekte/jme-common/src/main/java/pp/dialog/DialogManager.java
Normal file
157
Projekte/jme-common/src/main/java/pp/dialog/DialogManager.java
Normal file
@@ -0,0 +1,157 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.system.AppSettings;
|
||||
import com.simsilica.lemur.Panel;
|
||||
import com.simsilica.lemur.focus.FocusManagerState;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
/**
|
||||
* Manages dialog boxes within the application, handling their display, positioning, and focus.
|
||||
*/
|
||||
public class DialogManager {
|
||||
/**
|
||||
* The application instance.
|
||||
*/
|
||||
private final SimpleApplication app;
|
||||
|
||||
/**
|
||||
* A stack to keep track of the dialogs.
|
||||
*/
|
||||
private final Deque<Dialog> dialogStack = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* Constructs a DialogManager for the specified application.
|
||||
*
|
||||
* @param app the SimpleApplication instance
|
||||
*/
|
||||
public DialogManager(SimpleApplication app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any dialog is currently displayed.
|
||||
*
|
||||
* @return true if any dialog is currently shown, false otherwise
|
||||
*/
|
||||
public boolean showsDialog() {
|
||||
return !dialogStack.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stack of dialogs.
|
||||
*
|
||||
* @return the dialog stack
|
||||
*/
|
||||
public Deque<Dialog> getDialogStack() {
|
||||
return dialogStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the depth for the next dialog to be displayed.
|
||||
*
|
||||
* @return the next depth value
|
||||
*/
|
||||
public int nextDepth() {
|
||||
return dialogStack.isEmpty() ? 10 : dialogStack.peek().depth + 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the specified panel in the center of the screen, with a specified z coordinate.
|
||||
*
|
||||
* @param panel the panel to center
|
||||
* @param z the z coordinate
|
||||
*/
|
||||
public void centering(Panel panel, float z) {
|
||||
final Vector3f size = panel.getPreferredSize();
|
||||
centering(panel, size.getX(), size.getY(), z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the specified spatial in the center of the screen, with specified width, height, and z coordinate.
|
||||
*
|
||||
* @param spatial the spatial to center
|
||||
* @param width the width reserved for the spatial
|
||||
* @param height the height reserved for the spatial
|
||||
* @param z the z coordinate
|
||||
*/
|
||||
public void centering(Spatial spatial, float width, float height, float z) {
|
||||
final AppSettings settings = app.getContext().getSettings();
|
||||
spatial.setLocalTranslation(max(0f, 0.5f * (settings.getWidth() - width)),
|
||||
max(0f, 0.5f * (settings.getHeight() + height)),
|
||||
z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges for the specified spatial to receive the focus.
|
||||
*
|
||||
* @param spatial the spatial to focus
|
||||
*/
|
||||
public void setFocus(Spatial spatial) {
|
||||
final var focusManager = app.getStateManager().getState(FocusManagerState.class);
|
||||
if (focusManager != null)
|
||||
focusManager.setFocus(spatial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the specified dialog and adds it to the dialog stack.
|
||||
*
|
||||
* @param dialog the dialog to open
|
||||
*/
|
||||
void open(Dialog dialog) {
|
||||
dialogStack.push(dialog);
|
||||
dialog.update();
|
||||
app.getGuiNode().attachChild(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified dialog is the topmost dialog in the dialog stack.
|
||||
*
|
||||
* @param dialog a dialog.
|
||||
* @return true if the dialog is the top dialog, false otherwise.
|
||||
*/
|
||||
boolean isTop(Dialog dialog) {
|
||||
return !dialogStack.isEmpty() && dialogStack.peek() == dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the specified dialog, removing it from the dialog stack.
|
||||
*
|
||||
* @param dialog the dialog to close
|
||||
* @throws IllegalArgumentException if the specified dialog is not the top dialog
|
||||
*/
|
||||
void close(Dialog dialog) {
|
||||
if (!isTop(dialog))
|
||||
throw new IllegalArgumentException(dialog + " is not the top dialog");
|
||||
dialogStack.pop();
|
||||
if (!dialogStack.isEmpty())
|
||||
dialogStack.peek().update();
|
||||
app.getGuiNode().detachChild(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the escape action of the top dialog, if a dialog is shown.
|
||||
*/
|
||||
public void escape() {
|
||||
if (dialogStack.isEmpty()) return;
|
||||
dialogStack.peek().escape();
|
||||
}
|
||||
|
||||
public void update(float delta) {
|
||||
for (Dialog dialog : dialogStack)
|
||||
dialog.update(delta);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.simsilica.lemur.Button;
|
||||
|
||||
/**
|
||||
* Represents a simple dialog with OK and Cancel buttons.
|
||||
* It extends the Dialog class and provides methods to get and set these buttons.
|
||||
*/
|
||||
public class SimpleDialog extends Dialog {
|
||||
private Button okButton;
|
||||
private Button cancelButton;
|
||||
|
||||
/**
|
||||
* Constructs a SimpleDialog with the specified DialogManager.
|
||||
*
|
||||
* @param manager the DialogManager to manage this dialog
|
||||
*/
|
||||
public SimpleDialog(DialogManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OK button of this dialog.
|
||||
*
|
||||
* @return the OK button
|
||||
*/
|
||||
public Button getOkButton() {
|
||||
return okButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OK button of this dialog.
|
||||
*
|
||||
* @param okButton the OK button to set
|
||||
*/
|
||||
void setOkButton(Button okButton) {
|
||||
this.okButton = okButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Cancel button of this dialog.
|
||||
*
|
||||
* @return the Cancel button
|
||||
*/
|
||||
public Button getCancelButton() {
|
||||
return cancelButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Cancel button of this dialog.
|
||||
*
|
||||
* @param cancelButton the Cancel button to set
|
||||
*/
|
||||
void setCancelButton(Button cancelButton) {
|
||||
this.cancelButton = cancelButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.state.AppState;
|
||||
import com.simsilica.lemur.DefaultCheckboxModel;
|
||||
|
||||
/**
|
||||
* A checkbox model for enabling and disabling app states.
|
||||
* This model links a checkbox with an AppState, so that checking
|
||||
* or unchecking the box enables or disables the state, respectively.
|
||||
*/
|
||||
public class StateCheckboxModel extends DefaultCheckboxModel {
|
||||
private final AppState state;
|
||||
|
||||
/**
|
||||
* Constructs a StateCheckboxModel for the specified app state class.
|
||||
*
|
||||
* @param app the application containing the state manager
|
||||
* @param stateClass the class of the app state to be controlled
|
||||
*/
|
||||
public StateCheckboxModel(Application app, Class<? extends AppState> stateClass) {
|
||||
this(app.getStateManager().getState(stateClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a StateCheckboxModel for the specified app state.
|
||||
*
|
||||
* @param state the app state to be controlled
|
||||
*/
|
||||
public StateCheckboxModel(AppState state) {
|
||||
this.state = state;
|
||||
setChecked(state.isEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checked state of the checkbox and enables or disables
|
||||
* the associated app state accordingly.
|
||||
*
|
||||
* @param checked true to check the box and enable the state, false to uncheck the box and disable the state
|
||||
*/
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
super.setChecked(checked);
|
||||
state.setEnabled(checked);
|
||||
}
|
||||
}
|
||||
132
Projekte/jme-common/src/main/java/pp/dialog/TextInputDialog.java
Normal file
132
Projekte/jme-common/src/main/java/pp/dialog/TextInputDialog.java
Normal file
@@ -0,0 +1,132 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.dialog;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.Label;
|
||||
import com.simsilica.lemur.TextField;
|
||||
import com.simsilica.lemur.component.SpringGridLayout;
|
||||
import com.simsilica.lemur.event.KeyAction;
|
||||
|
||||
/**
|
||||
* A dialog class that asks for text input. Usually, this is a single-line
|
||||
* text input.
|
||||
*
|
||||
* @see #getInput()
|
||||
*/
|
||||
public class TextInputDialog extends SimpleDialog {
|
||||
// TextField for user input
|
||||
private final TextField input = new TextField("");
|
||||
|
||||
/**
|
||||
* Constructs a TextInputDialog associated with the specified dialog manager.
|
||||
*
|
||||
* @param manager the dialog manager to associate the dialog with
|
||||
*/
|
||||
private TextInputDialog(DialogManager manager) {
|
||||
super(manager);
|
||||
input.setSingleLine(true); // Set the input field to be single-line
|
||||
input.setPreferredWidth(500f); // Set preferred width of the input field
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input field.
|
||||
*
|
||||
* @return the input field
|
||||
*/
|
||||
public TextField getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the specified key action to trigger a click on the OK button.
|
||||
*
|
||||
* @param action the key action to map
|
||||
*/
|
||||
private void clickOkOn(KeyAction action) {
|
||||
input.getActionMap().put(action, (c, k) -> {
|
||||
if (getOkButton() != null)
|
||||
getOkButton().click();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for TextInputDialog.
|
||||
*
|
||||
* @param manager the dialog manager to associate the dialog with
|
||||
* @return a TextInputDialogBuilder instance
|
||||
*/
|
||||
public static TextInputDialogBuilder builder(DialogManager manager) {
|
||||
return new TextInputDialogBuilder(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder class for creating TextInputDialog instances.
|
||||
*/
|
||||
public static class TextInputDialogBuilder extends DialogBuilder<TextInputDialog> {
|
||||
private String label;
|
||||
private boolean returnHitsOK = true;
|
||||
|
||||
/**
|
||||
* Constructs a TextInputDialogBuilder with the specified dialog manager.
|
||||
*
|
||||
* @param manager the dialog manager to associate the dialog with
|
||||
*/
|
||||
private TextInputDialogBuilder(DialogManager manager) {
|
||||
super(manager, TextInputDialog::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the dialog with additional components like a label and input field.
|
||||
*
|
||||
* @param dialog the dialog to be extended
|
||||
*/
|
||||
@Override
|
||||
protected void extendDialog(TextInputDialog dialog) {
|
||||
final TextField textField = dialog.getInput();
|
||||
if (label == null) {
|
||||
dialog.addChild(textField);
|
||||
}
|
||||
else {
|
||||
final Container c = dialog.addChild(new Container(new SpringGridLayout()));
|
||||
c.addChild(new Label(label));
|
||||
c.addChild(textField, 1);
|
||||
}
|
||||
if (returnHitsOK) {
|
||||
// move the caret right so that it becomes visible at the end of a long text
|
||||
textField.getDocumentModel().right();
|
||||
// Hitting a return key is like pushing the ok button
|
||||
dialog.clickOkOn(new KeyAction(KeyInput.KEY_RETURN));
|
||||
dialog.clickOkOn(new KeyAction(KeyInput.KEY_NUMPADENTER));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for the input field.
|
||||
*
|
||||
* @param label the label text
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public TextInputDialogBuilder setLabel(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether hitting the return key triggers the OK button.
|
||||
*
|
||||
* @param returnHitsOK true to trigger OK button on return key, false otherwise
|
||||
* @return this builder instance for chaining
|
||||
*/
|
||||
public TextInputDialogBuilder setReturnHitsOK(boolean returnHitsOK) {
|
||||
this.returnHitsOK = returnHitsOK;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
229
Projekte/jme-common/src/main/java/pp/graphics/Draw.java
Normal file
229
Projekte/jme-common/src/main/java/pp/graphics/Draw.java
Normal file
@@ -0,0 +1,229 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.graphics;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.material.RenderState.BlendMode;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import pp.util.Position;
|
||||
import pp.util.SegmentLike;
|
||||
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static pp.util.FloatMath.FLT_EPSILON;
|
||||
import static pp.util.FloatMath.TWO_PI;
|
||||
import static pp.util.FloatMath.cos;
|
||||
import static pp.util.FloatMath.sin;
|
||||
import static pp.util.FloatMath.sqr;
|
||||
import static pp.util.FloatMath.sqrt;
|
||||
|
||||
/**
|
||||
* Class for creating graphical primitives.
|
||||
*/
|
||||
public class Draw {
|
||||
private static final Logger LOGGER = System.getLogger(Draw.class.getName());
|
||||
private static final int NUM = 10;
|
||||
private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
|
||||
private static final String COLOR = "Color"; //NON-NLS
|
||||
private final AssetManager am;
|
||||
private final Map<ColorRGBA, Geometry> lineMap = new HashMap<>();
|
||||
private final Map<ColorRGBA, Geometry> rectangleMap = new HashMap<>();
|
||||
private final Map<ColorRGBA, Geometry> circleMap = new HashMap<>();
|
||||
private Mesh lineMesh;
|
||||
private Mesh circleMesh;
|
||||
|
||||
/**
|
||||
* Creates an in stance of the Draw class with the specified
|
||||
* asset manager.
|
||||
*
|
||||
* @param assetManager the specified asset manager
|
||||
*/
|
||||
public Draw(AssetManager assetManager) {
|
||||
am = assetManager;
|
||||
}
|
||||
|
||||
private Geometry makeLine(ColorRGBA color) {
|
||||
LOGGER.log(Level.DEBUG, "create line with color {0}", color); //NON-NLS
|
||||
if (lineMesh == null) {
|
||||
lineMesh = new Mesh();
|
||||
lineMesh.setMode(Mesh.Mode.Lines);
|
||||
lineMesh.setBuffer(VertexBuffer.Type.Position, 3, new float[]{0, 0, 0, 0, 1, 0});
|
||||
lineMesh.setBuffer(VertexBuffer.Type.Index, 2, new short[]{0, 1});
|
||||
}
|
||||
final Geometry lineGeom = new Geometry("lineMesh", lineMesh.clone());
|
||||
Material matWireframe = new Material(am, UNSHADED); //NON-NLS
|
||||
matWireframe.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
matWireframe.setColor(COLOR, color); //NON-NLS
|
||||
lineGeom.setMaterial(matWireframe);
|
||||
|
||||
return lineGeom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a line for the specified segment.
|
||||
*
|
||||
* @param segment the segment with its start and end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeLine(SegmentLike segment, float z, ColorRGBA color) {
|
||||
return makeLine(segment.from(), segment.to(), z, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified color.
|
||||
*
|
||||
* @param p1 start point
|
||||
* @param p2 end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeLine(Position p1, Position p2, float z, ColorRGBA color) {
|
||||
return makeLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), z, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified color.
|
||||
*
|
||||
* @param x1 x-coordinate of the start point
|
||||
* @param y1 y-coordinate of the start point
|
||||
* @param x2 x-coordinate of the end point
|
||||
* @param y2 y-coordinate of the end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeLine(float x1, float y1, float x2, float y2, float z, ColorRGBA color) {
|
||||
final Geometry line = lineMap.computeIfAbsent(color, this::makeLine).clone();
|
||||
line.lookAt(Vector3f.UNIT_Z, new Vector3f(x2 - x1, y2 - y1, 0));
|
||||
line.setLocalScale(sqrt(sqr(x2 - x1) + sqr(y2 - y1)));
|
||||
line.setLocalTranslation(x1, y1, z);
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified width and color.
|
||||
*
|
||||
* @param p1 start point
|
||||
* @param p2 end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
* @param width width of the line
|
||||
*/
|
||||
public Geometry makeFatLine(Position p1, Position p2, float z, ColorRGBA color, float width) {
|
||||
return makeFatLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), z, color, width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a straight line between the specified points with the specified width and color.
|
||||
*
|
||||
* @param x1 x-coordinate of the start point
|
||||
* @param y1 y-coordinate of the start point
|
||||
* @param x2 x-coordinate of the end point
|
||||
* @param y2 y-coordinate of the end point
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
* @param width width of the line
|
||||
*/
|
||||
public Geometry makeFatLine(float x1, float y1, float x2, float y2, float z, ColorRGBA color, float width) {
|
||||
final Geometry line = rectangleMap.computeIfAbsent(color, this::makeRectangle).clone();
|
||||
final float dx = x2 - x1;
|
||||
final float dy = y2 - y1;
|
||||
final float len = sqrt(dx * dx + dy * dy);
|
||||
line.setLocalScale(width, len + width, 1f);
|
||||
if (len <= FLT_EPSILON)
|
||||
line.setLocalTranslation(x1 - 0.5f * width, y1 - 0.5f * width, z);
|
||||
else {
|
||||
final float f = 0.5f * width / len;
|
||||
line.setLocalTranslation(x1 - f * (dy + dx), y1 - f * (dy - dx), z);
|
||||
line.getLocalRotation().lookAt(Vector3f.UNIT_Z, new Vector3f(dx, dy, 0f));
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private Geometry makeRectangle(ColorRGBA color) {
|
||||
final Mesh quad = new Quad(1f, 1f);
|
||||
final Geometry rectangle = new Geometry("quad", quad); //NON-NLS
|
||||
Material mat = new Material(am, UNSHADED); //NON-NLS
|
||||
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
mat.setColor(COLOR, color); //NON-NLS
|
||||
rectangle.setMaterial(mat);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an axis-parallel rectangle with the specified color.
|
||||
*
|
||||
* @param x x-coordinate of the bottom-left corner
|
||||
* @param y y-coordinate of the bottom-left corner
|
||||
* @param w width of the rectangle
|
||||
* @param h height of the rectangle
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeRectangle(float x, float y, float z, float w, float h, ColorRGBA color) {
|
||||
final Geometry rectangle = rectangleMap.computeIfAbsent(color, this::makeRectangle).clone();
|
||||
rectangle.setLocalScale(w, h, 1f);
|
||||
rectangle.setLocalTranslation(x, y, z);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
private Geometry makeCircle(ColorRGBA color) {
|
||||
if (circleMesh == null) {
|
||||
circleMesh = new Mesh();
|
||||
circleMesh.setMode(Mesh.Mode.LineLoop);
|
||||
final float[] pointBuffer = new float[3 * NUM];
|
||||
final short[] indexBuffer = new short[NUM];
|
||||
int j = 0;
|
||||
for (short i = 0; i < NUM; i++) {
|
||||
final float a = TWO_PI / NUM * i;
|
||||
pointBuffer[j++] = 0.5f * cos(a);
|
||||
pointBuffer[j++] = 0.5f * sin(a);
|
||||
pointBuffer[j++] = 0f;
|
||||
indexBuffer[i] = i;
|
||||
}
|
||||
circleMesh.setBuffer(VertexBuffer.Type.Position, 3, pointBuffer);
|
||||
circleMesh.setBuffer(VertexBuffer.Type.Index, 2, indexBuffer);
|
||||
}
|
||||
|
||||
final Geometry circle = new Geometry("circleMesh", circleMesh.clone());
|
||||
Material matWireframe = new Material(am, UNSHADED); //NON-NLS
|
||||
matWireframe.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
matWireframe.setColor(COLOR, color); //NON-NLS
|
||||
circle.setMaterial(matWireframe);
|
||||
|
||||
return circle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ellipse with the specified color.
|
||||
*
|
||||
* @param x x-coordinate of the center point
|
||||
* @param y y-coordinate of the center point
|
||||
* @param w width of the ellipse
|
||||
* @param h height of the ellipse
|
||||
* @param z depth information
|
||||
* @param color line color
|
||||
*/
|
||||
public Geometry makeEllipse(float x, float y, float z, float w, float h, ColorRGBA color) {
|
||||
final Geometry ellipse = circleMap.computeIfAbsent(color, this::makeCircle).clone();
|
||||
ellipse.setLocalScale(w, h, 1f);
|
||||
ellipse.setLocalTranslation(x, y, z);
|
||||
return ellipse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.view;
|
||||
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract base class for keeping the scene graph (=view) in sync with the model.
|
||||
*/
|
||||
public abstract class ModelViewSynchronizer<I> {
|
||||
private static final Logger LOGGER = System.getLogger(ModelViewSynchronizer.class.getName());
|
||||
private final Node itemNode = new Node("items"); //NON-NLS
|
||||
private final Map<I, Spatial> itemMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Saves the game state and the node.
|
||||
*
|
||||
* @param root particular node
|
||||
*/
|
||||
protected ModelViewSynchronizer(Node root) {
|
||||
root.attachChild(itemNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the spatial representing the specified item,
|
||||
* or null if the item has no view counterpart.
|
||||
*/
|
||||
public Spatial getSpatial(I item) {
|
||||
return itemMap.get(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes spatial from map
|
||||
*
|
||||
* @param item spatial that should be removed
|
||||
*/
|
||||
public void delete(I item) {
|
||||
final Spatial spatial = itemMap.remove(item);
|
||||
if (spatial != null) {
|
||||
spatial.removeFromParent();
|
||||
LOGGER.log(Level.DEBUG, "removed spatial for {0} in {1}", item, this); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add spatial to map
|
||||
*
|
||||
* @param item spatial that schuld be added
|
||||
*/
|
||||
public void add(I item) {
|
||||
if (itemMap.containsKey(item)) {
|
||||
LOGGER.log(Level.WARNING, "Item {0} already managed by {1}", item, this); //NON-NLS
|
||||
return;
|
||||
}
|
||||
final Spatial spatial = translate(item);
|
||||
itemMap.put(item, spatial);
|
||||
LOGGER.log(Level.DEBUG, "added spatial for {0} in {1}", item, this); //NON-NLS
|
||||
if (spatial != null)
|
||||
itemNode.attachChild(spatial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed every item
|
||||
*/
|
||||
public void clear() {
|
||||
LOGGER.log(Level.DEBUG, "clear"); //NON-NLS
|
||||
itemMap.clear();
|
||||
itemNode.detachAllChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the spatial for the specified item. Implementations may decide to return null. This
|
||||
* means that the item shall not be represented in the scene graph.
|
||||
*
|
||||
* @param item the item whose representing spatial is asked for
|
||||
* @return the spatial of the item, or null if the item shall not be represented by a spatial.
|
||||
*/
|
||||
protected abstract Spatial translate(I item);
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
info face="Metropolis Bold" size=32 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
||||
common lineHeight=33 base=26 scaleW=512 scaleH=512 pages=1 packed=0
|
||||
page id=0 file="Metropolis-Bold-32.png"
|
||||
chars count=170
|
||||
char id=0 x=0 y=0 width=14 height=35 xoffset=1 yoffset=-1 xadvance=16 page=0 chnl=0
|
||||
char id=10 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=0 page=0 chnl=0
|
||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=9 page=0 chnl=0
|
||||
char id=33 x=359 y=117 width=9 height=24 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=0
|
||||
char id=34 x=44 y=165 width=16 height=12 xoffset=0 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=35 x=422 y=117 width=23 height=24 xoffset=-1 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=36 x=281 y=35 width=22 height=29 xoffset=-1 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=37 x=395 y=117 width=27 height=24 xoffset=0 yoffset=3 xadvance=27 page=0 chnl=0
|
||||
char id=38 x=280 y=66 width=23 height=25 xoffset=-1 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=39 x=503 y=141 width=8 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
|
||||
char id=40 x=48 y=35 width=13 height=30 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
|
||||
char id=41 x=61 y=35 width=12 height=30 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
|
||||
char id=42 x=490 y=141 width=13 height=16 xoffset=0 yoffset=1 xadvance=13 page=0 chnl=0
|
||||
char id=43 x=471 y=141 width=19 height=18 xoffset=0 yoffset=6 xadvance=19 page=0 chnl=0
|
||||
char id=44 x=16 y=165 width=9 height=13 xoffset=0 yoffset=19 xadvance=9 page=0 chnl=0
|
||||
char id=45 x=142 y=165 width=12 height=6 xoffset=0 yoffset=14 xadvance=12 page=0 chnl=0
|
||||
char id=46 x=85 y=165 width=9 height=8 xoffset=0 yoffset=19 xadvance=9 page=0 chnl=0
|
||||
char id=47 x=243 y=35 width=19 height=29 xoffset=-2 yoffset=1 xadvance=15 page=0 chnl=0
|
||||
char id=48 x=336 y=117 width=23 height=24 xoffset=0 yoffset=3 xadvance=23 page=0 chnl=0
|
||||
char id=49 x=201 y=117 width=13 height=24 xoffset=-1 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=50 x=214 y=117 width=19 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=51 x=239 y=66 width=21 height=25 xoffset=-1 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=52 x=233 y=117 width=22 height=24 xoffset=-1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=53 x=260 y=66 width=20 height=25 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=54 x=255 y=117 width=21 height=24 xoffset=0 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=55 x=276 y=117 width=19 height=24 xoffset=0 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=56 x=295 y=117 width=20 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=57 x=315 y=117 width=21 height=24 xoffset=0 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=58 x=375 y=141 width=9 height=19 xoffset=0 yoffset=8 xadvance=9 page=0 chnl=0
|
||||
char id=59 x=386 y=117 width=9 height=24 xoffset=0 yoffset=8 xadvance=9 page=0 chnl=0
|
||||
char id=60 x=384 y=141 width=18 height=19 xoffset=0 yoffset=6 xadvance=19 page=0 chnl=0
|
||||
char id=61 x=25 y=165 width=19 height=13 xoffset=0 yoffset=9 xadvance=19 page=0 chnl=0
|
||||
char id=62 x=402 y=141 width=18 height=19 xoffset=1 yoffset=6 xadvance=19 page=0 chnl=0
|
||||
char id=63 x=368 y=117 width=18 height=24 xoffset=-1 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=64 x=303 y=35 width=28 height=28 xoffset=0 yoffset=3 xadvance=28 page=0 chnl=0
|
||||
char id=65 x=21 y=92 width=26 height=24 xoffset=-1 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=66 x=47 y=92 width=22 height=24 xoffset=1 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=67 x=69 y=92 width=23 height=24 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=68 x=92 y=92 width=23 height=24 xoffset=1 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=69 x=115 y=92 width=20 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=70 x=135 y=92 width=20 height=24 xoffset=1 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=71 x=155 y=92 width=23 height=24 xoffset=0 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=72 x=178 y=92 width=22 height=24 xoffset=1 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=73 x=200 y=92 width=7 height=24 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
|
||||
char id=74 x=207 y=92 width=18 height=24 xoffset=-1 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=75 x=225 y=92 width=23 height=24 xoffset=1 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=76 x=248 y=92 width=18 height=24 xoffset=1 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=77 x=266 y=92 width=26 height=24 xoffset=1 yoffset=3 xadvance=28 page=0 chnl=0
|
||||
char id=78 x=292 y=92 width=23 height=24 xoffset=1 yoffset=3 xadvance=25 page=0 chnl=0
|
||||
char id=79 x=315 y=92 width=26 height=24 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=80 x=341 y=92 width=21 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=81 x=362 y=92 width=26 height=24 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=82 x=388 y=92 width=21 height=24 xoffset=1 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=83 x=409 y=92 width=22 height=24 xoffset=-1 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=84 x=431 y=92 width=21 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=85 x=452 y=92 width=23 height=24 xoffset=1 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=86 x=475 y=92 width=26 height=24 xoffset=-1 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=87 x=0 y=117 width=36 height=24 xoffset=-1 yoffset=3 xadvance=35 page=0 chnl=0
|
||||
char id=88 x=36 y=117 width=25 height=24 xoffset=-1 yoffset=3 xadvance=23 page=0 chnl=0
|
||||
char id=89 x=61 y=117 width=25 height=24 xoffset=-1 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=90 x=86 y=117 width=21 height=24 xoffset=0 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=91 x=73 y=35 width=11 height=30 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
|
||||
char id=92 x=262 y=35 width=19 height=29 xoffset=-2 yoffset=1 xadvance=15 page=0 chnl=0
|
||||
char id=93 x=84 y=35 width=12 height=30 xoffset=0 yoffset=2 xadvance=12 page=0 chnl=0
|
||||
char id=94 x=60 y=165 width=16 height=11 xoffset=0 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=95 x=154 y=165 width=22 height=5 xoffset=-2 yoffset=27 xadvance=19 page=0 chnl=0
|
||||
char id=96 x=94 y=165 width=10 height=7 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
|
||||
char id=97 x=128 y=141 width=18 height=19 xoffset=0 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=98 x=107 y=117 width=20 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=99 x=146 y=141 width=18 height=19 xoffset=0 yoffset=8 xadvance=18 page=0 chnl=0
|
||||
char id=100 x=127 y=117 width=21 height=24 xoffset=0 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=101 x=164 y=141 width=19 height=19 xoffset=0 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=102 x=357 y=35 width=14 height=26 xoffset=-1 yoffset=1 xadvance=12 page=0 chnl=0
|
||||
char id=103 x=157 y=66 width=20 height=25 xoffset=0 yoffset=8 xadvance=21 page=0 chnl=0
|
||||
char id=104 x=148 y=117 width=19 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=105 x=349 y=35 width=8 height=27 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
|
||||
char id=106 x=14 y=0 width=12 height=33 xoffset=-3 yoffset=0 xadvance=9 page=0 chnl=0
|
||||
char id=107 x=167 y=117 width=20 height=24 xoffset=0 yoffset=3 xadvance=18 page=0 chnl=0
|
||||
char id=108 x=501 y=92 width=7 height=24 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
|
||||
char id=109 x=183 y=141 width=29 height=19 xoffset=0 yoffset=8 xadvance=30 page=0 chnl=0
|
||||
char id=110 x=212 y=141 width=19 height=19 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=111 x=231 y=141 width=21 height=19 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=112 x=177 y=66 width=20 height=25 xoffset=1 yoffset=8 xadvance=21 page=0 chnl=0
|
||||
char id=113 x=197 y=66 width=21 height=25 xoffset=0 yoffset=8 xadvance=21 page=0 chnl=0
|
||||
char id=114 x=491 y=117 width=13 height=20 xoffset=1 yoffset=7 xadvance=13 page=0 chnl=0
|
||||
char id=115 x=252 y=141 width=17 height=19 xoffset=-1 yoffset=8 xadvance=16 page=0 chnl=0
|
||||
char id=116 x=187 y=117 width=14 height=24 xoffset=-1 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=117 x=269 y=141 width=19 height=19 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=118 x=288 y=141 width=21 height=19 xoffset=-1 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=119 x=309 y=141 width=29 height=19 xoffset=-1 yoffset=8 xadvance=27 page=0 chnl=0
|
||||
char id=120 x=338 y=141 width=20 height=19 xoffset=-1 yoffset=8 xadvance=18 page=0 chnl=0
|
||||
char id=121 x=218 y=66 width=21 height=25 xoffset=-1 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=122 x=358 y=141 width=17 height=19 xoffset=0 yoffset=8 xadvance=17 page=0 chnl=0
|
||||
char id=123 x=96 y=35 width=13 height=30 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=124 x=502 y=0 width=6 height=29 xoffset=2 yoffset=1 xadvance=10 page=0 chnl=0
|
||||
char id=125 x=109 y=35 width=13 height=30 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=126 x=104 y=165 width=14 height=7 xoffset=0 yoffset=11 xadvance=14 page=0 chnl=0
|
||||
char id=161 x=497 y=35 width=8 height=25 xoffset=1 yoffset=8 xadvance=10 page=0 chnl=0
|
||||
char id=162 x=303 y=66 width=18 height=25 xoffset=0 yoffset=5 xadvance=18 page=0 chnl=0
|
||||
char id=163 x=445 y=117 width=21 height=24 xoffset=0 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=165 x=466 y=117 width=25 height=24 xoffset=-1 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=168 x=118 y=165 width=14 height=7 xoffset=1 yoffset=2 xadvance=15 page=0 chnl=0
|
||||
char id=175 x=176 y=165 width=14 height=5 xoffset=1 yoffset=4 xadvance=15 page=0 chnl=0
|
||||
char id=180 x=132 y=165 width=10 height=7 xoffset=1 yoffset=2 xadvance=12 page=0 chnl=0
|
||||
char id=184 x=76 y=165 width=9 height=9 xoffset=1 yoffset=25 xadvance=11 page=0 chnl=0
|
||||
char id=191 x=321 y=66 width=18 height=25 xoffset=-1 yoffset=8 xadvance=16 page=0 chnl=0
|
||||
char id=192 x=94 y=0 width=26 height=31 xoffset=-1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=193 x=120 y=0 width=26 height=31 xoffset=-1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=194 x=146 y=0 width=26 height=31 xoffset=-1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=195 x=172 y=0 width=26 height=31 xoffset=-1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=196 x=198 y=0 width=26 height=31 xoffset=-1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=197 x=26 y=0 width=26 height=33 xoffset=-1 yoffset=-6 xadvance=24 page=0 chnl=0
|
||||
char id=198 x=0 y=141 width=36 height=24 xoffset=-1 yoffset=3 xadvance=34 page=0 chnl=0
|
||||
char id=199 x=122 y=35 width=23 height=30 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=200 x=224 y=0 width=20 height=31 xoffset=1 yoffset=-4 xadvance=21 page=0 chnl=0
|
||||
char id=201 x=244 y=0 width=20 height=31 xoffset=1 yoffset=-4 xadvance=21 page=0 chnl=0
|
||||
char id=202 x=264 y=0 width=20 height=31 xoffset=1 yoffset=-4 xadvance=21 page=0 chnl=0
|
||||
char id=203 x=284 y=0 width=20 height=31 xoffset=1 yoffset=-4 xadvance=21 page=0 chnl=0
|
||||
char id=204 x=304 y=0 width=12 height=31 xoffset=-3 yoffset=-4 xadvance=9 page=0 chnl=0
|
||||
char id=205 x=316 y=0 width=12 height=31 xoffset=1 yoffset=-4 xadvance=9 page=0 chnl=0
|
||||
char id=206 x=328 y=0 width=13 height=31 xoffset=-2 yoffset=-4 xadvance=9 page=0 chnl=0
|
||||
char id=207 x=341 y=0 width=17 height=31 xoffset=-3 yoffset=-4 xadvance=9 page=0 chnl=0
|
||||
char id=208 x=36 y=141 width=26 height=24 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=209 x=358 y=0 width=23 height=31 xoffset=1 yoffset=-4 xadvance=25 page=0 chnl=0
|
||||
char id=210 x=381 y=0 width=26 height=31 xoffset=0 yoffset=-4 xadvance=26 page=0 chnl=0
|
||||
char id=211 x=407 y=0 width=26 height=31 xoffset=0 yoffset=-4 xadvance=26 page=0 chnl=0
|
||||
char id=212 x=145 y=35 width=26 height=30 xoffset=0 yoffset=-3 xadvance=26 page=0 chnl=0
|
||||
char id=213 x=171 y=35 width=26 height=30 xoffset=0 yoffset=-3 xadvance=26 page=0 chnl=0
|
||||
char id=214 x=197 y=35 width=26 height=30 xoffset=0 yoffset=-3 xadvance=26 page=0 chnl=0
|
||||
char id=215 x=0 y=165 width=16 height=16 xoffset=1 yoffset=7 xadvance=18 page=0 chnl=0
|
||||
char id=216 x=62 y=141 width=26 height=24 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=217 x=433 y=0 width=23 height=31 xoffset=1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=218 x=456 y=0 width=23 height=31 xoffset=1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=219 x=479 y=0 width=23 height=31 xoffset=1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=220 x=0 y=35 width=23 height=31 xoffset=1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=221 x=23 y=35 width=25 height=31 xoffset=-1 yoffset=-4 xadvance=22 page=0 chnl=0
|
||||
char id=222 x=88 y=141 width=21 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=223 x=339 y=66 width=19 height=25 xoffset=1 yoffset=2 xadvance=20 page=0 chnl=0
|
||||
char id=224 x=371 y=35 width=18 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=225 x=389 y=35 width=18 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=226 x=358 y=66 width=18 height=25 xoffset=0 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=227 x=376 y=66 width=18 height=25 xoffset=0 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=228 x=394 y=66 width=18 height=25 xoffset=0 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=229 x=331 y=35 width=18 height=28 xoffset=0 yoffset=-1 xadvance=19 page=0 chnl=0
|
||||
char id=230 x=420 y=141 width=30 height=19 xoffset=0 yoffset=8 xadvance=30 page=0 chnl=0
|
||||
char id=231 x=412 y=66 width=18 height=25 xoffset=0 yoffset=8 xadvance=18 page=0 chnl=0
|
||||
char id=232 x=407 y=35 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=233 x=426 y=35 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=234 x=430 y=66 width=19 height=25 xoffset=0 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=235 x=449 y=66 width=19 height=25 xoffset=0 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=236 x=445 y=35 width=12 height=26 xoffset=-3 yoffset=1 xadvance=9 page=0 chnl=0
|
||||
char id=237 x=457 y=35 width=12 height=26 xoffset=1 yoffset=1 xadvance=9 page=0 chnl=0
|
||||
char id=238 x=469 y=35 width=13 height=26 xoffset=-2 yoffset=1 xadvance=9 page=0 chnl=0
|
||||
char id=239 x=482 y=35 width=15 height=26 xoffset=-3 yoffset=1 xadvance=9 page=0 chnl=0
|
||||
char id=240 x=0 y=66 width=20 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=241 x=20 y=66 width=19 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=242 x=39 y=66 width=21 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=243 x=60 y=66 width=21 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=244 x=468 y=66 width=21 height=25 xoffset=0 yoffset=2 xadvance=20 page=0 chnl=0
|
||||
char id=245 x=489 y=66 width=21 height=25 xoffset=0 yoffset=2 xadvance=20 page=0 chnl=0
|
||||
char id=246 x=0 y=92 width=21 height=25 xoffset=0 yoffset=2 xadvance=20 page=0 chnl=0
|
||||
char id=247 x=109 y=141 width=19 height=20 xoffset=0 yoffset=5 xadvance=19 page=0 chnl=0
|
||||
char id=248 x=450 y=141 width=21 height=19 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=249 x=81 y=66 width=19 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=250 x=100 y=66 width=19 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=251 x=119 y=66 width=19 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=252 x=138 y=66 width=19 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=253 x=52 y=0 width=21 height=32 xoffset=-1 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=254 x=223 y=35 width=20 height=30 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=255 x=73 y=0 width=21 height=32 xoffset=-1 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
kernings count=349
|
||||
kerning first=244 second=119 amount=-1
|
||||
kerning first=86 second=225 amount=-1
|
||||
kerning first=119 second=245 amount=-1
|
||||
kerning first=253 second=243 amount=-1
|
||||
kerning first=111 second=89 amount=-4
|
||||
kerning first=248 second=86 amount=-2
|
||||
kerning first=84 second=211 amount=-1
|
||||
kerning first=84 second=194 amount=-2
|
||||
kerning first=221 second=235 amount=-4
|
||||
kerning first=192 second=221 amount=-1
|
||||
kerning first=121 second=44 amount=-3
|
||||
kerning first=65 second=89 amount=-1
|
||||
kerning first=75 second=216 amount=-1
|
||||
kerning first=87 second=248 amount=-2
|
||||
kerning first=89 second=242 amount=-4
|
||||
kerning first=221 second=111 amount=-4
|
||||
kerning first=233 second=221 amount=-4
|
||||
kerning first=75 second=195 amount=1
|
||||
kerning first=234 second=86 amount=-2
|
||||
kerning first=121 second=233 amount=-1
|
||||
kerning first=119 second=235 amount=-1
|
||||
kerning first=111 second=119 amount=-1
|
||||
kerning first=76 second=86 amount=-3
|
||||
kerning first=246 second=221 amount=-4
|
||||
kerning first=86 second=198 amount=-3
|
||||
kerning first=118 second=242 amount=-1
|
||||
kerning first=89 second=194 amount=-1
|
||||
kerning first=92 second=92 amount=-4
|
||||
kerning first=119 second=111 amount=-1
|
||||
kerning first=86 second=232 amount=-2
|
||||
kerning first=194 second=221 amount=-1
|
||||
kerning first=89 second=101 amount=-4
|
||||
kerning first=87 second=234 amount=-2
|
||||
kerning first=192 second=86 amount=-3
|
||||
kerning first=86 second=246 amount=-2
|
||||
kerning first=233 second=86 amount=-2
|
||||
kerning first=197 second=84 amount=-2
|
||||
kerning first=92 second=47 amount=3
|
||||
kerning first=75 second=193 amount=1
|
||||
kerning first=118 second=101 amount=-1
|
||||
kerning first=243 second=221 amount=-4
|
||||
kerning first=245 second=87 amount=-2
|
||||
kerning first=246 second=86 amount=-2
|
||||
kerning first=84 second=197 amount=-2
|
||||
kerning first=121 second=46 amount=-3
|
||||
kerning first=193 second=221 amount=-1
|
||||
kerning first=197 second=87 amount=-1
|
||||
kerning first=194 second=86 amount=-3
|
||||
kerning first=89 second=99 amount=-2
|
||||
kerning first=87 second=231 amount=-2
|
||||
kerning first=235 second=87 amount=-2
|
||||
kerning first=195 second=71 amount=-1
|
||||
kerning first=99 second=89 amount=-4
|
||||
kerning first=89 second=197 amount=-1
|
||||
kerning first=121 second=244 amount=-1
|
||||
kerning first=221 second=196 amount=-1
|
||||
kerning first=243 second=86 amount=-2
|
||||
kerning first=68 second=196 amount=-1
|
||||
kerning first=84 second=65 amount=-2
|
||||
kerning first=221 second=233 amount=-4
|
||||
kerning first=86 second=245 amount=-2
|
||||
kerning first=119 second=44 amount=-3
|
||||
kerning first=75 second=210 amount=-1
|
||||
kerning first=89 second=243 amount=-4
|
||||
kerning first=193 second=86 amount=-3
|
||||
kerning first=87 second=242 amount=-2
|
||||
kerning first=231 second=221 amount=-4
|
||||
kerning first=255 second=232 amount=-1
|
||||
kerning first=230 second=89 amount=-4
|
||||
kerning first=196 second=84 amount=-2
|
||||
kerning first=119 second=233 amount=-1
|
||||
kerning first=255 second=246 amount=-1
|
||||
kerning first=89 second=65 amount=-1
|
||||
kerning first=118 second=243 amount=-1
|
||||
kerning first=242 second=87 amount=-2
|
||||
kerning first=87 second=194 amount=-1
|
||||
kerning first=118 second=228 amount=-1
|
||||
kerning first=86 second=235 amount=-2
|
||||
kerning first=87 second=101 amount=-2
|
||||
kerning first=196 second=87 amount=-1
|
||||
kerning first=230 second=119 amount=-1
|
||||
kerning first=213 second=87 amount=-1
|
||||
kerning first=86 second=111 amount=-2
|
||||
kerning first=253 second=232 amount=-1
|
||||
kerning first=101 second=87 amount=-2
|
||||
kerning first=192 second=71 amount=-1
|
||||
kerning first=245 second=118 amount=-1
|
||||
kerning first=231 second=86 amount=-2
|
||||
kerning first=121 second=248 amount=-1
|
||||
kerning first=86 second=226 amount=-1
|
||||
kerning first=221 second=195 amount=-1
|
||||
kerning first=253 second=246 amount=-1
|
||||
kerning first=68 second=195 amount=-1
|
||||
kerning first=84 second=214 amount=-1
|
||||
kerning first=244 second=87 amount=-2
|
||||
kerning first=84 second=192 amount=-2
|
||||
kerning first=232 second=89 amount=-4
|
||||
kerning first=119 second=46 amount=-3
|
||||
kerning first=221 second=244 amount=-4
|
||||
kerning first=47 second=47 amount=-4
|
||||
kerning first=87 second=99 amount=-2
|
||||
kerning first=121 second=234 amount=-1
|
||||
kerning first=194 second=71 amount=-1
|
||||
kerning first=65 second=84 amount=-2
|
||||
kerning first=255 second=245 amount=-1
|
||||
kerning first=89 second=192 amount=-1
|
||||
kerning first=87 second=197 amount=-1
|
||||
kerning first=119 second=244 amount=-1
|
||||
kerning first=221 second=193 amount=-1
|
||||
kerning first=111 second=87 amount=-2
|
||||
kerning first=118 second=227 amount=-1
|
||||
kerning first=68 second=193 amount=-1
|
||||
kerning first=195 second=89 amount=-1
|
||||
kerning first=232 second=119 amount=-1
|
||||
kerning first=82 second=89 amount=-1
|
||||
kerning first=65 second=87 amount=-1
|
||||
kerning first=75 second=211 amount=-1
|
||||
kerning first=87 second=243 amount=-2
|
||||
kerning first=210 second=87 amount=-1
|
||||
kerning first=255 second=235 amount=-1
|
||||
kerning first=86 second=229 amount=-1
|
||||
kerning first=75 second=194 amount=1
|
||||
kerning first=193 second=71 amount=-1
|
||||
kerning first=242 second=118 amount=-1
|
||||
kerning first=253 second=245 amount=-1
|
||||
kerning first=248 second=89 amount=-4
|
||||
kerning first=255 second=111 amount=-1
|
||||
kerning first=84 second=213 amount=-1
|
||||
kerning first=86 second=196 amount=-3
|
||||
kerning first=84 second=198 amount=-2
|
||||
kerning first=87 second=65 amount=-1
|
||||
kerning first=118 second=225 amount=-1
|
||||
kerning first=221 second=248 amount=-4
|
||||
kerning first=86 second=233 amount=-2
|
||||
kerning first=234 second=89 amount=-4
|
||||
kerning first=212 second=87 amount=-1
|
||||
kerning first=248 second=119 amount=-1
|
||||
kerning first=253 second=235 amount=-1
|
||||
kerning first=244 second=118 amount=-1
|
||||
kerning first=89 second=198 amount=-1
|
||||
kerning first=121 second=242 amount=-1
|
||||
kerning first=86 second=97 amount=-1
|
||||
kerning first=119 second=248 amount=-1
|
||||
kerning first=253 second=111 amount=-1
|
||||
kerning first=84 second=79 amount=-1
|
||||
kerning first=89 second=232 amount=-4
|
||||
kerning first=221 second=234 amount=-4
|
||||
kerning first=192 second=89 amount=-1
|
||||
kerning first=234 second=119 amount=-1
|
||||
kerning first=89 second=246 amount=-4
|
||||
kerning first=233 second=89 amount=-4
|
||||
kerning first=75 second=197 amount=1
|
||||
kerning first=79 second=87 amount=-1
|
||||
kerning first=118 second=232 amount=-1
|
||||
kerning first=121 second=101 amount=-1
|
||||
kerning first=119 second=234 amount=-1
|
||||
kerning first=99 second=87 amount=-2
|
||||
kerning first=245 second=221 amount=-4
|
||||
kerning first=111 second=118 amount=-1
|
||||
kerning first=246 second=89 amount=-4
|
||||
kerning first=86 second=195 amount=-3
|
||||
kerning first=118 second=246 amount=-1
|
||||
kerning first=87 second=192 amount=-1
|
||||
kerning first=197 second=221 amount=-1
|
||||
kerning first=194 second=89 amount=-1
|
||||
kerning first=233 second=119 amount=-1
|
||||
kerning first=255 second=44 amount=-3
|
||||
kerning first=221 second=231 amount=-2
|
||||
kerning first=235 second=221 amount=-4
|
||||
kerning first=86 second=244 amount=-2
|
||||
kerning first=255 second=233 amount=-1
|
||||
kerning first=86 second=224 amount=-1
|
||||
kerning first=246 second=119 amount=-1
|
||||
kerning first=75 second=65 amount=1
|
||||
kerning first=230 second=87 amount=-2
|
||||
kerning first=243 second=89 amount=-4
|
||||
kerning first=245 second=86 amount=-2
|
||||
kerning first=86 second=193 amount=-3
|
||||
kerning first=89 second=245 amount=-4
|
||||
kerning first=193 second=89 amount=-1
|
||||
kerning first=253 second=44 amount=-3
|
||||
kerning first=197 second=86 amount=-3
|
||||
kerning first=221 second=242 amount=-4
|
||||
kerning first=235 second=86 amount=-2
|
||||
kerning first=253 second=233 amount=-1
|
||||
kerning first=243 second=119 amount=-1
|
||||
kerning first=118 second=245 amount=-1
|
||||
kerning first=242 second=221 amount=-4
|
||||
kerning first=87 second=198 amount=-1
|
||||
kerning first=121 second=243 amount=-1
|
||||
kerning first=221 second=194 amount=-1
|
||||
kerning first=119 second=242 amount=-1
|
||||
kerning first=68 second=194 amount=-1
|
||||
kerning first=255 second=46 amount=-3
|
||||
kerning first=89 second=235 amount=-4
|
||||
kerning first=87 second=232 amount=-2
|
||||
kerning first=196 second=221 amount=-1
|
||||
kerning first=221 second=101 amount=-4
|
||||
kerning first=86 second=248 amount=-2
|
||||
kerning first=75 second=214 amount=-1
|
||||
kerning first=101 second=221 amount=-4
|
||||
kerning first=89 second=111 amount=-4
|
||||
kerning first=87 second=246 amount=-2
|
||||
kerning first=232 second=87 amount=-2
|
||||
kerning first=231 second=89 amount=-4
|
||||
kerning first=195 second=84 amount=-2
|
||||
kerning first=86 second=230 amount=-1
|
||||
kerning first=75 second=192 amount=1
|
||||
kerning first=118 second=235 amount=-1
|
||||
kerning first=119 second=101 amount=-1
|
||||
kerning first=255 second=244 amount=-1
|
||||
kerning first=244 second=221 amount=-4
|
||||
kerning first=118 second=111 amount=-1
|
||||
kerning first=242 second=86 amount=-2
|
||||
kerning first=118 second=226 amount=-1
|
||||
kerning first=253 second=46 amount=-3
|
||||
kerning first=86 second=234 amount=-2
|
||||
kerning first=195 second=87 amount=-1
|
||||
kerning first=196 second=86 amount=-3
|
||||
kerning first=221 second=99 amount=-2
|
||||
kerning first=101 second=86 amount=-2
|
||||
kerning first=221 second=197 amount=-1
|
||||
kerning first=253 second=244 amount=-1
|
||||
kerning first=118 second=100 amount=-1
|
||||
kerning first=111 second=221 amount=-4
|
||||
kerning first=248 second=87 amount=-2
|
||||
kerning first=84 second=212 amount=-1
|
||||
kerning first=68 second=197 amount=-1
|
||||
kerning first=244 second=86 amount=-2
|
||||
kerning first=76 second=84 amount=-1
|
||||
kerning first=84 second=196 amount=-2
|
||||
kerning first=65 second=221 amount=-1
|
||||
kerning first=75 second=213 amount=-1
|
||||
kerning first=87 second=245 amount=-2
|
||||
kerning first=221 second=243 amount=-4
|
||||
kerning first=86 second=231 amount=-2
|
||||
kerning first=75 second=198 amount=1
|
||||
kerning first=234 second=87 amount=-2
|
||||
kerning first=197 second=71 amount=-1
|
||||
kerning first=192 second=84 amount=-2
|
||||
kerning first=255 second=248 amount=-1
|
||||
kerning first=89 second=196 amount=-1
|
||||
kerning first=221 second=65 amount=-1
|
||||
kerning first=119 second=243 amount=-1
|
||||
kerning first=118 second=229 amount=-1
|
||||
kerning first=111 second=86 amount=-2
|
||||
kerning first=68 second=65 amount=-1
|
||||
kerning first=89 second=233 amount=-4
|
||||
kerning first=87 second=235 amount=-2
|
||||
kerning first=192 second=87 amount=-1
|
||||
kerning first=118 second=44 amount=-3
|
||||
kerning first=65 second=86 amount=-3
|
||||
kerning first=86 second=242 amount=-2
|
||||
kerning first=75 second=79 amount=-1
|
||||
kerning first=87 second=111 amount=-2
|
||||
kerning first=233 second=87 amount=-2
|
||||
kerning first=194 second=84 amount=-2
|
||||
kerning first=253 second=248 amount=-1
|
||||
kerning first=255 second=234 amount=-1
|
||||
kerning first=118 second=233 amount=-1
|
||||
kerning first=84 second=216 amount=-1
|
||||
kerning first=246 second=87 amount=-2
|
||||
kerning first=86 second=194 amount=-3
|
||||
kerning first=84 second=195 amount=-2
|
||||
kerning first=118 second=97 amount=-1
|
||||
kerning first=86 second=101 amount=-2
|
||||
kerning first=194 second=87 amount=-1
|
||||
kerning first=216 second=87 amount=-1
|
||||
kerning first=99 second=221 amount=-4
|
||||
kerning first=121 second=232 amount=-1
|
||||
kerning first=253 second=234 amount=-1
|
||||
kerning first=196 second=71 amount=-1
|
||||
kerning first=248 second=118 amount=-1
|
||||
kerning first=193 second=84 amount=-2
|
||||
kerning first=89 second=195 amount=-1
|
||||
kerning first=121 second=246 amount=-1
|
||||
kerning first=221 second=192 amount=-1
|
||||
kerning first=243 second=87 amount=-2
|
||||
kerning first=68 second=192 amount=-1
|
||||
kerning first=84 second=193 amount=-2
|
||||
kerning first=118 second=46 amount=-3
|
||||
kerning first=89 second=244 amount=-4
|
||||
kerning first=193 second=87 amount=-1
|
||||
kerning first=86 second=99 amount=-2
|
||||
kerning first=230 second=221 amount=-4
|
||||
kerning first=99 second=86 amount=-2
|
||||
kerning first=255 second=242 amount=-1
|
||||
kerning first=245 second=89 amount=-4
|
||||
kerning first=86 second=197 amount=-3
|
||||
kerning first=89 second=193 amount=-1
|
||||
kerning first=87 second=196 amount=-1
|
||||
kerning first=118 second=244 amount=-1
|
||||
kerning first=118 second=224 amount=-1
|
||||
kerning first=197 second=89 amount=-1
|
||||
kerning first=87 second=233 amount=-2
|
||||
kerning first=235 second=89 amount=-4
|
||||
kerning first=86 second=243 amount=-2
|
||||
kerning first=214 second=87 amount=-1
|
||||
kerning first=245 second=119 amount=-1
|
||||
kerning first=255 second=101 amount=-1
|
||||
kerning first=231 second=87 amount=-2
|
||||
kerning first=246 second=118 amount=-1
|
||||
kerning first=65 second=71 amount=-1
|
||||
kerning first=86 second=228 amount=-1
|
||||
kerning first=221 second=198 amount=-1
|
||||
kerning first=121 second=245 amount=-1
|
||||
kerning first=230 second=86 amount=-2
|
||||
kerning first=253 second=242 amount=-1
|
||||
kerning first=68 second=198 amount=-1
|
||||
kerning first=84 second=210 amount=-1
|
||||
kerning first=86 second=65 amount=-3
|
||||
kerning first=221 second=232 amount=-4
|
||||
kerning first=235 second=119 amount=-1
|
||||
kerning first=89 second=248 amount=-4
|
||||
kerning first=232 second=221 amount=-4
|
||||
kerning first=221 second=246 amount=-4
|
||||
kerning first=211 second=87 amount=-1
|
||||
kerning first=121 second=235 amount=-1
|
||||
kerning first=119 second=232 amount=-1
|
||||
kerning first=253 second=101 amount=-1
|
||||
kerning first=243 second=118 amount=-1
|
||||
kerning first=118 second=248 amount=-1
|
||||
kerning first=242 second=89 amount=-4
|
||||
kerning first=87 second=195 amount=-1
|
||||
kerning first=121 second=111 amount=-1
|
||||
kerning first=119 second=246 amount=-1
|
||||
kerning first=118 second=230 amount=-1
|
||||
kerning first=195 second=221 amount=-1
|
||||
kerning first=89 second=234 amount=-4
|
||||
kerning first=196 second=89 amount=-1
|
||||
kerning first=82 second=221 amount=-1
|
||||
kerning first=75 second=212 amount=-1
|
||||
kerning first=101 second=89 amount=-4
|
||||
kerning first=87 second=244 amount=-2
|
||||
kerning first=232 second=86 amount=-2
|
||||
kerning first=86 second=227 amount=-1
|
||||
kerning first=75 second=196 amount=1
|
||||
kerning first=242 second=119 amount=-1
|
||||
kerning first=118 second=234 amount=-1
|
||||
kerning first=248 second=221 amount=-4
|
||||
kerning first=255 second=243 amount=-1
|
||||
kerning first=244 second=89 amount=-4
|
||||
kerning first=86 second=192 amount=-3
|
||||
kerning first=87 second=193 amount=-1
|
||||
kerning first=221 second=245 amount=-4
|
||||
kerning first=101 second=119 amount=-1
|
||||
kerning first=195 second=86 amount=-3
|
||||
kerning first=89 second=231 amount=-2
|
||||
kerning first=234 second=221 amount=-4
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,545 @@
|
||||
info face="Metropolis Bold" size=42 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
||||
common lineHeight=43 base=34 scaleW=512 scaleH=512 pages=1 packed=0
|
||||
page id=0 file="Metropolis-Bold-42.png"
|
||||
chars count=170
|
||||
char id=0 x=0 y=0 width=17 height=44 xoffset=2 yoffset=0 xadvance=21 page=0 chnl=0
|
||||
char id=10 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=0 page=0 chnl=0
|
||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=12 page=0 chnl=0
|
||||
char id=33 x=467 y=155 width=11 height=32 xoffset=1 yoffset=4 xadvance=13 page=0 chnl=0
|
||||
char id=34 x=397 y=251 width=20 height=15 xoffset=1 yoffset=4 xadvance=22 page=0 chnl=0
|
||||
char id=35 x=144 y=220 width=29 height=31 xoffset=0 yoffset=4 xadvance=28 page=0 chnl=0
|
||||
char id=36 x=426 y=44 width=27 height=39 xoffset=-1 yoffset=0 xadvance=27 page=0 chnl=0
|
||||
char id=37 x=51 y=155 width=35 height=33 xoffset=0 yoffset=3 xadvance=35 page=0 chnl=0
|
||||
char id=38 x=322 y=84 width=29 height=34 xoffset=0 yoffset=3 xadvance=29 page=0 chnl=0
|
||||
char id=39 x=417 y=251 width=9 height=15 xoffset=1 yoffset=4 xadvance=12 page=0 chnl=0
|
||||
char id=40 x=362 y=44 width=15 height=39 xoffset=1 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=41 x=377 y=44 width=15 height=39 xoffset=0 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=42 x=346 y=251 width=17 height=18 xoffset=0 yoffset=2 xadvance=17 page=0 chnl=0
|
||||
char id=43 x=302 y=251 width=23 height=23 xoffset=1 yoffset=8 xadvance=25 page=0 chnl=0
|
||||
char id=44 x=363 y=251 width=11 height=16 xoffset=1 yoffset=26 xadvance=12 page=0 chnl=0
|
||||
char id=45 x=14 y=277 width=15 height=8 xoffset=0 yoffset=18 xadvance=15 page=0 chnl=0
|
||||
char id=46 x=458 y=251 width=10 height=10 xoffset=1 yoffset=26 xadvance=12 page=0 chnl=0
|
||||
char id=47 x=0 y=84 width=25 height=37 xoffset=-3 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=48 x=0 y=155 width=29 height=33 xoffset=0 yoffset=3 xadvance=30 page=0 chnl=0
|
||||
char id=49 x=75 y=220 width=17 height=31 xoffset=-1 yoffset=4 xadvance=17 page=0 chnl=0
|
||||
char id=50 x=416 y=155 width=25 height=32 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=51 x=402 y=121 width=25 height=33 xoffset=0 yoffset=3 xadvance=25 page=0 chnl=0
|
||||
char id=52 x=92 y=220 width=27 height=31 xoffset=0 yoffset=4 xadvance=27 page=0 chnl=0
|
||||
char id=53 x=441 y=155 width=26 height=32 xoffset=0 yoffset=4 xadvance=26 page=0 chnl=0
|
||||
char id=54 x=427 y=121 width=27 height=33 xoffset=0 yoffset=3 xadvance=27 page=0 chnl=0
|
||||
char id=55 x=119 y=220 width=25 height=31 xoffset=0 yoffset=4 xadvance=25 page=0 chnl=0
|
||||
char id=56 x=454 y=121 width=26 height=33 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=57 x=480 y=121 width=27 height=33 xoffset=0 yoffset=3 xadvance=27 page=0 chnl=0
|
||||
char id=58 x=112 y=251 width=10 height=25 xoffset=1 yoffset=11 xadvance=12 page=0 chnl=0
|
||||
char id=59 x=499 y=155 width=11 height=31 xoffset=1 yoffset=11 xadvance=12 page=0 chnl=0
|
||||
char id=60 x=234 y=251 width=22 height=24 xoffset=1 yoffset=8 xadvance=25 page=0 chnl=0
|
||||
char id=61 x=374 y=251 width=23 height=16 xoffset=1 yoffset=12 xadvance=25 page=0 chnl=0
|
||||
char id=62 x=256 y=251 width=23 height=24 xoffset=1 yoffset=8 xadvance=25 page=0 chnl=0
|
||||
char id=63 x=29 y=155 width=22 height=33 xoffset=-1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=64 x=50 y=84 width=37 height=36 xoffset=0 yoffset=4 xadvance=37 page=0 chnl=0
|
||||
char id=65 x=28 y=188 width=33 height=31 xoffset=-1 yoffset=4 xadvance=32 page=0 chnl=0
|
||||
char id=66 x=61 y=188 width=27 height=31 xoffset=2 yoffset=4 xadvance=29 page=0 chnl=0
|
||||
char id=67 x=96 y=121 width=30 height=33 xoffset=0 yoffset=3 xadvance=29 page=0 chnl=0
|
||||
char id=68 x=88 y=188 width=30 height=31 xoffset=2 yoffset=4 xadvance=32 page=0 chnl=0
|
||||
char id=69 x=118 y=188 width=26 height=31 xoffset=1 yoffset=4 xadvance=27 page=0 chnl=0
|
||||
char id=70 x=144 y=188 width=26 height=31 xoffset=1 yoffset=4 xadvance=27 page=0 chnl=0
|
||||
char id=71 x=126 y=121 width=30 height=33 xoffset=0 yoffset=3 xadvance=31 page=0 chnl=0
|
||||
char id=72 x=170 y=188 width=29 height=31 xoffset=1 yoffset=4 xadvance=31 page=0 chnl=0
|
||||
char id=73 x=489 y=155 width=10 height=31 xoffset=1 yoffset=4 xadvance=12 page=0 chnl=0
|
||||
char id=74 x=289 y=155 width=23 height=32 xoffset=-1 yoffset=4 xadvance=23 page=0 chnl=0
|
||||
char id=75 x=199 y=188 width=29 height=31 xoffset=2 yoffset=4 xadvance=29 page=0 chnl=0
|
||||
char id=76 x=228 y=188 width=24 height=31 xoffset=1 yoffset=4 xadvance=24 page=0 chnl=0
|
||||
char id=77 x=252 y=188 width=33 height=31 xoffset=2 yoffset=4 xadvance=36 page=0 chnl=0
|
||||
char id=78 x=285 y=188 width=29 height=31 xoffset=2 yoffset=4 xadvance=33 page=0 chnl=0
|
||||
char id=79 x=156 y=121 width=34 height=33 xoffset=0 yoffset=3 xadvance=34 page=0 chnl=0
|
||||
char id=80 x=314 y=188 width=27 height=31 xoffset=1 yoffset=4 xadvance=28 page=0 chnl=0
|
||||
char id=81 x=190 y=121 width=34 height=33 xoffset=0 yoffset=3 xadvance=34 page=0 chnl=0
|
||||
char id=82 x=341 y=188 width=28 height=31 xoffset=1 yoffset=4 xadvance=28 page=0 chnl=0
|
||||
char id=83 x=224 y=121 width=27 height=33 xoffset=-1 yoffset=3 xadvance=27 page=0 chnl=0
|
||||
char id=84 x=369 y=188 width=27 height=31 xoffset=0 yoffset=4 xadvance=27 page=0 chnl=0
|
||||
char id=85 x=312 y=155 width=29 height=32 xoffset=1 yoffset=4 xadvance=32 page=0 chnl=0
|
||||
char id=86 x=396 y=188 width=33 height=31 xoffset=-1 yoffset=4 xadvance=32 page=0 chnl=0
|
||||
char id=87 x=429 y=188 width=47 height=31 xoffset=-1 yoffset=4 xadvance=45 page=0 chnl=0
|
||||
char id=88 x=476 y=188 width=32 height=31 xoffset=-1 yoffset=4 xadvance=30 page=0 chnl=0
|
||||
char id=89 x=0 y=220 width=31 height=31 xoffset=-1 yoffset=4 xadvance=29 page=0 chnl=0
|
||||
char id=90 x=31 y=220 width=27 height=31 xoffset=0 yoffset=4 xadvance=28 page=0 chnl=0
|
||||
char id=91 x=453 y=44 width=15 height=38 xoffset=1 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=92 x=25 y=84 width=25 height=37 xoffset=-2 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=93 x=468 y=44 width=15 height=38 xoffset=0 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=94 x=426 y=251 width=20 height=14 xoffset=0 yoffset=4 xadvance=21 page=0 chnl=0
|
||||
char id=95 x=47 y=277 width=28 height=6 xoffset=-2 yoffset=36 xadvance=25 page=0 chnl=0
|
||||
char id=96 x=486 y=251 width=14 height=9 xoffset=1 yoffset=2 xadvance=16 page=0 chnl=0
|
||||
char id=97 x=333 y=220 width=23 height=26 xoffset=0 yoffset=10 xadvance=24 page=0 chnl=0
|
||||
char id=98 x=251 y=121 width=27 height=33 xoffset=1 yoffset=3 xadvance=28 page=0 chnl=0
|
||||
char id=99 x=356 y=220 width=23 height=26 xoffset=0 yoffset=10 xadvance=23 page=0 chnl=0
|
||||
char id=100 x=278 y=121 width=27 height=33 xoffset=0 yoffset=3 xadvance=28 page=0 chnl=0
|
||||
char id=101 x=379 y=220 width=25 height=26 xoffset=0 yoffset=10 xadvance=25 page=0 chnl=0
|
||||
char id=102 x=305 y=121 width=17 height=33 xoffset=0 yoffset=2 xadvance=16 page=0 chnl=0
|
||||
char id=103 x=322 y=121 width=26 height=33 xoffset=0 yoffset=10 xadvance=27 page=0 chnl=0
|
||||
char id=104 x=341 y=155 width=24 height=32 xoffset=1 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=105 x=312 y=84 width=10 height=34 xoffset=1 yoffset=1 xadvance=11 page=0 chnl=0
|
||||
char id=106 x=50 y=0 width=17 height=42 xoffset=-4 yoffset=1 xadvance=11 page=0 chnl=0
|
||||
char id=107 x=365 y=155 width=24 height=32 xoffset=1 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=108 x=499 y=84 width=9 height=32 xoffset=1 yoffset=3 xadvance=11 page=0 chnl=0
|
||||
char id=109 x=27 y=251 width=37 height=25 xoffset=1 yoffset=10 xadvance=39 page=0 chnl=0
|
||||
char id=110 x=64 y=251 width=24 height=25 xoffset=1 yoffset=10 xadvance=26 page=0 chnl=0
|
||||
char id=111 x=404 y=220 width=27 height=26 xoffset=0 yoffset=10 xadvance=27 page=0 chnl=0
|
||||
char id=112 x=348 y=121 width=27 height=33 xoffset=1 yoffset=10 xadvance=28 page=0 chnl=0
|
||||
char id=113 x=375 y=121 width=27 height=33 xoffset=0 yoffset=10 xadvance=28 page=0 chnl=0
|
||||
char id=114 x=491 y=220 width=17 height=25 xoffset=1 yoffset=10 xadvance=17 page=0 chnl=0
|
||||
char id=115 x=431 y=220 width=21 height=26 xoffset=0 yoffset=10 xadvance=21 page=0 chnl=0
|
||||
char id=116 x=58 y=220 width=17 height=31 xoffset=0 yoffset=5 xadvance=17 page=0 chnl=0
|
||||
char id=117 x=88 y=251 width=24 height=25 xoffset=1 yoffset=11 xadvance=26 page=0 chnl=0
|
||||
char id=118 x=122 y=251 width=27 height=24 xoffset=-1 yoffset=11 xadvance=25 page=0 chnl=0
|
||||
char id=119 x=149 y=251 width=37 height=24 xoffset=-1 yoffset=11 xadvance=35 page=0 chnl=0
|
||||
char id=120 x=186 y=251 width=26 height=24 xoffset=-1 yoffset=11 xadvance=24 page=0 chnl=0
|
||||
char id=121 x=389 y=155 width=27 height=32 xoffset=-1 yoffset=11 xadvance=25 page=0 chnl=0
|
||||
char id=122 x=212 y=251 width=22 height=24 xoffset=0 yoffset=11 xadvance=22 page=0 chnl=0
|
||||
char id=123 x=392 y=44 width=17 height=39 xoffset=0 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=124 x=503 y=0 width=7 height=37 xoffset=3 yoffset=1 xadvance=13 page=0 chnl=0
|
||||
char id=125 x=409 y=44 width=17 height=39 xoffset=0 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=126 x=468 y=251 width=18 height=10 xoffset=0 yoffset=14 xadvance=19 page=0 chnl=0
|
||||
char id=161 x=478 y=155 width=11 height=32 xoffset=1 yoffset=10 xadvance=13 page=0 chnl=0
|
||||
char id=162 x=173 y=220 width=23 height=31 xoffset=0 yoffset=8 xadvance=23 page=0 chnl=0
|
||||
char id=163 x=0 y=188 width=28 height=32 xoffset=0 yoffset=3 xadvance=27 page=0 chnl=0
|
||||
char id=165 x=196 y=220 width=31 height=31 xoffset=-1 yoffset=4 xadvance=29 page=0 chnl=0
|
||||
char id=168 x=29 y=277 width=18 height=8 xoffset=1 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=175 x=75 y=277 width=18 height=6 xoffset=1 yoffset=5 xadvance=20 page=0 chnl=0
|
||||
char id=180 x=0 y=277 width=14 height=9 xoffset=1 yoffset=2 xadvance=16 page=0 chnl=0
|
||||
char id=184 x=446 y=251 width=12 height=11 xoffset=1 yoffset=32 xadvance=14 page=0 chnl=0
|
||||
char id=191 x=86 y=155 width=22 height=33 xoffset=0 yoffset=10 xadvance=21 page=0 chnl=0
|
||||
char id=192 x=437 y=0 width=33 height=40 xoffset=-1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=193 x=470 y=0 width=33 height=40 xoffset=-1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=194 x=0 y=44 width=33 height=40 xoffset=-1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=195 x=33 y=44 width=33 height=40 xoffset=-1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=196 x=66 y=44 width=33 height=40 xoffset=-1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=197 x=17 y=0 width=33 height=43 xoffset=-1 yoffset=-8 xadvance=32 page=0 chnl=0
|
||||
char id=198 x=227 y=220 width=46 height=31 xoffset=-1 yoffset=4 xadvance=45 page=0 chnl=0
|
||||
char id=199 x=169 y=0 width=30 height=41 xoffset=0 yoffset=3 xadvance=29 page=0 chnl=0
|
||||
char id=200 x=99 y=44 width=26 height=40 xoffset=1 yoffset=-5 xadvance=27 page=0 chnl=0
|
||||
char id=201 x=125 y=44 width=26 height=40 xoffset=1 yoffset=-5 xadvance=27 page=0 chnl=0
|
||||
char id=202 x=151 y=44 width=26 height=40 xoffset=1 yoffset=-5 xadvance=27 page=0 chnl=0
|
||||
char id=203 x=177 y=44 width=26 height=40 xoffset=1 yoffset=-5 xadvance=27 page=0 chnl=0
|
||||
char id=204 x=203 y=44 width=17 height=40 xoffset=-4 yoffset=-5 xadvance=12 page=0 chnl=0
|
||||
char id=205 x=220 y=44 width=17 height=40 xoffset=1 yoffset=-5 xadvance=12 page=0 chnl=0
|
||||
char id=206 x=237 y=44 width=18 height=40 xoffset=-2 yoffset=-5 xadvance=12 page=0 chnl=0
|
||||
char id=207 x=255 y=44 width=20 height=40 xoffset=-3 yoffset=-5 xadvance=12 page=0 chnl=0
|
||||
char id=208 x=273 y=220 width=33 height=31 xoffset=0 yoffset=4 xadvance=34 page=0 chnl=0
|
||||
char id=209 x=275 y=44 width=29 height=40 xoffset=2 yoffset=-5 xadvance=33 page=0 chnl=0
|
||||
char id=210 x=67 y=0 width=34 height=42 xoffset=0 yoffset=-6 xadvance=34 page=0 chnl=0
|
||||
char id=211 x=101 y=0 width=34 height=42 xoffset=0 yoffset=-6 xadvance=34 page=0 chnl=0
|
||||
char id=212 x=135 y=0 width=34 height=42 xoffset=0 yoffset=-6 xadvance=34 page=0 chnl=0
|
||||
char id=213 x=199 y=0 width=34 height=41 xoffset=0 yoffset=-5 xadvance=34 page=0 chnl=0
|
||||
char id=214 x=233 y=0 width=34 height=41 xoffset=0 yoffset=-5 xadvance=34 page=0 chnl=0
|
||||
char id=215 x=325 y=251 width=21 height=21 xoffset=1 yoffset=9 xadvance=24 page=0 chnl=0
|
||||
char id=216 x=108 y=155 width=34 height=33 xoffset=0 yoffset=3 xadvance=34 page=0 chnl=0
|
||||
char id=217 x=267 y=0 width=29 height=41 xoffset=1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=218 x=296 y=0 width=29 height=41 xoffset=1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=219 x=325 y=0 width=29 height=41 xoffset=1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=220 x=354 y=0 width=29 height=41 xoffset=1 yoffset=-5 xadvance=32 page=0 chnl=0
|
||||
char id=221 x=304 y=44 width=31 height=40 xoffset=-1 yoffset=-5 xadvance=29 page=0 chnl=0
|
||||
char id=222 x=306 y=220 width=27 height=31 xoffset=1 yoffset=4 xadvance=28 page=0 chnl=0
|
||||
char id=223 x=142 y=155 width=25 height=33 xoffset=1 yoffset=2 xadvance=26 page=0 chnl=0
|
||||
char id=224 x=87 y=84 width=23 height=35 xoffset=0 yoffset=1 xadvance=24 page=0 chnl=0
|
||||
char id=225 x=110 y=84 width=23 height=35 xoffset=0 yoffset=1 xadvance=24 page=0 chnl=0
|
||||
char id=226 x=133 y=84 width=23 height=35 xoffset=0 yoffset=1 xadvance=24 page=0 chnl=0
|
||||
char id=227 x=351 y=84 width=23 height=34 xoffset=0 yoffset=2 xadvance=24 page=0 chnl=0
|
||||
char id=228 x=374 y=84 width=23 height=34 xoffset=0 yoffset=2 xadvance=24 page=0 chnl=0
|
||||
char id=229 x=483 y=44 width=23 height=38 xoffset=0 yoffset=-2 xadvance=24 page=0 chnl=0
|
||||
char id=230 x=452 y=220 width=39 height=26 xoffset=0 yoffset=10 xadvance=39 page=0 chnl=0
|
||||
char id=231 x=397 y=84 width=23 height=34 xoffset=0 yoffset=10 xadvance=23 page=0 chnl=0
|
||||
char id=232 x=156 y=84 width=25 height=35 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=0
|
||||
char id=233 x=181 y=84 width=25 height=35 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=0
|
||||
char id=234 x=206 y=84 width=25 height=35 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=0
|
||||
char id=235 x=420 y=84 width=25 height=34 xoffset=0 yoffset=2 xadvance=25 page=0 chnl=0
|
||||
char id=236 x=167 y=155 width=16 height=33 xoffset=-4 yoffset=2 xadvance=11 page=0 chnl=0
|
||||
char id=237 x=183 y=155 width=16 height=33 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=0
|
||||
char id=238 x=199 y=155 width=19 height=33 xoffset=-3 yoffset=2 xadvance=11 page=0 chnl=0
|
||||
char id=239 x=218 y=155 width=21 height=33 xoffset=-3 yoffset=2 xadvance=11 page=0 chnl=0
|
||||
char id=240 x=239 y=155 width=26 height=33 xoffset=0 yoffset=3 xadvance=27 page=0 chnl=0
|
||||
char id=241 x=265 y=155 width=24 height=33 xoffset=1 yoffset=2 xadvance=26 page=0 chnl=0
|
||||
char id=242 x=231 y=84 width=27 height=35 xoffset=0 yoffset=1 xadvance=27 page=0 chnl=0
|
||||
char id=243 x=258 y=84 width=27 height=35 xoffset=0 yoffset=1 xadvance=27 page=0 chnl=0
|
||||
char id=244 x=285 y=84 width=27 height=35 xoffset=0 yoffset=1 xadvance=27 page=0 chnl=0
|
||||
char id=245 x=445 y=84 width=27 height=34 xoffset=0 yoffset=2 xadvance=27 page=0 chnl=0
|
||||
char id=246 x=472 y=84 width=27 height=34 xoffset=0 yoffset=2 xadvance=27 page=0 chnl=0
|
||||
char id=247 x=279 y=251 width=23 height=24 xoffset=1 yoffset=8 xadvance=25 page=0 chnl=0
|
||||
char id=248 x=0 y=251 width=27 height=26 xoffset=0 yoffset=10 xadvance=27 page=0 chnl=0
|
||||
char id=249 x=0 y=121 width=24 height=34 xoffset=1 yoffset=2 xadvance=26 page=0 chnl=0
|
||||
char id=250 x=24 y=121 width=24 height=34 xoffset=1 yoffset=2 xadvance=26 page=0 chnl=0
|
||||
char id=251 x=48 y=121 width=24 height=34 xoffset=1 yoffset=2 xadvance=26 page=0 chnl=0
|
||||
char id=252 x=72 y=121 width=24 height=34 xoffset=1 yoffset=2 xadvance=26 page=0 chnl=0
|
||||
char id=253 x=383 y=0 width=27 height=41 xoffset=-1 yoffset=2 xadvance=25 page=0 chnl=0
|
||||
char id=254 x=335 y=44 width=27 height=40 xoffset=1 yoffset=3 xadvance=28 page=0 chnl=0
|
||||
char id=255 x=410 y=0 width=27 height=41 xoffset=-1 yoffset=2 xadvance=25 page=0 chnl=0
|
||||
kernings count=370
|
||||
kerning first=244 second=119 amount=-1
|
||||
kerning first=86 second=225 amount=-2
|
||||
kerning first=119 second=245 amount=-1
|
||||
kerning first=253 second=243 amount=-1
|
||||
kerning first=111 second=89 amount=-5
|
||||
kerning first=248 second=86 amount=-3
|
||||
kerning first=67 second=79 amount=-1
|
||||
kerning first=84 second=211 amount=-1
|
||||
kerning first=84 second=194 amount=-2
|
||||
kerning first=221 second=235 amount=-5
|
||||
kerning first=192 second=221 amount=-1
|
||||
kerning first=121 second=44 amount=-4
|
||||
kerning first=65 second=89 amount=-1
|
||||
kerning first=75 second=216 amount=-1
|
||||
kerning first=87 second=248 amount=-2
|
||||
kerning first=89 second=242 amount=-5
|
||||
kerning first=221 second=111 amount=-5
|
||||
kerning first=233 second=221 amount=-5
|
||||
kerning first=75 second=195 amount=1
|
||||
kerning first=234 second=86 amount=-3
|
||||
kerning first=121 second=233 amount=-1
|
||||
kerning first=119 second=235 amount=-1
|
||||
kerning first=111 second=119 amount=-1
|
||||
kerning first=76 second=86 amount=-5
|
||||
kerning first=246 second=221 amount=-5
|
||||
kerning first=86 second=198 amount=-4
|
||||
kerning first=118 second=242 amount=-1
|
||||
kerning first=89 second=194 amount=-1
|
||||
kerning first=92 second=92 amount=-6
|
||||
kerning first=199 second=211 amount=-1
|
||||
kerning first=119 second=111 amount=-1
|
||||
kerning first=114 second=242 amount=-1
|
||||
kerning first=86 second=232 amount=-3
|
||||
kerning first=194 second=221 amount=-1
|
||||
kerning first=89 second=101 amount=-5
|
||||
kerning first=87 second=234 amount=-2
|
||||
kerning first=192 second=86 amount=-4
|
||||
kerning first=86 second=246 amount=-3
|
||||
kerning first=233 second=86 amount=-3
|
||||
kerning first=197 second=84 amount=-3
|
||||
kerning first=92 second=47 amount=3
|
||||
kerning first=75 second=193 amount=1
|
||||
kerning first=118 second=101 amount=-1
|
||||
kerning first=243 second=221 amount=-5
|
||||
kerning first=245 second=87 amount=-2
|
||||
kerning first=246 second=86 amount=-3
|
||||
kerning first=84 second=197 amount=-2
|
||||
kerning first=121 second=46 amount=-4
|
||||
kerning first=193 second=221 amount=-1
|
||||
kerning first=197 second=87 amount=-2
|
||||
kerning first=194 second=86 amount=-4
|
||||
kerning first=89 second=99 amount=-3
|
||||
kerning first=87 second=231 amount=-2
|
||||
kerning first=235 second=87 amount=-2
|
||||
kerning first=195 second=71 amount=-1
|
||||
kerning first=99 second=89 amount=-5
|
||||
kerning first=89 second=197 amount=-1
|
||||
kerning first=121 second=244 amount=-1
|
||||
kerning first=221 second=196 amount=-1
|
||||
kerning first=243 second=86 amount=-3
|
||||
kerning first=68 second=196 amount=-2
|
||||
kerning first=84 second=65 amount=-2
|
||||
kerning first=221 second=233 amount=-5
|
||||
kerning first=86 second=245 amount=-3
|
||||
kerning first=119 second=44 amount=-3
|
||||
kerning first=75 second=210 amount=-1
|
||||
kerning first=89 second=243 amount=-5
|
||||
kerning first=193 second=86 amount=-4
|
||||
kerning first=87 second=242 amount=-2
|
||||
kerning first=231 second=221 amount=-5
|
||||
kerning first=255 second=232 amount=-1
|
||||
kerning first=230 second=89 amount=-5
|
||||
kerning first=196 second=84 amount=-3
|
||||
kerning first=119 second=233 amount=-1
|
||||
kerning first=255 second=246 amount=-1
|
||||
kerning first=89 second=65 amount=-1
|
||||
kerning first=118 second=243 amount=-1
|
||||
kerning first=242 second=87 amount=-2
|
||||
kerning first=87 second=194 amount=-2
|
||||
kerning first=118 second=228 amount=-1
|
||||
kerning first=114 second=243 amount=-1
|
||||
kerning first=86 second=235 amount=-3
|
||||
kerning first=87 second=101 amount=-2
|
||||
kerning first=196 second=87 amount=-2
|
||||
kerning first=230 second=119 amount=-1
|
||||
kerning first=213 second=87 amount=-1
|
||||
kerning first=86 second=111 amount=-3
|
||||
kerning first=253 second=232 amount=-1
|
||||
kerning first=101 second=87 amount=-2
|
||||
kerning first=192 second=71 amount=-1
|
||||
kerning first=245 second=118 amount=-1
|
||||
kerning first=231 second=86 amount=-3
|
||||
kerning first=121 second=248 amount=-1
|
||||
kerning first=86 second=226 amount=-2
|
||||
kerning first=221 second=195 amount=-1
|
||||
kerning first=253 second=246 amount=-1
|
||||
kerning first=67 second=212 amount=-1
|
||||
kerning first=68 second=195 amount=-2
|
||||
kerning first=84 second=214 amount=-1
|
||||
kerning first=244 second=87 amount=-2
|
||||
kerning first=84 second=192 amount=-2
|
||||
kerning first=232 second=89 amount=-5
|
||||
kerning first=119 second=46 amount=-3
|
||||
kerning first=221 second=244 amount=-5
|
||||
kerning first=47 second=47 amount=-6
|
||||
kerning first=87 second=99 amount=-2
|
||||
kerning first=121 second=234 amount=-1
|
||||
kerning first=194 second=71 amount=-1
|
||||
kerning first=65 second=84 amount=-3
|
||||
kerning first=255 second=245 amount=-1
|
||||
kerning first=89 second=192 amount=-1
|
||||
kerning first=87 second=197 amount=-2
|
||||
kerning first=199 second=214 amount=-1
|
||||
kerning first=119 second=244 amount=-1
|
||||
kerning first=221 second=193 amount=-1
|
||||
kerning first=111 second=87 amount=-2
|
||||
kerning first=118 second=227 amount=-1
|
||||
kerning first=68 second=193 amount=-2
|
||||
kerning first=195 second=89 amount=-1
|
||||
kerning first=232 second=119 amount=-1
|
||||
kerning first=82 second=89 amount=-2
|
||||
kerning first=65 second=87 amount=-2
|
||||
kerning first=75 second=211 amount=-1
|
||||
kerning first=87 second=243 amount=-2
|
||||
kerning first=210 second=87 amount=-1
|
||||
kerning first=255 second=235 amount=-1
|
||||
kerning first=86 second=229 amount=-2
|
||||
kerning first=75 second=194 amount=1
|
||||
kerning first=193 second=71 amount=-1
|
||||
kerning first=242 second=118 amount=-1
|
||||
kerning first=253 second=245 amount=-1
|
||||
kerning first=248 second=89 amount=-5
|
||||
kerning first=255 second=111 amount=-1
|
||||
kerning first=67 second=216 amount=-1
|
||||
kerning first=84 second=213 amount=-1
|
||||
kerning first=86 second=196 amount=-4
|
||||
kerning first=84 second=198 amount=-2
|
||||
kerning first=87 second=65 amount=-2
|
||||
kerning first=118 second=225 amount=-1
|
||||
kerning first=221 second=248 amount=-5
|
||||
kerning first=86 second=233 amount=-3
|
||||
kerning first=234 second=89 amount=-5
|
||||
kerning first=212 second=87 amount=-1
|
||||
kerning first=248 second=119 amount=-1
|
||||
kerning first=253 second=235 amount=-1
|
||||
kerning first=244 second=118 amount=-1
|
||||
kerning first=89 second=198 amount=-1
|
||||
kerning first=199 second=213 amount=-1
|
||||
kerning first=121 second=242 amount=-1
|
||||
kerning first=86 second=97 amount=-2
|
||||
kerning first=119 second=248 amount=-1
|
||||
kerning first=253 second=111 amount=-1
|
||||
kerning first=84 second=79 amount=-1
|
||||
kerning first=89 second=232 amount=-5
|
||||
kerning first=221 second=234 amount=-5
|
||||
kerning first=192 second=89 amount=-1
|
||||
kerning first=234 second=119 amount=-1
|
||||
kerning first=89 second=246 amount=-5
|
||||
kerning first=233 second=89 amount=-5
|
||||
kerning first=75 second=197 amount=1
|
||||
kerning first=79 second=87 amount=-1
|
||||
kerning first=118 second=232 amount=-1
|
||||
kerning first=121 second=101 amount=-1
|
||||
kerning first=119 second=234 amount=-1
|
||||
kerning first=99 second=87 amount=-2
|
||||
kerning first=245 second=221 amount=-5
|
||||
kerning first=111 second=118 amount=-1
|
||||
kerning first=246 second=89 amount=-5
|
||||
kerning first=86 second=195 amount=-4
|
||||
kerning first=118 second=246 amount=-1
|
||||
kerning first=87 second=192 amount=-2
|
||||
kerning first=199 second=79 amount=-1
|
||||
kerning first=197 second=221 amount=-1
|
||||
kerning first=114 second=246 amount=-1
|
||||
kerning first=194 second=89 amount=-1
|
||||
kerning first=233 second=119 amount=-1
|
||||
kerning first=255 second=44 amount=-4
|
||||
kerning first=221 second=231 amount=-3
|
||||
kerning first=235 second=221 amount=-5
|
||||
kerning first=86 second=244 amount=-3
|
||||
kerning first=255 second=233 amount=-1
|
||||
kerning first=86 second=224 amount=-2
|
||||
kerning first=246 second=119 amount=-1
|
||||
kerning first=75 second=65 amount=1
|
||||
kerning first=230 second=87 amount=-2
|
||||
kerning first=243 second=89 amount=-5
|
||||
kerning first=67 second=210 amount=-1
|
||||
kerning first=245 second=86 amount=-3
|
||||
kerning first=86 second=193 amount=-4
|
||||
kerning first=89 second=245 amount=-5
|
||||
kerning first=193 second=89 amount=-1
|
||||
kerning first=253 second=44 amount=-4
|
||||
kerning first=197 second=86 amount=-4
|
||||
kerning first=221 second=242 amount=-5
|
||||
kerning first=235 second=86 amount=-3
|
||||
kerning first=253 second=233 amount=-1
|
||||
kerning first=243 second=119 amount=-1
|
||||
kerning first=118 second=245 amount=-1
|
||||
kerning first=242 second=221 amount=-5
|
||||
kerning first=87 second=198 amount=-2
|
||||
kerning first=121 second=243 amount=-1
|
||||
kerning first=221 second=194 amount=-1
|
||||
kerning first=119 second=242 amount=-1
|
||||
kerning first=68 second=194 amount=-2
|
||||
kerning first=114 second=245 amount=-1
|
||||
kerning first=255 second=46 amount=-4
|
||||
kerning first=89 second=235 amount=-5
|
||||
kerning first=87 second=232 amount=-2
|
||||
kerning first=196 second=221 amount=-1
|
||||
kerning first=221 second=101 amount=-5
|
||||
kerning first=86 second=248 amount=-3
|
||||
kerning first=75 second=214 amount=-1
|
||||
kerning first=101 second=221 amount=-5
|
||||
kerning first=89 second=111 amount=-5
|
||||
kerning first=87 second=246 amount=-2
|
||||
kerning first=232 second=87 amount=-2
|
||||
kerning first=231 second=89 amount=-5
|
||||
kerning first=195 second=84 amount=-3
|
||||
kerning first=86 second=230 amount=-2
|
||||
kerning first=75 second=192 amount=1
|
||||
kerning first=118 second=235 amount=-1
|
||||
kerning first=119 second=101 amount=-1
|
||||
kerning first=255 second=244 amount=-1
|
||||
kerning first=244 second=221 amount=-5
|
||||
kerning first=118 second=111 amount=-1
|
||||
kerning first=242 second=86 amount=-3
|
||||
kerning first=118 second=226 amount=-1
|
||||
kerning first=253 second=46 amount=-4
|
||||
kerning first=114 second=111 amount=-1
|
||||
kerning first=86 second=234 amount=-3
|
||||
kerning first=195 second=87 amount=-2
|
||||
kerning first=196 second=86 amount=-4
|
||||
kerning first=221 second=99 amount=-3
|
||||
kerning first=101 second=86 amount=-3
|
||||
kerning first=221 second=197 amount=-1
|
||||
kerning first=253 second=244 amount=-1
|
||||
kerning first=118 second=100 amount=-1
|
||||
kerning first=111 second=221 amount=-5
|
||||
kerning first=248 second=87 amount=-2
|
||||
kerning first=67 second=211 amount=-1
|
||||
kerning first=68 second=197 amount=-2
|
||||
kerning first=84 second=212 amount=-1
|
||||
kerning first=244 second=86 amount=-3
|
||||
kerning first=76 second=84 amount=-1
|
||||
kerning first=84 second=196 amount=-2
|
||||
kerning first=65 second=221 amount=-1
|
||||
kerning first=75 second=213 amount=-1
|
||||
kerning first=87 second=245 amount=-2
|
||||
kerning first=221 second=243 amount=-5
|
||||
kerning first=86 second=231 amount=-3
|
||||
kerning first=75 second=198 amount=1
|
||||
kerning first=234 second=87 amount=-2
|
||||
kerning first=197 second=71 amount=-1
|
||||
kerning first=192 second=84 amount=-3
|
||||
kerning first=255 second=248 amount=-1
|
||||
kerning first=89 second=196 amount=-1
|
||||
kerning first=199 second=212 amount=-1
|
||||
kerning first=221 second=65 amount=-1
|
||||
kerning first=119 second=243 amount=-1
|
||||
kerning first=118 second=229 amount=-1
|
||||
kerning first=111 second=86 amount=-3
|
||||
kerning first=68 second=65 amount=-2
|
||||
kerning first=89 second=233 amount=-5
|
||||
kerning first=87 second=235 amount=-2
|
||||
kerning first=192 second=87 amount=-2
|
||||
kerning first=118 second=44 amount=-4
|
||||
kerning first=65 second=86 amount=-4
|
||||
kerning first=86 second=242 amount=-3
|
||||
kerning first=75 second=79 amount=-1
|
||||
kerning first=87 second=111 amount=-2
|
||||
kerning first=233 second=87 amount=-2
|
||||
kerning first=194 second=84 amount=-3
|
||||
kerning first=253 second=248 amount=-1
|
||||
kerning first=255 second=234 amount=-1
|
||||
kerning first=118 second=233 amount=-1
|
||||
kerning first=84 second=216 amount=-1
|
||||
kerning first=246 second=87 amount=-2
|
||||
kerning first=86 second=194 amount=-4
|
||||
kerning first=84 second=195 amount=-2
|
||||
kerning first=118 second=97 amount=-1
|
||||
kerning first=86 second=101 amount=-3
|
||||
kerning first=194 second=87 amount=-2
|
||||
kerning first=216 second=87 amount=-1
|
||||
kerning first=99 second=221 amount=-5
|
||||
kerning first=121 second=232 amount=-1
|
||||
kerning first=253 second=234 amount=-1
|
||||
kerning first=196 second=71 amount=-1
|
||||
kerning first=248 second=118 amount=-1
|
||||
kerning first=193 second=84 amount=-3
|
||||
kerning first=89 second=195 amount=-1
|
||||
kerning first=199 second=216 amount=-1
|
||||
kerning first=121 second=246 amount=-1
|
||||
kerning first=221 second=192 amount=-1
|
||||
kerning first=243 second=87 amount=-2
|
||||
kerning first=68 second=192 amount=-2
|
||||
kerning first=84 second=193 amount=-2
|
||||
kerning first=118 second=46 amount=-4
|
||||
kerning first=89 second=244 amount=-5
|
||||
kerning first=193 second=87 amount=-2
|
||||
kerning first=86 second=99 amount=-3
|
||||
kerning first=230 second=221 amount=-5
|
||||
kerning first=99 second=86 amount=-3
|
||||
kerning first=255 second=242 amount=-1
|
||||
kerning first=245 second=89 amount=-5
|
||||
kerning first=86 second=197 amount=-4
|
||||
kerning first=89 second=193 amount=-1
|
||||
kerning first=87 second=196 amount=-2
|
||||
kerning first=118 second=244 amount=-1
|
||||
kerning first=118 second=224 amount=-1
|
||||
kerning first=197 second=89 amount=-1
|
||||
kerning first=114 second=244 amount=-1
|
||||
kerning first=87 second=233 amount=-2
|
||||
kerning first=235 second=89 amount=-5
|
||||
kerning first=86 second=243 amount=-3
|
||||
kerning first=214 second=87 amount=-1
|
||||
kerning first=245 second=119 amount=-1
|
||||
kerning first=255 second=101 amount=-1
|
||||
kerning first=231 second=87 amount=-2
|
||||
kerning first=246 second=118 amount=-1
|
||||
kerning first=65 second=71 amount=-1
|
||||
kerning first=86 second=228 amount=-2
|
||||
kerning first=221 second=198 amount=-1
|
||||
kerning first=121 second=245 amount=-1
|
||||
kerning first=230 second=86 amount=-3
|
||||
kerning first=253 second=242 amount=-1
|
||||
kerning first=67 second=214 amount=-1
|
||||
kerning first=68 second=198 amount=-2
|
||||
kerning first=84 second=210 amount=-1
|
||||
kerning first=86 second=65 amount=-4
|
||||
kerning first=221 second=232 amount=-5
|
||||
kerning first=235 second=119 amount=-1
|
||||
kerning first=89 second=248 amount=-5
|
||||
kerning first=232 second=221 amount=-5
|
||||
kerning first=221 second=246 amount=-5
|
||||
kerning first=211 second=87 amount=-1
|
||||
kerning first=121 second=235 amount=-1
|
||||
kerning first=119 second=232 amount=-1
|
||||
kerning first=253 second=101 amount=-1
|
||||
kerning first=243 second=118 amount=-1
|
||||
kerning first=118 second=248 amount=-1
|
||||
kerning first=242 second=89 amount=-5
|
||||
kerning first=87 second=195 amount=-2
|
||||
kerning first=199 second=210 amount=-1
|
||||
kerning first=121 second=111 amount=-1
|
||||
kerning first=119 second=246 amount=-1
|
||||
kerning first=118 second=230 amount=-1
|
||||
kerning first=114 second=248 amount=-1
|
||||
kerning first=195 second=221 amount=-1
|
||||
kerning first=89 second=234 amount=-5
|
||||
kerning first=196 second=89 amount=-1
|
||||
kerning first=82 second=221 amount=-2
|
||||
kerning first=75 second=212 amount=-1
|
||||
kerning first=101 second=89 amount=-5
|
||||
kerning first=87 second=244 amount=-2
|
||||
kerning first=232 second=86 amount=-3
|
||||
kerning first=86 second=227 amount=-2
|
||||
kerning first=75 second=196 amount=1
|
||||
kerning first=242 second=119 amount=-1
|
||||
kerning first=118 second=234 amount=-1
|
||||
kerning first=248 second=221 amount=-5
|
||||
kerning first=255 second=243 amount=-1
|
||||
kerning first=67 second=213 amount=-1
|
||||
kerning first=244 second=89 amount=-5
|
||||
kerning first=86 second=192 amount=-4
|
||||
kerning first=87 second=193 amount=-2
|
||||
kerning first=221 second=245 amount=-5
|
||||
kerning first=101 second=119 amount=-1
|
||||
kerning first=195 second=86 amount=-4
|
||||
kerning first=89 second=231 amount=-3
|
||||
kerning first=234 second=221 amount=-5
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,581 @@
|
||||
info face="Metropolis Bold" size=64 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
||||
common lineHeight=65 base=51 scaleW=512 scaleH=512 pages=2 packed=0
|
||||
page id=0 file="Metropolis-Bold-641.png"
|
||||
page id=1 file="Metropolis-Bold-642.png"
|
||||
chars count=170
|
||||
char id=0 x=0 y=0 width=24 height=66 xoffset=4 yoffset=-1 xadvance=32 page=0 chnl=0
|
||||
char id=10 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=0 page=0 chnl=0
|
||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=18 page=0 chnl=0
|
||||
char id=33 x=114 y=386 width=15 height=47 xoffset=2 yoffset=6 xadvance=20 page=0 chnl=0
|
||||
char id=34 x=103 y=479 width=29 height=22 xoffset=2 yoffset=6 xadvance=33 page=0 chnl=0
|
||||
char id=36 x=394 y=126 width=40 height=57 xoffset=0 yoffset=1 xadvance=41 page=0 chnl=0
|
||||
char id=37 x=43 y=338 width=52 height=48 xoffset=1 yoffset=5 xadvance=54 page=0 chnl=0
|
||||
char id=38 x=109 y=240 width=43 height=49 xoffset=0 yoffset=5 xadvance=43 page=0 chnl=0
|
||||
char id=39 x=132 y=479 width=13 height=22 xoffset=2 yoffset=6 xadvance=18 page=0 chnl=0
|
||||
char id=40 x=261 y=126 width=22 height=58 xoffset=2 yoffset=5 xadvance=25 page=0 chnl=0
|
||||
char id=41 x=283 y=126 width=22 height=58 xoffset=1 yoffset=5 xadvance=25 page=0 chnl=0
|
||||
char id=42 x=30 y=479 width=24 height=26 xoffset=1 yoffset=4 xadvance=25 page=0 chnl=0
|
||||
char id=44 x=88 y=479 width=15 height=23 xoffset=2 yoffset=39 xadvance=19 page=0 chnl=0
|
||||
char id=45 x=291 y=479 width=21 height=10 xoffset=1 yoffset=28 xadvance=23 page=0 chnl=0
|
||||
char id=46 x=190 y=479 width=15 height=14 xoffset=2 yoffset=39 xadvance=19 page=0 chnl=0
|
||||
char id=47 x=434 y=126 width=36 height=55 xoffset=-3 yoffset=2 xadvance=30 page=0 chnl=0
|
||||
char id=48 x=0 y=338 width=43 height=48 xoffset=1 yoffset=5 xadvance=45 page=0 chnl=0
|
||||
char id=50 x=40 y=386 width=37 height=47 xoffset=1 yoffset=5 xadvance=39 page=0 chnl=0
|
||||
char id=51 x=319 y=290 width=38 height=48 xoffset=0 yoffset=5 xadvance=39 page=0 chnl=0
|
||||
char id=53 x=77 y=386 width=37 height=47 xoffset=1 yoffset=6 xadvance=40 page=0 chnl=0
|
||||
char id=54 x=357 y=290 width=39 height=48 xoffset=1 yoffset=5 xadvance=41 page=0 chnl=0
|
||||
char id=56 x=396 y=290 width=38 height=48 xoffset=1 yoffset=5 xadvance=40 page=0 chnl=0
|
||||
char id=57 x=434 y=290 width=39 height=48 xoffset=1 yoffset=5 xadvance=41 page=0 chnl=0
|
||||
char id=58 x=495 y=433 width=15 height=37 xoffset=2 yoffset=16 xadvance=19 page=0 chnl=0
|
||||
char id=59 x=489 y=386 width=15 height=46 xoffset=2 yoffset=16 xadvance=19 page=0 chnl=0
|
||||
char id=61 x=54 y=479 width=34 height=25 xoffset=2 yoffset=17 xadvance=39 page=0 chnl=0
|
||||
char id=63 x=473 y=290 width=32 height=48 xoffset=0 yoffset=5 xadvance=32 page=0 chnl=0
|
||||
char id=64 x=34 y=185 width=54 height=53 xoffset=1 yoffset=7 xadvance=56 page=0 chnl=0
|
||||
char id=65 x=183 y=386 width=50 height=46 xoffset=-1 yoffset=6 xadvance=48 page=0 chnl=0
|
||||
char id=66 x=233 y=386 width=41 height=46 xoffset=3 yoffset=6 xadvance=45 page=0 chnl=0
|
||||
char id=67 x=412 y=240 width=44 height=48 xoffset=1 yoffset=5 xadvance=45 page=0 chnl=0
|
||||
char id=68 x=274 y=386 width=44 height=46 xoffset=3 yoffset=6 xadvance=49 page=0 chnl=0
|
||||
char id=69 x=318 y=386 width=37 height=46 xoffset=3 yoffset=6 xadvance=41 page=0 chnl=0
|
||||
char id=70 x=355 y=386 width=37 height=46 xoffset=3 yoffset=6 xadvance=41 page=0 chnl=0
|
||||
char id=71 x=456 y=240 width=44 height=48 xoffset=1 yoffset=5 xadvance=48 page=0 chnl=0
|
||||
char id=72 x=392 y=386 width=42 height=46 xoffset=3 yoffset=6 xadvance=48 page=0 chnl=0
|
||||
char id=73 x=434 y=386 width=12 height=46 xoffset=3 yoffset=6 xadvance=18 page=0 chnl=0
|
||||
char id=74 x=305 y=338 width=33 height=47 xoffset=-1 yoffset=6 xadvance=35 page=0 chnl=0
|
||||
char id=75 x=446 y=386 width=43 height=46 xoffset=3 yoffset=6 xadvance=45 page=0 chnl=0
|
||||
char id=76 x=0 y=433 width=34 height=46 xoffset=3 yoffset=6 xadvance=37 page=0 chnl=0
|
||||
char id=77 x=34 y=433 width=49 height=46 xoffset=3 yoffset=6 xadvance=55 page=0 chnl=0
|
||||
char id=78 x=83 y=433 width=43 height=46 xoffset=3 yoffset=6 xadvance=50 page=0 chnl=0
|
||||
char id=79 x=0 y=290 width=50 height=48 xoffset=1 yoffset=5 xadvance=52 page=0 chnl=0
|
||||
char id=80 x=126 y=433 width=39 height=46 xoffset=3 yoffset=6 xadvance=42 page=0 chnl=0
|
||||
char id=81 x=50 y=290 width=50 height=48 xoffset=1 yoffset=5 xadvance=52 page=0 chnl=0
|
||||
char id=82 x=165 y=433 width=40 height=46 xoffset=3 yoffset=6 xadvance=43 page=0 chnl=0
|
||||
char id=83 x=100 y=290 width=40 height=48 xoffset=0 yoffset=5 xadvance=41 page=0 chnl=0
|
||||
char id=84 x=205 y=433 width=39 height=46 xoffset=1 yoffset=6 xadvance=41 page=0 chnl=0
|
||||
char id=85 x=338 y=338 width=43 height=47 xoffset=3 yoffset=6 xadvance=48 page=0 chnl=0
|
||||
char id=86 x=244 y=433 width=50 height=46 xoffset=-1 yoffset=6 xadvance=48 page=0 chnl=0
|
||||
char id=87 x=294 y=433 width=69 height=46 xoffset=0 yoffset=6 xadvance=69 page=0 chnl=0
|
||||
char id=88 x=363 y=433 width=46 height=46 xoffset=0 yoffset=6 xadvance=46 page=0 chnl=0
|
||||
char id=89 x=409 y=433 width=47 height=46 xoffset=-1 yoffset=6 xadvance=44 page=0 chnl=0
|
||||
char id=90 x=456 y=433 width=39 height=46 xoffset=2 yoffset=6 xadvance=42 page=0 chnl=0
|
||||
char id=91 x=305 y=126 width=20 height=58 xoffset=3 yoffset=4 xadvance=25 page=0 chnl=0
|
||||
char id=92 x=470 y=126 width=36 height=55 xoffset=-2 yoffset=2 xadvance=30 page=0 chnl=0
|
||||
char id=93 x=325 y=126 width=21 height=58 xoffset=1 yoffset=4 xadvance=25 page=0 chnl=0
|
||||
char id=94 x=145 y=479 width=29 height=20 xoffset=1 yoffset=6 xadvance=31 page=0 chnl=0
|
||||
char id=95 x=312 y=479 width=41 height=8 xoffset=-2 yoffset=54 xadvance=38 page=0 chnl=0
|
||||
char id=96 x=230 y=479 width=18 height=12 xoffset=3 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=98 x=140 y=290 width=38 height=48 xoffset=3 yoffset=5 xadvance=42 page=0 chnl=0
|
||||
char id=100 x=178 y=290 width=39 height=48 xoffset=1 yoffset=5 xadvance=42 page=0 chnl=0
|
||||
char id=102 x=217 y=290 width=25 height=48 xoffset=0 yoffset=4 xadvance=24 page=0 chnl=0
|
||||
char id=103 x=381 y=338 width=38 height=47 xoffset=1 yoffset=16 xadvance=42 page=0 chnl=0
|
||||
char id=104 x=419 y=338 width=35 height=47 xoffset=2 yoffset=5 xadvance=39 page=0 chnl=0
|
||||
char id=105 x=88 y=185 width=13 height=50 xoffset=2 yoffset=2 xadvance=17 page=0 chnl=0
|
||||
char id=106 x=74 y=0 width=23 height=62 xoffset=-5 yoffset=2 xadvance=17 page=0 chnl=0
|
||||
char id=107 x=454 y=338 width=36 height=47 xoffset=2 yoffset=5 xadvance=37 page=0 chnl=0
|
||||
char id=108 x=490 y=338 width=12 height=47 xoffset=3 yoffset=5 xadvance=17 page=0 chnl=0
|
||||
char id=112 x=242 y=290 width=38 height=48 xoffset=3 yoffset=16 xadvance=42 page=0 chnl=0
|
||||
char id=113 x=280 y=290 width=39 height=48 xoffset=1 yoffset=16 xadvance=42 page=0 chnl=0
|
||||
char id=121 x=0 y=386 width=40 height=47 xoffset=-1 yoffset=17 xadvance=38 page=0 chnl=0
|
||||
char id=123 x=346 y=126 width=24 height=58 xoffset=1 yoffset=5 xadvance=26 page=0 chnl=0
|
||||
char id=124 x=500 y=66 width=9 height=55 xoffset=5 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=125 x=370 y=126 width=24 height=58 xoffset=1 yoffset=5 xadvance=26 page=0 chnl=0
|
||||
char id=126 x=205 y=479 width=25 height=13 xoffset=2 yoffset=21 xadvance=29 page=0 chnl=0
|
||||
char id=161 x=129 y=386 width=14 height=47 xoffset=3 yoffset=16 xadvance=20 page=0 chnl=0
|
||||
char id=163 x=143 y=386 width=40 height=47 xoffset=1 yoffset=5 xadvance=42 page=0 chnl=0
|
||||
char id=168 x=266 y=479 width=25 height=11 xoffset=3 yoffset=4 xadvance=31 page=0 chnl=0
|
||||
char id=175 x=353 y=479 width=25 height=8 xoffset=3 yoffset=7 xadvance=30 page=0 chnl=0
|
||||
char id=180 x=248 y=479 width=18 height=12 xoffset=3 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=184 x=174 y=479 width=16 height=16 xoffset=3 yoffset=49 xadvance=21 page=0 chnl=0
|
||||
char id=191 x=95 y=338 width=33 height=48 xoffset=0 yoffset=16 xadvance=32 page=0 chnl=0
|
||||
char id=192 x=423 y=0 width=50 height=60 xoffset=-1 yoffset=-8 xadvance=48 page=0 chnl=0
|
||||
char id=193 x=0 y=66 width=50 height=60 xoffset=-1 yoffset=-8 xadvance=48 page=0 chnl=0
|
||||
char id=194 x=400 y=66 width=50 height=59 xoffset=-1 yoffset=-7 xadvance=48 page=0 chnl=0
|
||||
char id=195 x=450 y=66 width=50 height=59 xoffset=-1 yoffset=-7 xadvance=48 page=0 chnl=0
|
||||
char id=196 x=0 y=126 width=50 height=59 xoffset=-1 yoffset=-7 xadvance=48 page=0 chnl=0
|
||||
char id=197 x=24 y=0 width=50 height=65 xoffset=-1 yoffset=-13 xadvance=48 page=0 chnl=0
|
||||
char id=199 x=50 y=66 width=44 height=60 xoffset=1 yoffset=5 xadvance=45 page=0 chnl=0
|
||||
char id=200 x=473 y=0 width=37 height=60 xoffset=3 yoffset=-8 xadvance=41 page=0 chnl=0
|
||||
char id=201 x=94 y=66 width=37 height=60 xoffset=3 yoffset=-8 xadvance=41 page=0 chnl=0
|
||||
char id=202 x=50 y=126 width=37 height=59 xoffset=3 yoffset=-7 xadvance=41 page=0 chnl=0
|
||||
char id=203 x=87 y=126 width=37 height=59 xoffset=3 yoffset=-7 xadvance=41 page=0 chnl=0
|
||||
char id=204 x=131 y=66 width=23 height=60 xoffset=-5 yoffset=-8 xadvance=18 page=0 chnl=0
|
||||
char id=205 x=154 y=66 width=23 height=60 xoffset=3 yoffset=-8 xadvance=18 page=0 chnl=0
|
||||
char id=206 x=124 y=126 width=26 height=59 xoffset=-3 yoffset=-7 xadvance=18 page=0 chnl=0
|
||||
char id=207 x=150 y=126 width=30 height=59 xoffset=-4 yoffset=-7 xadvance=18 page=0 chnl=0
|
||||
char id=209 x=180 y=126 width=43 height=59 xoffset=3 yoffset=-7 xadvance=50 page=0 chnl=0
|
||||
char id=210 x=97 y=0 width=50 height=61 xoffset=1 yoffset=-8 xadvance=52 page=0 chnl=0
|
||||
char id=211 x=147 y=0 width=50 height=61 xoffset=1 yoffset=-8 xadvance=52 page=0 chnl=0
|
||||
char id=212 x=197 y=0 width=50 height=61 xoffset=1 yoffset=-8 xadvance=52 page=0 chnl=0
|
||||
char id=213 x=247 y=0 width=50 height=61 xoffset=1 yoffset=-8 xadvance=52 page=0 chnl=0
|
||||
char id=214 x=177 y=66 width=50 height=60 xoffset=1 yoffset=-7 xadvance=52 page=0 chnl=0
|
||||
char id=215 x=0 y=479 width=30 height=30 xoffset=3 yoffset=15 xadvance=36 page=0 chnl=0
|
||||
char id=216 x=128 y=338 width=50 height=48 xoffset=1 yoffset=5 xadvance=52 page=0 chnl=0
|
||||
char id=217 x=297 y=0 width=43 height=61 xoffset=3 yoffset=-8 xadvance=48 page=0 chnl=0
|
||||
char id=218 x=340 y=0 width=43 height=61 xoffset=3 yoffset=-8 xadvance=48 page=0 chnl=0
|
||||
char id=219 x=227 y=66 width=43 height=60 xoffset=3 yoffset=-7 xadvance=48 page=0 chnl=0
|
||||
char id=220 x=270 y=66 width=43 height=60 xoffset=3 yoffset=-7 xadvance=48 page=0 chnl=0
|
||||
char id=221 x=313 y=66 width=47 height=60 xoffset=-1 yoffset=-8 xadvance=44 page=0 chnl=0
|
||||
char id=223 x=178 y=338 width=36 height=48 xoffset=3 yoffset=4 xadvance=39 page=0 chnl=0
|
||||
char id=224 x=101 y=185 width=34 height=50 xoffset=1 yoffset=3 xadvance=37 page=0 chnl=0
|
||||
char id=225 x=135 y=185 width=34 height=50 xoffset=1 yoffset=3 xadvance=37 page=0 chnl=0
|
||||
char id=226 x=169 y=185 width=34 height=50 xoffset=1 yoffset=3 xadvance=37 page=0 chnl=0
|
||||
char id=227 x=203 y=185 width=34 height=50 xoffset=1 yoffset=3 xadvance=37 page=0 chnl=0
|
||||
char id=228 x=152 y=240 width=34 height=49 xoffset=1 yoffset=4 xadvance=37 page=0 chnl=0
|
||||
char id=229 x=0 y=185 width=34 height=55 xoffset=1 yoffset=-2 xadvance=37 page=0 chnl=0
|
||||
char id=231 x=186 y=240 width=34 height=49 xoffset=1 yoffset=16 xadvance=35 page=0 chnl=0
|
||||
char id=232 x=237 y=185 width=36 height=50 xoffset=1 yoffset=3 xadvance=38 page=0 chnl=0
|
||||
char id=233 x=273 y=185 width=36 height=50 xoffset=1 yoffset=3 xadvance=38 page=0 chnl=0
|
||||
char id=234 x=309 y=185 width=36 height=50 xoffset=1 yoffset=3 xadvance=38 page=0 chnl=0
|
||||
char id=235 x=220 y=240 width=36 height=49 xoffset=1 yoffset=4 xadvance=38 page=0 chnl=0
|
||||
char id=236 x=256 y=240 width=23 height=49 xoffset=-5 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=237 x=279 y=240 width=24 height=49 xoffset=3 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=238 x=214 y=338 width=27 height=48 xoffset=-3 yoffset=4 xadvance=17 page=0 chnl=0
|
||||
char id=239 x=241 y=338 width=29 height=48 xoffset=-4 yoffset=4 xadvance=17 page=0 chnl=0
|
||||
char id=240 x=345 y=185 width=38 height=50 xoffset=1 yoffset=3 xadvance=40 page=0 chnl=0
|
||||
char id=241 x=270 y=338 width=35 height=48 xoffset=2 yoffset=4 xadvance=39 page=0 chnl=0
|
||||
char id=242 x=383 y=185 width=39 height=50 xoffset=1 yoffset=3 xadvance=41 page=0 chnl=0
|
||||
char id=243 x=422 y=185 width=39 height=50 xoffset=1 yoffset=3 xadvance=41 page=0 chnl=0
|
||||
char id=244 x=461 y=185 width=39 height=50 xoffset=1 yoffset=3 xadvance=41 page=0 chnl=0
|
||||
char id=245 x=0 y=240 width=39 height=50 xoffset=1 yoffset=3 xadvance=41 page=0 chnl=0
|
||||
char id=246 x=303 y=240 width=39 height=49 xoffset=1 yoffset=4 xadvance=41 page=0 chnl=0
|
||||
char id=249 x=39 y=240 width=35 height=50 xoffset=2 yoffset=3 xadvance=39 page=0 chnl=0
|
||||
char id=250 x=74 y=240 width=35 height=50 xoffset=2 yoffset=3 xadvance=39 page=0 chnl=0
|
||||
char id=251 x=342 y=240 width=35 height=49 xoffset=2 yoffset=4 xadvance=39 page=0 chnl=0
|
||||
char id=252 x=377 y=240 width=35 height=49 xoffset=2 yoffset=4 xadvance=39 page=0 chnl=0
|
||||
char id=253 x=383 y=0 width=40 height=61 xoffset=-1 yoffset=3 xadvance=38 page=0 chnl=0
|
||||
char id=254 x=223 y=126 width=38 height=59 xoffset=3 yoffset=5 xadvance=42 page=0 chnl=0
|
||||
char id=255 x=360 y=66 width=40 height=60 xoffset=-1 yoffset=4 xadvance=38 page=0 chnl=0
|
||||
char id=35 x=125 y=0 width=43 height=46 xoffset=0 yoffset=6 xadvance=43 page=1 chnl=0
|
||||
char id=43 x=70 y=83 width=34 height=34 xoffset=2 yoffset=13 xadvance=39 page=1 chnl=0
|
||||
char id=49 x=25 y=0 width=24 height=46 xoffset=-1 yoffset=6 xadvance=27 page=1 chnl=0
|
||||
char id=52 x=49 y=0 width=41 height=46 xoffset=0 yoffset=6 xadvance=41 page=1 chnl=0
|
||||
char id=55 x=90 y=0 width=35 height=46 xoffset=2 yoffset=6 xadvance=39 page=1 chnl=0
|
||||
char id=60 x=349 y=46 width=33 height=36 xoffset=2 yoffset=11 xadvance=38 page=1 chnl=0
|
||||
char id=62 x=382 y=46 width=33 height=36 xoffset=3 yoffset=11 xadvance=38 page=1 chnl=0
|
||||
char id=97 x=406 y=0 width=34 height=37 xoffset=1 yoffset=16 xadvance=37 page=1 chnl=0
|
||||
char id=99 x=440 y=0 width=34 height=37 xoffset=1 yoffset=16 xadvance=35 page=1 chnl=0
|
||||
char id=101 x=474 y=0 width=36 height=37 xoffset=1 yoffset=16 xadvance=38 page=1 chnl=0
|
||||
char id=109 x=201 y=46 width=55 height=36 xoffset=2 yoffset=16 xadvance=59 page=1 chnl=0
|
||||
char id=110 x=256 y=46 width=35 height=36 xoffset=2 yoffset=16 xadvance=39 page=1 chnl=0
|
||||
char id=111 x=0 y=46 width=39 height=37 xoffset=1 yoffset=16 xadvance=41 page=1 chnl=0
|
||||
char id=114 x=291 y=46 width=23 height=36 xoffset=3 yoffset=16 xadvance=26 page=1 chnl=0
|
||||
char id=115 x=39 y=46 width=31 height=37 xoffset=0 yoffset=16 xadvance=32 page=1 chnl=0
|
||||
char id=116 x=0 y=0 width=25 height=46 xoffset=0 yoffset=7 xadvance=25 page=1 chnl=0
|
||||
char id=117 x=314 y=46 width=35 height=36 xoffset=2 yoffset=17 xadvance=39 page=1 chnl=0
|
||||
char id=118 x=415 y=46 width=40 height=35 xoffset=-1 yoffset=17 xadvance=39 page=1 chnl=0
|
||||
char id=119 x=455 y=46 width=54 height=35 xoffset=0 yoffset=17 xadvance=54 page=1 chnl=0
|
||||
char id=120 x=0 y=83 width=38 height=35 xoffset=-1 yoffset=17 xadvance=36 page=1 chnl=0
|
||||
char id=122 x=38 y=83 width=32 height=35 xoffset=1 yoffset=17 xadvance=34 page=1 chnl=0
|
||||
char id=162 x=168 y=0 width=34 height=46 xoffset=1 yoffset=12 xadvance=35 page=1 chnl=0
|
||||
char id=165 x=202 y=0 width=47 height=46 xoffset=-1 yoffset=6 xadvance=44 page=1 chnl=0
|
||||
char id=198 x=249 y=0 width=69 height=46 xoffset=-1 yoffset=6 xadvance=69 page=1 chnl=0
|
||||
char id=208 x=318 y=0 width=49 height=46 xoffset=1 yoffset=6 xadvance=51 page=1 chnl=0
|
||||
char id=222 x=367 y=0 width=39 height=46 xoffset=3 yoffset=6 xadvance=42 page=1 chnl=0
|
||||
char id=230 x=70 y=46 width=58 height=37 xoffset=1 yoffset=16 xadvance=60 page=1 chnl=0
|
||||
char id=247 x=128 y=46 width=34 height=37 xoffset=2 yoffset=11 xadvance=39 page=1 chnl=0
|
||||
char id=248 x=162 y=46 width=39 height=37 xoffset=1 yoffset=16 xadvance=41 page=1 chnl=0
|
||||
kernings count=405
|
||||
kerning first=244 second=119 amount=-2
|
||||
kerning first=86 second=225 amount=-2
|
||||
kerning first=119 second=245 amount=-2
|
||||
kerning first=253 second=243 amount=-2
|
||||
kerning first=111 second=89 amount=-7
|
||||
kerning first=248 second=86 amount=-4
|
||||
kerning first=67 second=79 amount=-1
|
||||
kerning first=84 second=211 amount=-2
|
||||
kerning first=99 second=242 amount=-1
|
||||
kerning first=84 second=194 amount=-3
|
||||
kerning first=221 second=235 amount=-7
|
||||
kerning first=192 second=221 amount=-2
|
||||
kerning first=121 second=44 amount=-6
|
||||
kerning first=65 second=89 amount=-2
|
||||
kerning first=75 second=216 amount=-2
|
||||
kerning first=87 second=248 amount=-3
|
||||
kerning first=89 second=242 amount=-7
|
||||
kerning first=221 second=111 amount=-7
|
||||
kerning first=233 second=221 amount=-7
|
||||
kerning first=246 second=255 amount=-1
|
||||
kerning first=75 second=195 amount=2
|
||||
kerning first=234 second=86 amount=-4
|
||||
kerning first=121 second=233 amount=-1
|
||||
kerning first=119 second=235 amount=-2
|
||||
kerning first=111 second=119 amount=-2
|
||||
kerning first=76 second=86 amount=-7
|
||||
kerning first=246 second=221 amount=-7
|
||||
kerning first=86 second=198 amount=-6
|
||||
kerning first=118 second=242 amount=-2
|
||||
kerning first=89 second=194 amount=-1
|
||||
kerning first=92 second=92 amount=-8
|
||||
kerning first=199 second=211 amount=-1
|
||||
kerning first=119 second=111 amount=-2
|
||||
kerning first=114 second=242 amount=-1
|
||||
kerning first=86 second=232 amount=-4
|
||||
kerning first=194 second=221 amount=-2
|
||||
kerning first=89 second=101 amount=-7
|
||||
kerning first=87 second=234 amount=-3
|
||||
kerning first=192 second=86 amount=-6
|
||||
kerning first=86 second=246 amount=-4
|
||||
kerning first=243 second=255 amount=-1
|
||||
kerning first=248 second=121 amount=-1
|
||||
kerning first=233 second=86 amount=-4
|
||||
kerning first=197 second=84 amount=-4
|
||||
kerning first=92 second=47 amount=5
|
||||
kerning first=75 second=193 amount=2
|
||||
kerning first=118 second=101 amount=-2
|
||||
kerning first=243 second=221 amount=-7
|
||||
kerning first=245 second=87 amount=-3
|
||||
kerning first=246 second=86 amount=-4
|
||||
kerning first=84 second=197 amount=-3
|
||||
kerning first=121 second=46 amount=-6
|
||||
kerning first=193 second=221 amount=-2
|
||||
kerning first=197 second=87 amount=-3
|
||||
kerning first=194 second=86 amount=-6
|
||||
kerning first=89 second=99 amount=-4
|
||||
kerning first=87 second=231 amount=-3
|
||||
kerning first=235 second=87 amount=-3
|
||||
kerning first=195 second=71 amount=-1
|
||||
kerning first=99 second=89 amount=-7
|
||||
kerning first=89 second=197 amount=-1
|
||||
kerning first=121 second=244 amount=-2
|
||||
kerning first=221 second=196 amount=-1
|
||||
kerning first=243 second=86 amount=-4
|
||||
kerning first=68 second=196 amount=-3
|
||||
kerning first=99 second=243 amount=-1
|
||||
kerning first=84 second=65 amount=-3
|
||||
kerning first=221 second=233 amount=-7
|
||||
kerning first=86 second=245 amount=-4
|
||||
kerning first=119 second=44 amount=-5
|
||||
kerning first=75 second=210 amount=-2
|
||||
kerning first=89 second=243 amount=-7
|
||||
kerning first=193 second=86 amount=-6
|
||||
kerning first=87 second=242 amount=-3
|
||||
kerning first=231 second=221 amount=-7
|
||||
kerning first=246 second=121 amount=-1
|
||||
kerning first=245 second=253 amount=-1
|
||||
kerning first=230 second=89 amount=-7
|
||||
kerning first=255 second=232 amount=-1
|
||||
kerning first=196 second=84 amount=-4
|
||||
kerning first=119 second=233 amount=-2
|
||||
kerning first=255 second=246 amount=-2
|
||||
kerning first=89 second=65 amount=-1
|
||||
kerning first=118 second=243 amount=-2
|
||||
kerning first=242 second=87 amount=-3
|
||||
kerning first=87 second=194 amount=-3
|
||||
kerning first=231 second=244 amount=-1
|
||||
kerning first=118 second=228 amount=-2
|
||||
kerning first=114 second=243 amount=-1
|
||||
kerning first=86 second=235 amount=-4
|
||||
kerning first=87 second=101 amount=-3
|
||||
kerning first=196 second=87 amount=-3
|
||||
kerning first=230 second=119 amount=-2
|
||||
kerning first=213 second=87 amount=-1
|
||||
kerning first=86 second=111 amount=-4
|
||||
kerning first=101 second=87 amount=-3
|
||||
kerning first=243 second=121 amount=-1
|
||||
kerning first=192 second=71 amount=-1
|
||||
kerning first=245 second=118 amount=-2
|
||||
kerning first=231 second=86 amount=-4
|
||||
kerning first=253 second=232 amount=-1
|
||||
kerning first=121 second=248 amount=-2
|
||||
kerning first=86 second=226 amount=-2
|
||||
kerning first=221 second=195 amount=-1
|
||||
kerning first=253 second=246 amount=-2
|
||||
kerning first=67 second=212 amount=-1
|
||||
kerning first=68 second=195 amount=-3
|
||||
kerning first=84 second=214 amount=-2
|
||||
kerning first=244 second=87 amount=-3
|
||||
kerning first=84 second=192 amount=-3
|
||||
kerning first=232 second=89 amount=-7
|
||||
kerning first=119 second=46 amount=-5
|
||||
kerning first=221 second=244 amount=-7
|
||||
kerning first=47 second=47 amount=-8
|
||||
kerning first=87 second=99 amount=-3
|
||||
kerning first=242 second=253 amount=-1
|
||||
kerning first=121 second=234 amount=-1
|
||||
kerning first=194 second=71 amount=-1
|
||||
kerning first=65 second=84 amount=-4
|
||||
kerning first=255 second=245 amount=-2
|
||||
kerning first=89 second=192 amount=-1
|
||||
kerning first=87 second=197 amount=-3
|
||||
kerning first=199 second=214 amount=-1
|
||||
kerning first=119 second=244 amount=-2
|
||||
kerning first=221 second=193 amount=-1
|
||||
kerning first=231 second=248 amount=-1
|
||||
kerning first=111 second=87 amount=-3
|
||||
kerning first=118 second=227 amount=-2
|
||||
kerning first=68 second=193 amount=-3
|
||||
kerning first=195 second=89 amount=-2
|
||||
kerning first=232 second=119 amount=-2
|
||||
kerning first=82 second=89 amount=-2
|
||||
kerning first=65 second=87 amount=-3
|
||||
kerning first=75 second=211 amount=-2
|
||||
kerning first=87 second=243 amount=-3
|
||||
kerning first=210 second=87 amount=-1
|
||||
kerning first=244 second=253 amount=-1
|
||||
kerning first=86 second=229 amount=-2
|
||||
kerning first=75 second=194 amount=2
|
||||
kerning first=193 second=71 amount=-1
|
||||
kerning first=242 second=118 amount=-2
|
||||
kerning first=253 second=245 amount=-2
|
||||
kerning first=255 second=235 amount=-1
|
||||
kerning first=248 second=89 amount=-7
|
||||
kerning first=255 second=111 amount=-2
|
||||
kerning first=67 second=216 amount=-1
|
||||
kerning first=84 second=213 amount=-2
|
||||
kerning first=86 second=196 amount=-6
|
||||
kerning first=84 second=198 amount=-3
|
||||
kerning first=87 second=65 amount=-3
|
||||
kerning first=118 second=225 amount=-2
|
||||
kerning first=221 second=248 amount=-7
|
||||
kerning first=86 second=233 amount=-4
|
||||
kerning first=234 second=89 amount=-7
|
||||
kerning first=212 second=87 amount=-1
|
||||
kerning first=111 second=253 amount=-1
|
||||
kerning first=248 second=119 amount=-2
|
||||
kerning first=253 second=235 amount=-1
|
||||
kerning first=244 second=118 amount=-2
|
||||
kerning first=89 second=198 amount=-1
|
||||
kerning first=199 second=213 amount=-1
|
||||
kerning first=121 second=242 amount=-2
|
||||
kerning first=86 second=97 amount=-2
|
||||
kerning first=119 second=248 amount=-2
|
||||
kerning first=253 second=111 amount=-2
|
||||
kerning first=84 second=79 amount=-2
|
||||
kerning first=99 second=246 amount=-1
|
||||
kerning first=89 second=232 amount=-7
|
||||
kerning first=221 second=234 amount=-7
|
||||
kerning first=192 second=89 amount=-2
|
||||
kerning first=234 second=119 amount=-2
|
||||
kerning first=89 second=246 amount=-7
|
||||
kerning first=233 second=89 amount=-7
|
||||
kerning first=245 second=255 amount=-1
|
||||
kerning first=75 second=197 amount=2
|
||||
kerning first=79 second=87 amount=-1
|
||||
kerning first=118 second=232 amount=-2
|
||||
kerning first=121 second=101 amount=-1
|
||||
kerning first=119 second=234 amount=-2
|
||||
kerning first=99 second=87 amount=-3
|
||||
kerning first=245 second=221 amount=-7
|
||||
kerning first=111 second=118 amount=-2
|
||||
kerning first=246 second=89 amount=-7
|
||||
kerning first=86 second=195 amount=-6
|
||||
kerning first=118 second=246 amount=-2
|
||||
kerning first=87 second=192 amount=-3
|
||||
kerning first=199 second=79 amount=-1
|
||||
kerning first=231 second=242 amount=-1
|
||||
kerning first=197 second=221 amount=-2
|
||||
kerning first=114 second=246 amount=-1
|
||||
kerning first=194 second=89 amount=-2
|
||||
kerning first=233 second=119 amount=-2
|
||||
kerning first=255 second=44 amount=-6
|
||||
kerning first=221 second=231 amount=-4
|
||||
kerning first=235 second=221 amount=-7
|
||||
kerning first=86 second=244 amount=-4
|
||||
kerning first=255 second=233 amount=-1
|
||||
kerning first=86 second=224 amount=-2
|
||||
kerning first=246 second=119 amount=-2
|
||||
kerning first=75 second=65 amount=2
|
||||
kerning first=230 second=87 amount=-3
|
||||
kerning first=243 second=89 amount=-7
|
||||
kerning first=67 second=210 amount=-1
|
||||
kerning first=245 second=86 amount=-4
|
||||
kerning first=99 second=245 amount=-1
|
||||
kerning first=86 second=193 amount=-6
|
||||
kerning first=89 second=245 amount=-7
|
||||
kerning first=193 second=89 amount=-2
|
||||
kerning first=253 second=44 amount=-6
|
||||
kerning first=197 second=86 amount=-6
|
||||
kerning first=221 second=242 amount=-7
|
||||
kerning first=242 second=255 amount=-1
|
||||
kerning first=235 second=86 amount=-4
|
||||
kerning first=253 second=233 amount=-1
|
||||
kerning first=243 second=119 amount=-2
|
||||
kerning first=118 second=245 amount=-2
|
||||
kerning first=242 second=221 amount=-7
|
||||
kerning first=87 second=198 amount=-3
|
||||
kerning first=121 second=243 amount=-2
|
||||
kerning first=221 second=194 amount=-1
|
||||
kerning first=119 second=242 amount=-2
|
||||
kerning first=68 second=194 amount=-3
|
||||
kerning first=114 second=245 amount=-1
|
||||
kerning first=99 second=111 amount=-1
|
||||
kerning first=255 second=46 amount=-6
|
||||
kerning first=89 second=235 amount=-7
|
||||
kerning first=87 second=232 amount=-3
|
||||
kerning first=196 second=221 amount=-2
|
||||
kerning first=221 second=101 amount=-7
|
||||
kerning first=86 second=248 amount=-4
|
||||
kerning first=75 second=214 amount=-2
|
||||
kerning first=101 second=221 amount=-7
|
||||
kerning first=89 second=111 amount=-7
|
||||
kerning first=87 second=246 amount=-3
|
||||
kerning first=232 second=87 amount=-3
|
||||
kerning first=244 second=255 amount=-1
|
||||
kerning first=231 second=89 amount=-7
|
||||
kerning first=245 second=121 amount=-1
|
||||
kerning first=195 second=84 amount=-4
|
||||
kerning first=86 second=230 amount=-2
|
||||
kerning first=75 second=192 amount=2
|
||||
kerning first=118 second=235 amount=-2
|
||||
kerning first=119 second=101 amount=-2
|
||||
kerning first=255 second=244 amount=-2
|
||||
kerning first=244 second=221 amount=-7
|
||||
kerning first=118 second=111 amount=-2
|
||||
kerning first=242 second=86 amount=-4
|
||||
kerning first=231 second=243 amount=-1
|
||||
kerning first=118 second=226 amount=-2
|
||||
kerning first=253 second=46 amount=-6
|
||||
kerning first=114 second=111 amount=-1
|
||||
kerning first=86 second=234 amount=-4
|
||||
kerning first=195 second=87 amount=-3
|
||||
kerning first=196 second=86 amount=-6
|
||||
kerning first=221 second=99 amount=-4
|
||||
kerning first=111 second=255 amount=-1
|
||||
kerning first=101 second=86 amount=-4
|
||||
kerning first=221 second=197 amount=-1
|
||||
kerning first=253 second=244 amount=-2
|
||||
kerning first=118 second=100 amount=-1
|
||||
kerning first=111 second=221 amount=-7
|
||||
kerning first=248 second=87 amount=-3
|
||||
kerning first=67 second=211 amount=-1
|
||||
kerning first=68 second=197 amount=-3
|
||||
kerning first=84 second=212 amount=-2
|
||||
kerning first=244 second=86 amount=-4
|
||||
kerning first=76 second=84 amount=-1
|
||||
kerning first=84 second=196 amount=-3
|
||||
kerning first=65 second=221 amount=-2
|
||||
kerning first=75 second=213 amount=-2
|
||||
kerning first=87 second=245 amount=-3
|
||||
kerning first=221 second=243 amount=-7
|
||||
kerning first=86 second=231 amount=-4
|
||||
kerning first=75 second=198 amount=2
|
||||
kerning first=242 second=121 amount=-1
|
||||
kerning first=234 second=87 amount=-3
|
||||
kerning first=197 second=71 amount=-1
|
||||
kerning first=192 second=84 amount=-4
|
||||
kerning first=255 second=248 amount=-2
|
||||
kerning first=89 second=196 amount=-1
|
||||
kerning first=199 second=212 amount=-1
|
||||
kerning first=221 second=65 amount=-1
|
||||
kerning first=119 second=243 amount=-2
|
||||
kerning first=118 second=229 amount=-2
|
||||
kerning first=111 second=86 amount=-4
|
||||
kerning first=68 second=65 amount=-3
|
||||
kerning first=89 second=233 amount=-7
|
||||
kerning first=87 second=235 amount=-3
|
||||
kerning first=192 second=87 amount=-3
|
||||
kerning first=118 second=44 amount=-6
|
||||
kerning first=65 second=86 amount=-6
|
||||
kerning first=86 second=242 amount=-4
|
||||
kerning first=75 second=79 amount=-2
|
||||
kerning first=87 second=111 amount=-3
|
||||
kerning first=233 second=87 amount=-3
|
||||
kerning first=244 second=121 amount=-1
|
||||
kerning first=248 second=253 amount=-1
|
||||
kerning first=194 second=84 amount=-4
|
||||
kerning first=253 second=248 amount=-2
|
||||
kerning first=118 second=233 amount=-2
|
||||
kerning first=255 second=234 amount=-1
|
||||
kerning first=84 second=216 amount=-2
|
||||
kerning first=246 second=87 amount=-3
|
||||
kerning first=86 second=194 amount=-6
|
||||
kerning first=84 second=195 amount=-3
|
||||
kerning first=118 second=97 amount=-2
|
||||
kerning first=86 second=101 amount=-4
|
||||
kerning first=194 second=87 amount=-3
|
||||
kerning first=216 second=87 amount=-1
|
||||
kerning first=99 second=221 amount=-7
|
||||
kerning first=121 second=232 amount=-1
|
||||
kerning first=111 second=121 amount=-1
|
||||
kerning first=253 second=234 amount=-1
|
||||
kerning first=196 second=71 amount=-1
|
||||
kerning first=248 second=118 amount=-2
|
||||
kerning first=193 second=84 amount=-4
|
||||
kerning first=89 second=195 amount=-1
|
||||
kerning first=199 second=216 amount=-1
|
||||
kerning first=121 second=246 amount=-2
|
||||
kerning first=221 second=192 amount=-1
|
||||
kerning first=243 second=87 amount=-3
|
||||
kerning first=68 second=192 amount=-3
|
||||
kerning first=99 second=244 amount=-1
|
||||
kerning first=84 second=193 amount=-3
|
||||
kerning first=118 second=46 amount=-6
|
||||
kerning first=89 second=244 amount=-7
|
||||
kerning first=193 second=87 amount=-3
|
||||
kerning first=246 second=253 amount=-1
|
||||
kerning first=86 second=99 amount=-4
|
||||
kerning first=230 second=221 amount=-7
|
||||
kerning first=99 second=86 amount=-4
|
||||
kerning first=255 second=242 amount=-2
|
||||
kerning first=245 second=89 amount=-7
|
||||
kerning first=86 second=197 amount=-6
|
||||
kerning first=89 second=193 amount=-1
|
||||
kerning first=87 second=196 amount=-3
|
||||
kerning first=118 second=244 amount=-2
|
||||
kerning first=231 second=246 amount=-1
|
||||
kerning first=118 second=224 amount=-2
|
||||
kerning first=197 second=89 amount=-2
|
||||
kerning first=114 second=244 amount=-1
|
||||
kerning first=87 second=233 amount=-3
|
||||
kerning first=235 second=89 amount=-7
|
||||
kerning first=86 second=243 amount=-4
|
||||
kerning first=214 second=87 amount=-1
|
||||
kerning first=245 second=119 amount=-2
|
||||
kerning first=243 second=253 amount=-1
|
||||
kerning first=231 second=87 amount=-3
|
||||
kerning first=255 second=101 amount=-1
|
||||
kerning first=246 second=118 amount=-2
|
||||
kerning first=65 second=71 amount=-1
|
||||
kerning first=86 second=228 amount=-2
|
||||
kerning first=221 second=198 amount=-1
|
||||
kerning first=121 second=245 amount=-2
|
||||
kerning first=230 second=86 amount=-4
|
||||
kerning first=253 second=242 amount=-2
|
||||
kerning first=67 second=214 amount=-1
|
||||
kerning first=68 second=198 amount=-3
|
||||
kerning first=84 second=210 amount=-2
|
||||
kerning first=99 second=248 amount=-1
|
||||
kerning first=86 second=65 amount=-6
|
||||
kerning first=221 second=232 amount=-7
|
||||
kerning first=235 second=119 amount=-2
|
||||
kerning first=89 second=248 amount=-7
|
||||
kerning first=232 second=221 amount=-7
|
||||
kerning first=221 second=246 amount=-7
|
||||
kerning first=211 second=87 amount=-1
|
||||
kerning first=121 second=235 amount=-1
|
||||
kerning first=119 second=232 amount=-2
|
||||
kerning first=253 second=101 amount=-1
|
||||
kerning first=243 second=118 amount=-2
|
||||
kerning first=118 second=248 amount=-2
|
||||
kerning first=242 second=89 amount=-7
|
||||
kerning first=87 second=195 amount=-3
|
||||
kerning first=199 second=210 amount=-1
|
||||
kerning first=121 second=111 amount=-2
|
||||
kerning first=119 second=246 amount=-2
|
||||
kerning first=231 second=245 amount=-1
|
||||
kerning first=118 second=230 amount=-2
|
||||
kerning first=114 second=248 amount=-1
|
||||
kerning first=195 second=221 amount=-2
|
||||
kerning first=89 second=234 amount=-7
|
||||
kerning first=196 second=89 amount=-2
|
||||
kerning first=82 second=221 amount=-2
|
||||
kerning first=75 second=212 amount=-2
|
||||
kerning first=101 second=89 amount=-7
|
||||
kerning first=87 second=244 amount=-3
|
||||
kerning first=232 second=86 amount=-4
|
||||
kerning first=248 second=255 amount=-1
|
||||
kerning first=86 second=227 amount=-2
|
||||
kerning first=75 second=196 amount=2
|
||||
kerning first=242 second=119 amount=-2
|
||||
kerning first=118 second=234 amount=-2
|
||||
kerning first=248 second=221 amount=-7
|
||||
kerning first=255 second=243 amount=-2
|
||||
kerning first=67 second=213 amount=-1
|
||||
kerning first=244 second=89 amount=-7
|
||||
kerning first=86 second=192 amount=-6
|
||||
kerning first=87 second=193 amount=-3
|
||||
kerning first=231 second=111 amount=-1
|
||||
kerning first=221 second=245 amount=-7
|
||||
kerning first=101 second=119 amount=-2
|
||||
kerning first=195 second=86 amount=-6
|
||||
kerning first=89 second=231 amount=-4
|
||||
kerning first=234 second=221 amount=-7
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,550 @@
|
||||
info face="Metropolis-Regular" size=32 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=-2,-2
|
||||
common lineHeight=33 base=26 scaleW=512 scaleH=512 pages=1 packed=0
|
||||
page id=0 file="Metropolis-Regular-32.png"
|
||||
chars count=170
|
||||
char id=0 x=0 y=0 width=14 height=35 xoffset=1 yoffset=-1 xadvance=16 page=0 chnl=0
|
||||
char id=10 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=0 page=0 chnl=0
|
||||
char id=32 x=0 y=0 width=0 height=0 xoffset=-1 yoffset=0 xadvance=9 page=0 chnl=0
|
||||
char id=33 x=494 y=91 width=7 height=24 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
|
||||
char id=34 x=353 y=139 width=11 height=10 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=35 x=158 y=115 width=22 height=24 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=36 x=212 y=35 width=19 height=29 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=37 x=133 y=115 width=25 height=24 xoffset=0 yoffset=3 xadvance=25 page=0 chnl=0
|
||||
char id=38 x=180 y=115 width=22 height=24 xoffset=0 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=39 x=364 y=139 width=6 height=10 xoffset=1 yoffset=3 xadvance=7 page=0 chnl=0
|
||||
char id=40 x=449 y=0 width=11 height=30 xoffset=0 yoffset=3 xadvance=11 page=0 chnl=0
|
||||
char id=41 x=460 y=0 width=11 height=30 xoffset=0 yoffset=3 xadvance=11 page=0 chnl=0
|
||||
char id=42 x=308 y=139 width=13 height=14 xoffset=0 yoffset=2 xadvance=13 page=0 chnl=0
|
||||
char id=43 x=259 y=139 width=17 height=18 xoffset=1 yoffset=6 xadvance=19 page=0 chnl=0
|
||||
char id=44 x=370 y=139 width=7 height=10 xoffset=1 yoffset=21 xadvance=8 page=0 chnl=0
|
||||
char id=45 x=423 y=139 width=12 height=5 xoffset=0 yoffset=15 xadvance=12 page=0 chnl=0
|
||||
char id=46 x=417 y=139 width=6 height=6 xoffset=1 yoffset=21 xadvance=8 page=0 chnl=0
|
||||
char id=47 x=178 y=35 width=17 height=29 xoffset=-2 yoffset=1 xadvance=13 page=0 chnl=0
|
||||
char id=48 x=94 y=115 width=22 height=24 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=49 x=427 y=91 width=11 height=24 xoffset=-1 yoffset=3 xadvance=11 page=0 chnl=0
|
||||
char id=50 x=438 y=91 width=18 height=24 xoffset=0 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=51 x=456 y=91 width=18 height=24 xoffset=0 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=52 x=474 y=91 width=20 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=53 x=0 y=115 width=19 height=24 xoffset=0 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=54 x=19 y=115 width=19 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=55 x=38 y=115 width=18 height=24 xoffset=0 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=56 x=56 y=115 width=19 height=24 xoffset=0 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=57 x=75 y=115 width=19 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=58 x=504 y=115 width=6 height=19 xoffset=1 yoffset=8 xadvance=8 page=0 chnl=0
|
||||
char id=59 x=501 y=91 width=7 height=23 xoffset=1 yoffset=8 xadvance=8 page=0 chnl=0
|
||||
char id=60 x=173 y=139 width=18 height=19 xoffset=0 yoffset=6 xadvance=19 page=0 chnl=0
|
||||
char id=61 x=321 y=139 width=17 height=12 xoffset=1 yoffset=9 xadvance=19 page=0 chnl=0
|
||||
char id=62 x=191 y=139 width=17 height=19 xoffset=1 yoffset=6 xadvance=19 page=0 chnl=0
|
||||
char id=63 x=116 y=115 width=17 height=24 xoffset=-1 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=64 x=257 y=35 width=28 height=28 xoffset=0 yoffset=3 xadvance=28 page=0 chnl=0
|
||||
char id=65 x=312 y=65 width=25 height=24 xoffset=0 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=66 x=337 y=65 width=19 height=24 xoffset=2 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=67 x=356 y=65 width=22 height=24 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=68 x=378 y=65 width=22 height=24 xoffset=2 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=69 x=400 y=65 width=19 height=24 xoffset=1 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=70 x=419 y=65 width=19 height=24 xoffset=1 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=71 x=438 y=65 width=23 height=24 xoffset=0 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=72 x=461 y=65 width=21 height=24 xoffset=1 yoffset=3 xadvance=23 page=0 chnl=0
|
||||
char id=73 x=482 y=65 width=6 height=24 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0
|
||||
char id=74 x=488 y=65 width=17 height=24 xoffset=-1 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=75 x=0 y=91 width=21 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=76 x=21 y=91 width=17 height=24 xoffset=1 yoffset=3 xadvance=18 page=0 chnl=0
|
||||
char id=77 x=38 y=91 width=24 height=24 xoffset=2 yoffset=3 xadvance=27 page=0 chnl=0
|
||||
char id=78 x=62 y=91 width=21 height=24 xoffset=2 yoffset=3 xadvance=25 page=0 chnl=0
|
||||
char id=79 x=83 y=91 width=26 height=24 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=80 x=109 y=91 width=20 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=81 x=129 y=91 width=26 height=24 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=82 x=155 y=91 width=20 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=83 x=17 y=65 width=19 height=25 xoffset=0 yoffset=2 xadvance=20 page=0 chnl=0
|
||||
char id=84 x=175 y=91 width=20 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=85 x=195 y=91 width=22 height=24 xoffset=1 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=86 x=217 y=91 width=25 height=24 xoffset=0 yoffset=3 xadvance=24 page=0 chnl=0
|
||||
char id=87 x=242 y=91 width=35 height=24 xoffset=0 yoffset=3 xadvance=35 page=0 chnl=0
|
||||
char id=88 x=277 y=91 width=23 height=24 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=89 x=300 y=91 width=23 height=24 xoffset=-1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=90 x=323 y=91 width=20 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=91 x=471 y=0 width=11 height=30 xoffset=1 yoffset=1 xadvance=12 page=0 chnl=0
|
||||
char id=92 x=195 y=35 width=17 height=29 xoffset=-2 yoffset=1 xadvance=13 page=0 chnl=0
|
||||
char id=93 x=482 y=0 width=11 height=30 xoffset=0 yoffset=1 xadvance=12 page=0 chnl=0
|
||||
char id=94 x=338 y=139 width=15 height=11 xoffset=0 yoffset=3 xadvance=16 page=0 chnl=0
|
||||
char id=95 x=435 y=139 width=22 height=5 xoffset=-2 yoffset=27 xadvance=19 page=0 chnl=0
|
||||
char id=96 x=385 y=139 width=9 height=7 xoffset=1 yoffset=2 xadvance=10 page=0 chnl=0
|
||||
char id=97 x=433 y=115 width=17 height=19 xoffset=0 yoffset=8 xadvance=18 page=0 chnl=0
|
||||
char id=98 x=343 y=91 width=19 height=24 xoffset=1 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=99 x=450 y=115 width=18 height=19 xoffset=0 yoffset=8 xadvance=17 page=0 chnl=0
|
||||
char id=100 x=362 y=91 width=19 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=101 x=468 y=115 width=19 height=19 xoffset=0 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=102 x=36 y=65 width=12 height=25 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=0
|
||||
char id=103 x=48 y=65 width=19 height=25 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=104 x=381 y=91 width=17 height=24 xoffset=1 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=105 x=302 y=35 width=6 height=26 xoffset=1 yoffset=1 xadvance=7 page=0 chnl=0
|
||||
char id=106 x=39 y=0 width=11 height=32 xoffset=-3 yoffset=1 xadvance=7 page=0 chnl=0
|
||||
char id=107 x=398 y=91 width=17 height=24 xoffset=1 yoffset=3 xadvance=17 page=0 chnl=0
|
||||
char id=108 x=505 y=65 width=5 height=24 xoffset=1 yoffset=3 xadvance=7 page=0 chnl=0
|
||||
char id=109 x=0 y=139 width=27 height=19 xoffset=1 yoffset=8 xadvance=29 page=0 chnl=0
|
||||
char id=110 x=487 y=115 width=17 height=19 xoffset=1 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=111 x=27 y=139 width=20 height=19 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=112 x=67 y=65 width=19 height=25 xoffset=1 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=113 x=86 y=65 width=19 height=25 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=114 x=47 y=139 width=12 height=19 xoffset=1 yoffset=8 xadvance=12 page=0 chnl=0
|
||||
char id=115 x=59 y=139 width=16 height=19 xoffset=0 yoffset=8 xadvance=16 page=0 chnl=0
|
||||
char id=116 x=415 y=91 width=12 height=24 xoffset=0 yoffset=3 xadvance=12 page=0 chnl=0
|
||||
char id=117 x=75 y=139 width=17 height=19 xoffset=1 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=118 x=92 y=139 width=20 height=19 xoffset=-1 yoffset=8 xadvance=18 page=0 chnl=0
|
||||
char id=119 x=112 y=139 width=27 height=19 xoffset=0 yoffset=8 xadvance=27 page=0 chnl=0
|
||||
char id=120 x=139 y=139 width=18 height=19 xoffset=0 yoffset=8 xadvance=18 page=0 chnl=0
|
||||
char id=121 x=105 y=65 width=20 height=25 xoffset=-1 yoffset=8 xadvance=19 page=0 chnl=0
|
||||
char id=122 x=157 y=139 width=16 height=19 xoffset=0 yoffset=8 xadvance=16 page=0 chnl=0
|
||||
char id=123 x=493 y=0 width=13 height=30 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=124 x=506 y=0 width=5 height=29 xoffset=2 yoffset=1 xadvance=9 page=0 chnl=0
|
||||
char id=125 x=0 y=35 width=13 height=30 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=126 x=394 y=139 width=14 height=7 xoffset=0 yoffset=11 xadvance=14 page=0 chnl=0
|
||||
char id=161 x=125 y=65 width=7 height=25 xoffset=1 yoffset=8 xadvance=9 page=0 chnl=0
|
||||
char id=162 x=132 y=65 width=18 height=25 xoffset=0 yoffset=5 xadvance=17 page=0 chnl=0
|
||||
char id=163 x=202 y=115 width=20 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=165 x=222 y=115 width=23 height=24 xoffset=-1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=168 x=457 y=139 width=12 height=5 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0
|
||||
char id=175 x=469 y=139 width=12 height=4 xoffset=1 yoffset=4 xadvance=14 page=0 chnl=0
|
||||
char id=180 x=408 y=139 width=9 height=7 xoffset=1 yoffset=2 xadvance=10 page=0 chnl=0
|
||||
char id=184 x=377 y=139 width=8 height=9 xoffset=1 yoffset=24 xadvance=10 page=0 chnl=0
|
||||
char id=191 x=150 y=65 width=17 height=25 xoffset=0 yoffset=8 xadvance=16 page=0 chnl=0
|
||||
char id=192 x=70 y=0 width=25 height=31 xoffset=0 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=193 x=95 y=0 width=25 height=31 xoffset=0 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=194 x=120 y=0 width=25 height=31 xoffset=0 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=195 x=145 y=0 width=25 height=31 xoffset=0 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=196 x=13 y=35 width=25 height=30 xoffset=0 yoffset=-3 xadvance=24 page=0 chnl=0
|
||||
char id=197 x=14 y=0 width=25 height=33 xoffset=0 yoffset=-6 xadvance=24 page=0 chnl=0
|
||||
char id=198 x=245 y=115 width=32 height=24 xoffset=0 yoffset=3 xadvance=33 page=0 chnl=0
|
||||
char id=199 x=38 y=35 width=22 height=30 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=0
|
||||
char id=200 x=170 y=0 width=19 height=31 xoffset=1 yoffset=-4 xadvance=20 page=0 chnl=0
|
||||
char id=201 x=189 y=0 width=19 height=31 xoffset=1 yoffset=-4 xadvance=20 page=0 chnl=0
|
||||
char id=202 x=208 y=0 width=19 height=31 xoffset=1 yoffset=-4 xadvance=20 page=0 chnl=0
|
||||
char id=203 x=60 y=35 width=19 height=30 xoffset=1 yoffset=-3 xadvance=20 page=0 chnl=0
|
||||
char id=204 x=227 y=0 width=11 height=31 xoffset=-3 yoffset=-4 xadvance=8 page=0 chnl=0
|
||||
char id=205 x=238 y=0 width=11 height=31 xoffset=1 yoffset=-4 xadvance=8 page=0 chnl=0
|
||||
char id=206 x=249 y=0 width=12 height=31 xoffset=-2 yoffset=-4 xadvance=8 page=0 chnl=0
|
||||
char id=207 x=79 y=35 width=12 height=30 xoffset=-2 yoffset=-3 xadvance=8 page=0 chnl=0
|
||||
char id=208 x=277 y=115 width=24 height=24 xoffset=0 yoffset=3 xadvance=25 page=0 chnl=0
|
||||
char id=209 x=261 y=0 width=21 height=31 xoffset=2 yoffset=-4 xadvance=25 page=0 chnl=0
|
||||
char id=210 x=282 y=0 width=26 height=31 xoffset=0 yoffset=-4 xadvance=26 page=0 chnl=0
|
||||
char id=211 x=308 y=0 width=26 height=31 xoffset=0 yoffset=-4 xadvance=26 page=0 chnl=0
|
||||
char id=212 x=91 y=35 width=26 height=30 xoffset=0 yoffset=-3 xadvance=26 page=0 chnl=0
|
||||
char id=213 x=334 y=0 width=26 height=31 xoffset=0 yoffset=-4 xadvance=26 page=0 chnl=0
|
||||
char id=214 x=231 y=35 width=26 height=29 xoffset=0 yoffset=-2 xadvance=26 page=0 chnl=0
|
||||
char id=215 x=293 y=139 width=15 height=15 xoffset=1 yoffset=8 xadvance=17 page=0 chnl=0
|
||||
char id=216 x=301 y=115 width=26 height=24 xoffset=0 yoffset=3 xadvance=26 page=0 chnl=0
|
||||
char id=217 x=360 y=0 width=22 height=31 xoffset=1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=218 x=382 y=0 width=22 height=31 xoffset=1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=219 x=404 y=0 width=22 height=31 xoffset=1 yoffset=-4 xadvance=24 page=0 chnl=0
|
||||
char id=220 x=117 y=35 width=22 height=30 xoffset=1 yoffset=-3 xadvance=24 page=0 chnl=0
|
||||
char id=221 x=426 y=0 width=23 height=31 xoffset=-1 yoffset=-4 xadvance=21 page=0 chnl=0
|
||||
char id=222 x=327 y=115 width=20 height=24 xoffset=1 yoffset=3 xadvance=21 page=0 chnl=0
|
||||
char id=223 x=167 y=65 width=17 height=25 xoffset=1 yoffset=2 xadvance=18 page=0 chnl=0
|
||||
char id=224 x=308 y=35 width=17 height=26 xoffset=0 yoffset=1 xadvance=18 page=0 chnl=0
|
||||
char id=225 x=325 y=35 width=17 height=26 xoffset=0 yoffset=1 xadvance=18 page=0 chnl=0
|
||||
char id=226 x=184 y=65 width=17 height=25 xoffset=0 yoffset=2 xadvance=18 page=0 chnl=0
|
||||
char id=227 x=201 y=65 width=17 height=25 xoffset=0 yoffset=2 xadvance=18 page=0 chnl=0
|
||||
char id=228 x=347 y=115 width=17 height=24 xoffset=0 yoffset=3 xadvance=18 page=0 chnl=0
|
||||
char id=229 x=285 y=35 width=17 height=28 xoffset=0 yoffset=-1 xadvance=18 page=0 chnl=0
|
||||
char id=230 x=208 y=139 width=31 height=19 xoffset=0 yoffset=8 xadvance=31 page=0 chnl=0
|
||||
char id=231 x=218 y=65 width=18 height=25 xoffset=0 yoffset=8 xadvance=17 page=0 chnl=0
|
||||
char id=232 x=342 y=35 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=233 x=361 y=35 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=234 x=236 y=65 width=19 height=25 xoffset=0 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=235 x=364 y=115 width=19 height=24 xoffset=0 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=236 x=380 y=35 width=10 height=26 xoffset=-3 yoffset=1 xadvance=7 page=0 chnl=0
|
||||
char id=237 x=390 y=35 width=12 height=26 xoffset=1 yoffset=1 xadvance=7 page=0 chnl=0
|
||||
char id=238 x=402 y=35 width=12 height=26 xoffset=-2 yoffset=1 xadvance=7 page=0 chnl=0
|
||||
char id=239 x=383 y=115 width=13 height=24 xoffset=-2 yoffset=3 xadvance=7 page=0 chnl=0
|
||||
char id=240 x=414 y=35 width=20 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=241 x=255 y=65 width=17 height=25 xoffset=1 yoffset=2 xadvance=19 page=0 chnl=0
|
||||
char id=242 x=434 y=35 width=20 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=243 x=454 y=35 width=20 height=26 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0
|
||||
char id=244 x=272 y=65 width=20 height=25 xoffset=0 yoffset=2 xadvance=20 page=0 chnl=0
|
||||
char id=245 x=292 y=65 width=20 height=25 xoffset=0 yoffset=2 xadvance=20 page=0 chnl=0
|
||||
char id=246 x=396 y=115 width=20 height=24 xoffset=0 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=247 x=276 y=139 width=17 height=16 xoffset=1 yoffset=7 xadvance=19 page=0 chnl=0
|
||||
char id=248 x=239 y=139 width=20 height=19 xoffset=0 yoffset=8 xadvance=20 page=0 chnl=0
|
||||
char id=249 x=474 y=35 width=17 height=26 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=250 x=491 y=35 width=17 height=26 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=251 x=0 y=65 width=17 height=26 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=252 x=416 y=115 width=17 height=24 xoffset=1 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
char id=253 x=50 y=0 width=20 height=32 xoffset=-1 yoffset=1 xadvance=19 page=0 chnl=0
|
||||
char id=254 x=139 y=35 width=19 height=30 xoffset=1 yoffset=3 xadvance=20 page=0 chnl=0
|
||||
char id=255 x=158 y=35 width=20 height=30 xoffset=-1 yoffset=3 xadvance=19 page=0 chnl=0
|
||||
kernings count=375
|
||||
kerning first=244 second=119 amount=-1
|
||||
kerning first=86 second=225 amount=-1
|
||||
kerning first=119 second=245 amount=-1
|
||||
kerning first=253 second=243 amount=-1
|
||||
kerning first=111 second=89 amount=-4
|
||||
kerning first=248 second=86 amount=-2
|
||||
kerning first=84 second=211 amount=-1
|
||||
kerning first=84 second=194 amount=-2
|
||||
kerning first=221 second=235 amount=-4
|
||||
kerning first=192 second=221 amount=-1
|
||||
kerning first=121 second=44 amount=-4
|
||||
kerning first=65 second=89 amount=-1
|
||||
kerning first=75 second=216 amount=-1
|
||||
kerning first=87 second=248 amount=-2
|
||||
kerning first=89 second=242 amount=-4
|
||||
kerning first=221 second=111 amount=-4
|
||||
kerning first=233 second=221 amount=-4
|
||||
kerning first=246 second=255 amount=-1
|
||||
kerning first=75 second=195 amount=1
|
||||
kerning first=234 second=86 amount=-2
|
||||
kerning first=121 second=233 amount=-1
|
||||
kerning first=119 second=235 amount=-1
|
||||
kerning first=111 second=119 amount=-1
|
||||
kerning first=76 second=86 amount=-3
|
||||
kerning first=246 second=221 amount=-4
|
||||
kerning first=86 second=198 amount=-3
|
||||
kerning first=118 second=242 amount=-1
|
||||
kerning first=89 second=194 amount=-1
|
||||
kerning first=92 second=92 amount=-4
|
||||
kerning first=119 second=111 amount=-1
|
||||
kerning first=114 second=242 amount=-1
|
||||
kerning first=86 second=232 amount=-2
|
||||
kerning first=194 second=221 amount=-1
|
||||
kerning first=89 second=101 amount=-4
|
||||
kerning first=87 second=234 amount=-2
|
||||
kerning first=192 second=86 amount=-3
|
||||
kerning first=86 second=246 amount=-2
|
||||
kerning first=243 second=255 amount=-1
|
||||
kerning first=248 second=121 amount=-1
|
||||
kerning first=233 second=86 amount=-2
|
||||
kerning first=197 second=84 amount=-2
|
||||
kerning first=92 second=47 amount=2
|
||||
kerning first=75 second=193 amount=1
|
||||
kerning first=118 second=101 amount=-1
|
||||
kerning first=243 second=221 amount=-4
|
||||
kerning first=245 second=87 amount=-2
|
||||
kerning first=246 second=86 amount=-2
|
||||
kerning first=84 second=197 amount=-2
|
||||
kerning first=121 second=46 amount=-4
|
||||
kerning first=193 second=221 amount=-1
|
||||
kerning first=197 second=87 amount=-1
|
||||
kerning first=194 second=86 amount=-3
|
||||
kerning first=89 second=99 amount=-4
|
||||
kerning first=87 second=231 amount=-2
|
||||
kerning first=235 second=87 amount=-2
|
||||
kerning first=195 second=71 amount=-1
|
||||
kerning first=99 second=89 amount=-4
|
||||
kerning first=89 second=197 amount=-1
|
||||
kerning first=121 second=244 amount=-1
|
||||
kerning first=221 second=196 amount=-1
|
||||
kerning first=243 second=86 amount=-2
|
||||
kerning first=68 second=196 amount=-2
|
||||
kerning first=84 second=65 amount=-2
|
||||
kerning first=221 second=233 amount=-4
|
||||
kerning first=86 second=245 amount=-2
|
||||
kerning first=119 second=44 amount=-3
|
||||
kerning first=75 second=210 amount=-1
|
||||
kerning first=89 second=243 amount=-4
|
||||
kerning first=193 second=86 amount=-3
|
||||
kerning first=87 second=242 amount=-2
|
||||
kerning first=231 second=221 amount=-4
|
||||
kerning first=246 second=121 amount=-1
|
||||
kerning first=245 second=253 amount=-1
|
||||
kerning first=230 second=89 amount=-4
|
||||
kerning first=255 second=232 amount=-1
|
||||
kerning first=196 second=84 amount=-2
|
||||
kerning first=119 second=233 amount=-1
|
||||
kerning first=255 second=246 amount=-1
|
||||
kerning first=89 second=65 amount=-1
|
||||
kerning first=118 second=243 amount=-1
|
||||
kerning first=242 second=87 amount=-2
|
||||
kerning first=87 second=194 amount=-1
|
||||
kerning first=118 second=228 amount=-1
|
||||
kerning first=114 second=243 amount=-1
|
||||
kerning first=86 second=235 amount=-2
|
||||
kerning first=87 second=101 amount=-2
|
||||
kerning first=196 second=87 amount=-1
|
||||
kerning first=230 second=119 amount=-1
|
||||
kerning first=213 second=87 amount=-1
|
||||
kerning first=86 second=111 amount=-2
|
||||
kerning first=101 second=87 amount=-2
|
||||
kerning first=243 second=121 amount=-1
|
||||
kerning first=192 second=71 amount=-1
|
||||
kerning first=245 second=118 amount=-1
|
||||
kerning first=231 second=86 amount=-2
|
||||
kerning first=253 second=232 amount=-1
|
||||
kerning first=121 second=248 amount=-1
|
||||
kerning first=86 second=226 amount=-1
|
||||
kerning first=221 second=195 amount=-1
|
||||
kerning first=253 second=246 amount=-1
|
||||
kerning first=68 second=195 amount=-2
|
||||
kerning first=84 second=214 amount=-1
|
||||
kerning first=244 second=87 amount=-2
|
||||
kerning first=84 second=192 amount=-2
|
||||
kerning first=119 second=46 amount=-3
|
||||
kerning first=232 second=89 amount=-4
|
||||
kerning first=221 second=244 amount=-4
|
||||
kerning first=47 second=47 amount=-4
|
||||
kerning first=87 second=99 amount=-2
|
||||
kerning first=242 second=253 amount=-1
|
||||
kerning first=121 second=234 amount=-1
|
||||
kerning first=194 second=71 amount=-1
|
||||
kerning first=65 second=84 amount=-2
|
||||
kerning first=255 second=245 amount=-1
|
||||
kerning first=89 second=192 amount=-1
|
||||
kerning first=87 second=197 amount=-1
|
||||
kerning first=119 second=244 amount=-1
|
||||
kerning first=221 second=193 amount=-1
|
||||
kerning first=111 second=87 amount=-2
|
||||
kerning first=118 second=227 amount=-1
|
||||
kerning first=68 second=193 amount=-2
|
||||
kerning first=195 second=89 amount=-1
|
||||
kerning first=232 second=119 amount=-1
|
||||
kerning first=82 second=89 amount=-1
|
||||
kerning first=65 second=87 amount=-1
|
||||
kerning first=75 second=211 amount=-1
|
||||
kerning first=87 second=243 amount=-2
|
||||
kerning first=210 second=87 amount=-1
|
||||
kerning first=244 second=253 amount=-1
|
||||
kerning first=86 second=229 amount=-1
|
||||
kerning first=75 second=194 amount=1
|
||||
kerning first=193 second=71 amount=-1
|
||||
kerning first=242 second=118 amount=-1
|
||||
kerning first=253 second=245 amount=-1
|
||||
kerning first=255 second=235 amount=-1
|
||||
kerning first=248 second=89 amount=-4
|
||||
kerning first=255 second=111 amount=-1
|
||||
kerning first=84 second=213 amount=-1
|
||||
kerning first=86 second=196 amount=-3
|
||||
kerning first=84 second=198 amount=-2
|
||||
kerning first=87 second=65 amount=-1
|
||||
kerning first=118 second=225 amount=-1
|
||||
kerning first=221 second=248 amount=-4
|
||||
kerning first=86 second=233 amount=-2
|
||||
kerning first=234 second=89 amount=-4
|
||||
kerning first=212 second=87 amount=-1
|
||||
kerning first=111 second=253 amount=-1
|
||||
kerning first=248 second=119 amount=-1
|
||||
kerning first=253 second=235 amount=-1
|
||||
kerning first=244 second=118 amount=-1
|
||||
kerning first=89 second=198 amount=-1
|
||||
kerning first=121 second=242 amount=-1
|
||||
kerning first=86 second=97 amount=-1
|
||||
kerning first=119 second=248 amount=-1
|
||||
kerning first=253 second=111 amount=-1
|
||||
kerning first=84 second=79 amount=-1
|
||||
kerning first=89 second=232 amount=-4
|
||||
kerning first=221 second=234 amount=-4
|
||||
kerning first=192 second=89 amount=-1
|
||||
kerning first=234 second=119 amount=-1
|
||||
kerning first=89 second=246 amount=-4
|
||||
kerning first=233 second=89 amount=-4
|
||||
kerning first=245 second=255 amount=-1
|
||||
kerning first=75 second=197 amount=1
|
||||
kerning first=79 second=87 amount=-1
|
||||
kerning first=118 second=232 amount=-1
|
||||
kerning first=121 second=101 amount=-1
|
||||
kerning first=119 second=234 amount=-1
|
||||
kerning first=99 second=87 amount=-2
|
||||
kerning first=245 second=221 amount=-4
|
||||
kerning first=111 second=118 amount=-1
|
||||
kerning first=246 second=89 amount=-4
|
||||
kerning first=86 second=195 amount=-3
|
||||
kerning first=118 second=246 amount=-1
|
||||
kerning first=87 second=192 amount=-1
|
||||
kerning first=197 second=221 amount=-1
|
||||
kerning first=114 second=246 amount=-1
|
||||
kerning first=194 second=89 amount=-1
|
||||
kerning first=233 second=119 amount=-1
|
||||
kerning first=255 second=44 amount=-4
|
||||
kerning first=235 second=221 amount=-4
|
||||
kerning first=86 second=244 amount=-2
|
||||
kerning first=255 second=233 amount=-1
|
||||
kerning first=86 second=224 amount=-1
|
||||
kerning first=246 second=119 amount=-1
|
||||
kerning first=75 second=65 amount=1
|
||||
kerning first=230 second=87 amount=-2
|
||||
kerning first=243 second=89 amount=-4
|
||||
kerning first=245 second=86 amount=-2
|
||||
kerning first=86 second=193 amount=-3
|
||||
kerning first=89 second=245 amount=-4
|
||||
kerning first=193 second=89 amount=-1
|
||||
kerning first=253 second=44 amount=-4
|
||||
kerning first=197 second=86 amount=-3
|
||||
kerning first=221 second=242 amount=-4
|
||||
kerning first=242 second=255 amount=-1
|
||||
kerning first=235 second=86 amount=-2
|
||||
kerning first=253 second=233 amount=-1
|
||||
kerning first=243 second=119 amount=-1
|
||||
kerning first=118 second=245 amount=-1
|
||||
kerning first=242 second=221 amount=-4
|
||||
kerning first=87 second=198 amount=-1
|
||||
kerning first=121 second=243 amount=-1
|
||||
kerning first=221 second=194 amount=-1
|
||||
kerning first=119 second=242 amount=-1
|
||||
kerning first=68 second=194 amount=-2
|
||||
kerning first=114 second=245 amount=-1
|
||||
kerning first=255 second=46 amount=-4
|
||||
kerning first=89 second=235 amount=-4
|
||||
kerning first=87 second=232 amount=-2
|
||||
kerning first=196 second=221 amount=-1
|
||||
kerning first=221 second=101 amount=-4
|
||||
kerning first=86 second=248 amount=-2
|
||||
kerning first=75 second=214 amount=-1
|
||||
kerning first=101 second=221 amount=-4
|
||||
kerning first=89 second=111 amount=-4
|
||||
kerning first=87 second=246 amount=-2
|
||||
kerning first=232 second=87 amount=-2
|
||||
kerning first=244 second=255 amount=-1
|
||||
kerning first=231 second=89 amount=-4
|
||||
kerning first=245 second=121 amount=-1
|
||||
kerning first=195 second=84 amount=-2
|
||||
kerning first=86 second=230 amount=-1
|
||||
kerning first=75 second=192 amount=1
|
||||
kerning first=118 second=235 amount=-1
|
||||
kerning first=119 second=101 amount=-1
|
||||
kerning first=255 second=244 amount=-1
|
||||
kerning first=244 second=221 amount=-4
|
||||
kerning first=118 second=111 amount=-1
|
||||
kerning first=242 second=86 amount=-2
|
||||
kerning first=118 second=226 amount=-1
|
||||
kerning first=253 second=46 amount=-4
|
||||
kerning first=114 second=111 amount=-1
|
||||
kerning first=86 second=234 amount=-2
|
||||
kerning first=195 second=87 amount=-1
|
||||
kerning first=196 second=86 amount=-3
|
||||
kerning first=221 second=99 amount=-4
|
||||
kerning first=111 second=255 amount=-1
|
||||
kerning first=101 second=86 amount=-2
|
||||
kerning first=221 second=197 amount=-1
|
||||
kerning first=253 second=244 amount=-1
|
||||
kerning first=118 second=100 amount=-1
|
||||
kerning first=111 second=221 amount=-4
|
||||
kerning first=248 second=87 amount=-2
|
||||
kerning first=84 second=212 amount=-1
|
||||
kerning first=68 second=197 amount=-2
|
||||
kerning first=244 second=86 amount=-2
|
||||
kerning first=76 second=84 amount=-1
|
||||
kerning first=84 second=196 amount=-2
|
||||
kerning first=65 second=221 amount=-1
|
||||
kerning first=75 second=213 amount=-1
|
||||
kerning first=87 second=245 amount=-2
|
||||
kerning first=221 second=243 amount=-4
|
||||
kerning first=86 second=231 amount=-2
|
||||
kerning first=75 second=198 amount=1
|
||||
kerning first=242 second=121 amount=-1
|
||||
kerning first=234 second=87 amount=-2
|
||||
kerning first=197 second=71 amount=-1
|
||||
kerning first=192 second=84 amount=-2
|
||||
kerning first=255 second=248 amount=-1
|
||||
kerning first=89 second=196 amount=-1
|
||||
kerning first=221 second=65 amount=-1
|
||||
kerning first=119 second=243 amount=-1
|
||||
kerning first=118 second=229 amount=-1
|
||||
kerning first=111 second=86 amount=-2
|
||||
kerning first=68 second=65 amount=-2
|
||||
kerning first=89 second=233 amount=-4
|
||||
kerning first=87 second=235 amount=-2
|
||||
kerning first=192 second=87 amount=-1
|
||||
kerning first=118 second=44 amount=-4
|
||||
kerning first=65 second=86 amount=-3
|
||||
kerning first=86 second=242 amount=-2
|
||||
kerning first=75 second=79 amount=-1
|
||||
kerning first=87 second=111 amount=-2
|
||||
kerning first=233 second=87 amount=-2
|
||||
kerning first=244 second=121 amount=-1
|
||||
kerning first=248 second=253 amount=-1
|
||||
kerning first=194 second=84 amount=-2
|
||||
kerning first=253 second=248 amount=-1
|
||||
kerning first=118 second=233 amount=-1
|
||||
kerning first=255 second=234 amount=-1
|
||||
kerning first=84 second=216 amount=-1
|
||||
kerning first=246 second=87 amount=-2
|
||||
kerning first=86 second=194 amount=-3
|
||||
kerning first=84 second=195 amount=-2
|
||||
kerning first=118 second=97 amount=-1
|
||||
kerning first=86 second=101 amount=-2
|
||||
kerning first=194 second=87 amount=-1
|
||||
kerning first=216 second=87 amount=-1
|
||||
kerning first=99 second=221 amount=-4
|
||||
kerning first=121 second=232 amount=-1
|
||||
kerning first=111 second=121 amount=-1
|
||||
kerning first=253 second=234 amount=-1
|
||||
kerning first=196 second=71 amount=-1
|
||||
kerning first=248 second=118 amount=-1
|
||||
kerning first=193 second=84 amount=-2
|
||||
kerning first=89 second=195 amount=-1
|
||||
kerning first=121 second=246 amount=-1
|
||||
kerning first=221 second=192 amount=-1
|
||||
kerning first=243 second=87 amount=-2
|
||||
kerning first=68 second=192 amount=-2
|
||||
kerning first=84 second=193 amount=-2
|
||||
kerning first=118 second=46 amount=-4
|
||||
kerning first=89 second=244 amount=-4
|
||||
kerning first=193 second=87 amount=-1
|
||||
kerning first=246 second=253 amount=-1
|
||||
kerning first=86 second=99 amount=-2
|
||||
kerning first=230 second=221 amount=-4
|
||||
kerning first=99 second=86 amount=-2
|
||||
kerning first=255 second=242 amount=-1
|
||||
kerning first=245 second=89 amount=-4
|
||||
kerning first=86 second=197 amount=-3
|
||||
kerning first=89 second=193 amount=-1
|
||||
kerning first=87 second=196 amount=-1
|
||||
kerning first=118 second=244 amount=-1
|
||||
kerning first=118 second=224 amount=-1
|
||||
kerning first=197 second=89 amount=-1
|
||||
kerning first=114 second=244 amount=-1
|
||||
kerning first=87 second=233 amount=-2
|
||||
kerning first=235 second=89 amount=-4
|
||||
kerning first=86 second=243 amount=-2
|
||||
kerning first=214 second=87 amount=-1
|
||||
kerning first=245 second=119 amount=-1
|
||||
kerning first=243 second=253 amount=-1
|
||||
kerning first=231 second=87 amount=-2
|
||||
kerning first=255 second=101 amount=-1
|
||||
kerning first=246 second=118 amount=-1
|
||||
kerning first=65 second=71 amount=-1
|
||||
kerning first=86 second=228 amount=-1
|
||||
kerning first=221 second=198 amount=-1
|
||||
kerning first=121 second=245 amount=-1
|
||||
kerning first=230 second=86 amount=-2
|
||||
kerning first=253 second=242 amount=-1
|
||||
kerning first=68 second=198 amount=-2
|
||||
kerning first=84 second=210 amount=-1
|
||||
kerning first=86 second=65 amount=-3
|
||||
kerning first=221 second=232 amount=-4
|
||||
kerning first=235 second=119 amount=-1
|
||||
kerning first=89 second=248 amount=-4
|
||||
kerning first=232 second=221 amount=-4
|
||||
kerning first=221 second=246 amount=-4
|
||||
kerning first=211 second=87 amount=-1
|
||||
kerning first=121 second=235 amount=-1
|
||||
kerning first=119 second=232 amount=-1
|
||||
kerning first=253 second=101 amount=-1
|
||||
kerning first=243 second=118 amount=-1
|
||||
kerning first=118 second=248 amount=-1
|
||||
kerning first=242 second=89 amount=-4
|
||||
kerning first=87 second=195 amount=-1
|
||||
kerning first=121 second=111 amount=-1
|
||||
kerning first=119 second=246 amount=-1
|
||||
kerning first=118 second=230 amount=-1
|
||||
kerning first=114 second=248 amount=-1
|
||||
kerning first=195 second=221 amount=-1
|
||||
kerning first=89 second=234 amount=-4
|
||||
kerning first=196 second=89 amount=-1
|
||||
kerning first=82 second=221 amount=-1
|
||||
kerning first=75 second=212 amount=-1
|
||||
kerning first=101 second=89 amount=-4
|
||||
kerning first=87 second=244 amount=-2
|
||||
kerning first=232 second=86 amount=-2
|
||||
kerning first=248 second=255 amount=-1
|
||||
kerning first=86 second=227 amount=-1
|
||||
kerning first=75 second=196 amount=1
|
||||
kerning first=242 second=119 amount=-1
|
||||
kerning first=118 second=234 amount=-1
|
||||
kerning first=248 second=221 amount=-4
|
||||
kerning first=255 second=243 amount=-1
|
||||
kerning first=244 second=89 amount=-4
|
||||
kerning first=86 second=192 amount=-3
|
||||
kerning first=87 second=193 amount=-1
|
||||
kerning first=221 second=245 amount=-4
|
||||
kerning first=101 second=119 amount=-1
|
||||
kerning first=195 second=86 amount=-3
|
||||
kerning first=234 second=221 amount=-4
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,43 @@
|
||||
Copyright (c) 2015, Chris Simpson, with Reserved Font Name: "Metropolis".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,201 @@
|
||||
// Styling of Lemur components
|
||||
// For documentation, see
|
||||
// https://github.com/jMonkeyEngine-Contributions/Lemur/wiki/Styling
|
||||
|
||||
import com.simsilica.lemur.Button
|
||||
import com.simsilica.lemur.Button.ButtonAction
|
||||
import com.simsilica.lemur.Command
|
||||
import com.simsilica.lemur.HAlignment
|
||||
import com.simsilica.lemur.Insets3f
|
||||
import com.simsilica.lemur.component.QuadBackgroundComponent
|
||||
import com.simsilica.lemur.component.TbtQuadBackgroundComponent
|
||||
|
||||
def bgColor = color(0.25, 0.5, 0.5, 1)
|
||||
def buttonEnabledColor = color(0.8, 0.9, 1, 1)
|
||||
def buttonDisabledColor = color(0.8, 0.9, 1, 0.2)
|
||||
def buttonBgColor = color(0, 0.75, 0.75, 1)
|
||||
def sliderColor = color(0.6, 0.8, 0.8, 1)
|
||||
def sliderBgColor = color(0.5, 0.75, 0.75, 1)
|
||||
def gradientColor = color(0.5, 0.75, 0.85, 0.5)
|
||||
def tabbuttonEnabledColor = color(0.4, 0.45, 0.5, 1)
|
||||
|
||||
def gradient = TbtQuadBackgroundComponent.create(
|
||||
texture(name: "/com/simsilica/lemur/icons/bordered-gradient.png",
|
||||
generateMips: false),
|
||||
1, 1, 1, 126, 126,
|
||||
1f, false)
|
||||
|
||||
def doubleGradient = new QuadBackgroundComponent(gradientColor)
|
||||
doubleGradient.texture = texture(name: "/com/simsilica/lemur/icons/double-gradient-128.png",
|
||||
generateMips: false)
|
||||
|
||||
selector("pp") {
|
||||
font = font("Interface/Fonts/Metropolis/Metropolis-Regular-32.fnt")
|
||||
}
|
||||
|
||||
selector("label", "pp") {
|
||||
insets = new Insets3f(2, 2, 2, 2)
|
||||
color = buttonEnabledColor
|
||||
}
|
||||
|
||||
selector("header", "pp") {
|
||||
font = font("Interface/Fonts/Metropolis/Metropolis-Bold-42.fnt")
|
||||
insets = new Insets3f(2, 2, 2, 2)
|
||||
color = color(1, 0.5, 0, 1)
|
||||
textHAlignment = HAlignment.Center
|
||||
}
|
||||
|
||||
selector("container", "pp") {
|
||||
background = gradient.clone()
|
||||
background.setColor(bgColor)
|
||||
}
|
||||
|
||||
selector("slider", "pp") {
|
||||
background = gradient.clone()
|
||||
background.setColor(bgColor)
|
||||
}
|
||||
|
||||
def pressedCommand = new Command<Button>() {
|
||||
void execute(Button source) {
|
||||
if (source.isPressed())
|
||||
source.move(1, -1, 0)
|
||||
else
|
||||
source.move(-1, 1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
def enabledCommand = new Command<Button>() {
|
||||
void execute(Button source) {
|
||||
if (source.isEnabled())
|
||||
source.setColor(buttonEnabledColor)
|
||||
else
|
||||
source.setColor(buttonDisabledColor)
|
||||
}
|
||||
}
|
||||
|
||||
def repeatCommand = new Command<Button>() {
|
||||
private long startTime
|
||||
private long lastClick
|
||||
|
||||
void execute(Button source) {
|
||||
// Only do the repeating click while the mouse is
|
||||
// over the button (and pressed of course)
|
||||
if (source.isPressed() && source.isHighlightOn()) {
|
||||
long elapsedTime = System.currentTimeMillis() - startTime
|
||||
// After half a second pause, click 8 times a second
|
||||
if (elapsedTime > 500 && elapsedTime > lastClick + 125) {
|
||||
source.click()
|
||||
|
||||
// Try to quantize the last click time to prevent drift
|
||||
lastClick = ((elapsedTime - 500) / 125) * 125 + 500
|
||||
}
|
||||
}
|
||||
else {
|
||||
startTime = System.currentTimeMillis()
|
||||
lastClick = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def stdButtonCommands = [
|
||||
(ButtonAction.Down) : [pressedCommand],
|
||||
(ButtonAction.Up) : [pressedCommand],
|
||||
(ButtonAction.Enabled) : [enabledCommand],
|
||||
(ButtonAction.Disabled): [enabledCommand]
|
||||
]
|
||||
|
||||
def sliderButtonCommands = [
|
||||
(ButtonAction.Hover): [repeatCommand]
|
||||
]
|
||||
|
||||
selector("title", "pp") {
|
||||
color = color(0.8, 0.9, 1, 0.85f)
|
||||
highlightColor = color(1, 0.8, 1, 0.85f)
|
||||
shadowColor = color(0, 0, 0, 0.75f)
|
||||
shadowOffset = vec3(2, -2, -1)
|
||||
background = new QuadBackgroundComponent(color(0.5, 0.75, 0.85, 1))
|
||||
background.texture = texture(name: "/com/simsilica/lemur/icons/double-gradient-128.png",
|
||||
generateMips: false)
|
||||
insets = new Insets3f(2, 2, 2, 2)
|
||||
|
||||
buttonCommands = stdButtonCommands
|
||||
}
|
||||
|
||||
|
||||
selector("button", "pp") {
|
||||
background = gradient.clone()
|
||||
color = buttonEnabledColor
|
||||
background.setColor(buttonBgColor)
|
||||
insets = new Insets3f(2, 2, 2, 2)
|
||||
|
||||
buttonCommands = stdButtonCommands
|
||||
}
|
||||
|
||||
selector("slider", "pp") {
|
||||
insets = new Insets3f(1, 3, 1, 2)
|
||||
}
|
||||
|
||||
selector("slider", "button", "pp") {
|
||||
background = doubleGradient.clone()
|
||||
//background.setColor(sliderBgColor)
|
||||
insets = new Insets3f(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
selector("slider.thumb.button", "pp") {
|
||||
text = "[]"
|
||||
color = sliderColor
|
||||
}
|
||||
|
||||
selector("slider.left.button", "pp") {
|
||||
text = "-"
|
||||
background = doubleGradient.clone()
|
||||
//background.setColor(sliderBgColor)
|
||||
background.setMargin(5, 0)
|
||||
color = sliderColor
|
||||
|
||||
buttonCommands = sliderButtonCommands
|
||||
}
|
||||
|
||||
selector("slider.right.button", "pp") {
|
||||
text = "+"
|
||||
background = doubleGradient.clone()
|
||||
//background.setColor(sliderBgColor)
|
||||
background.setMargin(4, 0)
|
||||
color = sliderColor
|
||||
|
||||
buttonCommands = sliderButtonCommands
|
||||
}
|
||||
|
||||
selector("slider.up.button", "pp") {
|
||||
buttonCommands = sliderButtonCommands
|
||||
}
|
||||
|
||||
selector("slider.down.button", "pp") {
|
||||
buttonCommands = sliderButtonCommands
|
||||
}
|
||||
|
||||
selector("checkbox", "pp") {
|
||||
color = buttonEnabledColor
|
||||
}
|
||||
|
||||
selector("rollup", "pp") {
|
||||
background = gradient.clone()
|
||||
background.setColor(bgColor)
|
||||
}
|
||||
|
||||
selector("tabbedPanel", "pp") {
|
||||
activationColor = buttonEnabledColor
|
||||
}
|
||||
|
||||
selector("tabbedPanel.container", "pp") {
|
||||
background = null
|
||||
}
|
||||
|
||||
selector("tab.button", "pp") {
|
||||
background = gradient.clone()
|
||||
background.setColor(bgColor)
|
||||
color = tabbuttonEnabledColor
|
||||
insets = new Insets3f(4, 2, 0, 2)
|
||||
|
||||
buttonCommands = stdButtonCommands
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.view;
|
||||
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ModelViewSynchronizerTest {
|
||||
|
||||
private Node rootNode;
|
||||
private Node itemNode;
|
||||
private ModelViewSynchronizer<String> synchronizer;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
rootNode = new Node("root"); //NON-NLS
|
||||
synchronizer = new ModelViewSynchronizer<>(rootNode) {
|
||||
@Override
|
||||
protected Spatial translate(String item) {
|
||||
return new Node(item);
|
||||
}
|
||||
};
|
||||
itemNode = (Node) rootNode.getChild(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor() {
|
||||
assertNotNull(itemNode);
|
||||
assertEquals(1, rootNode.getQuantity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
String item = "item1"; //NON-NLS
|
||||
synchronizer.add(item);
|
||||
|
||||
Spatial spatial = synchronizer.getSpatial(item);
|
||||
assertNotNull(spatial);
|
||||
assertEquals(item, spatial.getName());
|
||||
assertTrue(itemNode.hasChild(spatial));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
String item = "item1"; //NON-NLS
|
||||
synchronizer.add(item);
|
||||
synchronizer.delete(item);
|
||||
|
||||
Spatial spatial = synchronizer.getSpatial(item);
|
||||
assertNull(spatial);
|
||||
assertFalse(itemNode.hasChild(spatial));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() {
|
||||
synchronizer.add("item1"); //NON-NLS
|
||||
synchronizer.add("item2"); //NON-NLS
|
||||
synchronizer.clear();
|
||||
|
||||
assertNull(synchronizer.getSpatial("item1")); //NON-NLS
|
||||
assertNull(synchronizer.getSpatial("item2")); //NON-NLS
|
||||
assertEquals(0, itemNode.getQuantity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddDuplicate() {
|
||||
String item = "item1"; //NON-NLS
|
||||
synchronizer.add(item);
|
||||
synchronizer.add(item);
|
||||
|
||||
assertEquals(1, itemNode.getQuantity());
|
||||
}
|
||||
}
|
||||
@@ -6,33 +6,16 @@
|
||||
|
||||
dependencies {
|
||||
implementation project(":mdga:model")
|
||||
implementation libs.lemur
|
||||
|
||||
implementation libs.jme3.desktop
|
||||
implementation libs.jme3.core
|
||||
implementation libs.jme3.lwjgl3
|
||||
implementation libs.jme3.lwjgl
|
||||
implementation libs.jme3.desktop
|
||||
implementation libs.jme3.effects
|
||||
|
||||
runtimeOnly libs.jme3.awt.dialogs
|
||||
runtimeOnly libs.jme3.plugins
|
||||
runtimeOnly libs.jme3.jogg
|
||||
runtimeOnly libs.jme3.testdata
|
||||
runtimeOnly libs.slf4j.nop
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'pp.mdga.client.MdgaApp'
|
||||
applicationName = 'MDGA'
|
||||
}
|
||||
|
||||
tasks.register('fatJar', Jar) {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
manifest {
|
||||
attributes 'Main-Class': 'pp.mdga.client.MdgaApp'
|
||||
}
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
with jar
|
||||
}
|
||||
}
|
||||
@@ -1,141 +1,66 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
/**
|
||||
* Represents different assets in the application. Each asset may have an associated model path,
|
||||
* diffuse texture path, and a size factor. The enum provides multiple constructors to handle
|
||||
* varying levels of detail for different assets.
|
||||
*/
|
||||
public enum Asset {
|
||||
bigTent,
|
||||
cardStack,
|
||||
cir,
|
||||
heer,
|
||||
jet,
|
||||
jet_noGear("Models/jet/jet_noGear.j3o", "Models/jet/jet_diff.png"),
|
||||
lw,
|
||||
marine,
|
||||
node_home_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
|
||||
node_wait_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
|
||||
node_home_black("Models/node_home/node_home.j3o", "Models/node_home/node_home_black_diff.png"),
|
||||
node_wait_black("Models/node_home/node_home.j3o", "Models/node_home/node_home_black_diff.png"),
|
||||
node_home_green("Models/node_home/node_home.j3o", "Models/node_home/node_home_green_diff.png"),
|
||||
node_wait_green("Models/node_home/node_home.j3o", "Models/node_home/node_home_green_diff.png"),
|
||||
node_home_yellow("Models/node_home/node_home.j3o", "Models/node_home/node_home_orange_diff.png"),
|
||||
node_wait_yellow("Models/node_home/node_home.j3o", "Models/node_home/node_home_orange_diff.png"),
|
||||
node_home_blue("./node_home/node_home.j3o", "./node_home/node_home_blue.png"),
|
||||
node_home_black("./node_home/node_home.j3o", "./node_home/node_home_black.png"),
|
||||
node_home_green("./node_home/node_home.j3o", "./node_home/node_home_green.png"),
|
||||
node_home_yellow("./node_home/node_home.j3o", "./node_home/node_home_yellow.png"),
|
||||
node_normal,
|
||||
node_start("Models/node_normal/node_normal.j3o", "Models/node_normal/node_start_diff.png"),
|
||||
node_bonus("Models/node_normal/node_normal.j3o", "Models/node_normal/node_bonus_diff.png"),
|
||||
node_start("./node_normal/node_normal.j3o", "./node_normal/node_normal_start.png"),
|
||||
node_bonus("./node_normal/node_normal.j3o", "./node_normal/node_normal_bonus.png"),
|
||||
radar,
|
||||
ship(0.8f),
|
||||
smallTent,
|
||||
tank,
|
||||
world(1.2f),
|
||||
shieldRing("Models/shieldRing/shieldRing.j3o", null),
|
||||
treeSmall(1.2f),
|
||||
treeBig(1.2f),
|
||||
turboCard,
|
||||
turboSymbol("Models/turboCard/turboSymbol.j3o", "Models/turboCard/turboCard_diff.png"),
|
||||
swapCard,
|
||||
swapSymbol("Models/swapCard/swapSymbol.j3o", "Models/swapCard/swapCard_diff.png"),
|
||||
shieldCard,
|
||||
shieldSymbol("Models/shieldCard/shieldSymbol.j3o", "Models/shieldCard/shieldCard_diff.png"),
|
||||
dice,
|
||||
missile("Models/missile/AVMT300.obj", "Models/missile/texture.jpg", 0.1f),
|
||||
tankShoot("Models/tank/tankShoot_bot.j3o", "Models/tank/tank_diff.png"),
|
||||
tankShootTop("Models/tank/tankShoot_top.j3o", "Models/tank/tank_diff.png"),
|
||||
treesSmallBackground("Models/treeSmall/treesSmallBackground.j3o", "Models/treeSmall/treeSmall_diff.png", 1.2f),
|
||||
treesBigBackground("Models/treeBig/treesBigBackground.j3o", "Models/treeBig/treeBig_diff.png", 1.2f),
|
||||
shell;
|
||||
ship,
|
||||
smallTent,
|
||||
swapCard,
|
||||
tank,
|
||||
turboCard,
|
||||
world(1.1f);
|
||||
|
||||
private final String modelPath;
|
||||
private final String diffPath;
|
||||
private final float size;
|
||||
private static final String ROOT = "Models/";
|
||||
|
||||
/**
|
||||
* Default constructor. Initializes modelPath and diffPath based on the enum name and sets default size to 1.0.
|
||||
*/
|
||||
Asset() {
|
||||
String folderFileName = "./" + ROOT + name() + "/" + name();
|
||||
this.modelPath = folderFileName + ".j3o";
|
||||
this.diffPath = folderFileName + "_diff.png";
|
||||
private static final String MODEL_FILETYPE = ".j3o";
|
||||
private static final String DIFFUSE_EXT = "_diff.png";
|
||||
|
||||
Asset(){
|
||||
String folderFileName = "./" + name() + "/" + name();
|
||||
this.modelPath = folderFileName + MODEL_FILETYPE;
|
||||
this.diffPath = folderFileName + DIFFUSE_EXT;
|
||||
this.size = 1f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with specific model path and diffuse texture path.
|
||||
*
|
||||
* @param modelPath Path to the 3D model file.
|
||||
* @param diffPath Path to the diffuse texture file.
|
||||
*/
|
||||
Asset(String modelPath, String diffPath) {
|
||||
Asset(String modelPath, String diffPath){
|
||||
this.modelPath = modelPath;
|
||||
this.diffPath = diffPath;
|
||||
this.size = 1f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with specific model path. Diffuse texture path is derived based on enum name.
|
||||
*
|
||||
* @param modelPath Path to the 3D model file.
|
||||
*/
|
||||
Asset(String modelPath) {
|
||||
String folderFileName = "./" + ROOT + name() + "/" + name();
|
||||
this.modelPath = modelPath;
|
||||
this.diffPath = folderFileName + "_diff.png";
|
||||
;
|
||||
this.size = 1f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with specific size. Model and texture paths are derived based on enum name.
|
||||
*
|
||||
* @param size Scaling factor for the asset.
|
||||
*/
|
||||
Asset(float size) {
|
||||
String folderFileName = "./" + ROOT + name() + "/" + name();
|
||||
this.modelPath = folderFileName + ".j3o";
|
||||
this.diffPath = folderFileName + "_diff.png";
|
||||
Asset(float size){
|
||||
String folderFileName = "./" + name() + "/" + name();
|
||||
this.modelPath = folderFileName + MODEL_FILETYPE;
|
||||
this.diffPath = folderFileName + DIFFUSE_EXT;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with specific model path, diffuse texture path, and size.
|
||||
*
|
||||
* @param modelPath Path to the 3D model file.
|
||||
* @param diffPath Path to the diffuse texture file.
|
||||
* @param size Scaling factor for the asset.
|
||||
*/
|
||||
Asset(String modelPath, String diffPath, float size) {
|
||||
this.modelPath = modelPath;
|
||||
this.diffPath = diffPath;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model path for the asset.
|
||||
*
|
||||
* @return Path to the 3D model file.
|
||||
*/
|
||||
public String getModelPath() {
|
||||
return modelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the diffuse texture path for the asset.
|
||||
*
|
||||
* @return Path to the diffuse texture file, or null if not applicable.
|
||||
*/
|
||||
public String getDiffPath() {
|
||||
return diffPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scaling factor for the asset.
|
||||
*
|
||||
* @return The size of the asset.
|
||||
*/
|
||||
public float getSize() {
|
||||
public float getSize(){
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
|
||||
/**
|
||||
* An abstract control class that serves as a base for initializing spatial objects
|
||||
* in jMonkeyEngine. This class overrides the controlUpdate and controlRender methods
|
||||
* from the AbstractControl class, providing default empty implementations,
|
||||
* and adds the ability to initialize spatial objects when they are set.
|
||||
*/
|
||||
public abstract class InitControl extends AbstractControl {
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spatial object to be controlled. This method also initializes the spatial
|
||||
* if it is being set for the first time.
|
||||
*
|
||||
* @param spatial The spatial object to control.
|
||||
*/
|
||||
@Override
|
||||
public void setSpatial(Spatial spatial) {
|
||||
if (this.spatial == null && spatial != null) {
|
||||
super.setSpatial(spatial);
|
||||
initSpatial();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the spatial object. This method can be overridden by subclasses
|
||||
* to define custom initialization logic for the spatial.
|
||||
* This method is called automatically when the spatial is set for the first time.
|
||||
*/
|
||||
protected void initSpatial() {
|
||||
// Default empty implementation. Override to add initialization logic.
|
||||
}
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
import com.jme3.collision.CollisionResult;
|
||||
import com.jme3.collision.CollisionResults;
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.*;
|
||||
import com.jme3.math.Ray;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import pp.mdga.client.board.OutlineOEControl;
|
||||
import pp.mdga.client.gui.CardControl;
|
||||
import pp.mdga.client.gui.DiceControl;
|
||||
import pp.mdga.client.view.GameView;
|
||||
import pp.mdga.game.Color;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class InputSynchronizer {
|
||||
|
||||
private MdgaApp app;
|
||||
private InputManager inputManager;
|
||||
|
||||
protected boolean rightMousePressed = false;
|
||||
private float rotationAngle = 180f;
|
||||
private int scrollValue = 0;
|
||||
private CardControl hoverCard;
|
||||
private OutlineOEControl hoverPiece;
|
||||
|
||||
private boolean clickAllowed = true;
|
||||
|
||||
private boolean isRotateLeft = false;
|
||||
private boolean isRotateRight = false;
|
||||
|
||||
/**
|
||||
* Constructor initializes the InputSynchronizer with the application context.
|
||||
* Sets up input mappings and listeners for user interactions.
|
||||
*
|
||||
* @param app The application instance
|
||||
*/
|
||||
InputSynchronizer(MdgaApp app) {
|
||||
this.app = app;
|
||||
|
||||
this.inputManager = app.getInputManager();
|
||||
hoverCard = null;
|
||||
hoverPiece = null;
|
||||
setupInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the rotation angle based on user input.
|
||||
*
|
||||
* @param tpf The time per frame.
|
||||
*/
|
||||
public void update(float tpf) {
|
||||
if (isRotateLeft && isRotateRight) {
|
||||
return;
|
||||
}
|
||||
if (isRotateLeft) {
|
||||
rotationAngle += 180 * tpf;
|
||||
}
|
||||
if (isRotateRight) {
|
||||
rotationAngle -= 180 * tpf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures input mappings for various actions and binds them to listeners.
|
||||
*/
|
||||
private void setupInput() {
|
||||
inputManager.addMapping("Settings", new KeyTrigger(KeyInput.KEY_ESCAPE));
|
||||
inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_RETURN));
|
||||
|
||||
inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_Q));
|
||||
inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_E));
|
||||
|
||||
inputManager.addMapping("RotateRightMouse", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
||||
inputManager.addMapping("MouseLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false)); // Left movement
|
||||
inputManager.addMapping("MouseRight", new MouseAxisTrigger(MouseInput.AXIS_X, true)); // Right movement
|
||||
inputManager.addMapping("MouseScrollUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); // Scroll up
|
||||
inputManager.addMapping("MouseScrollDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); // Scroll down
|
||||
inputManager.addMapping("Test", new KeyTrigger(KeyInput.KEY_J));
|
||||
inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
|
||||
|
||||
inputManager.addListener(actionListener, "Settings", "Forward", "RotateRightMouse", "Click", "Left", "Right", "Test");
|
||||
inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles action-based input events such as key presses and mouse clicks.
|
||||
*/
|
||||
private final ActionListener actionListener = new ActionListener() {
|
||||
@Override
|
||||
public void onAction(String name, boolean isPressed, float tpf) {
|
||||
if (name.equals("Settings") && isPressed) {
|
||||
app.getView().pressEscape();
|
||||
}
|
||||
if (name.equals("Forward") && isPressed) {
|
||||
app.getView().pressForward();
|
||||
}
|
||||
if (name.equals("RotateRightMouse")) {
|
||||
rightMousePressed = isPressed;
|
||||
}
|
||||
if (name.equals("Click") && isPressed) {
|
||||
if (!clickAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.getView() instanceof GameView gameView) {
|
||||
DiceControl diceSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), DiceControl.class);
|
||||
CardControl cardLayerSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), CardControl.class);
|
||||
OutlineOEControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
|
||||
|
||||
if (diceSelect != null) {
|
||||
app.getModelSynchronize().rolledDice();
|
||||
} else if (cardLayerSelect != null) {
|
||||
if (cardLayerSelect.isSelectable()) gameView.getGuiHandler().selectCard(cardLayerSelect);
|
||||
} else if (boardSelect != null) {
|
||||
if (boardSelect.isSelectable()) gameView.getBoardHandler().pieceSelect(boardSelect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (name.equals("Left")) {
|
||||
isRotateLeft = !isRotateLeft;
|
||||
}
|
||||
if (name.equals("Right")) {
|
||||
isRotateRight = !isRotateRight;
|
||||
}
|
||||
if (name.equals("Test") && isPressed) {
|
||||
if (app.getView() instanceof GameView gameView) {
|
||||
//Test Code
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles analog-based input events such as mouse movement and scrolling.
|
||||
*/
|
||||
private final AnalogListener analogListener = new AnalogListener() {
|
||||
@Override
|
||||
public void onAnalog(String name, float value, float tpf) {
|
||||
if (name.equals("MouseLeft") && rightMousePressed) {
|
||||
rotationAngle -= value * 360f;
|
||||
} else if (name.equals("MouseRight") && rightMousePressed) {
|
||||
rotationAngle += value * 360f;
|
||||
} else if (name.equals("MouseScrollUp")) {
|
||||
scrollValue = Math.max(1, scrollValue - 5);
|
||||
} else if (name.equals("MouseScrollDown")) {
|
||||
scrollValue = Math.min(100, scrollValue + 5);
|
||||
} else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("MouseVertical")) {
|
||||
hoverPiece();
|
||||
hoverCard();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Detects the hovered piece and updates its hover state.
|
||||
*/
|
||||
private <T extends AbstractControl> T checkHover(Camera cam, Node root, Class<T> controlType) {
|
||||
if (cam == null || root == null || controlType == null) return null;
|
||||
CollisionResults results = new CollisionResults();
|
||||
Ray ray = new Ray(cam.getLocation(), getMousePos(cam).subtract(cam.getLocation()).normalize());
|
||||
root.collideWith(ray, results);
|
||||
for (CollisionResult collisionResult : results) {
|
||||
if (collisionResult.getGeometry().getControl(controlType) != null)
|
||||
return collisionResult.getGeometry().getControl(controlType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the hovered card and updates its hover state.
|
||||
*/
|
||||
private <T extends AbstractControl> T checkHoverOrtho(Camera cam, Node root, Class<T> controlType) {
|
||||
if (cam == null || root == null || controlType == null) return null;
|
||||
CollisionResults results = new CollisionResults();
|
||||
Vector3f mousePos = getMousePos(cam);
|
||||
mousePos.setZ(cam.getLocation().getZ());
|
||||
Ray ray = new Ray(mousePos, getMousePos(cam).subtract(mousePos).normalize());
|
||||
root.collideWith(ray, results);
|
||||
if (results.size() > 0) {
|
||||
for (CollisionResult res : results) {
|
||||
T control = res.getGeometry().getControl(controlType);
|
||||
if (control != null) return control;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the hover state for a piece in the game.
|
||||
* Checks if a piece is being hovered over, updates the hover state, and triggers hover effects.
|
||||
*/
|
||||
private void hoverPiece() {
|
||||
if (app.getView() instanceof GameView gameView) {
|
||||
OutlineOEControl control = checkPiece();
|
||||
if (control != null) {
|
||||
if (control != hoverPiece) {
|
||||
pieceOff(gameView);
|
||||
hoverPiece = control;
|
||||
if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOn(hoverPiece);
|
||||
}
|
||||
} else {
|
||||
pieceOff(gameView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the hover state for a card in the game.
|
||||
* Checks if a card is being hovered over, updates the hover state, and triggers hover effects.
|
||||
*/
|
||||
private void hoverCard() {
|
||||
if (app.getView() instanceof GameView gameView) {
|
||||
CardControl control = checkCard(gameView);
|
||||
if (control != null) {
|
||||
if (control != hoverCard) {
|
||||
cardOff();
|
||||
hoverCard = control;
|
||||
hoverCard.hoverOn();
|
||||
}
|
||||
} else {
|
||||
cardOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a piece is being hovered over in the 3D game world.
|
||||
*
|
||||
* @return The PieceControl of the hovered piece, or null if no piece is hovered.
|
||||
*/
|
||||
private OutlineOEControl checkPiece() {
|
||||
return checkHover(app.getCamera(), app.getRootNode(), OutlineOEControl.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a card is being hovered over in the 2D card layer.
|
||||
*
|
||||
* @param gameView The current game view.
|
||||
* @return The CardControl of the hovered card, or null if no card is hovered.
|
||||
*/
|
||||
private CardControl checkCard(GameView gameView) {
|
||||
return checkHoverOrtho(
|
||||
gameView.getGuiHandler().getCardLayerCamera(),
|
||||
gameView.getGuiHandler().getCardLayerRootNode(),
|
||||
CardControl.class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the hover effect on the currently hovered piece, if any.
|
||||
*/
|
||||
private void pieceOff(GameView gameView) {
|
||||
if (hoverPiece != null) {
|
||||
if(hoverPiece.isHoverable()) gameView.getBoardHandler().hoverOff(hoverPiece);
|
||||
}
|
||||
hoverPiece = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the hover effect on the currently hovered card, if any.
|
||||
*/
|
||||
private void cardOff() {
|
||||
if (hoverCard != null) hoverCard.hoverOff();
|
||||
hoverCard = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current mouse position in the 3D world using the specified camera.
|
||||
*
|
||||
* @param cam The camera used for determining the mouse position.
|
||||
* @return A Vector3f representing the mouse position in the 3D world.
|
||||
*/
|
||||
private Vector3f getMousePos(Camera cam) {
|
||||
Vector2f mousePositionScreen = inputManager.getCursorPosition();
|
||||
Vector3f world = cam.getWorldCoordinates(mousePositionScreen, 0);
|
||||
if (cam.isParallelProjection()) world.setZ(0);
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current rotation angle of the game element.
|
||||
*
|
||||
* @return The rotation angle in degrees, normalized to 360 degrees.
|
||||
*/
|
||||
public float getRotation() {
|
||||
return (rotationAngle / 2) % 360;
|
||||
}
|
||||
|
||||
public void setRotation(float rotationAngle) {
|
||||
this.rotationAngle = rotationAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current scroll value.
|
||||
*
|
||||
* @return The scroll value as an integer.
|
||||
*/
|
||||
public int getScroll() {
|
||||
return scrollValue;
|
||||
}
|
||||
|
||||
public void setClickAllowed(boolean allowed) {
|
||||
clickAllowed = allowed;
|
||||
}
|
||||
|
||||
public boolean isClickAllowed() {
|
||||
return clickAllowed;
|
||||
}
|
||||
}
|
||||
@@ -1,432 +1,90 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.shadow.DirectionalLightShadowRenderer;
|
||||
import com.jme3.system.AppSettings;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import pp.mdga.client.acoustic.AcousticHandler;
|
||||
import pp.mdga.client.animation.TimerManager;
|
||||
import pp.mdga.client.dialog.JoinDialog;
|
||||
import pp.mdga.client.view.*;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* Main application class for the MdgaApp game.
|
||||
* This class extends {@link SimpleApplication} and manages the game's lifecycle, states, and main components.
|
||||
*/
|
||||
public class MdgaApp extends SimpleApplication {
|
||||
|
||||
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
|
||||
|
||||
/**
|
||||
* Handles acoustic effects and state-based sounds.
|
||||
*/
|
||||
private AcousticHandler acousticHandler;
|
||||
|
||||
/**
|
||||
* Synchronizes notifications throughout the application.
|
||||
*/
|
||||
private NotificationSynchronizer notificationSynchronizer;
|
||||
|
||||
/**
|
||||
* Manages input events and synchronization.
|
||||
*/
|
||||
private InputSynchronizer inputSynchronizer;
|
||||
|
||||
/**
|
||||
* Synchronizes game models.
|
||||
*/
|
||||
private ModelSynchronizer modelSynchronizer;
|
||||
|
||||
/**
|
||||
* The currently active view in the application.
|
||||
*/
|
||||
private MdgaView view = null;
|
||||
|
||||
/**
|
||||
* The current state of the application.
|
||||
*/
|
||||
private MdgaState state = null;
|
||||
|
||||
/**
|
||||
* Scale for rendering images.
|
||||
*/
|
||||
private final float imageScale = prefs.getInt("scale", 1);
|
||||
|
||||
/**
|
||||
* The main menu view.
|
||||
*/
|
||||
private MainView mainView;
|
||||
|
||||
/**
|
||||
* The lobby view.
|
||||
*/
|
||||
private LobbyView lobbyView;
|
||||
|
||||
/**
|
||||
* The game view.
|
||||
*/
|
||||
private GameView gameView;
|
||||
|
||||
/**
|
||||
* The ceremony view.
|
||||
*/
|
||||
private CeremonyView ceremonyView;
|
||||
|
||||
/**
|
||||
* The client game logic.
|
||||
*/
|
||||
private ClientGameLogic clientGameLogic;
|
||||
|
||||
private ExecutorService executor;
|
||||
|
||||
private ServerConnection networkConnection;
|
||||
|
||||
private final TimerManager timerManager = new TimerManager();
|
||||
|
||||
|
||||
public static final int DEBUG_MULTIPLIER = 1;
|
||||
|
||||
/**
|
||||
* Constructs a new MdgaApp instance.
|
||||
* Initializes the network connection and client game logic.
|
||||
*/
|
||||
public MdgaApp() {
|
||||
networkConnection = new NetworkSupport(this);
|
||||
this.clientGameLogic = new ClientGameLogic(networkConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point for the application.
|
||||
* Configures settings and starts the application.
|
||||
*
|
||||
* @param args command-line arguments (not used)
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
MdgaApp app = new MdgaApp();
|
||||
AppSettings settings = new AppSettings(true);
|
||||
settings.setSamples(128);
|
||||
|
||||
if (prefs.getBoolean("fullscreen", false)) {
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
int screenWidth = (int) screenSize.getWidth();
|
||||
int screenHeight = (int) screenSize.getHeight();
|
||||
|
||||
settings.setResolution(screenWidth, screenHeight);
|
||||
|
||||
settings.setFullscreen(true);
|
||||
} else {
|
||||
settings.setWidth(prefs.getInt("width", 1280));
|
||||
settings.setHeight(prefs.getInt("height", 720));
|
||||
}
|
||||
|
||||
settings.setCenterWindow(true);
|
||||
settings.setVSync(false);
|
||||
settings.setTitle("MDGA");
|
||||
settings.setVSync(true);
|
||||
MdgaApp app = new MdgaApp();
|
||||
settings.setWidth(1300);
|
||||
settings.setHeight(1000);
|
||||
app.setSettings(settings);
|
||||
app.setShowSettings(false);
|
||||
app.setPauseOnLostFocus(false);
|
||||
app.setDisplayStatView(false);
|
||||
|
||||
try {
|
||||
if (!System.getProperty("os.name").toLowerCase().contains("mac")) {
|
||||
settings.setIcons(new BufferedImage[]{
|
||||
ImageIO.read(Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResourceAsStream("Images/icon/icon128.png")))
|
||||
});
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
|
||||
app.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the application by setting up handlers, views, and entering the default state.
|
||||
*/
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
GuiGlobals.initialize(this);
|
||||
flyCam.setEnabled(true);
|
||||
int zoom = 20;
|
||||
cam.setLocation(new Vector3f(zoom,0,zoom));
|
||||
cam.lookAt(new Vector3f(0,0,0), new Vector3f(0,0,1));
|
||||
|
||||
inputManager.deleteMapping("SIMPLEAPP_Exit");
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
sun.setColor(ColorRGBA.White);
|
||||
sun.setDirection(new Vector3f(-1,0,-1));
|
||||
rootNode.addLight(sun);
|
||||
AmbientLight ambient = new AmbientLight();
|
||||
ambient.setColor(new ColorRGBA(0.3f,0.3f,0.3f,1));
|
||||
rootNode.addLight(ambient);
|
||||
|
||||
flyCam.setEnabled(false);
|
||||
final int SHADOWMAP_SIZE=1024*8;
|
||||
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
|
||||
dlsr.setLight(sun);
|
||||
viewPort.addProcessor(dlsr);
|
||||
|
||||
acousticHandler = new AcousticHandler(this);
|
||||
notificationSynchronizer = new NotificationSynchronizer(this);
|
||||
inputSynchronizer = new InputSynchronizer(this);
|
||||
modelSynchronizer = new ModelSynchronizer(this);
|
||||
createModel(Asset.lw).setLocalTranslation(new Vector3f(0,-10,0));
|
||||
createModel(Asset.cir).setLocalTranslation(new Vector3f(0,-8,0));
|
||||
createModel(Asset.marine).setLocalTranslation(new Vector3f(0,-6,0));
|
||||
createModel(Asset.heer).setLocalTranslation(new Vector3f(0,-4,0));
|
||||
createModel(Asset.node_normal).setLocalTranslation(new Vector3f(0,-2.5f,0));
|
||||
createModel(Asset.node_home_blue).setLocalTranslation(new Vector3f(0,-1,0));
|
||||
createModel(Asset.smallTent).setLocalTranslation(new Vector3f(0,1,0));
|
||||
createModel(Asset.tank).setLocalTranslation(new Vector3f(0,5,0));
|
||||
createModel(Asset.jet).setLocalTranslation(new Vector3f(0,12,0));
|
||||
createModel(Asset.ship).setLocalTranslation(new Vector3f(0,17,0));
|
||||
createModel(Asset.radar).setLocalTranslation(new Vector3f(0,20,0));
|
||||
|
||||
mainView = new MainView(this);
|
||||
lobbyView = new LobbyView(this);
|
||||
gameView = new GameView(this);
|
||||
ceremonyView = new CeremonyView(this);
|
||||
createModel(Asset.world);
|
||||
|
||||
enter(MdgaState.MAIN);
|
||||
System.out.println(Asset.node_normal.getModelPath());
|
||||
System.out.println(Asset.node_normal.getDiffPath());
|
||||
}
|
||||
|
||||
|
||||
private Spatial createModel(Asset asset){
|
||||
String modelName = asset.getModelPath();
|
||||
String texName = asset.getDiffPath();
|
||||
Spatial model = assetManager.loadModel(modelName);
|
||||
model.scale(asset.getSize());
|
||||
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90));
|
||||
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setTexture("DiffuseMap", assetManager.loadTexture(texName));
|
||||
model.setMaterial(mat);
|
||||
rootNode.attachChild(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the application on each frame. Updates the view, acoustic handler, and notifications.
|
||||
*
|
||||
* @param tpf time per frame, used for smooth updating
|
||||
*/
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {
|
||||
view.update(tpf);
|
||||
acousticHandler.update();
|
||||
notificationSynchronizer.update();
|
||||
inputSynchronizer.update(tpf);
|
||||
timerManager.update(tpf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions the application to a new state.
|
||||
*
|
||||
* @param state the new state to enter
|
||||
* @throws RuntimeException if attempting to enter the {@link MdgaState#NONE} state
|
||||
*/
|
||||
public void enter(MdgaState state) {
|
||||
if (null != view) {
|
||||
view.leave();
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
|
||||
switch (state) {
|
||||
case MAIN:
|
||||
view = mainView;
|
||||
clientGameLogic = new ClientGameLogic(networkConnection);
|
||||
break;
|
||||
case LOBBY:
|
||||
view = lobbyView;
|
||||
break;
|
||||
case GAME:
|
||||
view = gameView;
|
||||
break;
|
||||
case CEREMONY:
|
||||
view = ceremonyView;
|
||||
break;
|
||||
case NONE:
|
||||
throw new RuntimeException("Cannot enter state NONE");
|
||||
}
|
||||
|
||||
acousticHandler.playState(state);
|
||||
|
||||
view.enter();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the acoustic handler.
|
||||
*
|
||||
* @return the {@link AcousticHandler} instance
|
||||
*/
|
||||
public AcousticHandler getAcousticHandler() {
|
||||
return acousticHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current state of the application.
|
||||
*
|
||||
* @return the current {@link MdgaState}
|
||||
*/
|
||||
public MdgaState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image scaling factor.
|
||||
*
|
||||
* @return the image scale as a float
|
||||
*/
|
||||
public float getImageScale() {
|
||||
return imageScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently active view.
|
||||
*
|
||||
* @return the active {@link MdgaView}
|
||||
*/
|
||||
public MdgaView getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model synchronizer.
|
||||
*
|
||||
* @return the {@link ModelSynchronizer} instance
|
||||
*/
|
||||
public ModelSynchronizer getModelSynchronize() {
|
||||
return modelSynchronizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the input synchronizer.
|
||||
*
|
||||
* @return the {@link InputSynchronizer} instance
|
||||
*/
|
||||
public InputSynchronizer getInputSynchronize() {
|
||||
return inputSynchronizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the notification synchronizer.
|
||||
*
|
||||
* @return the {@link NotificationSynchronizer} instance
|
||||
*/
|
||||
public NotificationSynchronizer getNotificationSynchronizer() {
|
||||
return notificationSynchronizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the app for a new game cycle.
|
||||
*/
|
||||
public void setup() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client game logic.
|
||||
*
|
||||
* @return the {@link ClientGameLogic} instance
|
||||
*/
|
||||
public ClientGameLogic getGameLogic() {
|
||||
return clientGameLogic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the executor service.
|
||||
*
|
||||
* @return the {@link ExecutorService} instance
|
||||
*/
|
||||
public ExecutorService getExecutor() {
|
||||
if (this.executor == null) {
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
return this.executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the network connection.
|
||||
*
|
||||
* @return the {@link ServerConnection} instance
|
||||
*/
|
||||
public ServerConnection getNetworkSupport() {
|
||||
return networkConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the resolution settings.
|
||||
*
|
||||
* @param width the new width
|
||||
* @param height the new height
|
||||
* @param imageFactor the new image factor
|
||||
* @param isFullscreen whether the game is in fullscreen mode
|
||||
*/
|
||||
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
|
||||
if (isFullscreen) {
|
||||
int baseWidth = 1280;
|
||||
int baseHeight = 720;
|
||||
float baseAspectRatio = (float) baseWidth / baseHeight;
|
||||
float newAspectRatio = (float) width / height;
|
||||
|
||||
float scaleFactor = Math.max((float) width / baseWidth, (float) height / baseHeight);
|
||||
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
int screenWidth = (int) screenSize.getWidth();
|
||||
int screenHeight = (int) screenSize.getHeight();
|
||||
settings.setResolution(screenWidth, screenHeight);
|
||||
settings.setFullscreen(true);
|
||||
|
||||
prefs.putFloat("scale", scaleFactor);
|
||||
prefs.putBoolean("fullscreen", true);
|
||||
} else {
|
||||
prefs.putInt("width", width);
|
||||
prefs.putInt("height", height);
|
||||
prefs.putFloat("scale", imageFactor);
|
||||
prefs.putBoolean("fullscreen", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the application.
|
||||
*/
|
||||
public static void restartApp() {
|
||||
try {
|
||||
String javaBin = System.getProperty("java.home") + "/bin/java";
|
||||
String classPath = System.getProperty("java.class.path");
|
||||
String className = System.getProperty("sun.java.command");
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(
|
||||
javaBin, "-cp", classPath, className
|
||||
);
|
||||
|
||||
builder.start();
|
||||
|
||||
System.exit(0);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("restart failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the application after a game.
|
||||
*/
|
||||
public void afterGameCleanup() {
|
||||
MainView main = (MainView) mainView;
|
||||
|
||||
main.getJoinDialog().disconnect();
|
||||
if (clientGameLogic.isHost()) {
|
||||
main.getHostDialog().shutdownServer();
|
||||
}
|
||||
|
||||
ceremonyView.afterGameCleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the game view.
|
||||
*
|
||||
* @return the {@link GameView} instance
|
||||
*/
|
||||
public GameView getGameView() {
|
||||
return gameView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timer manager.
|
||||
*
|
||||
* @return the {@link TimerManager} instance
|
||||
*/
|
||||
public TimerManager getTimerManager() {
|
||||
return timerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ceremony view.
|
||||
*
|
||||
* @return the {@link CeremonyView} instance
|
||||
*/
|
||||
public CeremonyView getCeremonyView() {
|
||||
return ceremonyView;
|
||||
//this method will be called every game tick and can be used to make updates
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
afterGameCleanup();
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
super.destroy();
|
||||
public void simpleRender(RenderManager rm) {
|
||||
//add render code here (if any)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
/**
|
||||
* Enum representing the various states of the MdgaApp application.
|
||||
* Each state corresponds to a distinct phase or mode of the application.
|
||||
*/
|
||||
public enum MdgaState {
|
||||
|
||||
/**
|
||||
* Represents an undefined or uninitialized state.
|
||||
* This state should not be entered during normal application execution.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Represents the main menu state.
|
||||
* This is typically the first state entered when the application starts.
|
||||
*/
|
||||
MAIN,
|
||||
|
||||
/**
|
||||
* Represents the lobby state where players can prepare or wait before starting a game.
|
||||
*/
|
||||
LOBBY,
|
||||
|
||||
/**
|
||||
* Represents the main gameplay state where the core game mechanics take place.
|
||||
*/
|
||||
GAME,
|
||||
|
||||
/**
|
||||
* Represents the ceremony state, typically used for post-game events or celebrations.
|
||||
*/
|
||||
CEREMONY;
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
import pp.mdga.client.view.GameView;
|
||||
import pp.mdga.game.BonusCard;
|
||||
import pp.mdga.game.Color;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* The ModelSynchronizer class is responsible for synchronizing the model state with the view and game logic.
|
||||
*/
|
||||
public class ModelSynchronizer {
|
||||
private static final Logger LOGGER = Logger.getLogger(ModelSynchronizer.class.getName());
|
||||
private MdgaApp app;
|
||||
|
||||
private UUID a;
|
||||
private UUID b;
|
||||
private BonusCard card;
|
||||
private boolean swap;
|
||||
|
||||
/**
|
||||
* Constructor for ModelSynchronizer.
|
||||
*
|
||||
* @param app the MdgaApp instance
|
||||
*/
|
||||
ModelSynchronizer(MdgaApp app) {
|
||||
this.app = app;
|
||||
swap = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the end of an animation.
|
||||
*/
|
||||
public void animationEnd() {
|
||||
if (app.getNotificationSynchronizer().waitForAnimation) {
|
||||
app.getNotificationSynchronizer().waitForAnimation = false;
|
||||
} else {
|
||||
app.getGameLogic().selectAnimationEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a piece or swap based on the current state.
|
||||
*
|
||||
* @param a the first UUID
|
||||
* @param b the second UUID
|
||||
*/
|
||||
public void select(UUID a, UUID b) {
|
||||
if (swap) selectSwap(a, b);
|
||||
else selectPiece(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a swap between two pieces.
|
||||
*
|
||||
* @param a the first UUID
|
||||
* @param b the second UUID
|
||||
*/
|
||||
public void selectSwap(UUID a, UUID b) {
|
||||
LOGGER.log(Level.INFO, "selectPiece");
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
|
||||
GameView gameView = (GameView) app.getView();
|
||||
if (a != null && b != null) {
|
||||
gameView.needConfirm();
|
||||
} else {
|
||||
gameView.noConfirm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a single piece.
|
||||
*
|
||||
* @param piece the UUID of the piece
|
||||
*/
|
||||
public void selectPiece(UUID piece) {
|
||||
LOGGER.log(Level.INFO, "selectPiece");
|
||||
|
||||
this.a = piece;
|
||||
|
||||
GameView gameView = (GameView) app.getView();
|
||||
if (piece != null) {
|
||||
gameView.needConfirm();
|
||||
} else {
|
||||
gameView.noConfirm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a bonus card.
|
||||
*
|
||||
* @param card the BonusCard instance
|
||||
*/
|
||||
public void selectCard(BonusCard card) {
|
||||
LOGGER.log(Level.INFO, "selectCard");
|
||||
|
||||
this.card = card;
|
||||
|
||||
GameView gameView = (GameView) app.getView();
|
||||
|
||||
if (card != null) {
|
||||
gameView.needConfirm();
|
||||
} else {
|
||||
gameView.showNoPower();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the current selection.
|
||||
*/
|
||||
public void confirm() {
|
||||
LOGGER.log(Level.INFO, "confirm");
|
||||
|
||||
GameView gameView = (GameView) app.getView();
|
||||
|
||||
gameView.getGuiHandler().hideText();
|
||||
|
||||
if (a != null && b != null) {
|
||||
app.getGameLogic().selectPiece(a);
|
||||
app.getGameLogic().selectPiece(b);
|
||||
gameView.getBoardHandler().clearSelectable();
|
||||
} else if (a != null) {
|
||||
app.getGameLogic().selectPiece(a);
|
||||
gameView.getBoardHandler().clearSelectable();
|
||||
} else {
|
||||
app.getGameLogic().selectCard(card);
|
||||
gameView.getGuiHandler().clearSelectableCards();
|
||||
}
|
||||
|
||||
a = null;
|
||||
b = null;
|
||||
card = null;
|
||||
|
||||
gameView.noConfirm();
|
||||
gameView.hideNoPower();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a TSK color.
|
||||
*
|
||||
* @param color the Color instance
|
||||
*/
|
||||
public void selectTsk(Color color) {
|
||||
app.getGameLogic().selectTsk(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselects a TSK color.
|
||||
*
|
||||
* @param color the Color instance
|
||||
*/
|
||||
public void unselectTsk(Color color) {
|
||||
app.getGameLogic().deselectTSK(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event of rolling dice.
|
||||
*/
|
||||
public void rolledDice() {
|
||||
app.getGameLogic().selectDice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player's name.
|
||||
*
|
||||
* @param name the player's name
|
||||
*/
|
||||
public void setName(String name) {
|
||||
LOGGER.log(Level.INFO, "setName: {0}", name);
|
||||
app.getGameLogic().selectName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player's ready status.
|
||||
*
|
||||
* @param ready the ready status
|
||||
*/
|
||||
public void setReady(boolean ready) {
|
||||
app.getGameLogic().selectReady(ready);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the host port.
|
||||
*
|
||||
* @param port the host port
|
||||
*/
|
||||
public void setHost(int port) {
|
||||
app.getGameLogic().selectJoin("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the join IP and port.
|
||||
*
|
||||
* @param ip the IP address
|
||||
* @param port the port number
|
||||
*/
|
||||
public void setJoin(String ip, int port) {
|
||||
app.getGameLogic().selectJoin(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event of leaving the game.
|
||||
*/
|
||||
public void leave() {
|
||||
app.getGameLogic().selectLeave();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters a specific game state.
|
||||
*
|
||||
* @param state the MdgaState instance
|
||||
*/
|
||||
public void enter(MdgaState state) {
|
||||
LOGGER.log(Level.INFO, "enter: {0}", state);
|
||||
//app.enter(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proceeds to the next game state.
|
||||
*/
|
||||
public void next() {
|
||||
app.getGameLogic().selectNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the swap state.
|
||||
*
|
||||
* @param swap the swap state
|
||||
*/
|
||||
public void setSwap(boolean swap) {
|
||||
this.swap = swap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces an action.
|
||||
*/
|
||||
public void force() {
|
||||
// Implementation needed
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
import com.jme3.network.*;
|
||||
import pp.mdga.message.client.ClientMessage;
|
||||
import pp.mdga.message.server.ServerMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* The NetworkSupport class provides support for network communication between the client and server.
|
||||
* It implements the MessageListener and ClientStateListener interfaces to handle incoming messages
|
||||
* and client state changes, respectively.
|
||||
*/
|
||||
public class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
|
||||
|
||||
private static final System.Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
|
||||
private final MdgaApp app;
|
||||
private Client client;
|
||||
|
||||
/**
|
||||
* Constructor for NetworkSupport.
|
||||
*
|
||||
* @param app the MdgaApp instance
|
||||
*/
|
||||
public NetworkSupport(MdgaApp app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MdgaApp instance.
|
||||
*
|
||||
* @return the MdgaApp instance
|
||||
*/
|
||||
public MdgaApp getApp() {
|
||||
return this.app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client is connected to the server.
|
||||
*
|
||||
* @return true if the client is connected, false otherwise
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return this.client != null && this.client.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the client to the server.
|
||||
*/
|
||||
public void connect() {
|
||||
if (this.client != null) {
|
||||
throw new IllegalStateException("trying to join a game again");
|
||||
} else {
|
||||
try {
|
||||
this.initNetwork("localhost", 2345);
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(System.Logger.Level.ERROR, "could not connect to server", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the client from the server.
|
||||
*/
|
||||
public void disconnect() {
|
||||
if (this.client != null) {
|
||||
this.client.close();
|
||||
this.client = null;
|
||||
LOGGER.log(System.Logger.Level.INFO, "client closed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the network connection to the server.
|
||||
*
|
||||
* @param host the server host
|
||||
* @param port the server port
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public void initNetwork(String host, int port) throws IOException {
|
||||
if (this.client != null) {
|
||||
throw new IllegalStateException("trying to join a game again");
|
||||
} else {
|
||||
this.client = Network.connectToServer(host, port);
|
||||
this.client.start();
|
||||
this.client.addMessageListener(this);
|
||||
this.client.addClientStateListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming messages from the server.
|
||||
*
|
||||
* @param client the client
|
||||
* @param message the message
|
||||
*/
|
||||
public void messageReceived(Client client, Message message) {
|
||||
LOGGER.log(System.Logger.Level.INFO, "message received from server: {0}", new Object[]{message});
|
||||
if (message instanceof ServerMessage serverMessage) {
|
||||
this.app.enqueue(() -> serverMessage.accept(this.app.getGameLogic()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles client connection to the server.
|
||||
*
|
||||
* @param client the client
|
||||
*/
|
||||
public void clientConnected(Client client) {
|
||||
LOGGER.log(System.Logger.Level.INFO, "Client connected: {0}", new Object[]{client});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles client disconnection from the server.
|
||||
*
|
||||
* @param client the client
|
||||
* @param disconnectInfo the disconnect information
|
||||
*/
|
||||
public void clientDisconnected(Client client, ClientStateListener.DisconnectInfo disconnectInfo) {
|
||||
LOGGER.log(System.Logger.Level.INFO, "Client {0} disconnected: {1}", new Object[]{client, disconnectInfo});
|
||||
if (this.client != client) {
|
||||
throw new IllegalArgumentException("parameter value must be client");
|
||||
} else {
|
||||
LOGGER.log(System.Logger.Level.INFO, "client still connected: {0}", new Object[]{client.isConnected()});
|
||||
this.client = null;
|
||||
this.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the server.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
@Override
|
||||
public void send(ClientMessage message) {
|
||||
LOGGER.log(System.Logger.Level.INFO, "sending {0}", new Object[]{message});
|
||||
if (this.client == null) {
|
||||
LOGGER.log(System.Logger.Level.WARNING, "client not connected");
|
||||
} else {
|
||||
this.client.send(message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
import com.jme3.system.NanoTimer;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
import pp.mdga.client.board.BoardHandler;
|
||||
import pp.mdga.client.gui.GuiHandler;
|
||||
import pp.mdga.client.view.CeremonyView;
|
||||
import pp.mdga.client.view.GameView;
|
||||
import pp.mdga.client.view.LobbyView;
|
||||
import pp.mdga.game.BonusCard;
|
||||
import pp.mdga.game.Color;
|
||||
import pp.mdga.notification.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* The NotificationSynchronizer class is responsible for handling and synchronizing notifications
|
||||
* received from the game logic and updating the application state accordingly.
|
||||
*/
|
||||
public class NotificationSynchronizer {
|
||||
private final MdgaApp app;
|
||||
|
||||
private ArrayList<Notification> notifications = new ArrayList<>();
|
||||
|
||||
private NanoTimer timer = new NanoTimer();
|
||||
private float delay = 0;
|
||||
|
||||
private static final float STANDARD_DELAY = 2.5f;
|
||||
|
||||
public boolean waitForAnimation = false;
|
||||
|
||||
/**
|
||||
* Constructs a NotificationSynchronizer with the specified MdgaApp instance.
|
||||
*
|
||||
* @param app the MdgaApp instance
|
||||
*/
|
||||
NotificationSynchronizer(MdgaApp app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the notification synchronizer by processing notifications from the game logic.
|
||||
* Handles different types of notifications based on the current application state.
|
||||
*/
|
||||
public void update() {
|
||||
while (timer.getTimeInSeconds() >= delay) {
|
||||
if (waitForAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
Notification n = app.getGameLogic().getNotification();
|
||||
|
||||
if (n == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("receive notification:" + n.getClass().getName());
|
||||
|
||||
timer.reset();
|
||||
delay = 0;
|
||||
|
||||
if (n instanceof InfoNotification infoNotification) {
|
||||
app.getView().showInfo(infoNotification.getMessage(), infoNotification.isError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (n != null) {
|
||||
switch (app.getState()) {
|
||||
case MAIN:
|
||||
handleMain(n);
|
||||
break;
|
||||
case LOBBY:
|
||||
handleLobby(n);
|
||||
break;
|
||||
case GAME:
|
||||
handleGame(n);
|
||||
break;
|
||||
case CEREMONY:
|
||||
handleCeremony(n);
|
||||
break;
|
||||
case NONE:
|
||||
throw new RuntimeException("no notification expected: " + n.getClass().getName());
|
||||
}
|
||||
|
||||
if (0 == MdgaApp.DEBUG_MULTIPLIER) {
|
||||
delay = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications when the application is in the MAIN state.
|
||||
*
|
||||
* @param notification the notification to handle
|
||||
*/
|
||||
private void handleMain(Notification notification) {
|
||||
if (notification instanceof LobbyDialogNotification) {
|
||||
app.enter(MdgaState.LOBBY);
|
||||
} else if (notification instanceof StartDialogNotification) {
|
||||
//nothing
|
||||
} else {
|
||||
throw new RuntimeException("notification not expected in main: " + notification.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications when the application is in the LOBBY state.
|
||||
*
|
||||
* @param notification the notification to handle
|
||||
*/
|
||||
private void handleLobby(Notification notification) {
|
||||
LobbyView lobbyView = (LobbyView) app.getView();
|
||||
|
||||
if (notification instanceof TskSelectNotification n) {
|
||||
lobbyView.setTaken(n.getColor(), true, n.isSelf(), n.getName());
|
||||
} else if (notification instanceof StartDialogNotification) {
|
||||
app.afterGameCleanup();
|
||||
app.enter(MdgaState.MAIN);
|
||||
} else if (notification instanceof TskUnselectNotification n) {
|
||||
lobbyView.setTaken(n.getColor(), false, false, null);
|
||||
} else if (notification instanceof LobbyReadyNotification lobbyReadyNotification) {
|
||||
lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady());
|
||||
} else if (notification instanceof GameNotification n) {
|
||||
app.getGameView().setOwnColor(n.getOwnColor());
|
||||
app.enter(MdgaState.GAME);
|
||||
} else {
|
||||
throw new RuntimeException("notification not expected in lobby: " + notification.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications when the application is in the GAME state.
|
||||
*
|
||||
* @param notification the notification to handle
|
||||
*/
|
||||
private void handleGame(Notification notification) {
|
||||
GameView gameView = (GameView) app.getView();
|
||||
GuiHandler guiHandler = gameView.getGuiHandler();
|
||||
BoardHandler boardHandler = gameView.getBoardHandler();
|
||||
ModelSynchronizer modelSynchronizer = app.getModelSynchronize();
|
||||
Color ownColor = gameView.getOwnColor();
|
||||
|
||||
if (notification instanceof AcquireCardNotification n) {
|
||||
guiHandler.addCardOwn(n.getBonusCard());
|
||||
app.getAcousticHandler().playSound(MdgaSound.BONUS);
|
||||
delay = STANDARD_DELAY;
|
||||
} else if (notification instanceof RankingResponceNotification n) {
|
||||
guiHandler.hideText();
|
||||
n.getRankingResults().forEach((c, i) -> {
|
||||
guiHandler.rollRankingResult(c, i);
|
||||
});
|
||||
delay = STANDARD_DELAY;
|
||||
} else if (notification instanceof ActivePlayerNotification n) {
|
||||
guiHandler.hideText();
|
||||
boardHandler.hideDice();
|
||||
gameView.getGuiHandler().setActivePlayer(n.getColor());
|
||||
if (n.getColor() != ownColor) boardHandler.showDice(n.getColor());
|
||||
app.getAcousticHandler().playSound(MdgaSound.UI90);
|
||||
delay = STANDARD_DELAY;
|
||||
} else if (notification instanceof CeremonyNotification ceremonyNotification) {
|
||||
CeremonyView ceremonyView = app.getCeremonyView();
|
||||
int size = ceremonyNotification.getNames().size();
|
||||
|
||||
if (ceremonyNotification.getPiecesThrown().size() != size ||
|
||||
ceremonyNotification.getPiecesLost().size() != size ||
|
||||
ceremonyNotification.getBonusCardsPlayed().size() != size ||
|
||||
ceremonyNotification.getSixes().size() != size ||
|
||||
ceremonyNotification.getNodesMoved().size() != size ||
|
||||
ceremonyNotification.getBonusNodes().size() != size) {
|
||||
throw new IllegalArgumentException("All data lists in CeremonyNotification must have the same size.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
Color color = ceremonyNotification.getColors().get(i);
|
||||
String name = ceremonyNotification.getNames().get(i);
|
||||
int v1 = ceremonyNotification.getPiecesThrown().get(i);
|
||||
int v2 = ceremonyNotification.getPiecesLost().get(i);
|
||||
int v3 = ceremonyNotification.getBonusCardsPlayed().get(i);
|
||||
int v4 = ceremonyNotification.getSixes().get(i);
|
||||
int v5 = ceremonyNotification.getNodesMoved().get(i);
|
||||
int v6 = ceremonyNotification.getBonusNodes().get(i);
|
||||
|
||||
if(i < size - 1) {
|
||||
ceremonyView.addCeremonyParticipant(color, i + 1, name);
|
||||
}
|
||||
|
||||
ceremonyView.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
|
||||
}
|
||||
app.enter(MdgaState.CEREMONY);
|
||||
} else if (notification instanceof DiceNowNotification) {
|
||||
guiHandler.hideText();
|
||||
guiHandler.showDice();
|
||||
} else if (notification instanceof DrawCardNotification n) {
|
||||
app.getAcousticHandler().playSound(MdgaSound.BONUS);
|
||||
guiHandler.drawCard(n.getColor());
|
||||
delay = STANDARD_DELAY;
|
||||
} else if (notification instanceof HomeMoveNotification home) {
|
||||
boardHandler.movePieceHome(home.getPieceId(), home.getHomeIndex());
|
||||
guiHandler.hideText();
|
||||
waitForAnimation = true;
|
||||
} else if (notification instanceof InterruptNotification notification1) {
|
||||
gameView.enterInterrupt(notification1.getColor());
|
||||
} else if (notification instanceof MovePieceNotification n) {
|
||||
if (n.isMoveStart()) {
|
||||
//StartMove
|
||||
boardHandler.movePieceStart(n.getPiece(), n.getMoveIndex());
|
||||
waitForAnimation = true;
|
||||
} else {
|
||||
//InfieldMove
|
||||
boardHandler.movePiece(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
|
||||
waitForAnimation = true;
|
||||
}
|
||||
guiHandler.hideText();
|
||||
} else if (notification instanceof ThrowPieceNotification n) {
|
||||
boardHandler.throwPiece(n.getPieceId(), n.getThrowColor());
|
||||
waitForAnimation = true;
|
||||
} else if (notification instanceof RemoveShieldNotification n) {
|
||||
boardHandler.unshieldPiece(n.getPieceUuid());
|
||||
} else if (notification instanceof PlayCardNotification n) {
|
||||
if (n.getCard() == BonusCard.TURBO) {
|
||||
app.getAcousticHandler().playSound(MdgaSound.TURBO);
|
||||
guiHandler.turbo();
|
||||
} else if (n.getCard() == BonusCard.SHIELD) {
|
||||
app.getAcousticHandler().playSound(MdgaSound.SHIELD);
|
||||
} else if (n.getCard() == BonusCard.SWAP) {
|
||||
app.getAcousticHandler().playSound(MdgaSound.SWAP);
|
||||
}
|
||||
if (n.getColor() == ownColor) guiHandler.playCardOwn(n.getCard());
|
||||
else guiHandler.playCardEnemy(n.getColor(), n.getCard());
|
||||
|
||||
app.getTimerManager().addTask(
|
||||
2.2f * MdgaApp.DEBUG_MULTIPLIER,
|
||||
app.getModelSynchronize()::animationEnd
|
||||
);
|
||||
} else if (notification instanceof PlayerInGameNotification n) {
|
||||
boardHandler.addPlayer(n.getColor(), n.getPiecesList());
|
||||
guiHandler.addPlayer(n.getColor(), n.getName());
|
||||
} else if (notification instanceof ResumeNotification) {
|
||||
gameView.leaveInterrupt();
|
||||
} else if (notification instanceof RollDiceNotification n) {
|
||||
gameView.getGuiHandler().hideText();
|
||||
if (n.getColor() == ownColor) {
|
||||
guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1);
|
||||
waitForAnimation = true;
|
||||
} else {
|
||||
if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor());
|
||||
else guiHandler.showRolledDice(n.getEyes(), n.getColor());
|
||||
}
|
||||
} else if (notification instanceof SelectableCardsNotification n) {
|
||||
guiHandler.setSelectableCards(n.getCards());
|
||||
gameView.showNoPower();
|
||||
} else if (notification instanceof ShieldActiveNotification n) {
|
||||
boardHandler.shieldPiece(n.getPieceId());
|
||||
} else if (notification instanceof ShieldSuppressedNotification n) {
|
||||
boardHandler.suppressShield(n.getPieceId());
|
||||
} else if (notification instanceof StartDialogNotification) {
|
||||
app.afterGameCleanup();
|
||||
app.enter(MdgaState.MAIN);
|
||||
} else if (notification instanceof SwapPieceNotification n) {
|
||||
boardHandler.swapPieces(n.getFirstPiece(), n.getSecondPiece());
|
||||
guiHandler.swap();
|
||||
} else if (notification instanceof WaitMoveNotification) {
|
||||
//nothing
|
||||
} else if (notification instanceof SelectableMoveNotification n) {
|
||||
boardHandler.setSelectableMove(n.getPieces(), n.getMoveIndices(), n.getHomeMoves());
|
||||
modelSynchronizer.setSwap(false);
|
||||
} else if (notification instanceof SelectableSwapNotification n) {
|
||||
boardHandler.setSelectableSwap(n.getOwnPieces(), n.getEnemyPieces());
|
||||
modelSynchronizer.setSwap(true);
|
||||
} else if (notification instanceof SelectableShieldNotification n) {
|
||||
boardHandler.setSelectableShield(n.getPieces());
|
||||
modelSynchronizer.setSwap(false);
|
||||
} else if (notification instanceof TurboActiveNotification) {
|
||||
//nothing
|
||||
} else if (notification instanceof FinishNotification n) {
|
||||
guiHandler.finish(n.getColorFinished());
|
||||
} else {
|
||||
throw new RuntimeException("notification not expected in game: " + notification.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications when the application is in the CEREMONY state.
|
||||
*
|
||||
* @param notification the notification to handle
|
||||
*/
|
||||
private void handleCeremony(Notification notification) {
|
||||
if (notification instanceof StartDialogNotification) {
|
||||
app.afterGameCleanup();
|
||||
app.enter(MdgaState.MAIN);
|
||||
} else {
|
||||
throw new RuntimeException("notification not expected in ceremony: " + notification.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package pp.mdga.client;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
public class Util {
|
||||
private Util() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs linear interpolation between two values.
|
||||
*
|
||||
* @param start The starting value.
|
||||
* @param end The ending value.
|
||||
* @param t A parameter between 0 and 1 representing the interpolation progress.
|
||||
* @return The interpolated value.
|
||||
*/
|
||||
public static float linInt(float start, float end, float t) {
|
||||
return start + t * (end - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs quadratic interpolation between three points.
|
||||
*
|
||||
* @param p1 The initial point.
|
||||
* @param p2 The middle point.
|
||||
* @param p3 The final point.
|
||||
* @param t The interpolation parameter (0 <= t <= 1).
|
||||
* @return The interpolated point.
|
||||
*/
|
||||
public static Vector3f quadInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
|
||||
// Quadratic interpolation: (1-t)^2 * p1 + 2 * (1-t) * t * p2 + t^2 * p3
|
||||
float oneMinusT = 1 - t;
|
||||
return p1.mult(oneMinusT * oneMinusT)
|
||||
.add(p2.mult(2 * oneMinusT * t))
|
||||
.add(p3.mult(t * t));
|
||||
}
|
||||
|
||||
/**
|
||||
* A smooth ease-in-out function for interpolation.
|
||||
* It accelerates and decelerates the interpolation for a smoother effect.
|
||||
*
|
||||
* @param x The interpolation parameter (0 <= x <= 1).
|
||||
* @return The adjusted interpolation value.
|
||||
*/
|
||||
public static float easeInOut(float x) {
|
||||
return x < 0.5 ? 4 * x * x * x : (float) (1 - Math.pow(-2 * x + 2, 3) / 2);
|
||||
}
|
||||
}
|
||||
@@ -1,502 +0,0 @@
|
||||
package pp.mdga.client.acoustic;
|
||||
|
||||
import com.jme3.system.NanoTimer;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.MdgaState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public class AcousticHandler {
|
||||
private MdgaApp app;
|
||||
|
||||
private MdgaState state = MdgaState.NONE;
|
||||
|
||||
private boolean playGame = false;
|
||||
private ArrayList<MusicAsset> gameTracks = new ArrayList<>();
|
||||
private NanoTimer trackTimer = new NanoTimer();
|
||||
|
||||
private boolean fading = false; // Indicates if a fade is in progress
|
||||
private NanoTimer fadeTimer = new NanoTimer(); // Timer to track fade progress
|
||||
private static final float FADE_DURATION = 2.0f; // Duration for outfade
|
||||
private static final float CROSSFADE_DURATION = 1.5f; // Duration for infade
|
||||
private GameMusic playing = null; // Currently playing track
|
||||
private GameMusic scheduled = null; // Scheduled track to play next
|
||||
private GameMusic old = null; // Old track being faded out
|
||||
|
||||
private GameMusic birds;
|
||||
|
||||
private float mainVolume = 0.0f;
|
||||
private float musicVolume = 1.0f;
|
||||
private float soundVolume = 1.0f;
|
||||
|
||||
private ArrayList<GameSound> sounds = new ArrayList<>();
|
||||
|
||||
private Preferences prefs = Preferences.userNodeForPackage(AcousticHandler.class);
|
||||
|
||||
public AcousticHandler(MdgaApp app) {
|
||||
this.app = app;
|
||||
|
||||
mainVolume = prefs.getFloat("mainVolume", 1.0f);
|
||||
musicVolume = prefs.getFloat("musicVolume", 1.0f);
|
||||
soundVolume = prefs.getFloat("soundVolume", 1.0f);
|
||||
|
||||
birds = new GameMusic(app, MusicAsset.BIRDS, getSoundVolumeTotal(), MusicAsset.BIRDS.getSubVolume(), MusicAsset.BIRDS.getLoop(), 0.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method updates the acousticHandler and should be called every frame
|
||||
*/
|
||||
public void update() {
|
||||
updateVolumeAndTrack();
|
||||
|
||||
if (playGame) {
|
||||
updateGameTracks();
|
||||
}
|
||||
|
||||
Iterator<GameSound> iterator = sounds.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
GameSound s = iterator.next();
|
||||
|
||||
s.update(getSoundVolumeTotal());
|
||||
|
||||
if (!s.isPlaying()) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
birds.update(Math.min(getSoundVolumeTotal(), getMusicVolumeTotal() > 0 ? 0 : 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method instantly plays a sound
|
||||
*
|
||||
* @param sound the sound to be played
|
||||
*/
|
||||
public void playSound(MdgaSound sound) {
|
||||
ArrayList<SoundAssetDelayVolume> assets = new ArrayList<SoundAssetDelayVolume>();
|
||||
switch (sound) {
|
||||
case LOST:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.LOST, 1.0f, 0.0f));
|
||||
break;
|
||||
case VICTORY:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.VICTORY, 1.0f, 0.0f));
|
||||
break;
|
||||
case BUTTON_PRESSED:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.BUTTON_PRESS, 0.7f, 0.0f));
|
||||
break;
|
||||
case WRONG_INPUT:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.ERROR, 1.0f, 0.0f));
|
||||
break;
|
||||
case UI_CLICK:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_CLICK, 0.8f, 0.0f));
|
||||
break;
|
||||
case START:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.START, 0.8f, 0.5f));
|
||||
break;
|
||||
case THROW:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.LAUGHT, 1.0f, 0.2f));
|
||||
break;
|
||||
case POWERUP:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.POWERUP, 1.0f, 0.2f));
|
||||
break;
|
||||
case SELF_READY:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.ROBOT_READY, 1.0f, 0.0f));
|
||||
break;
|
||||
case OTHER_READY:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.UNIT_READY, 1.0f, 0.0f));
|
||||
break;
|
||||
case OTHER_CONNECTED:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.CONNECTED, 1.0f, 0.0f));
|
||||
break;
|
||||
case NOT_READY:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_SOUND, 1.0f, 0.0f));
|
||||
break;
|
||||
case LEAVE:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_SOUND2, 0.6f, 0.0f));
|
||||
break;
|
||||
case JET:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.JET, 1.0f, 0.0f));
|
||||
break;
|
||||
case EXPLOSION:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_2, 1.0f, 0f));
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.THUNDER, 1.0f, 0f));
|
||||
break;
|
||||
case LOSE:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.LOSE, 1.0f, 0.0f));
|
||||
break;
|
||||
case BONUS:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.BONUS, 1.0f, 0.0f));
|
||||
break;
|
||||
case UI90:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.UI90, 1.0f, 0.0f));
|
||||
break;
|
||||
case MISSILE:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.MISSILE, 1.0f, 0.0f));
|
||||
break;
|
||||
case MATRIX:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.MATRIX, 1.0f, 0.0f));
|
||||
break;
|
||||
case TURRET_ROTATE:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.TURRET_ROTATE, 0.7f, 0f));
|
||||
break;
|
||||
case TANK_SHOOT:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.TANK_SHOOT, 0.7f, 0f));
|
||||
break;
|
||||
case TANK_EXPLOSION:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
|
||||
break;
|
||||
case SHIELD:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.SHIELD, 1.0f, 0f));
|
||||
break;
|
||||
case TURBO:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 0.1f));
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.SPEED, 1.0f, 1.3f));
|
||||
break;
|
||||
case SWAP:
|
||||
assets.add(new SoundAssetDelayVolume(SoundAsset.SWAP, 1.0f, 0f));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (SoundAssetDelayVolume sawd : assets) {
|
||||
GameSound gameSound = new GameSound(app, sawd.asset(), getSoundVolumeTotal(), sawd.subVolume(), sawd.delay());
|
||||
sounds.add(gameSound);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fades the played music to fit the state.
|
||||
*
|
||||
* @param state the state of which the corresponding music should be played to be played
|
||||
*/
|
||||
public void playState(MdgaState state) {
|
||||
if (this.state == state) {
|
||||
return;
|
||||
}
|
||||
MusicAsset asset = null;
|
||||
|
||||
birds.pause();
|
||||
|
||||
float pause = 0.0f;
|
||||
|
||||
switch (state) {
|
||||
case MAIN:
|
||||
playGame = false;
|
||||
asset = MusicAsset.MAIN_MENU;
|
||||
break;
|
||||
case LOBBY:
|
||||
playGame = false;
|
||||
asset = MusicAsset.LOBBY;
|
||||
break;
|
||||
case GAME:
|
||||
birds.play();
|
||||
addGameTracks();
|
||||
playGame = true;
|
||||
assert (!gameTracks.isEmpty()) : "no more game music available";
|
||||
asset = gameTracks.remove(0);
|
||||
pause = 2.0f;
|
||||
break;
|
||||
case CEREMONY:
|
||||
playGame = false;
|
||||
asset = MusicAsset.CEREMONY;
|
||||
break;
|
||||
case NONE:
|
||||
throw new RuntimeException("no music for state NONE");
|
||||
}
|
||||
|
||||
assert (null != asset) : "music sceduling went wrong";
|
||||
|
||||
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), pause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs linear interpolation between two float values.
|
||||
*
|
||||
* @param start The starting value.
|
||||
* @param end The ending value.
|
||||
* @param t The interpolation factor, typically between 0 and 1.
|
||||
* @return The interpolated value between start and end.
|
||||
*/
|
||||
private float lerp(float start, float end, float t) {
|
||||
return start + t * (end - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of audio playback, handling track transitions and volume adjustments.
|
||||
* <p>
|
||||
* This method ensures smooth transitions between tracks using fade-in and fade-out effects.
|
||||
* It also handles cases where no track is playing, starting a scheduled track immediately at full volume.
|
||||
* The method prioritizes the latest scheduled track if multiple scheduling occurs quickly.
|
||||
* <p>
|
||||
* Behavior:
|
||||
* 1. If nothing is scheduled and no track is playing, it exits early.
|
||||
* 2. If a scheduled track exists and no track is playing, the scheduled track starts immediately at full volume.
|
||||
* 3. If a scheduled track exists while a track is playing, it initiates a fade-out for the currently playing track
|
||||
* and prepares for the new track to fade in.
|
||||
* 4. If a track transition is in progress (fading), it processes the fade-out and fade-in states.
|
||||
* If a new track is scheduled during this process, it interrupts the current transition and prioritizes the new track.
|
||||
* 5. If no fading is needed and a track is playing, it ensures the track's volume is updated.
|
||||
* <p>
|
||||
* Special cases:
|
||||
* - If no track is playing and a new track is scheduled, it starts the track immediately without fading.
|
||||
* - If a new track is scheduled during fading, it resets the transition to prioritize the new track.
|
||||
*/
|
||||
private void updateVolumeAndTrack() {
|
||||
if (scheduled == null && !fading && playing == null) {
|
||||
// Nothing to do, early exit
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduled != null && playing == null && !fading) {
|
||||
// No current track, start scheduled track immediately at full volume
|
||||
playing = scheduled;
|
||||
scheduled = null;
|
||||
playing.play();
|
||||
playing.update(getMusicVolumeTotal()); // Set volume to full
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduled != null && !fading) {
|
||||
// Initiate a fade process if a new track is scheduled
|
||||
fading = true;
|
||||
fadeTimer.reset();
|
||||
old = playing; // The currently playing track becomes the old track
|
||||
playing = null; // Clear the playing track during the fade process
|
||||
}
|
||||
|
||||
if (fading) {
|
||||
handleFadeProcess();
|
||||
|
||||
// Handle any interruptions due to newly scheduled tracks
|
||||
if (scheduled != null && playing != null && playing != scheduled) {
|
||||
// Interrupt the current infade and switch to the new scheduled track
|
||||
old = playing; // Treat the currently infading track as the old track
|
||||
playing = null; // Reset playing to allow switching
|
||||
fadeTimer.reset(); // Restart fade timer for the new track
|
||||
}
|
||||
} else if (playing != null) {
|
||||
// Update volume for the currently playing track
|
||||
playing.update(getMusicVolumeTotal());
|
||||
} else if (scheduled != null) {
|
||||
// If no track is playing and one is scheduled, start it immediately at full volume
|
||||
playing = scheduled;
|
||||
scheduled = null;
|
||||
playing.play();
|
||||
playing.update(getMusicVolumeTotal()); // Set volume to full
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the fading process during audio track transitions.
|
||||
* <p>
|
||||
* This method handles the fade-out of the currently playing (old) track, manages any pause between the fade-out
|
||||
* and fade-in, and initiates the fade-in for the new track if applicable. It ensures smooth transitions between
|
||||
* tracks while maintaining the correct volume adjustments.
|
||||
* <p>
|
||||
* Behavior:
|
||||
* 1. **Outfade:** Gradually decreases the volume of the `old` track over the duration of `FADE_DURATION`.
|
||||
* Once the outfade completes, the `old` track is paused and cleared.
|
||||
* 2. **Pause Handling:** Waits for a defined pause (if applicable) before initiating the infade for the next track.
|
||||
* 3. **Infade:** If a `scheduled` track exists and the outfade and pause are complete, it begins playing
|
||||
* the new track (`playing`) and initiates the infade process.
|
||||
* <p>
|
||||
* Key Details:
|
||||
* - The outfade volume adjustment is interpolated linearly from full volume to zero using the `lerp` function.
|
||||
* - The pause duration is retrieved from the scheduled track if it is specified.
|
||||
* - If a new track is scheduled during the fade process, it is handled by external logic to prioritize transitions.
|
||||
* <p>
|
||||
* Preconditions:
|
||||
* - `fading` is expected to be `true` when this method is called.
|
||||
* - The method is invoked as part of the `updateVolumeAndTrack` process.
|
||||
*/
|
||||
private void handleFadeProcess() {
|
||||
float time = fadeTimer.getTimeInSeconds();
|
||||
|
||||
// Handle outfade for the old track
|
||||
if (old != null && time <= FADE_DURATION) {
|
||||
float t = Math.min(time / FADE_DURATION, 1.0f);
|
||||
float oldVolume = lerp(1.0f, 0.0f, t);
|
||||
old.update(getMusicVolumeTotal() * oldVolume);
|
||||
}
|
||||
|
||||
if (old != null && time > FADE_DURATION) {
|
||||
// Complete outfade
|
||||
old.pause();
|
||||
old = null;
|
||||
}
|
||||
|
||||
// Handle pause duration before infade
|
||||
float pause = (scheduled != null) ? scheduled.getPause() : 0.0f;
|
||||
if (time > FADE_DURATION + pause) {
|
||||
if (playing == null && scheduled != null) {
|
||||
// Begin infade for the new track
|
||||
playing = scheduled;
|
||||
scheduled = null;
|
||||
playing.play(); // Start playing the new track
|
||||
}
|
||||
handleInfade(time - FADE_DURATION - pause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the fade-in process for the currently playing track.
|
||||
* <p>
|
||||
* This method gradually increases the volume of the `playing` track from zero to full volume
|
||||
* over the duration of `CROSSFADE_DURATION`. It ensures a smooth transition into the new track.
|
||||
* <p>
|
||||
* Behavior:
|
||||
* 1. If no track is set as `playing`, the method exits early, as there is nothing to fade in.
|
||||
* 2. Linearly interpolates the volume of the `playing` track from 0.0 to 1.0 based on the elapsed
|
||||
* `infadeTime` and the specified `CROSSFADE_DURATION`.
|
||||
* 3. Once the fade-in is complete (when `infadeTime` exceeds `CROSSFADE_DURATION`), the method:
|
||||
* - Marks the fade process (`fading`) as complete.
|
||||
* - Ensures the `playing` track is updated to its full volume.
|
||||
* <p>
|
||||
* Key Details:
|
||||
* - Uses the `lerp` function to calculate the volume level for the `playing` track during the fade-in.
|
||||
* - Ensures the volume is always a value between 0.0 and 1.0.
|
||||
* - The `infadeTime` parameter should be relative to the start of the fade-in process.
|
||||
* <p>
|
||||
* Preconditions:
|
||||
* - The `playing` track must be initialized and actively fading in for this method to have an effect.
|
||||
* - The method is invoked as part of the `updateVolumeAndTrack` process.
|
||||
*
|
||||
* @param infadeTime The elapsed time (in seconds) since the fade-in process started.
|
||||
*/
|
||||
private void handleInfade(float infadeTime) {
|
||||
if (playing == null) {
|
||||
// Nothing to infade
|
||||
return;
|
||||
}
|
||||
|
||||
// Proceed with the infade for the current playing track
|
||||
float t = Math.min(infadeTime / CROSSFADE_DURATION, 1.0f);
|
||||
float newVolume = lerp(0.0f, 1.0f, t);
|
||||
playing.update(getMusicVolumeTotal() * newVolume);
|
||||
|
||||
if (infadeTime > CROSSFADE_DURATION) {
|
||||
// Infade is complete, finalize state
|
||||
fading = false;
|
||||
playing.update(getMusicVolumeTotal()); // Ensure full volume
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list of game tracks to the gameTracks collection and shuffles them.
|
||||
* This method adds predefined game tracks to the track list and shuffles the order.
|
||||
*/
|
||||
private void addGameTracks() {
|
||||
Random random = new Random();
|
||||
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
gameTracks.add(MusicAsset.valueOf("GAME_" + i));
|
||||
}
|
||||
Collections.shuffle(gameTracks, random);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current game tracks. If the currently playing track is nearing its end,
|
||||
* a new track will be scheduled to play. If the list of game tracks is empty, it will be refreshed.
|
||||
*/
|
||||
private void updateGameTracks() {
|
||||
if (null == playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playing.nearEnd(10)) {
|
||||
if (gameTracks.isEmpty()) {
|
||||
addGameTracks();
|
||||
}
|
||||
}
|
||||
|
||||
if (playing != null && playing.nearEnd(3) && trackTimer.getTimeInSeconds() > 20) {
|
||||
trackTimer.reset();
|
||||
|
||||
MusicAsset nextTrack = gameTracks.remove(0);
|
||||
|
||||
scheduled = new GameMusic(app, nextTrack, getMusicVolumeTotal(), nextTrack.getSubVolume(), nextTrack.getLoop(), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the main volume level.
|
||||
*
|
||||
* @return The current main volume level.
|
||||
*/
|
||||
public float getMainVolume() {
|
||||
return mainVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the music volume level.
|
||||
*
|
||||
* @return The current music volume level.
|
||||
*/
|
||||
public float getMusicVolume() {
|
||||
return musicVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sound volume level.
|
||||
*
|
||||
* @return The current sound volume level.
|
||||
*/
|
||||
public float getSoundVolume() {
|
||||
return soundVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main volume level.
|
||||
*
|
||||
* @param mainVolume The desired main volume level.
|
||||
*/
|
||||
public void setMainVolume(float mainVolume) {
|
||||
this.mainVolume = mainVolume;
|
||||
prefs.putFloat("mainVolume", mainVolume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the music volume level.
|
||||
*
|
||||
* @param musicVolume The desired music volume level.
|
||||
*/
|
||||
public void setMusicVolume(float musicVolume) {
|
||||
this.musicVolume = musicVolume;
|
||||
prefs.putFloat("musicVolume", musicVolume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sound volume level.
|
||||
*
|
||||
* @param soundVolume The desired sound volume level.
|
||||
*/
|
||||
public void setSoundVolume(float soundVolume) {
|
||||
this.soundVolume = soundVolume;
|
||||
prefs.putFloat("soundVolume", soundVolume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total music volume by multiplying the music volume by the main volume.
|
||||
*
|
||||
* @return The total music volume.
|
||||
*/
|
||||
float getMusicVolumeTotal() {
|
||||
|
||||
return getMusicVolume() * getMainVolume() / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total sound volume by multiplying the sound volume by the main volume.
|
||||
*
|
||||
* @return The total sound volume.
|
||||
*/
|
||||
float getSoundVolumeTotal() {
|
||||
return getSoundVolume() * getMainVolume();
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package pp.mdga.client.acoustic;
|
||||
|
||||
import com.jme3.audio.AudioData;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.audio.AudioSource;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents a game music track, including its playback controls and volume settings.
|
||||
* This class manages the playback of a music track, allowing for playing, pausing,
|
||||
* volume adjustment, and tracking the current status of the music.
|
||||
*/
|
||||
class GameMusic {
|
||||
private float volume;
|
||||
private final float subVolume;
|
||||
private final AudioNode music;
|
||||
private float pause;
|
||||
|
||||
/**
|
||||
* Constructs a new GameMusic object.
|
||||
*
|
||||
* @param app The instance of the application, used to access the asset manager.
|
||||
* @param asset The music asset to be played.
|
||||
* @param volume The total volume of the music, adjusted by the main volume.
|
||||
* @param subVolume A relative volume that modifies the base music volume, typically a percentage.
|
||||
* @param loop A flag indicating whether the music should loop once it finishes.
|
||||
*/
|
||||
GameMusic(MdgaApp app, MusicAsset asset, float volume, float subVolume, boolean loop, float pause) {
|
||||
this.volume = volume;
|
||||
this.subVolume = subVolume;
|
||||
this.pause = pause;
|
||||
|
||||
music = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Stream);
|
||||
music.setPositional(false);
|
||||
music.setDirectional(false);
|
||||
music.setVolume(volume * subVolume);
|
||||
|
||||
music.setLooping(loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the current music track.
|
||||
* If the music is already initialized, it starts playback.
|
||||
* If the music is not available, no action is performed.
|
||||
*/
|
||||
void play() {
|
||||
if (null == music) {
|
||||
return;
|
||||
}
|
||||
|
||||
music.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the current music track.
|
||||
* If the music is not available or is not playing, no action is performed.
|
||||
*/
|
||||
void pause() {
|
||||
if (null == music) {
|
||||
return;
|
||||
}
|
||||
|
||||
music.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current music track is playing.
|
||||
*
|
||||
* @return true if the music is playing, false otherwise.
|
||||
*/
|
||||
boolean isPlaying() {
|
||||
|
||||
return music.getStatus() == AudioSource.Status.Playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current music track is near the end.
|
||||
*
|
||||
* @param thresholdSeconds The threshold in seconds. If the remaining time is less than or equal to this value,
|
||||
* the track is considered near the end.
|
||||
* @return true if the track is near its end (within the threshold), false otherwise.
|
||||
*/
|
||||
boolean nearEnd(float thresholdSeconds) {
|
||||
if (music == null || !isPlaying()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float currentTime = music.getPlaybackTime(); // Current playback time in seconds
|
||||
float duration = music.getAudioData().getDuration(); // Total duration in seconds
|
||||
|
||||
if (duration <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float remainingTime = duration - currentTime;
|
||||
|
||||
return remainingTime <= thresholdSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the volume of the music.
|
||||
* If the volume has changed, it will adjust the music's volume accordingly.
|
||||
*
|
||||
* @param newVolume The new total volume for the music.
|
||||
*/
|
||||
void update(float newVolume) {
|
||||
if (volume != newVolume) {
|
||||
volume = newVolume;
|
||||
music.setVolume(volume * subVolume);
|
||||
}
|
||||
}
|
||||
|
||||
float getPause() {
|
||||
return pause;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package pp.mdga.client.acoustic;
|
||||
|
||||
import com.jme3.audio.AudioData;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.audio.AudioSource;
|
||||
import com.jme3.system.NanoTimer;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents a game sound effect, with control over playback, volume, and timing.
|
||||
* This class manages the playback of a sound effect, including starting playback after a delay,
|
||||
* adjusting volume, and tracking whether the sound has finished playing.
|
||||
*/
|
||||
class GameSound {
|
||||
private float volume;
|
||||
final private float subVolume;
|
||||
|
||||
private final AudioNode sound;
|
||||
|
||||
private boolean playing = false;
|
||||
private boolean finished = false;
|
||||
|
||||
private float delay = 0.0f;
|
||||
private NanoTimer timer = null;
|
||||
|
||||
/**
|
||||
* Constructs a new GameSound object.
|
||||
*
|
||||
* @param app The instance of the application, used to access the asset manager.
|
||||
* @param asset The sound asset to be played.
|
||||
* @param volume The total volume of the sound, adjusted by the main volume.
|
||||
* @param subVolume A relative volume that modifies the base sound volume, typically a percentage.
|
||||
* @param delay The delay before the sound starts playing, in seconds.
|
||||
*/
|
||||
GameSound(MdgaApp app, SoundAsset asset, float volume, float subVolume, float delay) {
|
||||
this.volume = volume;
|
||||
this.subVolume = subVolume;
|
||||
this.delay = delay;
|
||||
|
||||
sound = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Buffer);
|
||||
sound.setPositional(false);
|
||||
sound.setDirectional(false);
|
||||
sound.setLooping(false);
|
||||
sound.setVolume(volume * subVolume);
|
||||
|
||||
timer = new NanoTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the sound is currently playing.
|
||||
*
|
||||
* @return true if the sound is playing, false otherwise.
|
||||
*/
|
||||
boolean isPlaying() {
|
||||
return !finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sound playback, adjusting the volume if necessary, and starting
|
||||
* the sound after the specified delay.
|
||||
*
|
||||
* @param newVolume The new total volume for the sound.
|
||||
*/
|
||||
void update(float newVolume) {
|
||||
if (!playing && timer.getTimeInSeconds() > delay) {
|
||||
sound.play();
|
||||
playing = true;
|
||||
}
|
||||
|
||||
if (!playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (volume != newVolume) {
|
||||
volume = newVolume;
|
||||
sound.setVolume(volume * subVolume);
|
||||
}
|
||||
|
||||
if (sound != null && sound.getStatus() == AudioSource.Status.Playing) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package pp.mdga.client.acoustic;
|
||||
|
||||
/**
|
||||
* Enum representing the various sound effects used in the game.
|
||||
* Each sound corresponds to an event or action in the game and may consist of one or more
|
||||
* audio files, potentially with time delays between them.
|
||||
* <p>
|
||||
* These sounds are used to play specific audio cues, such as when a dice is rolled,
|
||||
* a turn starts or ends, a piece is moved or lost, and various other events in the game.
|
||||
*/
|
||||
public enum MdgaSound {
|
||||
DICE_ROLL,
|
||||
TURN_START,
|
||||
TURN_END,
|
||||
PIECE_END,
|
||||
PIECE_MOVE,
|
||||
PIECE_LOST,
|
||||
SELECT,
|
||||
DESELECT,
|
||||
HURRY,
|
||||
VICTORY,
|
||||
LOST,
|
||||
BUTTON_PRESSED,
|
||||
WRONG_INPUT,
|
||||
UI_CLICK,
|
||||
START,
|
||||
THROW,
|
||||
POWERUP,
|
||||
SELF_READY,
|
||||
OTHER_READY,
|
||||
OTHER_CONNECTED,
|
||||
NOT_READY,
|
||||
LEAVE,
|
||||
JET,
|
||||
EXPLOSION,
|
||||
LOSE,
|
||||
BONUS,
|
||||
UI90,
|
||||
MISSILE,
|
||||
MATRIX,
|
||||
TURRET_ROTATE,
|
||||
TANK_SHOOT,
|
||||
TANK_EXPLOSION,
|
||||
SHIELD,
|
||||
TURBO,
|
||||
SWAP,
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package pp.mdga.client.acoustic;
|
||||
|
||||
/**
|
||||
* Enum representing various music assets used in the game.
|
||||
* Each constant corresponds to a specific music track, along with its properties such as file path,
|
||||
* looping behavior, and relative volume (subVolume).
|
||||
* These music assets are used to control the music that plays in different parts of the game, such as menus and in-game music.
|
||||
*/
|
||||
enum MusicAsset {
|
||||
MAIN_MENU("Spaceship.ogg", true, 1.0f),
|
||||
LOBBY("DeadPlanet.ogg", true, 1.0f),
|
||||
CEREMONY("80s,Disco,Life.ogg", true, 1.0f),
|
||||
GAME_1("NeonRoadTrip.ogg", 0.5f),
|
||||
GAME_2("NoPressureTrance.ogg", 0.5f),
|
||||
GAME_3("TheSynthRave.ogg", 0.5f),
|
||||
GAME_4("LaserParty.ogg", 0.5f),
|
||||
GAME_5("RetroNoir.ogg", 0.5f),
|
||||
GAME_6("SpaceInvaders.ogg", 0.5f),
|
||||
BIRDS("nature-ambience.ogg", true, 1.0f);
|
||||
|
||||
private final String path;
|
||||
private final boolean loop;
|
||||
private final float subVolume;
|
||||
private static final String ROOT = "Music/";
|
||||
|
||||
/**
|
||||
* Constructs a new MusicAsset object with the specified name and sub-volume.
|
||||
* The track will not loop by default.
|
||||
*
|
||||
* @param name The name of the music file.
|
||||
* @param subVolume A relative volume that modifies the base volume of the track (typically a percentage).
|
||||
*/
|
||||
MusicAsset(String name, float subVolume) {
|
||||
this.path = ROOT + name;
|
||||
this.loop = false;
|
||||
this.subVolume = subVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new MusicAsset object with the specified name, loop flag, and sub-volume.
|
||||
*
|
||||
* @param name The name of the music file.
|
||||
* @param loop If true, the track will loop; otherwise, it will play once.
|
||||
* @param subVolume A relative volume that modifies the base volume of the track (typically a percentage).
|
||||
*/
|
||||
MusicAsset(String name, boolean loop, float subVolume) {
|
||||
this.path = ROOT + name;
|
||||
this.loop = loop;
|
||||
this.subVolume = subVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path of the music track.
|
||||
*
|
||||
* @return The path to the music file (relative to the music folder).
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the music track should loop.
|
||||
*
|
||||
* @return true if the track should loop, false otherwise.
|
||||
*/
|
||||
public boolean getLoop() {
|
||||
return loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relative volume (subVolume) for the music track.
|
||||
*
|
||||
* @return The relative volume for the track, typically a value between 0.0 and 1.0.
|
||||
*/
|
||||
public float getSubVolume() {
|
||||
return subVolume;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package pp.mdga.client.acoustic;
|
||||
|
||||
/**
|
||||
* Enum representing various sound assets used in the game.
|
||||
* Each constant corresponds to a specific sound effect used throughout the game.
|
||||
* These sounds are associated with various events and actions, such as dice rolls,
|
||||
* turn changes, piece movements, and game outcomes.
|
||||
*/
|
||||
enum SoundAsset {
|
||||
DICE_ROLL(""),
|
||||
TURN_START(""),
|
||||
TURN_END(""),
|
||||
PIECE_END(""),
|
||||
PIECE_MOVE(""),
|
||||
PIECE_LOST(""),
|
||||
SELECT(""),
|
||||
DESELECT(""),
|
||||
HURRY(""),
|
||||
VICTORY("LevelUp2.wav"),
|
||||
LOST("GameOver.wav"),
|
||||
BUTTON_PRESS("menu_button.ogg"),
|
||||
ERROR("buzzer.wav"),
|
||||
UI_SOUND("ui_sound.ogg"),
|
||||
UI_SOUND2("ui_swoosch.wav"),
|
||||
UI_CLICK("uiclick.ogg"),
|
||||
START("gamestart.ogg"),
|
||||
LAUGHT("laughter.wav"),
|
||||
POWERUP("powerup.wav"),
|
||||
ROBOT_READY("robotReady.wav"),
|
||||
UNIT_READY("unitReady.wav"),
|
||||
JET("jet-overhead.wav"),
|
||||
EXPLOSION_1("exp.ogg"),
|
||||
EXPLOSION_2("exp2.ogg"),
|
||||
THUNDER("thunder.ogg"),
|
||||
UI90("ui90.ogg"),
|
||||
BONUS("bonus.ogg"),
|
||||
LOSE("lose.ogg"),
|
||||
MISSILE("missile.ogg"),
|
||||
MATRIX("matrix.wav"),
|
||||
CONNECTED("connected.wav"),
|
||||
TURRET_ROTATE("turret_rotate.ogg"),
|
||||
TANK_SHOOT("tank_shoot.ogg"),
|
||||
SHIELD("shield.ogg"),
|
||||
SPEED("speed.ogg"),
|
||||
SWAP("swap.ogg"),
|
||||
;
|
||||
|
||||
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Constructs a new SoundAsset object with the specified name.
|
||||
*
|
||||
* @param name The name of the sound file.
|
||||
*/
|
||||
SoundAsset(String name) {
|
||||
this.path = "Sounds/" + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path of the sound effect.
|
||||
*
|
||||
* @return The path to the sound file (relative to the sound folder).
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package pp.mdga.client.acoustic;
|
||||
|
||||
/**
|
||||
* A record that encapsulates a sound asset along with its playback settings:
|
||||
* the relative volume (subVolume) and a delay before it starts playing.
|
||||
*/
|
||||
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import pp.mdga.client.InitControl;
|
||||
|
||||
public class ActionControl extends InitControl {
|
||||
private final Runnable runnable;
|
||||
|
||||
/**
|
||||
* Constructs a new ActionControl object with the specified action.
|
||||
*
|
||||
* @param runnable The action to be performed.
|
||||
*/
|
||||
public ActionControl(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the action associated with this control.
|
||||
*/
|
||||
protected void action() {
|
||||
if (null != runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh.Type;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
|
||||
/**
|
||||
* The {@code Explosion} class represents an explosion effect in a 3D environment.
|
||||
* It manages the creation, configuration, and triggering of particle emitters for fire and smoke effects.
|
||||
*/
|
||||
public class Explosion {
|
||||
|
||||
private final Node rootNode;
|
||||
private final MdgaApp app;
|
||||
private final Vector3f location;
|
||||
private ParticleEmitter fire;
|
||||
private ParticleEmitter smoke;
|
||||
|
||||
private boolean triggered = false;
|
||||
|
||||
private final Material fireMat;
|
||||
private final Material smokeMat;
|
||||
|
||||
/**
|
||||
* Constructor for the {@code Explosion} class.
|
||||
*
|
||||
* @param app The main application managing the explosion.
|
||||
* @param rootNode The root node to which the explosion effects will be attached.
|
||||
* @param location The location of the explosion in world coordinates.
|
||||
*/
|
||||
public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
|
||||
this.app = app;
|
||||
this.rootNode = rootNode;
|
||||
this.location = location;
|
||||
|
||||
this.fireMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
this.smokeMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
fireMat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/flame.png"));
|
||||
smokeMat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the particle emitters for the explosion effect.
|
||||
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
|
||||
*/
|
||||
private void initializeEmitter() {
|
||||
fire = new ParticleEmitter("Effect", Type.Triangle, 50);
|
||||
fire.setMaterial(fireMat);
|
||||
fire.setImagesX(2);
|
||||
fire.setImagesY(2);
|
||||
fire.setSelectRandomImage(true);
|
||||
fire.setStartColor(ColorRGBA.Yellow);
|
||||
fire.setEndColor(ColorRGBA.Red);
|
||||
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
|
||||
fire.getParticleInfluencer().setVelocityVariation(0.4f);
|
||||
fire.setStartSize(0.7f);
|
||||
fire.setEndSize(1.8f);
|
||||
fire.setGravity(0, 0, -0.1f);
|
||||
fire.setLowLife(0.5f);
|
||||
fire.setHighLife(2.2f);
|
||||
fire.setParticlesPerSec(0);
|
||||
|
||||
fire.setLocalTranslation(location);
|
||||
|
||||
smoke = new ParticleEmitter("Effect2", Type.Triangle, 40);
|
||||
smoke.setMaterial(smokeMat);
|
||||
smoke.setImagesX(3);
|
||||
smoke.setImagesY(3);
|
||||
smoke.setSelectRandomImage(true);
|
||||
smoke.setStartColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f));
|
||||
smoke.setEndColor(new ColorRGBA(ColorRGBA.Black));
|
||||
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 1.5f));
|
||||
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
|
||||
smoke.setStartSize(0.8f);
|
||||
smoke.setEndSize(1.5f);
|
||||
smoke.setGravity(0, 0, -0.3f);
|
||||
smoke.setLowLife(1.2f);
|
||||
smoke.setHighLife(5.5f);
|
||||
smoke.setParticlesPerSec(0);
|
||||
|
||||
smoke.setLocalTranslation(location);
|
||||
|
||||
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
|
||||
* Both emitters are automatically detached after a predefined duration.
|
||||
*/
|
||||
public void trigger() {
|
||||
if (!triggered) {
|
||||
triggered = true;
|
||||
initializeEmitter();
|
||||
}
|
||||
|
||||
rootNode.attachChild(fire);
|
||||
fire.emitAllParticles();
|
||||
fire.addControl(new AbstractControl() {
|
||||
private float elapsedTime = 0;
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
elapsedTime += tpf;
|
||||
if (elapsedTime > 10f) {
|
||||
rootNode.detachChild(fire);
|
||||
fire.removeControl(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
|
||||
}
|
||||
});
|
||||
|
||||
rootNode.attachChild(smoke);
|
||||
smoke.emitAllParticles();
|
||||
smoke.addControl(new AbstractControl() {
|
||||
private float elapsedTime = 0;
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
elapsedTime += tpf;
|
||||
if (elapsedTime > 10f) {
|
||||
rootNode.detachChild(smoke);
|
||||
smoke.removeControl(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
|
||||
import static pp.mdga.client.Util.linInt;
|
||||
|
||||
public class FadeControl extends ActionControl {
|
||||
private float duration; // Duration of the fade effect
|
||||
private float timeElapsed = 0;
|
||||
private boolean init = false;
|
||||
private float startAlpha;
|
||||
private float endAlpha;
|
||||
|
||||
public FadeControl(float duration, float startAlpha, float endAlpha, Runnable actionAfter) {
|
||||
super(actionAfter);
|
||||
this.duration = duration;
|
||||
this.startAlpha = startAlpha;
|
||||
this.endAlpha = endAlpha;
|
||||
}
|
||||
|
||||
public FadeControl(float duration, float startAlpha, float endAlpha) {
|
||||
this(duration, startAlpha, endAlpha, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initSpatial() {
|
||||
init = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (!init) return;
|
||||
|
||||
timeElapsed += tpf;
|
||||
float t = timeElapsed / duration; // Calculate progress (0 to 1)
|
||||
|
||||
if (t >= 1) {
|
||||
// Fade complete
|
||||
t = 1;
|
||||
init = false;
|
||||
spatial.removeControl(this);
|
||||
action();
|
||||
}
|
||||
|
||||
float alpha = linInt(startAlpha, endAlpha, t); // Interpolate alpha
|
||||
|
||||
// Update the material's alpha
|
||||
if (spatial instanceof Geometry geometry) {
|
||||
Material mat = geometry.getMaterial();
|
||||
if (mat != null) {
|
||||
ColorRGBA diffuse = (ColorRGBA) mat.getParam("Diffuse").getValue();
|
||||
mat.setColor("Diffuse", new ColorRGBA(diffuse.r, diffuse.g, diffuse.b, alpha));
|
||||
|
||||
ColorRGBA ambient = (ColorRGBA) mat.getParam("Ambient").getValue();
|
||||
mat.setColor("Ambient", new ColorRGBA(ambient.r, ambient.g, ambient.b, alpha));
|
||||
|
||||
// Disable shadows when the object is nearly invisible
|
||||
if (alpha <= 0.1f) {
|
||||
geometry.setShadowMode(RenderQueue.ShadowMode.Off);
|
||||
} else {
|
||||
geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
}
|
||||
} else throw new RuntimeException("Material is null");
|
||||
} else throw new RuntimeException("Spatial is not instance of Geometry");
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import pp.mdga.client.Asset;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
|
||||
/**
|
||||
* The {@code JetAnimation} class handles the animation of a jet model in a 3D environment.
|
||||
* It creates a jet model, animates its movement along a curved path, triggers an explosion at a target point,
|
||||
* and performs additional actions upon animation completion.
|
||||
*/
|
||||
public class JetAnimation {
|
||||
|
||||
private final MdgaApp app;
|
||||
private final Node rootNode;
|
||||
private Spatial jetModel;
|
||||
private final Vector3f spawnPoint;
|
||||
private final Vector3f nodePoint;
|
||||
private final Vector3f despawnPoint;
|
||||
private final float curveHeight;
|
||||
private final float animationDuration;
|
||||
private Explosion explosion;
|
||||
private Runnable actionAfter;
|
||||
|
||||
/**
|
||||
* Constructor for the {@code JetAnimation} class.
|
||||
*
|
||||
* @param app The main application managing the jet animation.
|
||||
* @param rootNode The root node to which the jet model will be attached.
|
||||
* @param targetPoint The target point where the explosion will occur.
|
||||
* @param curveHeight The height of the curve for the jet's flight path.
|
||||
* @param animationDuration The total duration of the jet animation.
|
||||
*/
|
||||
public JetAnimation(MdgaApp app, Node rootNode, Vector3f targetPoint, float curveHeight, float animationDuration, Runnable actionAfter) {
|
||||
Vector3f spawnPoint = targetPoint.add(170, 50, 50);
|
||||
|
||||
Vector3f controlPoint = targetPoint.add(new Vector3f(0, 0, -45));
|
||||
|
||||
Vector3f despawnPoint = targetPoint.add(-100, -100, 40);
|
||||
|
||||
this.app = app;
|
||||
this.rootNode = rootNode;
|
||||
this.spawnPoint = spawnPoint;
|
||||
this.nodePoint = controlPoint;
|
||||
this.despawnPoint = despawnPoint;
|
||||
this.curveHeight = curveHeight;
|
||||
this.animationDuration = animationDuration;
|
||||
|
||||
explosion = new Explosion(app, rootNode, targetPoint);
|
||||
this.actionAfter = actionAfter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the jet animation by spawning the jet model and initiating its movement along the predefined path.
|
||||
*/
|
||||
public void start() {
|
||||
app.getAcousticHandler().playSound(MdgaSound.JET);
|
||||
spawnJet();
|
||||
animateJet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns the jet model at the designated spawn point, applying material, scaling, and rotation.
|
||||
*/
|
||||
private void spawnJet() {
|
||||
jetModel = app.getAssetManager().loadModel(Asset.jet_noGear.getModelPath());
|
||||
jetModel.setLocalTranslation(spawnPoint);
|
||||
jetModel.scale(Asset.jet_noGear.getSize());
|
||||
jetModel.rotate(FastMath.HALF_PI, 0, 0);
|
||||
jetModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.jet_noGear.getDiffPath()));
|
||||
jetModel.setMaterial(mat);
|
||||
|
||||
rootNode.attachChild(jetModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* actionAfter
|
||||
* Animates the jet along a Bezier curve path, triggers the explosion effect at the appropriate time,
|
||||
* and performs cleanup operations after the animation completes.
|
||||
*/
|
||||
private void animateJet() {
|
||||
Vector3f controlPoint1 = spawnPoint.add(0, curveHeight, 0);
|
||||
Vector3f controlPoint2 = nodePoint.add(0, curveHeight, 0);
|
||||
|
||||
BezierCurve3f curve = new BezierCurve3f(spawnPoint, controlPoint1, controlPoint2, despawnPoint);
|
||||
|
||||
app.getRootNode().addControl(new AbstractControl() {
|
||||
private float elapsedTime = 0;
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
elapsedTime += tpf;
|
||||
float progress = elapsedTime / animationDuration;
|
||||
|
||||
if (elapsedTime > 4.2f) {
|
||||
explosion.trigger();
|
||||
}
|
||||
|
||||
if (progress > 1) {
|
||||
rootNode.detachChild(jetModel);
|
||||
this.spatial.removeControl(this);
|
||||
} else {
|
||||
Vector3f currentPos = curve.interpolate(progress);
|
||||
Vector3f direction = curve.interpolateDerivative(progress).normalizeLocal();
|
||||
jetModel.setLocalTranslation(currentPos);
|
||||
jetModel.lookAt(currentPos.add(direction), Vector3f.UNIT_Z);
|
||||
jetModel.rotate(-FastMath.HALF_PI, 0, (float) Math.toRadians(-25));
|
||||
}
|
||||
|
||||
if (elapsedTime > 6.0f) {
|
||||
endAnim();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void endAnim() {
|
||||
actionAfter.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code BezierCurve3f} class represents a 3D cubic Bezier curve.
|
||||
* It provides methods to interpolate positions and derivatives along the curve.
|
||||
*/
|
||||
private static class BezierCurve3f {
|
||||
private final Vector3f p0, p1, p2, p3;
|
||||
|
||||
/**
|
||||
* Constructor for the {@code BezierCurve3f} class.
|
||||
*
|
||||
* @param p0 The starting point of the curve.
|
||||
* @param p1 The first control point influencing the curve's shape.
|
||||
* @param p2 The second control point influencing the curve's shape.
|
||||
* @param p3 The endpoint of the curve.
|
||||
*/
|
||||
public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
|
||||
this.p0 = p0;
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
this.p3 = p3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a position along the curve at a given progress value {@code t}.
|
||||
*
|
||||
* @param t The progress value (0.0 to 1.0) along the curve.
|
||||
* @return The interpolated position on the curve.
|
||||
*/
|
||||
public Vector3f interpolate(float t) {
|
||||
float u = 1 - t;
|
||||
float tt = t * t;
|
||||
float uu = u * u;
|
||||
float uuu = uu * u;
|
||||
float ttt = tt * t;
|
||||
|
||||
Vector3f point = p0.mult(uuu);
|
||||
point = point.add(p1.mult(3 * uu * t));
|
||||
point = point.add(p2.mult(3 * u * tt));
|
||||
point = point.add(p3.mult(ttt));
|
||||
return point;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the derivative at a given progress value {@code t}, representing the direction along the curve.
|
||||
*
|
||||
* @param t The progress value (0.0 to 1.0) along the curve.
|
||||
* @return The derivative (direction vector) at the specified progress.
|
||||
*/
|
||||
public Vector3f interpolateDerivative(float t) {
|
||||
float u = 1 - t;
|
||||
float tt = t * t;
|
||||
|
||||
Vector3f derivative = p0.mult(-3 * u * u);
|
||||
derivative = derivative.add(p1.mult(3 * u * u - 6 * u * t));
|
||||
derivative = derivative.add(p2.mult(6 * u * t - 3 * tt));
|
||||
derivative = derivative.add(p3.mult(3 * tt));
|
||||
return derivative;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh.Type;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* MatrixAnimation class handles the animation of radar and matrix particle effects.
|
||||
*/
|
||||
public class MatrixAnimation extends ActionControl {
|
||||
private MdgaApp app;
|
||||
private static final Random RANDOM = new Random();
|
||||
private Vector3f radarPos;
|
||||
private Runnable runnable;
|
||||
private boolean init = false;
|
||||
private List<ParticleEmitter> activeEmitter = new ArrayList<>();
|
||||
private ParticleEmitter radarEmitter = null;
|
||||
private float timeElapsed = 0f;
|
||||
|
||||
/**
|
||||
* Enum representing the states of the matrix animation.
|
||||
*/
|
||||
private enum MatrixState {
|
||||
RADAR_ON,
|
||||
RADAR_OFF,
|
||||
MATRIX_ON,
|
||||
MATRIX_OFF
|
||||
}
|
||||
|
||||
private MatrixState state;
|
||||
|
||||
/**
|
||||
* Constructor for MatrixAnimation.
|
||||
*
|
||||
* @param app the application instance
|
||||
* @param radarPos the position of the radar
|
||||
* @param runnable the runnable action to be executed
|
||||
*/
|
||||
public MatrixAnimation(MdgaApp app, Vector3f radarPos, Runnable runnable) {
|
||||
super(runnable);
|
||||
this.app = app;
|
||||
this.radarPos = radarPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the spatial and sets the initial state to RADAR_ON.
|
||||
*/
|
||||
@Override
|
||||
protected void initSpatial() {
|
||||
state = MatrixState.RADAR_ON;
|
||||
timeElapsed = 0;
|
||||
init = true;
|
||||
radar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the control based on the time per frame (tpf).
|
||||
*
|
||||
* @param tpf the time per frame
|
||||
*/
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (!init) return;
|
||||
|
||||
timeElapsed += tpf;
|
||||
|
||||
switch (state) {
|
||||
case RADAR_ON -> {
|
||||
if (timeElapsed >= 2f) {
|
||||
state = MatrixState.RADAR_OFF;
|
||||
timeElapsed = 0;
|
||||
radarEmitter.setParticlesPerSec(0);
|
||||
app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
|
||||
app.getRootNode().detachChild(radarEmitter);
|
||||
System.out.println("delete radar");
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
}
|
||||
case RADAR_OFF -> {
|
||||
if (timeElapsed >= 0.1f) {
|
||||
state = MatrixState.MATRIX_ON;
|
||||
timeElapsed = 0;
|
||||
matrix();
|
||||
}
|
||||
}
|
||||
case MATRIX_ON -> {
|
||||
if (timeElapsed >= 3f) {
|
||||
state = MatrixState.MATRIX_OFF;
|
||||
timeElapsed = 0;
|
||||
turnOff();
|
||||
app.getTimerManager().addTask(3f, () -> app.enqueue(() -> {
|
||||
for (ParticleEmitter particleEmitter : activeEmitter) {
|
||||
app.getRootNode().detachChild(particleEmitter);
|
||||
}
|
||||
System.out.println("delete particle");
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
}
|
||||
case MATRIX_OFF -> {
|
||||
if (timeElapsed >= 0.5f) {
|
||||
init = false;
|
||||
spatial.removeControl(this);
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off all active particle emitters.
|
||||
*/
|
||||
private void turnOff() {
|
||||
for (ParticleEmitter particleEmitter : activeEmitter) {
|
||||
particleEmitter.setParticlesPerSec(0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the radar particle emitter.
|
||||
*/
|
||||
private void radar() {
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/radar_beam.png"));
|
||||
ParticleEmitter emitter = new ParticleEmitter("Effect", Type.Triangle, 50);
|
||||
emitter.setMaterial(mat);
|
||||
emitter.setImagesX(1); // columns
|
||||
emitter.setImagesY(1); // rows
|
||||
emitter.setSelectRandomImage(true);
|
||||
emitter.setStartColor(ColorRGBA.White);
|
||||
emitter.setEndColor(ColorRGBA.Black);
|
||||
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, 2));
|
||||
emitter.getParticleInfluencer().setVelocityVariation(0f);
|
||||
emitter.setStartSize(0.1f);
|
||||
emitter.setEndSize(10);
|
||||
emitter.setGravity(0, 0, 0);
|
||||
float life = 2.6f;
|
||||
emitter.setLowLife(life);
|
||||
emitter.setHighLife(life);
|
||||
emitter.setLocalTranslation(radarPos.add(new Vector3f(0, 0, 5)));
|
||||
emitter.setParticlesPerSec(1.8f);
|
||||
app.getRootNode().attachChild(emitter);
|
||||
radarEmitter = emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes multiple matrix particle streams.
|
||||
*/
|
||||
private void matrix() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
particleStream(
|
||||
generateMatrixColor(),
|
||||
generateMatrixColor(),
|
||||
getRandomFloat(0, 1f),
|
||||
getRandomPosition(),
|
||||
getRandomFloat(1, 2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a particle stream with the specified parameters.
|
||||
*
|
||||
* @param start the start color of the particles
|
||||
* @param end the end color of the particles
|
||||
* @param speedVar the speed variation of the particles
|
||||
* @param pos the position of the particles
|
||||
* @param spawnVar the spawn rate variation of the particles
|
||||
*/
|
||||
private void particleStream(ColorRGBA start, ColorRGBA end, float speedVar, Vector3f pos, float spawnVar) {
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/particle_cir.png"));
|
||||
ParticleEmitter matrix = new ParticleEmitter("Effect", Type.Triangle, 50);
|
||||
matrix.setMaterial(mat);
|
||||
matrix.setImagesX(2); // columns
|
||||
matrix.setImagesY(1); // rows
|
||||
matrix.setSelectRandomImage(true);
|
||||
matrix.setStartColor(start);
|
||||
matrix.setEndColor(end);
|
||||
matrix.getParticleInfluencer().setInitialVelocity(new Vector3f(0f, 0f, -6f - speedVar));
|
||||
matrix.getParticleInfluencer().setVelocityVariation(0f);
|
||||
matrix.setStartSize(0.4f);
|
||||
matrix.setEndSize(0.6f);
|
||||
matrix.setGravity(0, 0, 2f);
|
||||
matrix.setLowLife(3f);
|
||||
matrix.setHighLife(3f);
|
||||
matrix.setLocalTranslation(spatial.getLocalTranslation().add(pos).add(new Vector3f(0, 0, 15)));
|
||||
matrix.setParticlesPerSec(spawnVar);
|
||||
app.getRootNode().attachChild(matrix);
|
||||
activeEmitter.add(matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random position vector.
|
||||
*
|
||||
* @return a random position vector
|
||||
*/
|
||||
public static Vector3f getRandomPosition() {
|
||||
// Generate a random angle in radians (0 to 2π)
|
||||
float angle = (float) (2 * Math.PI * RANDOM.nextDouble());
|
||||
|
||||
// Generate a random radius with uniform distribution
|
||||
float radius = (float) Math.sqrt(RANDOM.nextDouble());
|
||||
radius *= 1f;
|
||||
|
||||
// Convert polar coordinates to Cartesian
|
||||
float x = radius * (float) Math.cos(angle);
|
||||
float y = radius * (float) Math.sin(angle);
|
||||
|
||||
return new Vector3f(x, y, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random float between the specified start and end values.
|
||||
*
|
||||
* @param start the start value
|
||||
* @param end the end value
|
||||
* @return a random float between start and end
|
||||
*/
|
||||
public static float getRandomFloat(float start, float end) {
|
||||
if (start > end) {
|
||||
throw new IllegalArgumentException("Start must be less than or equal to end.");
|
||||
}
|
||||
return start + RANDOM.nextFloat() * (end - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random color for the matrix particles.
|
||||
*
|
||||
* @return a random ColorRGBA object
|
||||
*/
|
||||
public static ColorRGBA generateMatrixColor() {
|
||||
// Red is dominant
|
||||
float red = 0.8f + RANDOM.nextFloat() * 0.2f; // Red channel: 0.8 to 1.0
|
||||
// Green is moderately high
|
||||
float green = 0.4f + RANDOM.nextFloat() * 0.3f; // Green channel: 0.4 to 0.7
|
||||
// Blue is minimal
|
||||
float blue = RANDOM.nextFloat() * 0.2f; // Blue channel: 0.0 to 0.2
|
||||
float alpha = 1.0f; // Fully opaque
|
||||
|
||||
return new ColorRGBA(red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import pp.mdga.client.Asset;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
import pp.mdga.client.board.BoardHandler;
|
||||
|
||||
/**
|
||||
* The {@code MissileAnimation} class handles the animation of a missile moving along a parabolic path
|
||||
* towards a target point in a 3D environment. It also triggers an explosion at the target upon impact.
|
||||
*/
|
||||
public class MissileAnimation {
|
||||
|
||||
private final Node rootNode;
|
||||
private final MdgaApp app;
|
||||
private final Vector3f start;
|
||||
private final Vector3f target;
|
||||
private final float flightTime;
|
||||
private Explosion explosion;
|
||||
private Spatial missileModel;
|
||||
private Runnable actionAfter;
|
||||
private ParticleEmitter smoke;
|
||||
|
||||
private Node missileNode = new Node();
|
||||
|
||||
private final Material mat;
|
||||
|
||||
/**
|
||||
* Constructor for the {@code MissileAnimation} class.
|
||||
*
|
||||
* @param app The main application managing the missile animation.
|
||||
* @param rootNode The root node to which the missile model will be attached.
|
||||
* @param target The target point where the missile will explode.
|
||||
* @param flightTime The total flight time of the missile.
|
||||
*/
|
||||
public MissileAnimation(MdgaApp app, Node rootNode, Vector3f target, float flightTime, Runnable actionAfter) {
|
||||
this.app = app;
|
||||
this.rootNode = rootNode;
|
||||
this.flightTime = flightTime;
|
||||
this.actionAfter = actionAfter;
|
||||
|
||||
explosion = new Explosion(app, rootNode, target);
|
||||
|
||||
this.target = target.add(new Vector3f(1.5f, -1, 0));
|
||||
|
||||
|
||||
start = BoardHandler.gridToWorld(12, 0);
|
||||
start.add(new Vector3f(0, 0, 0));
|
||||
|
||||
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
|
||||
|
||||
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 400);
|
||||
smoke.setMaterial(mat);
|
||||
smoke.setImagesX(3);
|
||||
smoke.setImagesY(3);
|
||||
smoke.setStartColor(ColorRGBA.DarkGray);
|
||||
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
|
||||
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f, 0.0f, 0.0f));
|
||||
smoke.getParticleInfluencer().setVelocityVariation(0.1f);
|
||||
smoke.setStartSize(0.8f);
|
||||
smoke.setEndSize(1.5f);
|
||||
smoke.setGravity(0, 0, -0.3f);
|
||||
smoke.setLowLife(1.2f);
|
||||
smoke.setHighLife(3.5f);
|
||||
smoke.setParticlesPerSec(100);
|
||||
missileNode.attachChild(smoke);
|
||||
smoke.move(1, 0.85f, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the missile animation by loading the missile model and initiating its parabolic movement.
|
||||
*/
|
||||
public void start() {
|
||||
Smoke s = new Smoke(app, rootNode, start);
|
||||
s.trigger();
|
||||
loadMissile();
|
||||
app.getAcousticHandler().playSound(MdgaSound.MISSILE);
|
||||
animateMissile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the missile model into the scene, applies scaling, material, and sets its initial position.
|
||||
*/
|
||||
private void loadMissile() {
|
||||
missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath());
|
||||
missileModel.scale(Asset.missile.getSize());
|
||||
missileModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.missile.getDiffPath()));
|
||||
missileModel.setMaterial(mat);
|
||||
|
||||
missileNode.setLocalTranslation(start);
|
||||
missileNode.attachChild(missileModel);
|
||||
|
||||
rootNode.attachChild(missileNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the missile along a parabolic path, triggers the explosion near the target,
|
||||
* and removes the missile model after the animation completes.
|
||||
*/
|
||||
private void animateMissile() {
|
||||
missileNode.addControl(new AbstractControl() {
|
||||
private float elapsedTime = 0;
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (elapsedTime > 6) {
|
||||
endAnim();
|
||||
rootNode.detachChild(missileNode);
|
||||
this.spatial.removeControl(this);
|
||||
}
|
||||
|
||||
elapsedTime += tpf;
|
||||
float progress = elapsedTime / flightTime;
|
||||
|
||||
if (progress >= 0.55) {
|
||||
smoke.setParticlesPerSec(30);
|
||||
}
|
||||
|
||||
if (progress >= 0.7) {
|
||||
smoke.setParticlesPerSec(0);
|
||||
}
|
||||
|
||||
if (progress >= 0.95f) {
|
||||
explosion.trigger();
|
||||
}
|
||||
|
||||
if (progress >= 1) {
|
||||
explosion.trigger();
|
||||
missileNode.detachChild(missileModel);
|
||||
}
|
||||
|
||||
Vector3f currentPosition = computeParabolicPath(start, target, progress);
|
||||
missileNode.setLocalTranslation(currentPosition);
|
||||
|
||||
Vector3f direction = computeParabolicPath(start, target, progress + 0.01f)
|
||||
.subtract(currentPosition)
|
||||
.normalizeLocal();
|
||||
missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y);
|
||||
missileModel.rotate(0, FastMath.HALF_PI, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void endAnim() {
|
||||
actionAfter.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a position along a parabolic path at a given progress value {@code t}.
|
||||
*
|
||||
* @param start The starting point of the missile's flight.
|
||||
* @param target The target point of the missile's flight.
|
||||
* @param t The progress value (0.0 to 1.0) along the flight path.
|
||||
* @return The interpolated position along the parabolic path.
|
||||
*/
|
||||
private Vector3f computeParabolicPath(Vector3f start, Vector3f target, float t) {
|
||||
Vector3f midPoint = start.add(target).multLocal(0.5f);
|
||||
midPoint.addLocal(0, 0, 20);
|
||||
|
||||
Vector3f startToMid = FastMath.interpolateLinear(t, start, midPoint);
|
||||
Vector3f midToTarget = FastMath.interpolateLinear(t, midPoint, target);
|
||||
return FastMath.interpolateLinear(t, startToMid, midToTarget);
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import static pp.mdga.client.Util.easeInOut;
|
||||
import static pp.mdga.client.Util.quadInt;
|
||||
|
||||
/**
|
||||
* A control that smoothly moves a spatial from an initial position to an end position
|
||||
* using a quadratic interpolation, with the option to perform an action after the movement is complete.
|
||||
* The movement path includes an intermediate "middle" position at a specified height.
|
||||
*
|
||||
* <p>Movement speed can be adjusted by modifying the MOVE_SPEED constant. The movement easing follows
|
||||
* an ease-in-out curve to create a smooth start and stop effect.
|
||||
* </p>
|
||||
*/
|
||||
public class MoveControl extends ActionControl {
|
||||
|
||||
private boolean moving;
|
||||
private final Vector3f initPos;
|
||||
private final Vector3f endPos;
|
||||
private final Vector3f middlePos;
|
||||
private final float height;
|
||||
private final float duration;
|
||||
private float timer = 0;
|
||||
private boolean easing;
|
||||
|
||||
/**
|
||||
* Creates a new MoveControl with specified initial and end positions, and an action to run after the movement.
|
||||
* The movement follows a path with a midpoint at a fixed height.
|
||||
*
|
||||
* @param initPos The starting position of the spatial.
|
||||
* @param endPos The target position of the spatial.
|
||||
* @param actionAfter A Runnable that will be executed after the movement finishes.
|
||||
*/
|
||||
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter) {
|
||||
this(initPos, endPos, actionAfter, 2, 1, true);
|
||||
}
|
||||
|
||||
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter, float height, float duration, boolean easing) {
|
||||
super(actionAfter);
|
||||
moving = false;
|
||||
this.initPos = initPos;
|
||||
this.endPos = endPos;
|
||||
this.height = height;
|
||||
this.duration = duration;
|
||||
this.easing = easing;
|
||||
middlePos = new Vector3f(
|
||||
(initPos.x + endPos.x) / 2,
|
||||
(initPos.y + endPos.y) / 2,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the movement by resetting the progress and setting the moving flag to true.
|
||||
* This is called automatically when the spatial is set.
|
||||
*/
|
||||
@Override
|
||||
protected void initSpatial() {
|
||||
moving = true;
|
||||
timer = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the movement of the spatial by interpolating its position along the defined path.
|
||||
* The movement is smoothed using an easing function.
|
||||
* Once the movement reaches the target, the {@link #end()} method is called to finish the movement.
|
||||
*
|
||||
* @param tpf Time per frame, the time elapsed since the last frame.
|
||||
*/
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (!moving) return;
|
||||
timer += tpf;
|
||||
|
||||
float t = timer / duration;
|
||||
if (t >= 1) t = 1;
|
||||
|
||||
float interpolated = easing ? easeInOut(t) : t;
|
||||
|
||||
spatial.setLocalTranslation(quadInt(initPos, middlePos, endPos, interpolated));
|
||||
|
||||
if (t >= 1) end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the movement by stopping the interpolation, running the action after the movement,
|
||||
* and removing this control from the spatial.
|
||||
*/
|
||||
private void end() {
|
||||
moving = false;
|
||||
spatial.removeControl(this);
|
||||
action();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import pp.mdga.client.Asset;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
import pp.mdga.client.board.TankTopControl;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static com.jme3.material.Materials.LIGHTING;
|
||||
|
||||
/**
|
||||
* ShellAnimation class handles the animation of a shell being fired from a tank.
|
||||
*/
|
||||
public class ShellAnimation extends ActionControl {
|
||||
private static final float FLYING_DURATION = 1.25f;
|
||||
private static final float FLYING_HEIGHT = 12f;
|
||||
private TankTopControl tankTopControl;
|
||||
private MdgaApp app;
|
||||
|
||||
/**
|
||||
* Constructor for ShellAnimation.
|
||||
*
|
||||
* @param tankTopControl the control for the tank top
|
||||
* @param app the application instance
|
||||
* @param actionAfter the action to perform after the animation
|
||||
*/
|
||||
public ShellAnimation(TankTopControl tankTopControl, MdgaApp app, Runnable actionAfter) {
|
||||
super(actionAfter);
|
||||
this.tankTopControl = tankTopControl;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the spatial for the animation.
|
||||
*/
|
||||
@Override
|
||||
protected void initSpatial() {
|
||||
tankTopControl.rotate(spatial.getLocalTranslation(), this::shoot);
|
||||
app.getAcousticHandler().playSound(MdgaSound.TURRET_ROTATE);
|
||||
//app.getRootNode().attachChild(createShell());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the shooting position based on the tank's turret rotation.
|
||||
*
|
||||
* @return the shooting position as a Vector3f
|
||||
*/
|
||||
private Vector3f getShootPos() {
|
||||
Vector3f localOffset = new Vector3f(0, -5.4f, 2.9f);
|
||||
Quaternion turretRotation = tankTopControl.getSpatial().getLocalRotation();
|
||||
Vector3f transformedOffset = turretRotation.mult(localOffset);
|
||||
return tankTopControl.getSpatial().getLocalTranslation().add(transformedOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the shooting action, including sound and visual effects.
|
||||
*/
|
||||
private void shoot() {
|
||||
app.getAcousticHandler().playSound(MdgaSound.TANK_SHOOT);
|
||||
Vector3f shootPos = getShootPos();
|
||||
createEffect(
|
||||
shootPos,
|
||||
"Images/particle/flame.png",
|
||||
2, 2,
|
||||
1, 3,
|
||||
1f,
|
||||
0.3f, 0.7f,
|
||||
new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
|
||||
new ColorRGBA(1f, 0f, 0f, 0f)
|
||||
);
|
||||
createEffect(
|
||||
shootPos,
|
||||
"Images/particle/vapor_cloud.png",
|
||||
3, 3,
|
||||
0.3f, 0.8f,
|
||||
10,
|
||||
0.1f, 0.35f,
|
||||
new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f),
|
||||
ColorRGBA.Black
|
||||
);
|
||||
|
||||
Spatial shell = createShell();
|
||||
app.getRootNode().attachChild(shell);
|
||||
shell.addControl(new ShellControl(this::hitExplosion, shootPos, spatial.getLocalTranslation(), FLYING_HEIGHT, FLYING_DURATION, app.getAssetManager()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the shell model and sets its initial properties.
|
||||
*
|
||||
* @return the created shell as a Spatial
|
||||
*/
|
||||
private Spatial createShell() {
|
||||
Spatial model = app.getAssetManager().loadModel(Asset.shell.getModelPath());
|
||||
model.scale(.16f);
|
||||
model.setLocalTranslation(tankTopControl.getSpatial().getLocalTranslation());
|
||||
|
||||
Vector3f shootPos = tankTopControl.getSpatial().getLocalTranslation();
|
||||
Vector3f targetPos = spatial.getLocalTranslation();
|
||||
Vector3f direction = targetPos.subtract(shootPos).normalize();
|
||||
|
||||
Quaternion rotation = new Quaternion();
|
||||
rotation.lookAt(direction, new Vector3f(1, 0, 0)); // Assuming UNIT_Y is the up vector
|
||||
|
||||
model.setLocalRotation(rotation);
|
||||
model.rotate(FastMath.HALF_PI, 0, 0);
|
||||
|
||||
Material mat = new Material(app.getAssetManager(), LIGHTING);
|
||||
mat.setBoolean("UseMaterialColors", true);
|
||||
ColorRGBA color = ColorRGBA.fromRGBA255(143, 117, 0, 255);
|
||||
mat.setColor("Diffuse", color);
|
||||
mat.setColor("Ambient", color);
|
||||
model.setMaterial(mat);
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the explosion effect when the shell hits a target.
|
||||
*/
|
||||
private void hitExplosion() {
|
||||
app.getAcousticHandler().playSound(MdgaSound.TANK_EXPLOSION);
|
||||
createEffect(
|
||||
spatial.getLocalTranslation().setZ(1),
|
||||
"Images/particle/flame.png",
|
||||
2, 2,
|
||||
1, 5,
|
||||
2f,
|
||||
0.3f, 0.7f,
|
||||
new ColorRGBA(1f, 0.8f, 0.4f, 0.5f),
|
||||
new ColorRGBA(1f, 0f, 0f, 0f)
|
||||
);
|
||||
app.getTimerManager().addTask(0.8f, super::action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a particle effect at the specified position.
|
||||
*
|
||||
* @param shootPos the position to create the effect
|
||||
* @param image the image to use for the particles
|
||||
* @param x the number of columns in the texture
|
||||
* @param y the number of rows in the texture
|
||||
* @param startSize the initial size of the particles
|
||||
* @param endSize the final size of the particles
|
||||
* @param velocity the initial velocity of the particles
|
||||
* @param lowLife the minimum lifetime of the particles
|
||||
* @param highLife the maximum lifetime of the particles
|
||||
* @param start the starting color of the particles
|
||||
* @param end the ending color of the particles
|
||||
*/
|
||||
private void createEffect(Vector3f shootPos,
|
||||
String image,
|
||||
int x, int y,
|
||||
float startSize, float endSize,
|
||||
float velocity,
|
||||
float lowLife, float highLife,
|
||||
ColorRGBA start, ColorRGBA end) {
|
||||
// Create a particle emitter for the explosion
|
||||
ParticleEmitter explosionEmitter = new ParticleEmitter("Explosion", ParticleMesh.Type.Triangle, 100);
|
||||
Material explosionMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
explosionMat.setTexture("Texture", app.getAssetManager().loadTexture(image));
|
||||
explosionEmitter.setMaterial(explosionMat);
|
||||
|
||||
// Particle properties
|
||||
explosionEmitter.setImagesX(x); // Columns in the texture
|
||||
explosionEmitter.setImagesY(y); // Rows in the texture
|
||||
explosionEmitter.setSelectRandomImage(true); // Randomize images for variety
|
||||
|
||||
explosionEmitter.setStartColor(start); // Bright yellowish orange
|
||||
explosionEmitter.setEndColor(end); // Fade to transparent red
|
||||
|
||||
explosionEmitter.setStartSize(startSize); // Initial size
|
||||
explosionEmitter.setEndSize(endSize); // Final size
|
||||
explosionEmitter.setLowLife(lowLife); // Minimum lifetime of particles
|
||||
explosionEmitter.setHighLife(highLife); // Maximum lifetime of particles
|
||||
explosionEmitter.setGravity(0, 0, 1); // Gravity to pull particles down
|
||||
explosionEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, velocity));
|
||||
explosionEmitter.getParticleInfluencer().setVelocityVariation(1f); // Adds randomness to the initial velocity
|
||||
explosionEmitter.setFacingVelocity(true); // Particles face their velocity direction
|
||||
explosionEmitter.setLocalTranslation(shootPos);
|
||||
explosionEmitter.setParticlesPerSec(0);
|
||||
explosionEmitter.emitAllParticles();
|
||||
app.getRootNode().attachChild(explosionEmitter);
|
||||
app.getTimerManager().addTask(1, ()->app.getRootNode().detachChild(explosionEmitter));
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
/**
|
||||
* ShellControl is responsible for controlling the movement and visual effects of a shell.
|
||||
*/
|
||||
public class ShellControl extends ActionControl {
|
||||
private final Vector3f shootPos;
|
||||
private final Vector3f endPos;
|
||||
private final float height;
|
||||
private final float duration;
|
||||
private Vector3f oldPos;
|
||||
private ParticleEmitter emitter;
|
||||
private AssetManager assetManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ShellControl.
|
||||
*
|
||||
* @param runnable the action to perform when the shell reaches its destination
|
||||
* @param shootPos the starting position of the shell
|
||||
* @param endPos the ending position of the shell
|
||||
* @param height the height of the shell's trajectory
|
||||
* @param duration the duration of the shell's flight
|
||||
* @param assetManager the asset manager to load resources
|
||||
*/
|
||||
public ShellControl(Runnable runnable, Vector3f shootPos, Vector3f endPos, float height, float duration, AssetManager assetManager) {
|
||||
super(runnable);
|
||||
this.shootPos = shootPos;
|
||||
this.endPos = endPos;
|
||||
this.height = height;
|
||||
this.duration = duration;
|
||||
this.assetManager = assetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the spatial with the necessary controls and particle emitter.
|
||||
*/
|
||||
@Override
|
||||
protected void initSpatial() {
|
||||
spatial.addControl(new MoveControl(
|
||||
shootPos,
|
||||
endPos,
|
||||
() -> {
|
||||
emitter.killAllParticles();
|
||||
emitter.setParticlesPerSec(0);
|
||||
emitter.removeFromParent();
|
||||
spatial.removeControl(this);
|
||||
spatial.removeFromParent();
|
||||
action();
|
||||
},
|
||||
height,
|
||||
duration,
|
||||
false
|
||||
));
|
||||
oldPos = spatial.getLocalTranslation().clone();
|
||||
createEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures the particle emitter for the shell trail.
|
||||
*/
|
||||
private void createEmitter() {
|
||||
emitter = new ParticleEmitter("ShellTrail", ParticleMesh.Type.Triangle, 200);
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat.setTexture("Texture", assetManager.loadTexture("Images/particle/line.png")); // Nutze eine schmale, linienartige Textur
|
||||
emitter.setMaterial(mat);
|
||||
|
||||
// Comic-Style Farben
|
||||
emitter.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); // Reinweiß
|
||||
emitter.setEndColor(new ColorRGBA(1f, 1f, 1f, 0f)); // Transparent
|
||||
|
||||
// Partikelgröße und Lebensdauer
|
||||
emitter.setStartSize(0.15f); // Startgröße
|
||||
emitter.setEndSize(0.1f); // Endgröße
|
||||
emitter.setLowLife(0.14f); // Sehr kurze Lebensdauer
|
||||
emitter.setHighLife(0.14f);
|
||||
|
||||
emitter.setGravity(0, 0, 0); // Keine Gravitation
|
||||
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
|
||||
emitter.getParticleInfluencer().setVelocityVariation(0f); // Kein Variationsspielraum
|
||||
|
||||
// Hohe Dichte für eine glatte Spur
|
||||
emitter.setParticlesPerSec(500);
|
||||
|
||||
// Zur Shell hinzufügen
|
||||
spatial.getParent().attachChild(emitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the control, adjusting the shell's rotation and emitter position.
|
||||
*
|
||||
* @param tpf time per frame
|
||||
*/
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
Vector3f direction = spatial.getLocalTranslation().subtract(oldPos).normalize();
|
||||
if (direction.lengthSquared() > 0) {
|
||||
spatial.getLocalRotation().lookAt(direction, Vector3f.UNIT_X);
|
||||
spatial.rotate(FastMath.HALF_PI, 0, 0);
|
||||
}
|
||||
oldPos = spatial.getLocalTranslation().clone();
|
||||
|
||||
emitter.setLocalTranslation(spatial.getLocalTranslation().clone());
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
|
||||
public class Smoke {
|
||||
|
||||
private final Node rootNode;
|
||||
private final MdgaApp app;
|
||||
private final Vector3f location;
|
||||
private ParticleEmitter smoke;
|
||||
|
||||
private boolean triggered = false;
|
||||
|
||||
private final Material mat;
|
||||
|
||||
/**
|
||||
* Constructor for the {@code Explosion} class.
|
||||
*
|
||||
* @param app The main application managing the explosion.
|
||||
* @param rootNode The root node to which the explosion effects will be attached.
|
||||
* @param location The location of the explosion in world coordinates.
|
||||
*/
|
||||
public Smoke(MdgaApp app, Node rootNode, Vector3f location) {
|
||||
this.app = app;
|
||||
this.rootNode = rootNode;
|
||||
this.location = location;
|
||||
|
||||
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the particle emitters for the explosion effect.
|
||||
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
|
||||
*/
|
||||
private void initializeEmitter() {
|
||||
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle, 50);
|
||||
smoke.setMaterial(mat);
|
||||
smoke.setImagesX(3);
|
||||
smoke.setImagesY(3);
|
||||
smoke.setSelectRandomImage(true);
|
||||
smoke.setStartColor(ColorRGBA.DarkGray);
|
||||
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
|
||||
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f, 0.2f, 4f));
|
||||
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
|
||||
smoke.setStartSize(0.7f);
|
||||
smoke.setEndSize(3f);
|
||||
smoke.setGravity(0, 0, -0.3f);
|
||||
smoke.setLowLife(1.2f);
|
||||
smoke.setHighLife(2.5f);
|
||||
smoke.setParticlesPerSec(0);
|
||||
|
||||
smoke.setLocalTranslation(location);
|
||||
|
||||
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
|
||||
* Both emitters are automatically detached after a predefined duration.
|
||||
*/
|
||||
public void trigger() {
|
||||
if (!triggered) {
|
||||
triggered = true;
|
||||
initializeEmitter();
|
||||
}
|
||||
|
||||
rootNode.attachChild(smoke);
|
||||
smoke.emitAllParticles();
|
||||
smoke.addControl(new AbstractControl() {
|
||||
private float elapsedTime = 0;
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
elapsedTime += tpf;
|
||||
if (elapsedTime > 10f) {
|
||||
rootNode.detachChild(smoke);
|
||||
smoke.removeControl(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import pp.mdga.client.InitControl;
|
||||
import pp.mdga.game.BonusCard;
|
||||
|
||||
/**
|
||||
* A control that manages the animation of symbols representing different bonus card states.
|
||||
* The symbol can animate with zoom, rotation, and translation effects based on the state of the bonus card.
|
||||
*
|
||||
* <p>The control supports three main states: SHIELD, SWAP, and TURBO. Each state has its own specific animation logic:
|
||||
* <ul>
|
||||
* <li>SHIELD: Zooms in and out, with a scaling effect.</li>
|
||||
* <li>SWAP: Rotates the symbol 360 degrees.</li>
|
||||
* <li>TURBO: Moves the symbol along the Y-axis with a zoom effect.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public class SymbolControl extends InitControl {
|
||||
private boolean zoomingIn = false;
|
||||
private boolean zoomingOut = false;
|
||||
private float zoomSpeed = 1f;
|
||||
private float zoomFactor = 3f;
|
||||
private float progress = 0;
|
||||
private BonusCard state;
|
||||
private float rotationSpeed = 0.8f;
|
||||
private Quaternion initialRotation = null;
|
||||
private float y = 5;
|
||||
|
||||
/**
|
||||
* Updates the symbol animation based on the current bonus card state.
|
||||
* The method calls the corresponding update method for each state (SHIELD, SWAP, TURBO).
|
||||
*
|
||||
* @param tpf Time per frame, the time elapsed since the last frame.
|
||||
*/
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (state == null) return;
|
||||
switch (state) {
|
||||
case SHIELD -> shieldUpdate(tpf);
|
||||
case SWAP -> swapUpdate(tpf);
|
||||
case TURBO -> turboUpdate(tpf);
|
||||
case HIDDEN -> throw new RuntimeException("forbidden state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the symbol when the state is SHIELD. The symbol zooms in and then zooms out.
|
||||
* When the zooming out finishes, the symbol is removed from the parent spatial.
|
||||
*
|
||||
* @param tpf Time per frame, the time elapsed since the last frame.
|
||||
*/
|
||||
private void shieldUpdate(float tpf) {
|
||||
if (zoomingIn) {
|
||||
progress += tpf * zoomSpeed;
|
||||
if (progress > 1) progress = 1;
|
||||
spatial.setLocalScale(lerp(0, zoomFactor, easeOut(progress)));
|
||||
if (progress >= 1) {
|
||||
zoomingIn = false;
|
||||
zoomingOut = true;
|
||||
progress = 0;
|
||||
}
|
||||
} else if (zoomingOut) {
|
||||
progress += tpf * zoomSpeed;
|
||||
spatial.setLocalScale(lerp(zoomFactor, 0, easeIn(progress)));
|
||||
if (progress > 1) {
|
||||
zoomingIn = false;
|
||||
spatial.removeFromParent();
|
||||
state = null;
|
||||
progress = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the symbol when the state is SWAP. The symbol rotates 360 degrees.
|
||||
* After the rotation finishes, the symbol is removed from the parent spatial.
|
||||
*
|
||||
* @param tpf Time per frame, the time elapsed since the last frame.
|
||||
*/
|
||||
private void swapUpdate(float tpf) {
|
||||
if (initialRotation == null) {
|
||||
initialRotation = spatial.getLocalRotation().clone();
|
||||
}
|
||||
|
||||
progress += tpf * rotationSpeed;
|
||||
if (progress < 0) return;
|
||||
|
||||
float angle = lerp(0, 360, easeInOut(progress));
|
||||
|
||||
Quaternion newRotation = new Quaternion();
|
||||
newRotation.fromAngleAxis((float) Math.toRadians(angle), new Vector3f(0, 1, 0));
|
||||
|
||||
spatial.setLocalRotation(initialRotation.mult(newRotation));
|
||||
|
||||
if (progress >= 1.2f) {
|
||||
state = null;
|
||||
initialRotation = null;
|
||||
progress = 0;
|
||||
spatial.removeFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the symbol when the state is TURBO. The symbol moves along the Y-axis with a zoom effect.
|
||||
* After the movement finishes, the symbol is removed from the parent spatial.
|
||||
*
|
||||
* @param tpf Time per frame, the time elapsed since the last frame.
|
||||
*/
|
||||
private void turboUpdate(float tpf) {
|
||||
if (zoomingIn) {
|
||||
progress += tpf * zoomSpeed;
|
||||
if (progress > 1) progress = 1;
|
||||
float y = lerp(-this.y, 0, easeOut(progress));
|
||||
spatial.setLocalTranslation(0, y, 0);
|
||||
if (progress >= 1) {
|
||||
zoomingIn = false;
|
||||
zoomingOut = true;
|
||||
progress = 0;
|
||||
}
|
||||
} else if (zoomingOut) {
|
||||
progress += tpf * zoomSpeed;
|
||||
float y = lerp(0, this.y, easeIn(progress));
|
||||
spatial.setLocalTranslation(0, y, 0);
|
||||
if (progress > 1) {
|
||||
zoomingIn = false;
|
||||
spatial.removeFromParent();
|
||||
state = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the SHIELD animation by zooming the symbol in and out.
|
||||
* The symbol will first zoom in and then zoom out, and will be removed from the parent spatial once done.
|
||||
*/
|
||||
public void shield() {
|
||||
if (state != null) throw new RuntimeException("another state is avtive");
|
||||
state = BonusCard.SHIELD;
|
||||
zoomingIn = true;
|
||||
zoomingOut = false;
|
||||
progress = 0;
|
||||
spatial.setLocalScale(1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the SWAP animation by rotating the symbol 360 degrees.
|
||||
* The symbol will rotate once and then be removed from the parent spatial.
|
||||
*/
|
||||
public void swap() {
|
||||
if (state != null) throw new RuntimeException("another state is avtive");
|
||||
spatial.setLocalScale(3);
|
||||
state = BonusCard.SWAP;
|
||||
progress = -0.2f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the TURBO animation by moving the symbol along the Y-axis.
|
||||
* The symbol will move upwards and then return to its initial position.
|
||||
*/
|
||||
public void turbo() {
|
||||
if (state != null) throw new RuntimeException("another state is avtive");
|
||||
spatial.setLocalScale(2);
|
||||
state = BonusCard.TURBO;
|
||||
zoomingIn = true;
|
||||
zoomingOut = false;
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs linear interpolation between two values.
|
||||
*
|
||||
* @param start The starting value.
|
||||
* @param end The target value.
|
||||
* @param t The interpolation parameter (0 <= t <= 1).
|
||||
* @return The interpolated value.
|
||||
*/
|
||||
private static float lerp(float start, float end, float t) {
|
||||
return (1 - t) * start + t * end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ease-out function for smoothing the interpolation.
|
||||
*
|
||||
* @param t The interpolation parameter (0 <= t <= 1).
|
||||
* @return The eased value.
|
||||
*/
|
||||
private static float easeOut(float t) {
|
||||
return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ease-in-out function for smoothing the interpolation.
|
||||
*
|
||||
* @param t The interpolation parameter (0 <= t <= 1).
|
||||
* @return The eased value.
|
||||
*/
|
||||
private float easeInOut(float t) {
|
||||
if (t > 1) t = 1;
|
||||
return (float) -(Math.cos(Math.PI * t) - 1) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ease-in function for smoothing the interpolation.
|
||||
*
|
||||
* @param t The interpolation parameter (0 <= t <= 1).
|
||||
* @return The eased value.
|
||||
*/
|
||||
private float easeIn(float t) {
|
||||
return t * t * t * t;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class TimerManager {
|
||||
private final List<TimedTask> tasks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add a timed task that will execute after the specified delay.
|
||||
*
|
||||
* @param delaySeconds The delay in seconds.
|
||||
* @param task The Runnable task to execute after the delay.
|
||||
*/
|
||||
public void addTask(float delaySeconds, Runnable task) {
|
||||
tasks.add(new TimedTask(delaySeconds, task));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the timer manager to process and execute tasks when their delay has elapsed.
|
||||
* This should be called in the `controlUpdate` method or a similar update loop.
|
||||
*
|
||||
* @param tpf Time per frame (delta time) provided by the update loop.
|
||||
*/
|
||||
public void update(float tpf) {
|
||||
Iterator<TimedTask> iterator = tasks.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
TimedTask task = iterator.next();
|
||||
task.update(tpf);
|
||||
if (task.isReady()) {
|
||||
task.run();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all pending tasks from the manager.
|
||||
*/
|
||||
public void clearTasks() {
|
||||
tasks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the manager has any pending tasks.
|
||||
*
|
||||
* @return True if there are pending tasks, otherwise false.
|
||||
*/
|
||||
public boolean hasPendingTasks() {
|
||||
return !tasks.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class representing a single timed task.
|
||||
*/
|
||||
private static class TimedTask {
|
||||
private float remainingTime;
|
||||
private final Runnable task;
|
||||
|
||||
public TimedTask(float delaySeconds, Runnable task) {
|
||||
this.remainingTime = delaySeconds;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public void update(float tpf) {
|
||||
remainingTime -= tpf;
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return remainingTime <= 0;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package pp.mdga.client.animation;
|
||||
|
||||
import pp.mdga.client.InitControl;
|
||||
|
||||
/**
|
||||
* A control that applies a zoom effect to a spatial, smoothly scaling it in and out.
|
||||
* The zoom effect can be customized with speed and scaling factor.
|
||||
*
|
||||
* <p>The control supports zooming in and out with ease-in and ease-out transitions.
|
||||
* It starts by zooming in, and once complete, it zooms out, eventually removing the spatial from its parent when the animation ends.</p>
|
||||
*/
|
||||
public class ZoomControl extends InitControl {
|
||||
private boolean zoomingIn = false;
|
||||
private boolean zoomingOut = false;
|
||||
private float progress = 0;
|
||||
private float zoomSpeed = 1f;
|
||||
private float zoomFactor = 1f;
|
||||
|
||||
/**
|
||||
* Constructs a new ZoomControl with the default zoom speed.
|
||||
*/
|
||||
public ZoomControl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new ZoomControl with a specified zoom speed.
|
||||
*
|
||||
* @param speed The speed at which the zoom effect occurs.
|
||||
*/
|
||||
public ZoomControl(float speed) {
|
||||
zoomSpeed = speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the spatial for the zoom effect. This method is called when the control is added to the spatial.
|
||||
* It sets the zooming state to zooming in.
|
||||
*/
|
||||
@Override
|
||||
protected void initSpatial() {
|
||||
zoomingIn = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the zoom effect over time, either zooming in or zooming out.
|
||||
*
|
||||
* @param tpf Time per frame, the time elapsed since the last frame.
|
||||
*/
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (zoomingIn) {
|
||||
progress += tpf * zoomSpeed;
|
||||
if (progress > 1) progress = 1;
|
||||
spatial.setLocalScale(lerp(0, zoomFactor, easeOut(progress)));
|
||||
if (progress >= 1) {
|
||||
zoomingIn = false;
|
||||
zoomingOut = true;
|
||||
progress = 0;
|
||||
}
|
||||
} else if (zoomingOut) {
|
||||
progress += tpf * zoomSpeed;
|
||||
spatial.setLocalScale(lerp(zoomFactor, 0, easeIn(progress)));
|
||||
if (progress > 1) {
|
||||
zoomingOut = false;
|
||||
end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the zoom animation by removing the spatial from its parent and the control from the spatial.
|
||||
*/
|
||||
private void end() {
|
||||
spatial.removeFromParent();
|
||||
spatial.removeControl(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs linear interpolation between two values.
|
||||
*
|
||||
* @param start The starting value.
|
||||
* @param end The target value.
|
||||
* @param t The interpolation parameter (0 <= t <= 1).
|
||||
* @return The interpolated value.
|
||||
*/
|
||||
private static float lerp(float start, float end, float t) {
|
||||
return (1 - t) * start + t * end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ease-out function for smoothing the zoom-in transition.
|
||||
*
|
||||
* @param x The interpolation parameter (0 <= x <= 1).
|
||||
* @return The eased value.
|
||||
*/
|
||||
private float easeOut(float x) {
|
||||
return x == 1 ? 1 : (float) (1 - Math.pow(2, -10 * x));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ease-in function for smoothing the zoom-out transition.
|
||||
*
|
||||
* @param x The interpolation parameter (0 <= x <= 1).
|
||||
* @return The eased value.
|
||||
*/
|
||||
private float easeIn(float x) {
|
||||
return x == 0 ? 0 : (float) Math.pow(2, 10 * x - 10);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
import pp.mdga.client.Asset;
|
||||
|
||||
/**
|
||||
* Record for holding Asset information
|
||||
*/
|
||||
record AssetOnMap(Asset asset, int x, int y, float rot) {
|
||||
}
|
||||
@@ -1,919 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import pp.mdga.client.Asset;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
import pp.mdga.client.animation.*;
|
||||
import pp.mdga.client.gui.DiceControl;
|
||||
import pp.mdga.game.Color;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* BoardHandler is responsible for managing the game board in the MDGA client, including handling
|
||||
* the initialization, movement, and management of game pieces and assets.
|
||||
* It works closely with the MdgaApp to create and manipulate 3D models of assets, track player pieces,
|
||||
* and manage movement across the game board.
|
||||
*/
|
||||
public class BoardHandler {
|
||||
// Constants defining the grid size and elevation of the board
|
||||
private static final float GRID_SIZE = 1.72f;
|
||||
private static final float GRID_ELEVATION = 0.0f;
|
||||
private static final String MAP_NAME = "Maps/map.mdga";
|
||||
|
||||
// The main application instance for accessing game assets and logic
|
||||
private final MdgaApp app;
|
||||
|
||||
// Collection of in-game assets and board elements
|
||||
private ArrayList<NodeControl> infield;
|
||||
private Map<UUID, PieceControl> pieces;
|
||||
private Map<Color, List<AssetOnMap>> colorAssetsMap;
|
||||
private Map<Color, List<NodeControl>> homeNodesMap;
|
||||
private Map<Color, List<NodeControl>> waitingNodesMap;
|
||||
private Map<Color, List<PieceControl>> waitingPiecesMap;
|
||||
private Map<Color, Map<UUID, NodeControl>> waitingNodes;
|
||||
private Map<UUID, Color> pieceColor;
|
||||
|
||||
private final Node rootNodeBoard;
|
||||
private final Node rootNode;
|
||||
private final FilterPostProcessor fpp;
|
||||
|
||||
private boolean isInitialised;
|
||||
|
||||
// Flags and lists for handling piece selection and movement
|
||||
private List<PieceControl> selectableOwnPieces;
|
||||
private List<PieceControl> selectableEnemyPieces;
|
||||
private Map<PieceControl, NodeControl> selectedPieceNodeMap;
|
||||
private List<NodeControl> outlineNodes;
|
||||
private PieceControl selectedOwnPiece;
|
||||
private PieceControl selectedEnemyPiece;
|
||||
private DiceControl diceControl;
|
||||
//Radar Position for Matrix animation
|
||||
private Vector3f radarPos;
|
||||
//TankTop for shellAnimation
|
||||
private TankTopControl tankTop;
|
||||
|
||||
/**
|
||||
* Creates a new BoardHandler.
|
||||
*
|
||||
* @param app The main application instance
|
||||
* @param rootNode The root node where the board will be attached
|
||||
* @param fpp The post-processor for effects like shadows or filters
|
||||
* @throws RuntimeException if the app is null
|
||||
*/
|
||||
public BoardHandler(MdgaApp app, Node rootNode, FilterPostProcessor fpp) {
|
||||
if (app == null) throw new RuntimeException("app is null");
|
||||
|
||||
this.app = app;
|
||||
this.fpp = fpp;
|
||||
rootNodeBoard = new Node("Board Root Node");
|
||||
this.rootNode = rootNode;
|
||||
isInitialised = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the game board by setting up the pieces and nodes.
|
||||
*/
|
||||
public void init() {
|
||||
isInitialised = true;
|
||||
selectableOwnPieces = new ArrayList<>();
|
||||
selectableEnemyPieces = new ArrayList<>();
|
||||
selectedPieceNodeMap = new HashMap<>();
|
||||
outlineNodes = new ArrayList<>();
|
||||
selectedOwnPiece = null;
|
||||
selectedEnemyPiece = null;
|
||||
initMap();
|
||||
rootNode.attachChild(rootNodeBoard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the board handler by detaching all board-related nodes and clearing selected pieces.
|
||||
*/
|
||||
public void shutdown() {
|
||||
clearSelectable();
|
||||
isInitialised = false;
|
||||
rootNode.detachChild(rootNodeBoard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an asset to the map of player assets, ensuring that the player does not have too many assets.
|
||||
*
|
||||
* @param col The color of the player
|
||||
* @param assetOnMap The asset to be added
|
||||
* @throws RuntimeException if there are too many assets for the player
|
||||
*/
|
||||
private void addFigureToPlayerMap(Color col, AssetOnMap assetOnMap) {
|
||||
List<AssetOnMap> inMap = addItemToMapList(colorAssetsMap, col, assetOnMap);
|
||||
if (inMap.size() > 4) throw new RuntimeException("to many assets for " + col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the map with the assets loaded from the map file and corresponding nodes.
|
||||
*/
|
||||
private void initMap() {
|
||||
pieces = new HashMap<>();
|
||||
colorAssetsMap = new HashMap<>();
|
||||
infield = new ArrayList<>(40);
|
||||
homeNodesMap = new HashMap<>();
|
||||
waitingNodesMap = new HashMap<>();
|
||||
waitingPiecesMap = new HashMap<>();
|
||||
pieceColor = new HashMap<>();
|
||||
diceControl = new DiceControl(app.getAssetManager());
|
||||
diceControl.create(new Vector3f(0, 0, 0), 0.7f, true);
|
||||
waitingNodes = new HashMap<>();
|
||||
waitingNodes.put(Color.AIRFORCE, new HashMap<>());
|
||||
waitingNodes.put(Color.ARMY, new HashMap<>());
|
||||
waitingNodes.put(Color.NAVY, new HashMap<>());
|
||||
waitingNodes.put(Color.CYBER, new HashMap<>());
|
||||
|
||||
|
||||
List<AssetOnMap> assetOnMaps = MapLoader.loadMap(MAP_NAME);
|
||||
|
||||
for (AssetOnMap assetOnMap : assetOnMaps) {
|
||||
switch (assetOnMap.asset()) {
|
||||
case lw -> addFigureToPlayerMap(assetToColor(Asset.lw), assetOnMap);
|
||||
case heer -> addFigureToPlayerMap(assetToColor(Asset.heer), assetOnMap);
|
||||
case cir -> addFigureToPlayerMap(assetToColor(Asset.cir), assetOnMap);
|
||||
case marine -> addFigureToPlayerMap(assetToColor(Asset.marine), assetOnMap);
|
||||
case node_normal, node_bonus, node_start ->
|
||||
infield.add(displayAndControl(assetOnMap, new NodeControl(app, fpp)));
|
||||
case node_home_black -> addHomeNode(homeNodesMap, Color.AIRFORCE, assetOnMap);
|
||||
case node_home_blue -> addHomeNode(homeNodesMap, Color.NAVY, assetOnMap);
|
||||
case node_home_green -> addHomeNode(homeNodesMap, Color.ARMY, assetOnMap);
|
||||
case node_home_yellow -> addHomeNode(homeNodesMap, Color.CYBER, assetOnMap);
|
||||
case node_wait_black -> addHomeNode(waitingNodesMap, Color.AIRFORCE, assetOnMap);
|
||||
case node_wait_blue -> addHomeNode(waitingNodesMap, Color.NAVY, assetOnMap);
|
||||
case node_wait_green -> addHomeNode(waitingNodesMap, Color.ARMY, assetOnMap);
|
||||
case node_wait_yellow -> addHomeNode(waitingNodesMap, Color.CYBER, assetOnMap);
|
||||
case radar -> addRadar(assetOnMap);
|
||||
case tankShoot -> addTankShoot(assetOnMap);
|
||||
|
||||
default -> displayAsset(assetOnMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addTankShoot(AssetOnMap assetOnMap) {
|
||||
displayAsset(assetOnMap);
|
||||
tankTop = displayAndControl(new AssetOnMap(Asset.tankShootTop, assetOnMap.x(), assetOnMap.y(), assetOnMap.rot()), new TankTopControl());
|
||||
}
|
||||
|
||||
private void addRadar(AssetOnMap assetOnMap) {
|
||||
radarPos = gridToWorld(assetOnMap.x(), assetOnMap.y());
|
||||
displayAsset(assetOnMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an asset to its corresponding color.
|
||||
*
|
||||
* @param asset The asset to be converted
|
||||
* @return The color associated with the asset
|
||||
* @throws RuntimeException if the asset is invalid
|
||||
*/
|
||||
private Color assetToColor(Asset asset) {
|
||||
return switch (asset) {
|
||||
case lw -> Color.AIRFORCE;
|
||||
case heer -> Color.ARMY;
|
||||
case marine -> Color.NAVY;
|
||||
case cir -> Color.CYBER;
|
||||
default -> throw new RuntimeException("invalid asset");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 3D model of an asset and adds it to the board.
|
||||
*
|
||||
* @param asset The asset to be displayed
|
||||
* @param pos The position of the asset on the board
|
||||
* @param rot The rotation of the asset
|
||||
* @return The Spatial representation of the asset
|
||||
*/
|
||||
private Spatial createModel(Asset asset, Vector3f pos, float rot) {
|
||||
String modelName = asset.getModelPath();
|
||||
String texName = asset.getDiffPath();
|
||||
Spatial model = app.getAssetManager().loadModel(modelName);
|
||||
model.scale(asset.getSize());
|
||||
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(rot));
|
||||
model.setLocalTranslation(pos);
|
||||
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
|
||||
mat.setBoolean("UseMaterialColors", true); // Required for Material Colors
|
||||
mat.setColor("Diffuse", new ColorRGBA(1, 1, 1, 1)); // White color with full alpha
|
||||
mat.setColor("Ambient", new ColorRGBA(1, 1, 1, 1)); // Ambient color with full alpha
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
model.setMaterial(mat);
|
||||
|
||||
rootNodeBoard.attachChild(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts grid coordinates to world space.
|
||||
*
|
||||
* @param x The x-coordinate on the grid
|
||||
* @param y The y-coordinate on the grid
|
||||
* @return The corresponding world position
|
||||
*/
|
||||
public static Vector3f gridToWorld(int x, int y) {
|
||||
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an asset on the map at the given position with the specified rotation.
|
||||
*
|
||||
* @param assetOnMap The asset to be displayed.
|
||||
* @return A spatial representation of the asset at the specified location and rotation.
|
||||
*/
|
||||
private Spatial displayAsset(AssetOnMap assetOnMap) {
|
||||
int x = assetOnMap.x();
|
||||
int y = assetOnMap.y();
|
||||
return createModel(assetOnMap.asset(), gridToWorld(x, y), assetOnMap.rot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a visual representation of an asset to the scene, attaches a control to it, and returns the control.
|
||||
*
|
||||
* @param assetOnMap The asset to be displayed in the 3D environment.
|
||||
* @param control The control to be added to the spatial representing the asset.
|
||||
* @param <T> The type of control, extending {@code AbstractControl}.
|
||||
* @return The control that was added to the spatial.
|
||||
*/
|
||||
private <T extends AbstractControl> T displayAndControl(AssetOnMap assetOnMap, T control) {
|
||||
Spatial spatial = displayAsset(assetOnMap);
|
||||
spatial.addControl(control);
|
||||
return control;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a piece in the 3D environment to the location of a specified node.
|
||||
*
|
||||
* @param pieceControl The control managing the piece to be moved.
|
||||
* @param nodeControl The control managing the target node to which the piece will move.
|
||||
*/
|
||||
private void movePieceToNode(PieceControl pieceControl, NodeControl nodeControl) {
|
||||
pieceControl.setLocation(nodeControl.getLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a home node for a specific player color, attaching it to the map of home nodes.
|
||||
*
|
||||
* @param map The map storing lists of home nodes by player color.
|
||||
* @param color The color associated with the home nodes to be added.
|
||||
* @param assetOnMap The asset representing the home node in the 3D environment.
|
||||
* @throws RuntimeException if more than 4 home nodes are added for a single color.
|
||||
*/
|
||||
private void addHomeNode(Map<Color, List<NodeControl>> map, Color color, AssetOnMap assetOnMap) {
|
||||
List<NodeControl> homeNodes = addItemToMapList(map, color, displayAndControl(assetOnMap, new NodeControl(app, fpp)));
|
||||
if (homeNodes.size() > 4) throw new RuntimeException("too many homeNodes for " + color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the rotation angle required to move a piece from one position to another.
|
||||
*
|
||||
* @param prev The previous position.
|
||||
* @param next The target position.
|
||||
* @return The rotation angle in degrees.
|
||||
*/
|
||||
private float getRotationMove(Vector3f prev, Vector3f next) {
|
||||
Vector3f direction = next.subtract(prev).normalizeLocal();
|
||||
//I had to reverse dir.y, because then it worked.
|
||||
float newRot = (float) Math.toDegrees(Math.atan2(direction.x, -direction.y));
|
||||
if (newRot < 0) newRot += 360;
|
||||
return newRot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively moves a piece from its current index to the destination index,
|
||||
* to keep track of the piece rotation.
|
||||
*
|
||||
* @param uuid The UUID of the piece to move.
|
||||
* @param curIndex The current index of the piece.
|
||||
* @param moveIndex The target index to move the piece to.
|
||||
*/
|
||||
private void movePieceRek(UUID uuid, int curIndex, int moveIndex) {
|
||||
if (curIndex == moveIndex) return;
|
||||
|
||||
int nextIndex = (curIndex + 1) % infield.size();
|
||||
|
||||
PieceControl pieceControl = pieces.get(uuid);
|
||||
NodeControl nodeCur = infield.get(curIndex);
|
||||
NodeControl nodeMove = infield.get(nextIndex);
|
||||
|
||||
pieceControl.setRotation(getRotationMove(nodeCur.getLocation(), nodeMove.getLocation()));
|
||||
|
||||
movePieceToNode(pieceControl, nodeMove);
|
||||
|
||||
|
||||
movePieceRek(uuid, nextIndex, moveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to a list in a map. If the key does not exist in the map, a new list is created.
|
||||
*
|
||||
* @param map The map containing lists of items.
|
||||
* @param key The key associated with the list in the map.
|
||||
* @param item The item to be added to the list.
|
||||
* @param <T> The type of items in the list.
|
||||
* @param <E> The type of the key in the map.
|
||||
* @return The updated list associated with the specified key.
|
||||
*/
|
||||
private <T, E> List<T> addItemToMapList(Map<E, List<T>> map, E key, T item) {
|
||||
List<T> list = map.getOrDefault(key, new ArrayList<>());
|
||||
list.add(item);
|
||||
map.put(key, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from a list in a map. If the key does not exist in the map, a new list is created.
|
||||
*
|
||||
* @param map The map containing lists of items.
|
||||
* @param key The key associated with the list in the map.
|
||||
* @param item The item to be removed from the list.
|
||||
* @param <T> The type of items in the list.
|
||||
* @param <E> The type of the key in the map.
|
||||
*/
|
||||
private <T, E> void removeItemFromMapList(Map<E, List<T>> map, E key, T item) {
|
||||
List<T> list = map.getOrDefault(key, new ArrayList<>());
|
||||
list.remove(item);
|
||||
map.put(key, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the mean position of the waiting nodes for a specific color.
|
||||
*
|
||||
* @param color The color associated with the waiting nodes.
|
||||
* @return The mean position of the waiting nodes as a {@code Vector3f}.
|
||||
*/
|
||||
private Vector3f getWaitingPos(Color color) {
|
||||
return getMeanPosition(waitingNodesMap.get(color).stream().map(NodeControl::getLocation).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mean position of a list of vectors.
|
||||
*
|
||||
* @param vectors The list of vectors.
|
||||
* @return The mean position as a Vector3f.
|
||||
*/
|
||||
private static Vector3f getMeanPosition(List<Vector3f> vectors) {
|
||||
if (vectors.isEmpty()) return new Vector3f(0, 0, 0);
|
||||
|
||||
Vector3f sum = new Vector3f(0, 0, 0);
|
||||
for (Vector3f v : vectors) {
|
||||
sum.addLocal(v);
|
||||
}
|
||||
return sum.divide(vectors.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a player to the game by associating a color and a list of UUIDs to corresponding assets and waiting nodes.
|
||||
*
|
||||
* @param color the color of the player
|
||||
* @param uuid the list of UUIDs representing the player's assets
|
||||
* @throws RuntimeException if the number of assets or waiting nodes does not match the provided UUIDs
|
||||
*/
|
||||
public void addPlayer(Color color, List<UUID> uuid) {
|
||||
|
||||
List<AssetOnMap> playerAssets = colorAssetsMap.get(color);
|
||||
if (playerAssets == null) throw new RuntimeException("Assets for Player color are not defined");
|
||||
if (uuid.size() != playerAssets.size())
|
||||
throw new RuntimeException("UUID array and playerAssets are not the same size");
|
||||
|
||||
List<NodeControl> waitNodes = waitingNodesMap.get(color);
|
||||
if (waitNodes.size() != playerAssets.size())
|
||||
throw new RuntimeException("waitNodes size does not match playerAssets size");
|
||||
|
||||
|
||||
for (int i = 0; i < playerAssets.size(); i++) {
|
||||
AssetOnMap assetOnMap = playerAssets.get(i);
|
||||
UUID pieceUuid = uuid.get(i);
|
||||
|
||||
// Initialize PieceControl
|
||||
PieceControl pieceControl = displayAndControl(assetOnMap, new PieceControl(assetOnMap.rot(), app.getAssetManager(), app, fpp));
|
||||
pieceControl.setRotation(assetOnMap.rot());
|
||||
|
||||
// Assign piece to waiting node
|
||||
NodeControl waitNode = getNextWaitingNode(color);
|
||||
waitingNodes.get(color).put(pieceUuid, waitNode);
|
||||
|
||||
// Move piece to node
|
||||
movePieceToNode(pieceControl, waitNode);
|
||||
|
||||
// Update mappings
|
||||
pieces.put(pieceUuid, pieceControl);
|
||||
pieceColor.put(pieceUuid, color);
|
||||
addItemToMapList(waitingPiecesMap, color, pieceControl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a piece to its corresponding home node based on the given index.
|
||||
*
|
||||
* @param uuid the UUID of the piece to move
|
||||
* @param index the index of the home node to move the piece to
|
||||
* @throws RuntimeException if the UUID is not mapped to a color or if the home nodes are not properly defined
|
||||
*/
|
||||
private void executeHomeMove(UUID uuid, int index) {
|
||||
|
||||
Color color = pieceColor.get(uuid);
|
||||
if (color == null) throw new RuntimeException("uuid is not mapped to a color");
|
||||
|
||||
List<NodeControl> homeNodes = homeNodesMap.get(color);
|
||||
|
||||
if (homeNodesMap.size() != 4) throw new RuntimeException("HomeNodes for" + color + " are not properly defined");
|
||||
|
||||
PieceControl pieceControl = pieces.get(uuid);
|
||||
NodeControl nodeControl = homeNodes.get(index);
|
||||
movePieceToNode(pieceControl, nodeControl);
|
||||
|
||||
//rotate piece in direction of homeNodes
|
||||
NodeControl firstHomeNode = homeNodes.get(0);
|
||||
NodeControl lastHomeNode = homeNodes.get(homeNodes.size() - 1);
|
||||
|
||||
pieceControl.setRotation(getRotationMove(firstHomeNode.getLocation(), lastHomeNode.getLocation()));
|
||||
app.getModelSynchronize().animationEnd();
|
||||
app.getModelSynchronize().animationEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the movement of a piece to a target node based on the given index.
|
||||
*
|
||||
* @param uuid the UUID of the piece to move
|
||||
* @param nodeIndex the index of the target node to move the piece to
|
||||
* @throws RuntimeException if the UUID is not mapped to a color or the piece control is not found
|
||||
* @throws IllegalArgumentException if the node index is invalid
|
||||
*/
|
||||
private void executeStartMove(UUID uuid, int nodeIndex) {
|
||||
|
||||
// Farbe des Pieces abrufen
|
||||
Color color = pieceColor.get(uuid);
|
||||
if (color == null) throw new RuntimeException("UUID is not mapped to a color");
|
||||
|
||||
// PieceControl abrufen
|
||||
PieceControl pieceControl = pieces.get(uuid);
|
||||
if (pieceControl == null) throw new RuntimeException("PieceControl not found for UUID: " + uuid);
|
||||
|
||||
// Zielknoten abrufen und prüfen
|
||||
if (nodeIndex < 0 || nodeIndex >= infield.size()) {
|
||||
throw new IllegalArgumentException("Invalid nodeIndex: " + nodeIndex);
|
||||
}
|
||||
NodeControl targetNode = infield.get(nodeIndex);
|
||||
|
||||
movePieceToNode(pieceControl, targetNode);
|
||||
|
||||
removeItemFromMapList(waitingPiecesMap, color, pieceControl);
|
||||
waitingNodes.get(color).remove(uuid);
|
||||
app.getModelSynchronize().animationEnd();
|
||||
app.getModelSynchronize().animationEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a piece from its current position to the target position based on the given indexes.
|
||||
*
|
||||
* @param uuid the UUID of the piece to move
|
||||
* @param curIndex the current index of the piece
|
||||
* @param moveIndex the target index of the move
|
||||
*/
|
||||
private void executeMove(UUID uuid, int curIndex, int moveIndex) {
|
||||
|
||||
movePieceRek(uuid, curIndex, moveIndex);
|
||||
app.getModelSynchronize().animationEnd();
|
||||
app.getModelSynchronize().animationEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a piece to the next available waiting node and updates the waiting node mapping.
|
||||
*
|
||||
* @param uuid the UUID of the piece to throw
|
||||
* @throws RuntimeException if the UUID is not mapped to a color or if no available waiting nodes are found
|
||||
*/
|
||||
private void throwPiece(UUID uuid) {
|
||||
|
||||
// Farbe des Pieces abrufen
|
||||
Color color = pieceColor.get(uuid);
|
||||
if (color == null) throw new RuntimeException("UUID is not mapped to a color");
|
||||
|
||||
// PieceControl abrufen
|
||||
PieceControl pieceControl = pieces.get(uuid);
|
||||
if (pieceControl == null) throw new RuntimeException("PieceControl not found for UUID: " + uuid);
|
||||
|
||||
// Nächste freie Waiting Node abrufen
|
||||
NodeControl nextWaitNode = getNextWaitingNode(color);
|
||||
if (nextWaitNode == null) {
|
||||
throw new IllegalStateException("No available waiting nodes for color: " + color);
|
||||
}
|
||||
|
||||
// Bewegung durchführen
|
||||
movePieceToNode(pieceControl, nextWaitNode);
|
||||
|
||||
// Waiting Nodes aktualisieren
|
||||
waitingNodes.get(color).put(uuid, nextWaitNode);
|
||||
|
||||
// Synchronisation oder Animation
|
||||
pieceControl.rotateInit();
|
||||
app.getAcousticHandler().playSound(MdgaSound.LOSE);
|
||||
app.getModelSynchronize().animationEnd();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Activates the shield for the specified piece.
|
||||
*
|
||||
* @param uuid the UUID of the piece to shield
|
||||
*/
|
||||
public void shieldPiece(UUID uuid) {
|
||||
pieces.get(uuid).activateShield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the shield for the specified piece.
|
||||
*
|
||||
* @param uuid the UUID of the piece to unshield
|
||||
*/
|
||||
public void unshieldPiece(UUID uuid) {
|
||||
pieces.get(uuid).deactivateShield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses the shield for the specified piece.
|
||||
*
|
||||
* @param uuid the UUID of the piece to suppress the shield
|
||||
*/
|
||||
public void suppressShield(UUID uuid) {
|
||||
|
||||
pieces.get(uuid).suppressShield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the positions and rotations of two pieces.
|
||||
*
|
||||
* @param p1 the first piece to swap
|
||||
* @param p2 the second piece to swap
|
||||
* @param loc1 the original location of the first piece
|
||||
* @param rot1 the original rotation of the first piece
|
||||
* @param loc2 the original location of the second piece
|
||||
* @param rot2 the original rotation of the second piece
|
||||
*/
|
||||
private void executeSwap(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2) {
|
||||
p1.setLocation(loc2);
|
||||
p2.setLocation(loc1);
|
||||
|
||||
p1.setRotation(rot2);
|
||||
p2.setRotation(rot1);
|
||||
|
||||
app.getModelSynchronize().animationEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Outlines the possible move nodes for a list of pieces based on the move indices and whether it's a home move.
|
||||
*
|
||||
* @param pieces the list of UUIDs representing the pieces to outline
|
||||
* @param moveIndexe the list of indices for the target move nodes
|
||||
* @param homeMoves the list indicating whether the move is a home move
|
||||
* @throws RuntimeException if the sizes of the input lists do not match
|
||||
*/
|
||||
public void setSelectableMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolean> homeMoves) {
|
||||
if (pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size())
|
||||
throw new RuntimeException("arrays are not the same size");
|
||||
|
||||
selectableEnemyPieces.clear();
|
||||
selectableOwnPieces.clear();
|
||||
selectedOwnPiece = null;
|
||||
selectedEnemyPiece = null;
|
||||
|
||||
for (int i = 0; i < pieces.size(); i++) {
|
||||
UUID uuid = pieces.get(i);
|
||||
PieceControl pieceControl = this.pieces.get(uuid);
|
||||
NodeControl nodeControl;
|
||||
|
||||
if (homeMoves.get(i)) {
|
||||
Color color = pieceColor.get(uuid);
|
||||
nodeControl = homeNodesMap.get(color).get(moveIndexe.get(i));
|
||||
} else {
|
||||
nodeControl = infield.get(moveIndexe.get(i));
|
||||
}
|
||||
pieceControl.selectableOwn();
|
||||
nodeControl.selectableOwn();
|
||||
outlineNodes.add(nodeControl);
|
||||
selectableOwnPieces.add(pieceControl);
|
||||
selectedPieceNodeMap.put(pieceControl, nodeControl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outlines the pieces that can be swapped based on the provided own and enemy pieces.
|
||||
*
|
||||
* @param ownPieces the list of UUIDs representing the player's pieces
|
||||
* @param enemyPieces the list of UUIDs representing the enemy's pieces
|
||||
*/
|
||||
public void setSelectableSwap(List<UUID> ownPieces, List<UUID> enemyPieces) {
|
||||
|
||||
selectableEnemyPieces.clear();
|
||||
selectableOwnPieces.clear();
|
||||
selectedOwnPiece = null;
|
||||
selectedEnemyPiece = null;
|
||||
|
||||
for (UUID uuid : ownPieces) {
|
||||
PieceControl p = pieces.get(uuid);
|
||||
p.selectableOwn();
|
||||
selectableOwnPieces.add(p);
|
||||
}
|
||||
for (UUID uuid : enemyPieces) {
|
||||
PieceControl p = pieces.get(uuid);
|
||||
p.selectableEnemy();
|
||||
selectableEnemyPieces.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outlines the pieces that can be shielded based on the provided list of pieces.
|
||||
*
|
||||
* @param pieces the list of UUIDs representing the pieces to be shielded
|
||||
*/
|
||||
public void setSelectableShield(List<UUID> pieces) {
|
||||
selectableOwnPieces.clear();
|
||||
selectableEnemyPieces.clear();
|
||||
selectedOwnPiece = null;
|
||||
selectedEnemyPiece = null;
|
||||
|
||||
for (UUID uuid : pieces) {
|
||||
PieceControl p = this.pieces.get(uuid);
|
||||
p.selectableOwn();
|
||||
selectableOwnPieces.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a piece from either the own or enemy pieces based on the input and deselects others if needed.
|
||||
*
|
||||
* @param pieceSelected the PieceControl instance representing the piece selected by the user
|
||||
*/
|
||||
public void pieceSelect(OutlineOEControl selected) {
|
||||
PieceControl piece = getPieceByOE(selected);
|
||||
NodeControl node = selectedPieceNodeMap.get(piece);
|
||||
|
||||
boolean isSelected = piece.isSelected();
|
||||
if (selectableOwnPieces.contains(piece)) {
|
||||
for (PieceControl p : selectableOwnPieces) {
|
||||
p.selectOff();
|
||||
NodeControl n = selectedPieceNodeMap.get(p);
|
||||
if (n != null) n.selectOff();
|
||||
}
|
||||
if (!isSelected) {
|
||||
piece.selectOn();
|
||||
if (node != null) node.selectOn();
|
||||
selectedOwnPiece = piece;
|
||||
} else {
|
||||
piece.selectOff();
|
||||
if (node != null) node.selectOff();;
|
||||
selectedOwnPiece = null;
|
||||
}
|
||||
} else if (selectableEnemyPieces.contains(piece)) {
|
||||
for (PieceControl p : selectableEnemyPieces) {
|
||||
p.selectOff();
|
||||
}
|
||||
if (!isSelected) {
|
||||
piece.selectOn();
|
||||
selectedEnemyPiece = piece;
|
||||
} else {
|
||||
piece.selectOff();
|
||||
selectedEnemyPiece = null;
|
||||
}
|
||||
} else throw new RuntimeException("pieceSelected is not in own/enemySelectablePieces");
|
||||
|
||||
app.getModelSynchronize().select(getKeyByValue(pieces, selectedOwnPiece), getKeyByValue(pieces, selectedEnemyPiece));
|
||||
}
|
||||
|
||||
public void hoverOn(OutlineOEControl hover) {
|
||||
PieceControl piece = getPieceByOE(hover);
|
||||
NodeControl node = selectedPieceNodeMap.get(piece);
|
||||
|
||||
piece.hoverOn();
|
||||
if(node != null) node.hoverOn();
|
||||
}
|
||||
|
||||
public void hoverOff(OutlineOEControl hover) {
|
||||
PieceControl piece = getPieceByOE(hover);
|
||||
NodeControl node = selectedPieceNodeMap.get(piece);
|
||||
|
||||
piece.hoverOff();
|
||||
if(node != null) node.hoverOff();
|
||||
}
|
||||
|
||||
private PieceControl getPieceByOE(OutlineOEControl control){
|
||||
PieceControl piece;
|
||||
if (control instanceof PieceControl p){
|
||||
piece = p;
|
||||
}
|
||||
else if (control instanceof NodeControl n){
|
||||
piece = getKeyByValue(selectedPieceNodeMap, n);
|
||||
}
|
||||
else throw new RuntimeException("selected is not instanceof piece or node");
|
||||
return piece;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all highlighted, selectable, and selected pieces and nodes.
|
||||
*/
|
||||
public void clearSelectable() {
|
||||
for (PieceControl p : selectableOwnPieces) {
|
||||
p.selectableOff();
|
||||
NodeControl n = selectedPieceNodeMap.get(p);
|
||||
if(n != null) n.selectableOff();
|
||||
}
|
||||
for (PieceControl p : selectableEnemyPieces) {
|
||||
p.selectableOff();
|
||||
}
|
||||
|
||||
outlineNodes.clear();
|
||||
selectableEnemyPieces.clear();
|
||||
selectableOwnPieces.clear();
|
||||
selectedPieceNodeMap.clear();
|
||||
selectedEnemyPiece = null;
|
||||
selectedOwnPiece = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the dice for the specified color at the appropriate position.
|
||||
*
|
||||
* @param color the color of the player whose dice should be displayed
|
||||
*/
|
||||
public void showDice(Color color) {
|
||||
rootNodeBoard.attachChild(diceControl.getSpatial());
|
||||
diceControl.setPos(getWaitingPos(color).add(new Vector3f(0, 0, 4)));
|
||||
diceControl.spin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the dice from the view.
|
||||
*/
|
||||
public void hideDice() {
|
||||
diceControl.hide();
|
||||
}
|
||||
|
||||
private <K, V> K getKeyByValue(Map<K, V> map, V value) {
|
||||
for (Map.Entry<K, V> entry : map.entrySet()) {
|
||||
if (entry.getValue().equals(value)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the movement of a piece from its current index to a target index.
|
||||
*
|
||||
* @param uuid the UUID of the piece to animate
|
||||
* @param curIndex the current index of the piece
|
||||
* @param moveIndex the target index to animate the piece to
|
||||
*/
|
||||
public void movePiece(UUID uuid, int curIndex, int moveIndex) {
|
||||
|
||||
pieces.get(uuid).getSpatial().addControl(new MoveControl(
|
||||
infield.get(curIndex).getLocation(),
|
||||
infield.get(moveIndex).getLocation(),
|
||||
() -> executeMove(uuid, curIndex, moveIndex)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the movement of a piece to its home position based on the given home index.
|
||||
*
|
||||
* @param uuid the UUID of the piece to animate
|
||||
* @param homeIndex the index of the home node to move the piece to
|
||||
*/
|
||||
public void movePieceHome(UUID uuid, int homeIndex) {
|
||||
pieces.get(uuid).getSpatial().addControl(new MoveControl(
|
||||
pieces.get(uuid).getLocation(),
|
||||
homeNodesMap.get(pieceColor.get(uuid)).get(homeIndex).getLocation(),
|
||||
() -> executeHomeMove(uuid, homeIndex)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the start of the movement of a piece to a target index.
|
||||
*
|
||||
* @param uuid the UUID of the piece to animate
|
||||
* @param moveIndex the target index to animate the piece to
|
||||
*/
|
||||
public void movePieceStart(UUID uuid, int moveIndex) {
|
||||
pieces.get(uuid).getSpatial().addControl(new MoveControl(
|
||||
pieces.get(uuid).getLocation(),
|
||||
infield.get(moveIndex).getLocation(),
|
||||
() -> executeStartMove(uuid, moveIndex)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the throwing of a piece to the next available waiting node.
|
||||
*
|
||||
* @param uuid the UUID of the piece to animate
|
||||
*/
|
||||
private void throwPieceAnim(UUID uuid) {
|
||||
pieces.get(uuid).getSpatial().addControl(new MoveControl(
|
||||
pieces.get(uuid).getLocation(), getNextWaitingNode(pieceColor.get(uuid)).getLocation(),
|
||||
() -> throwPiece(uuid))
|
||||
);
|
||||
}
|
||||
|
||||
public void throwPiece(UUID uuid, Color throwColor) {
|
||||
switch (throwColor) {
|
||||
case ARMY -> throwShell(uuid);
|
||||
case NAVY -> throwMissile(uuid);
|
||||
case CYBER -> throwMatrix(uuid);
|
||||
case AIRFORCE -> throwBomb(uuid);
|
||||
default -> throw new RuntimeException("invalid color");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the throwing of a piece to the next available waiting node.
|
||||
*
|
||||
* @param uuid the UUID of the piece to animate
|
||||
*/
|
||||
private void throwBomb(UUID uuid) {
|
||||
Vector3f targetPoint = pieces.get(uuid).getLocation();
|
||||
|
||||
JetAnimation anim = new JetAnimation(app, rootNode, targetPoint, 40, 6, () -> throwPieceAnim(uuid));
|
||||
anim.start();
|
||||
}
|
||||
|
||||
private void throwMatrix(UUID uuid) {
|
||||
app.getAcousticHandler().playSound(MdgaSound.MATRIX);
|
||||
Spatial piece = pieces.get(uuid).getSpatial();
|
||||
piece.addControl(new MatrixAnimation(app, radarPos, () -> {
|
||||
throwPieceAnim(uuid);
|
||||
}));
|
||||
}
|
||||
|
||||
private void throwMissile(UUID uuid) {
|
||||
Vector3f targetPoint = pieces.get(uuid).getLocation();
|
||||
|
||||
MissileAnimation anim = new MissileAnimation(app, rootNode, targetPoint, 2f, () -> throwPieceAnim(uuid));
|
||||
anim.start();
|
||||
}
|
||||
|
||||
private void throwShell(UUID uuid) {
|
||||
pieces.get(uuid).getSpatial().addControl(new ShellAnimation(tankTop, app, () -> throwPieceAnim(uuid)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the swapping of two pieces by swapping their positions and rotations.
|
||||
*
|
||||
* @param piece1 the UUID of the first piece
|
||||
* @param piece2 the UUID of the second piece
|
||||
*/
|
||||
public void swapPieces(UUID piece1, UUID piece2) {
|
||||
PieceControl piece1Control = pieces.get(piece1);
|
||||
PieceControl piece2Control = pieces.get(piece2);
|
||||
|
||||
Vector3f loc1 = piece1Control.getLocation().clone();
|
||||
Vector3f loc2 = piece2Control.getLocation().clone();
|
||||
float rot1 = piece1Control.getRotation();
|
||||
float rot2 = piece2Control.getRotation();
|
||||
|
||||
piece1Control.getSpatial().addControl(new MoveControl(
|
||||
piece1Control.getLocation().clone(),
|
||||
piece2Control.getLocation().clone(),
|
||||
() -> {
|
||||
}
|
||||
));
|
||||
piece2Control.getSpatial().addControl(new MoveControl(
|
||||
piece2Control.getLocation().clone(),
|
||||
piece1Control.getLocation().clone(),
|
||||
() -> executeSwap(piece1Control, piece2Control, loc1, rot1, loc2, rot2)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the next available waiting node for the specified color.
|
||||
*
|
||||
* @param color the color of the player to get the next waiting node for
|
||||
* @return the next available NodeControl for the specified color
|
||||
* @throws IllegalStateException if no available waiting nodes are found for the color
|
||||
*/
|
||||
private NodeControl getNextWaitingNode(Color color) {
|
||||
List<NodeControl> nodes = waitingNodesMap.get(color);
|
||||
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
throw new IllegalStateException("Keine verfügbaren Warteschleifen-Knoten für die Farbe " + color);
|
||||
}
|
||||
|
||||
for (NodeControl node : nodes) {
|
||||
if (!waitingNodes.getOrDefault(color, new HashMap<>()).containsValue(node)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Keine freien Nodes im Wartebereich für die Farbe " + color);
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.post.filters.FXAAFilter;
|
||||
import com.jme3.post.ssao.SSAOFilter;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.shadow.DirectionalLightShadowFilter;
|
||||
import com.jme3.shadow.EdgeFilteringMode;
|
||||
import com.jme3.util.SkyFactory;
|
||||
import com.jme3.util.SkyFactory.EnvMapType;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.game.Color;
|
||||
|
||||
/**
|
||||
* Handles the camera position, rotation, and lighting effects for the game.
|
||||
* Provides methods for camera initialization, updates based on user input, and shutdown operations.
|
||||
*/
|
||||
public class CameraHandler {
|
||||
MdgaApp app;
|
||||
|
||||
private DirectionalLight sun;
|
||||
private AmbientLight ambient;
|
||||
|
||||
private static final int SHADOWMAP_SIZE = 1024 * 8;
|
||||
|
||||
private Vector3f defaultCameraPosition;
|
||||
private Quaternion defaultCameraRotation;
|
||||
|
||||
FilterPostProcessor fpp;
|
||||
DirectionalLightShadowFilter dlsf;
|
||||
|
||||
Spatial sky;
|
||||
private Color ownColor;
|
||||
private boolean init;
|
||||
private boolean initRot;
|
||||
private SSAOFilter ssaoFilter;
|
||||
private FXAAFilter fxaaFilter;
|
||||
|
||||
/**
|
||||
* Constructor for the CameraHandler. Initializes the camera settings and lighting.
|
||||
*
|
||||
* @param app The main application instance that provides the camera and root node.
|
||||
* @param fpp The FilterPostProcessor used for post-processing effects.
|
||||
*/
|
||||
public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
|
||||
init = false;
|
||||
initRot = false;
|
||||
this.app = app;
|
||||
this.fpp = fpp;
|
||||
// Save the default camera state
|
||||
this.defaultCameraPosition = app.getCamera().getLocation().clone();
|
||||
this.defaultCameraRotation = app.getCamera().getRotation().clone();
|
||||
|
||||
sun = new DirectionalLight();
|
||||
sun.setColor(ColorRGBA.White);
|
||||
sun.setDirection(new Vector3f(0.3f, 0, -1));
|
||||
|
||||
ambient = new AmbientLight();
|
||||
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
|
||||
|
||||
dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 1);
|
||||
dlsf.setLight(sun);
|
||||
dlsf.setEnabled(true);
|
||||
dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
|
||||
dlsf.setShadowIntensity(0.7f);
|
||||
ssaoFilter = new SSAOFilter(6, 10f, 0.33f, 0.61f);
|
||||
// ssaoFilter = new SSAOFilter();
|
||||
fxaaFilter = new FXAAFilter();
|
||||
|
||||
sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI * 1, 0, FastMath.HALF_PI * 0.2f);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the camera with a specific color orientation.
|
||||
* Adds lights, sky, and shadow filters to the scene.
|
||||
*
|
||||
* @param ownColor The color that defines the initial camera view angle.
|
||||
*/
|
||||
public void init(Color ownColor) {
|
||||
app.getRootNode().addLight(sun);
|
||||
app.getRootNode().addLight(ambient);
|
||||
app.getRootNode().attachChild(sky);
|
||||
fpp.addFilter(dlsf);
|
||||
fpp.addFilter(ssaoFilter);
|
||||
fpp.addFilter(fxaaFilter);
|
||||
init = true;
|
||||
initRot = true;
|
||||
this.ownColor = ownColor;
|
||||
app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor) * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the camera handler by removing all lights, sky, and filters,
|
||||
* and resets the camera position and rotation to its default state.
|
||||
*/
|
||||
public void shutdown() {
|
||||
init = false;
|
||||
fpp.removeFilter(fxaaFilter);
|
||||
fpp.removeFilter(ssaoFilter);
|
||||
fpp.removeFilter(dlsf);
|
||||
app.getRootNode().detachChild(sky);
|
||||
app.getRootNode().removeLight(ambient);
|
||||
app.getRootNode().removeLight(sun);
|
||||
|
||||
|
||||
// Reset the camera to its default state
|
||||
app.getCamera().setLocation(defaultCameraPosition);
|
||||
app.getCamera().setRotation(defaultCameraRotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the camera position and rotation based on user input (scroll and rotation).
|
||||
* Adjusts the vertical angle and radius based on zoom and rotation values.
|
||||
*
|
||||
* @param scroll The scroll input, determining zoom level.
|
||||
* @param rotation The rotation input, determining camera orientation.
|
||||
*/
|
||||
public void update(float scroll, float rotation) {
|
||||
if (!init) return;
|
||||
float scrollValue = Math.max(0, Math.min(scroll, 100));
|
||||
|
||||
float rotationValue = rotation % 360;
|
||||
if (rotationValue < 0) {
|
||||
rotationValue += 360;
|
||||
}
|
||||
|
||||
|
||||
float radius;
|
||||
|
||||
float verticalAngle;
|
||||
if (scroll < 100f) {
|
||||
verticalAngle = 20f + (scrollValue / 100f) * 45f;
|
||||
radius = 30f;
|
||||
} else {
|
||||
verticalAngle = 90f;
|
||||
rotationValue = getAngleByColor(ownColor);
|
||||
radius = 50f;
|
||||
}
|
||||
float verticalAngleRadians = FastMath.DEG_TO_RAD * verticalAngle;
|
||||
|
||||
float z = radius * FastMath.sin(verticalAngleRadians);
|
||||
float x = radius * FastMath.cos(verticalAngleRadians) * FastMath.sin(FastMath.DEG_TO_RAD * rotationValue);
|
||||
float y = radius * FastMath.cos(verticalAngleRadians) * FastMath.cos(FastMath.DEG_TO_RAD * rotationValue);
|
||||
|
||||
Vector3f cameraPosition = new Vector3f(x, y, z);
|
||||
app.getCamera().setLocation(cameraPosition);
|
||||
|
||||
app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the camera angle based on the specified color.
|
||||
*
|
||||
* @param color The color used to determine the camera angle.
|
||||
* @return The camera angle in degrees.
|
||||
*/
|
||||
private float getAngleByColor(Color color) {
|
||||
return switch (color) {
|
||||
case ARMY -> 0;
|
||||
case AIRFORCE -> 90;
|
||||
case NAVY -> 270;
|
||||
case CYBER -> 180;
|
||||
default -> throw new RuntimeException("None is not allowed");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial camera angle based on the specified color.
|
||||
*
|
||||
* @param color The color used to determine the camera angle.
|
||||
* @return The initial camera angle in degrees.
|
||||
*/
|
||||
private float getInitAngleByColor(Color color) {
|
||||
return (getAngleByColor(color) + 180) % 360;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
|
||||
import pp.mdga.client.Asset;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A utility class for loading and parsing map data from a file.
|
||||
* The map contains asset names and coordinates for objects placed on the map.
|
||||
*/
|
||||
class MapLoader {
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
*/
|
||||
private MapLoader() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a map file and parses its contents into a list of assets and their positions.
|
||||
* Each line in the map file defines an asset, its coordinates, and its rotation.
|
||||
*
|
||||
* @param mapName The name of the map file to load. The file is expected to be located in the resources directory.
|
||||
* @return A list of {@link AssetOnMap} objects representing the assets placed on the map.
|
||||
* @throws IOException If an error occurs while reading the map file.
|
||||
* @throws IllegalArgumentException If the map file contains invalid data.
|
||||
*/
|
||||
public static List<AssetOnMap> loadMap(String mapName) {
|
||||
List<AssetOnMap> assetsOnMap = new ArrayList<>();
|
||||
|
||||
try (
|
||||
InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
|
||||
) {
|
||||
|
||||
while (true) {
|
||||
String entry = reader.readLine();
|
||||
if (entry == null) break;
|
||||
|
||||
entry = entry.trim();
|
||||
|
||||
if (entry.isEmpty()) continue;
|
||||
if (entry.charAt(0) == '#') continue;
|
||||
|
||||
String[] parts = entry.trim().split(" ");
|
||||
|
||||
assert (parts.length == 3) : "MapLoader: line has not 3 parts";
|
||||
|
||||
String assetName = parts[0];
|
||||
String[] coordinates = parts[1].split(",");
|
||||
|
||||
assert (coordinates.length == 2) : "MapLoade: coordinates are wrong";
|
||||
|
||||
int x = Integer.parseInt(coordinates[0]);
|
||||
int y = Integer.parseInt(coordinates[1]);
|
||||
|
||||
float rot = Float.parseFloat(parts[2]);
|
||||
|
||||
Asset asset = getLoadedAsset(assetName);
|
||||
assetsOnMap.add(new AssetOnMap(asset, x, y, rot));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return assetsOnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding {@link Asset} for a given asset name.
|
||||
*
|
||||
* @param assetName The name of the asset to load.
|
||||
* @return The {@link Asset} associated with the given name.
|
||||
* @throws IllegalStateException If the asset name is unrecognized.
|
||||
*/
|
||||
private static Asset getLoadedAsset(String assetName) {
|
||||
return switch (assetName) {
|
||||
case "lw" -> Asset.lw;
|
||||
case "cir" -> Asset.cir;
|
||||
case "marine" -> Asset.marine;
|
||||
case "heer" -> Asset.heer;
|
||||
case "node" -> Asset.node_normal;
|
||||
case "node_start" -> Asset.node_start;
|
||||
case "node_bonus" -> Asset.node_bonus;
|
||||
case "node_home_blue" -> Asset.node_home_blue;
|
||||
case "node_home_yellow" -> Asset.node_home_yellow;
|
||||
case "node_home_black" -> Asset.node_home_black;
|
||||
case "node_home_green" -> Asset.node_home_green;
|
||||
case "node_wait_blue" -> Asset.node_wait_blue;
|
||||
case "node_wait_yellow" -> Asset.node_wait_yellow;
|
||||
case "node_wait_black" -> Asset.node_wait_black;
|
||||
case "node_wait_green" -> Asset.node_wait_green;
|
||||
case "world" -> Asset.world;
|
||||
case "jet" -> Asset.jet;
|
||||
case "big_tent" -> Asset.bigTent;
|
||||
case "small_tent" -> Asset.smallTent;
|
||||
case "radar" -> Asset.radar;
|
||||
case "ship" -> Asset.ship;
|
||||
case "tank" -> Asset.tank;
|
||||
case "treeSmall" -> Asset.treeSmall;
|
||||
case "treeBig" -> Asset.treeBig;
|
||||
case "tank_shoot" -> Asset.tankShoot;
|
||||
case "treesBigBackground" -> Asset.treesBigBackground;
|
||||
case "treesSmallBackground" -> Asset.treesSmallBackground;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + assetName);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.outline.OutlineControl;
|
||||
|
||||
/**
|
||||
* A control that adds highlighting functionality to a node in the game.
|
||||
* This class extends {@link OutlineControl} to add an outline effect when the node is highlighted.
|
||||
*/
|
||||
public class NodeControl extends OutlineOEControl {
|
||||
|
||||
/**
|
||||
* Constructs a {@link NodeControl} with the specified application and post processor.
|
||||
* This constructor sets up the necessary elements for highlighting functionality.
|
||||
*
|
||||
* @param app The {@link MdgaApp} instance to use for the application context.
|
||||
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
|
||||
*/
|
||||
public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
|
||||
super(app, fpp, app.getCamera());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location of the node in 3D space.
|
||||
* This is the node's local translation in the scene.
|
||||
*
|
||||
* @return The {@link Vector3f} representing the node's location.
|
||||
*/
|
||||
public Vector3f getLocation() {
|
||||
return this.getSpatial().getLocalTranslation();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.renderer.Camera;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.outline.OutlineControl;
|
||||
|
||||
/**
|
||||
* OutlineOEControl class extends OutlineControl to manage outline colors and widths
|
||||
* for own and enemy objects, including hover and select states.
|
||||
*/
|
||||
public class OutlineOEControl extends OutlineControl {
|
||||
private static final ColorRGBA OUTLINE_OWN_COLOR = ColorRGBA.White;
|
||||
private static final ColorRGBA OUTLINE_ENEMY_COLOR = ColorRGBA.Red;
|
||||
private static final ColorRGBA OUTLINE_OWN_HOVER_COLOR = ColorRGBA.Yellow;
|
||||
private static final ColorRGBA OUTLINE_ENEMY_HOVER_COLOR = ColorRGBA.Green;
|
||||
private static final ColorRGBA OUTLINE_OWN_SELECT_COLOR = ColorRGBA.Cyan;
|
||||
private static final ColorRGBA OUTLINE_ENEMY_SELECT_COLOR = ColorRGBA.Orange;
|
||||
private static final int OUTLINE_HIGHLIGHT_WIDTH = 8;
|
||||
private static final int OUTLINE_HOVER_WIDTH = 8;
|
||||
private static final int OUTLINE_SELECT_WIDTH = 10;
|
||||
|
||||
/**
|
||||
* Constructor for OutlineOEControl.
|
||||
*
|
||||
* @param app the MdgaApp instance
|
||||
* @param fpp the FilterPostProcessor instance
|
||||
* @param cam the Camera instance
|
||||
*/
|
||||
public OutlineOEControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
|
||||
super(app, fpp, cam,
|
||||
OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH,
|
||||
OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH,
|
||||
OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the outline colors and enables selection for own objects.
|
||||
*/
|
||||
public void selectableOwn(){
|
||||
setHighlightColor(OUTLINE_OWN_COLOR);
|
||||
setHoverColor(OUTLINE_OWN_HOVER_COLOR);
|
||||
setSelectColor(OUTLINE_OWN_SELECT_COLOR);
|
||||
selectableOn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the outline colors and enables selection for enemy objects.
|
||||
*/
|
||||
public void selectableEnemy(){
|
||||
setHighlightColor(OUTLINE_ENEMY_COLOR);
|
||||
setHoverColor(OUTLINE_ENEMY_HOVER_COLOR);
|
||||
setSelectColor(OUTLINE_ENEMY_SELECT_COLOR);
|
||||
selectableOn();
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import pp.mdga.client.Asset;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.outline.OutlineControl;
|
||||
|
||||
/**
|
||||
* A control that manages the behavior and properties of a game piece, such as its rotation,
|
||||
* position, shield activation, and highlighting. This class extends {@link OutlineControl}
|
||||
* to provide outline functionality and includes additional features like shield effects,
|
||||
* hover states, and selection states.
|
||||
*/
|
||||
public class PieceControl extends OutlineOEControl {
|
||||
private final float initRotation;
|
||||
private final AssetManager assetManager;
|
||||
private Spatial shieldRing;
|
||||
private final Material shieldMat;
|
||||
|
||||
private static final float SHIELD_SPEED = 1f;
|
||||
private static final float SHIELD_TRANSPARENCY = 0.6f;
|
||||
private static final ColorRGBA SHIELD_COLOR = new ColorRGBA(0, 0.9f, 1, SHIELD_TRANSPARENCY);
|
||||
private static final ColorRGBA SHIELD_SUPPRESSED_COLOR = new ColorRGBA(1f, 0.5f, 0, SHIELD_TRANSPARENCY);
|
||||
private static final float SHIELD_Z = 0f;
|
||||
|
||||
private final Node parentNode;
|
||||
private boolean enemy;
|
||||
private boolean hoverable;
|
||||
private boolean highlight;
|
||||
private boolean selectable;
|
||||
private boolean select;
|
||||
|
||||
/**
|
||||
* Constructs a {@link PieceControl} with the specified initial rotation, asset manager,
|
||||
* application, and post-processor.
|
||||
*
|
||||
* @param initRotation The initial rotation of the piece in degrees.
|
||||
* @param assetManager The {@link AssetManager} used for loading models and materials.
|
||||
* @param app The {@link MdgaApp} instance to use for the application context.
|
||||
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
|
||||
*/
|
||||
public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp) {
|
||||
super(app, fpp, app.getCamera());
|
||||
this.parentNode = new Node();
|
||||
this.initRotation = initRotation;
|
||||
this.assetManager = assetManager;
|
||||
this.shieldRing = null;
|
||||
this.shieldMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
this.shieldMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current rotation of the piece in degrees.
|
||||
*
|
||||
* @return The rotation of the piece in degrees.
|
||||
*/
|
||||
public float getRotation() {
|
||||
return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0, 0, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rotation of the piece to the specified value in degrees.
|
||||
*
|
||||
* @param rot The rotation in degrees to set.
|
||||
*/
|
||||
public void setRotation(float rot) {
|
||||
if (rot < 0) rot = -360;
|
||||
|
||||
Quaternion quaternion = new Quaternion();
|
||||
quaternion.fromAngleAxis((float) Math.toRadians(rot), new Vector3f(0, 0, 1));
|
||||
spatial.setLocalRotation(quaternion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current location (position) of the piece.
|
||||
*
|
||||
* @return The location of the piece as a {@link Vector3f}.
|
||||
*/
|
||||
public Vector3f getLocation() {
|
||||
return spatial.getLocalTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the piece control every frame. If the shield is active, it will rotate.
|
||||
*
|
||||
* @param delta The time difference between frames (time per frame).
|
||||
*/
|
||||
@Override
|
||||
protected void controlUpdate(float delta) {
|
||||
if (shieldRing != null) {
|
||||
shieldRing.rotate(0, 0, delta * SHIELD_SPEED);
|
||||
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location (position) of the piece.
|
||||
*
|
||||
* @param loc The location to set as a {@link Vector3f}.
|
||||
*/
|
||||
public void setLocation(Vector3f loc) {
|
||||
this.spatial.setLocalTranslation(loc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the spatial object and sets its rotation.
|
||||
* This also moves the spatial to a new parent node for organizational purposes.
|
||||
*/
|
||||
@Override
|
||||
public void initSpatial() {
|
||||
setRotation(this.initRotation);
|
||||
|
||||
Node oldParent = spatial.getParent();
|
||||
this.parentNode.setName(spatial.getName() + " Parent");
|
||||
oldParent.detachChild(this.getSpatial());
|
||||
this.parentNode.attachChild(this.getSpatial());
|
||||
oldParent.attachChild(this.parentNode);
|
||||
}
|
||||
|
||||
public void rotateInit() {
|
||||
setRotation(initRotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the shield around the piece.
|
||||
* This adds a visual shield effect in the form of a rotating ring.
|
||||
*/
|
||||
public void activateShield() {
|
||||
if (shieldRing != null) {
|
||||
deactivateShield();
|
||||
}
|
||||
shieldRing = assetManager.loadModel(Asset.shieldRing.getModelPath());
|
||||
shieldRing.scale(1f);
|
||||
shieldRing.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(0));
|
||||
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0, 0, SHIELD_Z)));
|
||||
|
||||
|
||||
shieldRing.setQueueBucket(RenderQueue.Bucket.Transparent); // Render in the transparent bucket
|
||||
shieldMat.setColor("Color", SHIELD_COLOR);
|
||||
shieldRing.setMaterial(shieldMat);
|
||||
|
||||
parentNode.attachChild(shieldRing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the shield by removing the shield ring from the scene.
|
||||
*/
|
||||
|
||||
public void deactivateShield() {
|
||||
parentNode.detachChild(shieldRing);
|
||||
shieldRing = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses the shield, changing its color to a suppressed state.
|
||||
*/
|
||||
public void suppressShield() {
|
||||
assert (shieldRing != null) : "PieceControl: shieldRing is not set";
|
||||
shieldMat.setColor("Color", SHIELD_SUPPRESSED_COLOR);
|
||||
}
|
||||
|
||||
public void setMaterial(Material mat) {
|
||||
spatial.setMaterial(mat);
|
||||
}
|
||||
|
||||
public Material getMaterial() {
|
||||
return ((Geometry) getSpatial()).getMaterial();
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package pp.mdga.client.board;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import pp.mdga.client.InitControl;
|
||||
|
||||
import static pp.mdga.client.Util.linInt;
|
||||
|
||||
/**
|
||||
* Controls the rotation of the tank's top part to face an enemy position.
|
||||
*/
|
||||
public class TankTopControl extends InitControl {
|
||||
|
||||
private float timer = 0; // Time elapsed
|
||||
private final static float DURATION = 1.5f; // Total rotation duration in seconds
|
||||
private boolean rotating = false; // Flag to track if rotation is active
|
||||
private float startAngle = 0;
|
||||
private float endAngle = 0;
|
||||
private Runnable actionAfter = null;
|
||||
|
||||
/**
|
||||
* Updates the control each frame.
|
||||
*
|
||||
* @param tpf Time per frame
|
||||
*/
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (!rotating) return;
|
||||
|
||||
// Update the timer
|
||||
timer += tpf;
|
||||
|
||||
// Calculate interpolation factor (0 to 1)
|
||||
float t = timer / DURATION;
|
||||
|
||||
if (t >= 1) t = 1;
|
||||
|
||||
float curAngle = linInt(startAngle, endAngle, t);
|
||||
|
||||
// Interpolate the rotation
|
||||
Quaternion interpolatedRotation = new Quaternion();
|
||||
interpolatedRotation.fromAngleAxis((float) Math.toRadians(curAngle), Vector3f.UNIT_Z);
|
||||
|
||||
// Apply the interpolated rotation to the spatial
|
||||
spatial.setLocalRotation(interpolatedRotation);
|
||||
|
||||
if (t >= 1) {
|
||||
rotating = false;
|
||||
if (actionAfter != null) actionAfter.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the rotation of the tank's top part to face the enemy position.
|
||||
*
|
||||
* @param enemyPos The position of the enemy
|
||||
* @param actionAfter The action to execute after the rotation is complete
|
||||
*/
|
||||
public void rotate(Vector3f enemyPos, Runnable actionAfter) {
|
||||
if (spatial == null) throw new RuntimeException("spatial is null");
|
||||
|
||||
startAngle = getOwnAngle();
|
||||
endAngle = getEnemyAngle(enemyPos);
|
||||
|
||||
// Adjust endAngle to ensure the shortest path
|
||||
float deltaAngle = endAngle - startAngle;
|
||||
if (deltaAngle > 180) {
|
||||
endAngle -= 360; // Rotate counterclockwise
|
||||
} else if (deltaAngle < -180) {
|
||||
endAngle += 360; // Rotate clockwise
|
||||
}
|
||||
|
||||
timer = 0;
|
||||
rotating = true;
|
||||
this.actionAfter = actionAfter; // Store the action to execute after rotation
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the angle to the enemy position.
|
||||
*
|
||||
* @param enemyPos The position of the enemy
|
||||
* @return The angle to the enemy in degrees
|
||||
*/
|
||||
private float getEnemyAngle(Vector3f enemyPos) {
|
||||
// Direction to the enemy in the XY plane
|
||||
Vector3f direction = enemyPos.subtract(spatial.getLocalTranslation());
|
||||
direction.z = 0; // Project to XY plane
|
||||
direction.normalizeLocal();
|
||||
|
||||
Vector3f reference = Vector3f.UNIT_Y.mult(-1);
|
||||
|
||||
// Calculate the angle between the direction vector and the reference vector
|
||||
float angle = FastMath.acos(reference.dot(direction));
|
||||
|
||||
// Determine rotation direction using the cross product
|
||||
Vector3f cross = reference.cross(direction);
|
||||
if (cross.z < 0) {
|
||||
angle = -angle;
|
||||
}
|
||||
|
||||
return (float) Math.toDegrees(angle); // Return the absolute angle in degrees
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the tank's current angle.
|
||||
*
|
||||
* @return The tank's current angle in degrees
|
||||
*/
|
||||
private float getOwnAngle() {
|
||||
// Tank's forward direction in the XY plane
|
||||
Vector3f forward = spatial.getLocalRotation().mult(Vector3f.UNIT_Y);
|
||||
forward.z = 0; // Project to XY plane
|
||||
forward.normalizeLocal();
|
||||
|
||||
// Reference vector: Positive X-axis
|
||||
Vector3f reference = Vector3f.UNIT_Y;
|
||||
|
||||
// Calculate the angle between the forward vector and the reference vector
|
||||
float angle = FastMath.acos(reference.dot(forward));
|
||||
|
||||
// Determine rotation direction using the cross product
|
||||
Vector3f cross = reference.cross(forward);
|
||||
if (cross.z < 0) { // For Z-up, check the Z component of the cross product
|
||||
angle = -angle;
|
||||
}
|
||||
|
||||
return (float) Math.toDegrees(angle); // Return the absolute angle in radians
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.ui.Picture;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents an abstract base class for creating customizable button components in a graphical user interface.
|
||||
* This class provides the framework for rendering buttons with different visual states, such as normal and pressed,
|
||||
* and supports position adjustments and font customization.
|
||||
*
|
||||
* <p>Subclasses must implement the {@link #show()} and {@link #hide()} methods to define how the button
|
||||
* is displayed and hidden in the application.</p>
|
||||
*/
|
||||
public abstract class AbstractButton {
|
||||
/**
|
||||
* Color representing the normal state of the button.
|
||||
*/
|
||||
public static final ColorRGBA BUTTON_NORMAL = ColorRGBA.fromRGBA255(233, 236, 239, 255);
|
||||
|
||||
/**
|
||||
* Color representing the pressed state of the button.
|
||||
*/
|
||||
public static final ColorRGBA BUTTON_PRESSED = ColorRGBA.fromRGBA255(105, 117, 89, 255);
|
||||
|
||||
/**
|
||||
* Color representing the normal state of the button text.
|
||||
*/
|
||||
public static final ColorRGBA TEXT_NORMAL = ColorRGBA.Black;
|
||||
|
||||
/**
|
||||
* Color representing the pressed state of the button text.
|
||||
*/
|
||||
public static final ColorRGBA TEXT_PRESSED = ColorRGBA.fromRGBA255(180, 195, 191, 255);
|
||||
|
||||
/**
|
||||
* The image representing the normal state of the button.
|
||||
*/
|
||||
protected Picture pictureNormal = new Picture("normalButton");
|
||||
|
||||
/**
|
||||
* The image representing the hover state of the button.
|
||||
*/
|
||||
protected Picture pictureHover = new Picture("normalButton");
|
||||
|
||||
/**
|
||||
* The number of horizontal divisions for calculating relative sizes.
|
||||
*/
|
||||
public static final float HORIZONTAL = 16;
|
||||
|
||||
/**
|
||||
* The number of vertical divisions for calculating relative sizes.
|
||||
*/
|
||||
public static final float VERTICAL = 9;
|
||||
|
||||
/**
|
||||
* The font used for rendering text on the button.
|
||||
*/
|
||||
protected BitmapFont font;
|
||||
|
||||
/**
|
||||
* Reference to the application instance for accessing assets and settings.
|
||||
*/
|
||||
protected final MdgaApp app;
|
||||
|
||||
/**
|
||||
* Node in the scene graph to which the button belongs.
|
||||
*/
|
||||
protected final Node node;
|
||||
|
||||
/**
|
||||
* The position of the button in 2D space.
|
||||
*/
|
||||
protected Vector2f pos;
|
||||
|
||||
/**
|
||||
* Factor for scaling the font size.
|
||||
*/
|
||||
protected float fontSizeFactor = 1.0f;
|
||||
|
||||
/**
|
||||
* Computed font size based on scaling factor and screen dimensions.
|
||||
*/
|
||||
protected float fontSize;
|
||||
|
||||
/**
|
||||
* Computed horizontal step size based on screen dimensions.
|
||||
*/
|
||||
protected float horizontalStep;
|
||||
|
||||
/**
|
||||
* Computed vertical step size based on screen dimensions.
|
||||
*/
|
||||
protected float verticalStep;
|
||||
|
||||
/**
|
||||
* Computed height step size based on vertical steps.
|
||||
*/
|
||||
protected float heightStep;
|
||||
|
||||
/**
|
||||
* Computed width step size based on horizontal steps.
|
||||
*/
|
||||
protected float widthStep;
|
||||
|
||||
/**
|
||||
* Flag indicating whether adjustments are applied to the button.
|
||||
*/
|
||||
protected boolean adjust = false;
|
||||
|
||||
/**
|
||||
* Constructs an AbstractButton instance with the specified application context and scene node.
|
||||
* Initializes the button's visual elements and font.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the button is attached
|
||||
*/
|
||||
public AbstractButton(MdgaApp app, Node node) {
|
||||
this.app = app;
|
||||
this.node = node;
|
||||
|
||||
pictureNormal.setImage(app.getAssetManager(), "Images/General_Button_normal.png", true);
|
||||
pictureHover.setImage(app.getAssetManager(), "Images/General_Button_hover.png", true);
|
||||
|
||||
font = app.getAssetManager().loadFont("Fonts/Gunplay.fnt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the button. Implementation must define how the button is rendered on the screen.
|
||||
*/
|
||||
public abstract void show();
|
||||
|
||||
/**
|
||||
* Hides the button. Implementation must define how the button is removed from the screen.
|
||||
*/
|
||||
public abstract void hide();
|
||||
|
||||
/**
|
||||
* Sets the position of the button in 2D space.
|
||||
*
|
||||
* @param pos the position to set
|
||||
*/
|
||||
public void setPos(Vector2f pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates relative sizes and dimensions for the button based on the screen resolution.
|
||||
*/
|
||||
protected void calculateRelative() {
|
||||
fontSize = fontSizeFactor * 15 * (float) app.getCamera().getWidth() / 720;
|
||||
|
||||
horizontalStep = (float) app.getCamera().getWidth() / HORIZONTAL;
|
||||
verticalStep = (float) app.getCamera().getHeight() / VERTICAL;
|
||||
heightStep = verticalStep / 2;
|
||||
widthStep = horizontalStep / 2;
|
||||
}
|
||||
|
||||
public Vector2f getPos() {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents a specific implementation of a clickable button positioned on the left side.
|
||||
* This class extends {@link ClickButton} and provides a predefined position and size for the button.
|
||||
* It also includes placeholder methods for handling hover events, which can be customized as needed.
|
||||
*/
|
||||
public class ButtonLeft extends ClickButton {
|
||||
|
||||
/**
|
||||
* Constructs a ButtonLeft instance with the specified properties.
|
||||
*
|
||||
* @param app the application instance for accessing resources and settings
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param action the action to execute when the button is clicked
|
||||
* @param label the text label to display on the button
|
||||
* @param narrowFactor a factor to adjust position of the button
|
||||
*/
|
||||
public ButtonLeft(MdgaApp app, Node node, Runnable action, String label, int narrowFactor) {
|
||||
super(app, node, action, label, new Vector2f(5, 2), new Vector2f(0.5f * narrowFactor, 1.8f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the button is hovered over by the pointer.
|
||||
* Subclasses can override this method to define specific hover behavior.
|
||||
*/
|
||||
@Override
|
||||
public void onHover() {
|
||||
// Placeholder for hover behavior
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the pointer stops hovering over the button.
|
||||
* Subclasses can override this method to define specific unhover behavior.
|
||||
*/
|
||||
@Override
|
||||
public void onUnHover() {
|
||||
// Placeholder for unhover behavior
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents a specific implementation of a clickable button positioned on the right side.
|
||||
* This class extends {@link ClickButton} and provides a predefined position and size for the button.
|
||||
* It includes placeholder methods for handling hover events, which can be customized as needed.
|
||||
*/
|
||||
public class ButtonRight extends ClickButton {
|
||||
|
||||
/**
|
||||
* Constructs a ButtonRight instance with the specified properties.
|
||||
*
|
||||
* @param app the application instance for accessing resources and settings
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param action the action to execute when the button is clicked
|
||||
* @param label the text label to display on the button
|
||||
* @param narrowFactor a factor to adjust the position of the button
|
||||
*/
|
||||
public ButtonRight(MdgaApp app, Node node, Runnable action, String label, int narrowFactor) {
|
||||
super(app, node, action, label, new Vector2f(5, 2), new Vector2f(HORIZONTAL - 0.5f * narrowFactor, 1.8f));
|
||||
|
||||
// Enable adjustments specific to this button
|
||||
adjust = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the button is hovered over by the pointer.
|
||||
* Subclasses can override this method to define specific hover behavior.
|
||||
*/
|
||||
@Override
|
||||
public void onHover() {
|
||||
// Placeholder for hover behavior
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the pointer stops hovering over the button.
|
||||
* Subclasses can override this method to define specific unhover behavior.
|
||||
*/
|
||||
@Override
|
||||
public void onUnHover() {
|
||||
// Placeholder for unhover behavior
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
|
||||
import pp.mdga.client.Asset;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.game.Color;
|
||||
|
||||
/**
|
||||
* Represents a button used in a ceremony screen, with 3D model integration, customizable
|
||||
* appearance based on type, and interactive behavior. The button can rotate and display
|
||||
* different positions such as FIRST, SECOND, THIRD, and LOST.
|
||||
*/
|
||||
public class CeremonyButton extends ClickButton {
|
||||
|
||||
/**
|
||||
* Enum representing the possible positions of the button in the ceremony screen.
|
||||
*/
|
||||
public enum Pos {
|
||||
FIRST,
|
||||
SECOND,
|
||||
THIRD,
|
||||
LOST,
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed width of the button in the UI layout.
|
||||
*/
|
||||
static final float WIDTH = 4.0f;
|
||||
|
||||
/**
|
||||
* Node to which the 3D model associated with this button is attached.
|
||||
*/
|
||||
private final Node node3d;
|
||||
|
||||
/**
|
||||
* Flag to determine if the button's 3D model should rotate.
|
||||
*/
|
||||
private boolean rotate = false;
|
||||
|
||||
/**
|
||||
* The 3D model associated with the button.
|
||||
*/
|
||||
private Spatial model;
|
||||
|
||||
/**
|
||||
* Current rotation angle of the button's 3D model.
|
||||
*/
|
||||
private float rot = 180;
|
||||
|
||||
/**
|
||||
* The taken state of the button (default is NOT taken).
|
||||
*/
|
||||
private LobbyButton.Taken taken = LobbyButton.Taken.NOT;
|
||||
|
||||
/**
|
||||
* A label associated with the button for displaying additional information.
|
||||
*/
|
||||
private LabelButton label;
|
||||
|
||||
/**
|
||||
* Constructs a CeremonyButton with specified attributes such as type, position, and label.
|
||||
* The button supports both 2D and 3D components for UI and visual effects.
|
||||
*
|
||||
* @param app the application instance for accessing resources and settings
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param node3d the node for 3D scene components associated with this button
|
||||
* @param tsk the type/color associated with the button
|
||||
* @param pos the position of the button in the ceremony layout
|
||||
* @param name the label or name displayed on the button
|
||||
*/
|
||||
public CeremonyButton(MdgaApp app, Node node, Node node3d, Color tsk, Pos pos, String name) {
|
||||
super(app, node, () -> {
|
||||
}, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
|
||||
|
||||
this.node3d = node3d;
|
||||
|
||||
label = new LabelButton(app, node, name, new Vector2f(WIDTH, 1), new Vector2f(0, 0), true);
|
||||
|
||||
final float mid = HORIZONTAL / 2;
|
||||
final float uiSpacing = 1.4f;
|
||||
final float figSpacingX = 0.9f;
|
||||
final float figSpacingY = 0.25f;
|
||||
|
||||
float uiX = mid;
|
||||
float uiY = 6;
|
||||
float figX = 0;
|
||||
float figY = -0.32f;
|
||||
|
||||
Asset asset = switch (tsk) {
|
||||
case CYBER -> {
|
||||
instance.setText("CIR");
|
||||
yield Asset.cir;
|
||||
}
|
||||
case AIRFORCE -> {
|
||||
instance.setText("Luftwaffe");
|
||||
yield Asset.lw;
|
||||
}
|
||||
case ARMY -> {
|
||||
instance.setText("Heer");
|
||||
yield Asset.heer;
|
||||
}
|
||||
case NAVY -> {
|
||||
instance.setText("Marine");
|
||||
yield Asset.marine;
|
||||
}
|
||||
default -> throw new RuntimeException("None is not valid");
|
||||
};
|
||||
|
||||
switch (pos) {
|
||||
case FIRST:
|
||||
rotate = true;
|
||||
uiX = 0;
|
||||
figY -= 1 * figSpacingY;
|
||||
break;
|
||||
case SECOND:
|
||||
adjust = true;
|
||||
label.adjust = true;
|
||||
uiX -= uiSpacing;
|
||||
uiY -= 1;
|
||||
figX -= figSpacingX;
|
||||
figY -= 2 * figSpacingY;
|
||||
figY -= 0.1f;
|
||||
break;
|
||||
case THIRD:
|
||||
uiX += uiSpacing;
|
||||
uiY -= 1.5f;
|
||||
figX += figSpacingX;
|
||||
figY -= 3 * figSpacingY;
|
||||
figY -= 0.07f;
|
||||
break;
|
||||
case LOST:
|
||||
adjust = true;
|
||||
label.adjust = true;
|
||||
uiX -= 2 * uiSpacing + 0.4f;
|
||||
uiX -= WIDTH / 2;
|
||||
uiY -= 2.0f;
|
||||
figX -= 2.5f * figSpacingX + 0.05f;
|
||||
figY -= 4.5f * figSpacingY;
|
||||
break;
|
||||
}
|
||||
|
||||
setPos(new Vector2f(uiX, uiY));
|
||||
label.setPos(new Vector2f(uiX, uiY + 1));
|
||||
|
||||
createModel(asset, new Vector3f(figX, figY, 6));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles hover behavior by changing the button's background appearance.
|
||||
*/
|
||||
@Override
|
||||
public void onHover() {
|
||||
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
|
||||
buttonNormal.a = 0.1f;
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
|
||||
instance.setBackground(background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unhover behavior by resetting the button's background appearance.
|
||||
*/
|
||||
@Override
|
||||
public void onUnHover() {
|
||||
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
|
||||
buttonNormal.a = 0.1f;
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
|
||||
instance.setBackground(background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the button along with its 3D model and associated label.
|
||||
*/
|
||||
@Override
|
||||
public void show() {
|
||||
release();
|
||||
|
||||
calculateRelative();
|
||||
setRelative();
|
||||
|
||||
node.attachChild(instance);
|
||||
node3d.attachChild(model);
|
||||
|
||||
label.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the button along with its 3D model and associated label.
|
||||
*/
|
||||
@Override
|
||||
public void hide() {
|
||||
node.detachChild(instance);
|
||||
node3d.detachChild(model);
|
||||
|
||||
label.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the rotation of the button's 3D model over time.
|
||||
*
|
||||
* @param tpf time per frame, used for smooth rotation calculations
|
||||
*/
|
||||
public void update(float tpf) {
|
||||
if (rotate) {
|
||||
rot += 140.0f * tpf;
|
||||
rot %= 360;
|
||||
} else {
|
||||
rot = 180;
|
||||
}
|
||||
|
||||
model.setLocalRotation(new Quaternion().fromAngles(
|
||||
(float) Math.toRadians(90),
|
||||
(float) Math.toRadians(rot),
|
||||
(float) Math.toRadians(180)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 3D model associated with the button and applies its materials and position.
|
||||
*
|
||||
* @param asset the asset representing the 3D model and texture
|
||||
* @param pos the initial position of the model in 3D space
|
||||
*/
|
||||
private void createModel(Asset asset, Vector3f pos) {
|
||||
String modelName = asset.getModelPath();
|
||||
String texName = asset.getDiffPath();
|
||||
|
||||
model = app.getAssetManager().loadModel(modelName);
|
||||
model.scale(asset.getSize() / 2);
|
||||
model.rotate(
|
||||
(float) Math.toRadians(90),
|
||||
(float) Math.toRadians(rot),
|
||||
(float) Math.toRadians(180)
|
||||
);
|
||||
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
model.setLocalTranslation(pos);
|
||||
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
|
||||
model.setMaterial(mat);
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.ui.Picture;
|
||||
import com.simsilica.lemur.Button;
|
||||
import com.simsilica.lemur.HAlignment;
|
||||
import com.simsilica.lemur.VAlignment;
|
||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.acoustic.MdgaSound;
|
||||
|
||||
/**
|
||||
* Abstract base class for creating interactive buttons with click functionality.
|
||||
* This class extends {@link AbstractButton} and provides additional behavior such as
|
||||
* click handling, hover effects, and alignment management.
|
||||
*/
|
||||
public abstract class ClickButton extends AbstractButton {
|
||||
|
||||
/**
|
||||
* The action to be executed when the button is clicked.
|
||||
*/
|
||||
protected final Runnable action;
|
||||
|
||||
/**
|
||||
* The label or text displayed on the button.
|
||||
*/
|
||||
protected String label;
|
||||
|
||||
/**
|
||||
* The size of the button in relative units.
|
||||
*/
|
||||
protected Vector2f size;
|
||||
|
||||
/**
|
||||
* The instance of the button being managed.
|
||||
*/
|
||||
protected Button instance;
|
||||
|
||||
/**
|
||||
* Constructs a ClickButton with the specified properties.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param action the action to execute on button click
|
||||
* @param label the text label displayed on the button
|
||||
* @param size the size of the button
|
||||
* @param pos the position of the button in relative units
|
||||
*/
|
||||
ClickButton(MdgaApp app, Node node, Runnable action, String label, Vector2f size, Vector2f pos) {
|
||||
super(app, node);
|
||||
|
||||
this.action = action;
|
||||
this.label = label;
|
||||
this.pos = pos;
|
||||
this.size = size;
|
||||
|
||||
instance = new Button(label);
|
||||
|
||||
// Add click behavior
|
||||
instance.addClickCommands((button) -> {
|
||||
app.getAcousticHandler().playSound(MdgaSound.BUTTON_PRESSED);
|
||||
action.run();
|
||||
});
|
||||
|
||||
// Set text alignment
|
||||
instance.setTextHAlignment(HAlignment.Center);
|
||||
instance.setTextVAlignment(VAlignment.Center);
|
||||
|
||||
// Add hover commands
|
||||
instance.addCommands(Button.ButtonAction.HighlightOn, (button) -> click());
|
||||
instance.addCommands(Button.ButtonAction.HighlightOff, (button) -> release());
|
||||
|
||||
// Set font and colors
|
||||
instance.setFont(font);
|
||||
instance.setFocusColor(TEXT_NORMAL);
|
||||
|
||||
calculateRelative();
|
||||
setRelative();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the button by attaching it and its background image to the node.
|
||||
*/
|
||||
@Override
|
||||
public void show() {
|
||||
node.attachChild(pictureNormal);
|
||||
release();
|
||||
|
||||
calculateRelative();
|
||||
setRelative();
|
||||
setImageRelative(pictureNormal);
|
||||
|
||||
node.attachChild(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the button by detaching it and its background images from the node.
|
||||
*/
|
||||
@Override
|
||||
public void hide() {
|
||||
node.detachChild(instance);
|
||||
|
||||
if (node.hasChild(pictureNormal)) {
|
||||
node.detachChild(pictureNormal);
|
||||
}
|
||||
if (node.hasChild(pictureHover)) {
|
||||
node.detachChild(pictureHover);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method to define hover behavior. Must be implemented by subclasses.
|
||||
*/
|
||||
protected abstract void onHover();
|
||||
|
||||
/**
|
||||
* Abstract method to define unhover behavior. Must be implemented by subclasses.
|
||||
*/
|
||||
protected abstract void onUnHover();
|
||||
|
||||
/**
|
||||
* Handles the button click behavior, including visual feedback and sound effects.
|
||||
*/
|
||||
protected void click() {
|
||||
instance.setColor(TEXT_PRESSED);
|
||||
instance.setHighlightColor(TEXT_PRESSED);
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_PRESSED);
|
||||
instance.setBackground(background);
|
||||
|
||||
app.getAcousticHandler().playSound(MdgaSound.UI_CLICK);
|
||||
|
||||
if (node.hasChild(pictureNormal)) {
|
||||
node.detachChild(pictureNormal);
|
||||
setImageRelative(pictureHover);
|
||||
node.attachChild(pictureHover);
|
||||
}
|
||||
|
||||
onHover();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the button to its normal state after a click or hover event.
|
||||
*/
|
||||
protected void release() {
|
||||
instance.setColor(TEXT_NORMAL);
|
||||
instance.setHighlightColor(TEXT_NORMAL);
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
|
||||
instance.setBackground(background);
|
||||
|
||||
if (node.hasChild(pictureHover)) {
|
||||
node.detachChild(pictureHover);
|
||||
setImageRelative(pictureNormal);
|
||||
node.attachChild(pictureNormal);
|
||||
}
|
||||
|
||||
onUnHover();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the relative size and position of the button based on screen dimensions.
|
||||
*/
|
||||
protected void setRelative() {
|
||||
instance.setFontSize(fontSize);
|
||||
|
||||
instance.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
|
||||
|
||||
float xAdjust = 0.0f;
|
||||
if (adjust) {
|
||||
xAdjust = instance.getPreferredSize().x;
|
||||
}
|
||||
|
||||
instance.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
|
||||
|
||||
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (instance.getPreferredSize().x / 2);
|
||||
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - instance.getPreferredSize().y / 2;
|
||||
|
||||
if (0 == pos.x) {
|
||||
instance.setLocalTranslation(horizontalMid, instance.getLocalTranslation().y, instance.getLocalTranslation().z);
|
||||
}
|
||||
|
||||
if (0 == pos.y) {
|
||||
instance.setLocalTranslation(instance.getLocalTranslation().x, verticalMid, instance.getLocalTranslation().z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the relative size and position of the button's background image.
|
||||
*
|
||||
* @param picture the background image to set
|
||||
*/
|
||||
protected void setImageRelative(Picture picture) {
|
||||
if (null == picture) {
|
||||
return;
|
||||
}
|
||||
|
||||
final float larger = 10;
|
||||
|
||||
picture.setWidth(instance.getPreferredSize().x + larger);
|
||||
picture.setHeight(instance.getPreferredSize().y + larger);
|
||||
|
||||
picture.setLocalTranslation(
|
||||
instance.getLocalTranslation().x - larger / 2,
|
||||
(instance.getLocalTranslation().y - picture.getHeight()) + larger / 2,
|
||||
instance.getLocalTranslation().z + 0.01f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.simsilica.lemur.*;
|
||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents an input button with a label and a text field, allowing users to input text.
|
||||
* The button is designed for graphical user interfaces and supports configurable
|
||||
* size, position, and character limit.
|
||||
*/
|
||||
public class InputButton extends AbstractButton {
|
||||
|
||||
/**
|
||||
* The label associated with the input field, displayed above or beside the text field.
|
||||
*/
|
||||
private Label label;
|
||||
|
||||
/**
|
||||
* The text field where users input their text.
|
||||
*/
|
||||
private TextField field;
|
||||
|
||||
/**
|
||||
* A container to hold the label and the text field for layout management.
|
||||
*/
|
||||
private Container container = new Container();
|
||||
|
||||
/**
|
||||
* The maximum allowed length of the input text.
|
||||
*/
|
||||
private final int maxLenght;
|
||||
|
||||
/**
|
||||
* The size of the input button in relative units.
|
||||
*/
|
||||
protected Vector2f size;
|
||||
|
||||
/**
|
||||
* Constructs an InputButton with the specified label, character limit, and other properties.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the input button belongs
|
||||
* @param label the label displayed with the input field
|
||||
* @param maxLenght the maximum number of characters allowed in the input field
|
||||
*/
|
||||
public InputButton(MdgaApp app, Node node, String label, int maxLenght) {
|
||||
super(app, node);
|
||||
|
||||
this.label = new Label(label);
|
||||
this.maxLenght = maxLenght;
|
||||
|
||||
// Configure label properties
|
||||
this.label.setColor(TEXT_NORMAL);
|
||||
|
||||
// Configure text field properties
|
||||
field = new TextField("");
|
||||
field.setColor(TEXT_NORMAL);
|
||||
field.setTextHAlignment(HAlignment.Left);
|
||||
field.setTextVAlignment(VAlignment.Center);
|
||||
|
||||
// Set background for the text field
|
||||
QuadBackgroundComponent grayBackground = new QuadBackgroundComponent(BUTTON_NORMAL);
|
||||
field.setBackground(grayBackground);
|
||||
|
||||
// Set fonts for label and text field
|
||||
this.label.setFont(font);
|
||||
field.setFont(font);
|
||||
|
||||
// Default position and size
|
||||
pos = new Vector2f(0, 0);
|
||||
size = new Vector2f(5.5f, 1);
|
||||
|
||||
// Add components to the container
|
||||
container.addChild(this.label);
|
||||
container.addChild(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the input button by attaching it to the scene graph node.
|
||||
*/
|
||||
@Override
|
||||
public void show() {
|
||||
calculateRelative();
|
||||
setRelative();
|
||||
|
||||
node.attachChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the input button by detaching it from the scene graph node.
|
||||
*/
|
||||
@Override
|
||||
public void hide() {
|
||||
node.detachChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the input field, enforcing the character limit.
|
||||
* Trims the text if it exceeds the maximum allowed length.
|
||||
*/
|
||||
public void update() {
|
||||
String text = field.getText();
|
||||
int length = text.length();
|
||||
|
||||
if (length > maxLenght) {
|
||||
field.setText(text.substring(0, maxLenght));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the relative size and position of the input button based on the screen resolution.
|
||||
*/
|
||||
protected void setRelative() {
|
||||
this.label.setFontSize(fontSize);
|
||||
field.setFontSize(fontSize);
|
||||
|
||||
field.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
|
||||
|
||||
float xAdjust = 0.0f;
|
||||
if (adjust) {
|
||||
xAdjust = container.getPreferredSize().x;
|
||||
}
|
||||
|
||||
container.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
|
||||
|
||||
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (container.getPreferredSize().x / 2);
|
||||
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - container.getPreferredSize().y / 2;
|
||||
|
||||
if (0 == pos.x) {
|
||||
container.setLocalTranslation(horizontalMid, container.getLocalTranslation().y, -1);
|
||||
}
|
||||
|
||||
if (0 == pos.y) {
|
||||
container.setLocalTranslation(container.getLocalTranslation().x, verticalMid, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the text currently entered in the input field.
|
||||
*
|
||||
* @return the current text in the input field
|
||||
*/
|
||||
public String getString() {
|
||||
return field.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text of the input field to the specified string.
|
||||
*
|
||||
* @param string the text to set in the input field
|
||||
*/
|
||||
public void setString(String string) {
|
||||
field.setText(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the input field by clearing its text.
|
||||
*/
|
||||
public void reset() {
|
||||
field.setText("");
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* A specialized button that can function as a label or a clickable button.
|
||||
* It inherits from {@link ClickButton} and allows for flexible usage with or without button-like behavior.
|
||||
*/
|
||||
public class LabelButton extends ClickButton {
|
||||
|
||||
/**
|
||||
* The color of the text displayed on the label or button.
|
||||
*/
|
||||
private ColorRGBA text = TEXT_NORMAL;
|
||||
|
||||
/**
|
||||
* The color of the button's background.
|
||||
*/
|
||||
private ColorRGBA button = BUTTON_NORMAL;
|
||||
|
||||
/**
|
||||
* Flag indicating whether this component functions as a button.
|
||||
*/
|
||||
private boolean isButton;
|
||||
|
||||
/**
|
||||
* Constructs a LabelButton with specified properties.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param label the text displayed on the label or button
|
||||
* @param size the size of the label or button
|
||||
* @param pos the position of the label or button in relative units
|
||||
* @param isButton whether this component acts as a button or a simple label
|
||||
*/
|
||||
public LabelButton(MdgaApp app, Node node, String label, Vector2f size, Vector2f pos, boolean isButton) {
|
||||
super(app, node, () -> {
|
||||
}, label, size, pos);
|
||||
|
||||
this.isButton = isButton;
|
||||
|
||||
// Use the same image for hover and normal states
|
||||
pictureHover = pictureNormal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the label or button, attaching it to the scene graph.
|
||||
* If the component is a button, it also attaches the background image.
|
||||
*/
|
||||
@Override
|
||||
public void show() {
|
||||
if (isButton) {
|
||||
node.attachChild(pictureNormal);
|
||||
}
|
||||
release();
|
||||
|
||||
calculateRelative();
|
||||
setRelative();
|
||||
setImageRelative(pictureNormal);
|
||||
|
||||
instance.setFontSize(fontSize / 2);
|
||||
|
||||
node.attachChild(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the label or button, detaching it from the scene graph.
|
||||
*/
|
||||
@Override
|
||||
public void hide() {
|
||||
node.detachChild(instance);
|
||||
|
||||
if (node.hasChild(pictureNormal)) {
|
||||
node.detachChild(pictureNormal);
|
||||
}
|
||||
if (node.hasChild(pictureHover)) {
|
||||
node.detachChild(pictureHover);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles hover behavior, updating the colors of the text and background.
|
||||
*/
|
||||
@Override
|
||||
public void onHover() {
|
||||
instance.setColor(text);
|
||||
instance.setHighlightColor(text);
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(button);
|
||||
instance.setBackground(background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unhover behavior, restoring the colors of the text and background.
|
||||
*/
|
||||
@Override
|
||||
public void onUnHover() {
|
||||
instance.setColor(text);
|
||||
instance.setHighlightColor(text);
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(button);
|
||||
instance.setBackground(background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text displayed on the label or button.
|
||||
*
|
||||
* @param text the text to display
|
||||
*/
|
||||
public void setText(String text) {
|
||||
instance.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the colors of the text and background, and refreshes the label or button.
|
||||
*
|
||||
* @param text the color of the text
|
||||
* @param button the color of the button's background
|
||||
*/
|
||||
public void setColor(ColorRGBA text, ColorRGBA button) {
|
||||
this.text = text;
|
||||
this.button = button;
|
||||
|
||||
hide();
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
|
||||
import pp.mdga.client.Asset;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.game.Color;
|
||||
|
||||
/**
|
||||
* Represents a button in a multiplayer lobby screen. The button supports multiple states
|
||||
* (not taken, self, other) and displays a 3D model alongside its label. It can also indicate readiness
|
||||
* and interactively respond to hover and click events.
|
||||
*/
|
||||
public class LobbyButton extends ClickButton {
|
||||
|
||||
/**
|
||||
* Enum representing the possible ownership states of the lobby button.
|
||||
*/
|
||||
public enum Taken {
|
||||
NOT, // The button is not taken
|
||||
SELF, // The button is taken by the user
|
||||
OTHER // The button is taken by another user
|
||||
}
|
||||
|
||||
/**
|
||||
* Color for a lobby button that is taken by another user.
|
||||
*/
|
||||
static final ColorRGBA LOBBY_TAKEN = ColorRGBA.fromRGBA255(193, 58, 59, 100);
|
||||
|
||||
/**
|
||||
* Color for a lobby button that is ready but not hovered.
|
||||
*/
|
||||
static final ColorRGBA LOBBY_READY = ColorRGBA.fromRGBA255(55, 172, 190, 100);
|
||||
|
||||
/**
|
||||
* Color for a lobby button that is ready and hovered.
|
||||
*/
|
||||
static final ColorRGBA LOBBY_READY_HOVER = ColorRGBA.fromRGBA255(17, 211, 218, 100);
|
||||
|
||||
/**
|
||||
* Color for a lobby button owned by the user in normal state.
|
||||
*/
|
||||
static final ColorRGBA LOBBY_SELF_NORMAL = ColorRGBA.fromRGBA255(0, 151, 19, 100);
|
||||
|
||||
/**
|
||||
* Color for a lobby button owned by the user when hovered.
|
||||
*/
|
||||
static final ColorRGBA LOBBY_SELF_HOVER = ColorRGBA.fromRGBA255(0, 230, 19, 100);
|
||||
|
||||
/**
|
||||
* Fixed width for the lobby button.
|
||||
*/
|
||||
static final float WIDTH = 4.0f;
|
||||
|
||||
/**
|
||||
* Node to which the 3D model associated with this button is attached.
|
||||
*/
|
||||
private final Node node3d;
|
||||
|
||||
/**
|
||||
* Indicates whether the 3D model should rotate.
|
||||
*/
|
||||
private boolean rotate = false;
|
||||
|
||||
/**
|
||||
* The 3D model displayed alongside the button.
|
||||
*/
|
||||
private Spatial model;
|
||||
|
||||
/**
|
||||
* The rotation angle of the 3D model.
|
||||
*/
|
||||
private float rot = 180;
|
||||
|
||||
/**
|
||||
* The current ownership state of the lobby button.
|
||||
*/
|
||||
private Taken taken = Taken.NOT;
|
||||
|
||||
/**
|
||||
* Label displayed on the lobby button.
|
||||
*/
|
||||
private LabelButton label;
|
||||
|
||||
/**
|
||||
* Indicates whether the button represents a ready state.
|
||||
*/
|
||||
private boolean isReady = false;
|
||||
|
||||
/**
|
||||
* Constructs a LobbyButton with specified properties, including a 3D model and label.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param node3d the node for 3D scene components associated with this button
|
||||
* @param action the action to execute when the button is clicked
|
||||
* @param tsk the type or category of the button (e.g., CYBER, AIRFORCE)
|
||||
*/
|
||||
public LobbyButton(MdgaApp app, Node node, Node node3d, Runnable action, Color tsk) {
|
||||
super(app, node, action, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
|
||||
|
||||
this.node3d = node3d;
|
||||
|
||||
label = new LabelButton(app, node, "- leer -", new Vector2f(WIDTH, 1), new Vector2f(0, 0), true);
|
||||
|
||||
final float mid = HORIZONTAL / 2;
|
||||
final float uiSpacing = 0.4f;
|
||||
final float figSpacing = 0.51f;
|
||||
|
||||
float uiX = mid;
|
||||
float figX = 0;
|
||||
Asset asset = null;
|
||||
|
||||
// Configure the button based on its type
|
||||
switch (tsk) {
|
||||
case CYBER:
|
||||
adjust = true;
|
||||
label.adjust = true;
|
||||
uiX -= 3 * uiSpacing;
|
||||
uiX -= WIDTH / 2;
|
||||
asset = Asset.cir;
|
||||
figX -= 3 * figSpacing;
|
||||
instance.setText("CIR");
|
||||
break;
|
||||
case AIRFORCE:
|
||||
adjust = true;
|
||||
label.adjust = true;
|
||||
uiX -= uiSpacing;
|
||||
asset = Asset.lw;
|
||||
figX -= figSpacing;
|
||||
instance.setText("Luftwaffe");
|
||||
break;
|
||||
case ARMY:
|
||||
uiX += uiSpacing;
|
||||
asset = Asset.heer;
|
||||
figX += figSpacing;
|
||||
instance.setText("Heer");
|
||||
break;
|
||||
case NAVY:
|
||||
uiX += 3 * uiSpacing;
|
||||
uiX += WIDTH / 2;
|
||||
asset = Asset.marine;
|
||||
figX += 3 * figSpacing;
|
||||
instance.setText("Marine");
|
||||
break;
|
||||
}
|
||||
|
||||
setPos(new Vector2f(uiX, 6));
|
||||
label.setPos(new Vector2f(uiX, 7));
|
||||
|
||||
createModel(asset, new Vector3f(figX, -0.55f, 6));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles hover behavior, updating the button's color and enabling rotation.
|
||||
*/
|
||||
@Override
|
||||
public void onHover() {
|
||||
ColorRGBA buttonPressed = BUTTON_PRESSED.clone();
|
||||
|
||||
switch (taken) {
|
||||
case NOT:
|
||||
buttonPressed.a = 0.3f;
|
||||
break;
|
||||
case SELF:
|
||||
buttonPressed = LOBBY_SELF_HOVER;
|
||||
break;
|
||||
case OTHER:
|
||||
buttonPressed = LOBBY_TAKEN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isReady) {
|
||||
buttonPressed = LOBBY_READY_HOVER;
|
||||
}
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonPressed);
|
||||
instance.setBackground(background);
|
||||
|
||||
rotate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unhover behavior, restoring the button's color and disabling rotation.
|
||||
*/
|
||||
@Override
|
||||
public void onUnHover() {
|
||||
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
|
||||
|
||||
switch (taken) {
|
||||
case NOT:
|
||||
buttonNormal.a = 0.3f;
|
||||
break;
|
||||
case SELF:
|
||||
buttonNormal = LOBBY_SELF_NORMAL;
|
||||
break;
|
||||
case OTHER:
|
||||
buttonNormal = LOBBY_TAKEN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isReady) {
|
||||
buttonNormal = LOBBY_READY;
|
||||
}
|
||||
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
|
||||
instance.setBackground(background);
|
||||
|
||||
rotate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the lobby button and its associated components.
|
||||
*/
|
||||
@Override
|
||||
public void show() {
|
||||
release();
|
||||
|
||||
calculateRelative();
|
||||
setRelative();
|
||||
|
||||
node.attachChild(instance);
|
||||
node3d.attachChild(model);
|
||||
|
||||
label.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the lobby button and its associated components.
|
||||
*/
|
||||
@Override
|
||||
public void hide() {
|
||||
node.detachChild(instance);
|
||||
node3d.detachChild(model);
|
||||
|
||||
label.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 3D model's rotation if the button is being hovered.
|
||||
*
|
||||
* @param tpf time per frame, used for smooth rotation calculations
|
||||
*/
|
||||
public void update(float tpf) {
|
||||
if (rotate) {
|
||||
rot += 140.0f * tpf;
|
||||
rot %= 360;
|
||||
} else {
|
||||
rot = 180;
|
||||
}
|
||||
|
||||
model.setLocalRotation(new Quaternion().fromAngles(
|
||||
(float) Math.toRadians(90),
|
||||
(float) Math.toRadians(rot),
|
||||
(float) Math.toRadians(180)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the 3D model associated with the lobby button and applies textures and positioning.
|
||||
*
|
||||
* @param asset the asset representing the 3D model
|
||||
* @param pos the initial position of the 3D model
|
||||
*/
|
||||
private void createModel(Asset asset, Vector3f pos) {
|
||||
String modelName = asset.getModelPath();
|
||||
String texName = asset.getDiffPath();
|
||||
|
||||
model = app.getAssetManager().loadModel(modelName);
|
||||
model.scale(asset.getSize() / 2);
|
||||
model.rotate(
|
||||
(float) Math.toRadians(90),
|
||||
(float) Math.toRadians(rot),
|
||||
(float) Math.toRadians(180)
|
||||
);
|
||||
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
model.setLocalTranslation(pos);
|
||||
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
|
||||
model.setMaterial(mat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current ownership state of the lobby button.
|
||||
*
|
||||
* @return the current state of the button
|
||||
*/
|
||||
public Taken getTaken() {
|
||||
return taken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ownership state of the lobby button and updates its label accordingly.
|
||||
*
|
||||
* @param taken the new ownership state
|
||||
* @param name the name to display on the button
|
||||
*/
|
||||
public void setTaken(Taken taken, String name) {
|
||||
this.taken = taken;
|
||||
|
||||
if (taken == Taken.NOT) {
|
||||
label.setText("- leer -");
|
||||
isReady = false;
|
||||
} else {
|
||||
label.setText(name);
|
||||
}
|
||||
onUnHover();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ready state of the lobby button and updates its appearance.
|
||||
*
|
||||
* @param isReady whether the button represents a ready state
|
||||
*/
|
||||
public void setReady(boolean isReady) {
|
||||
this.isReady = isReady;
|
||||
onUnHover();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents a button used in menu screens for navigation or executing actions.
|
||||
* Inherits from {@link ClickButton} and provides customizable text and actions.
|
||||
*/
|
||||
public class MenuButton extends ClickButton {
|
||||
|
||||
/**
|
||||
* Constructs a MenuButton with specified properties.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param action the action to execute when the button is clicked
|
||||
* @param label the text label displayed on the button
|
||||
*/
|
||||
public MenuButton(MdgaApp app, Node node, Runnable action, String label) {
|
||||
super(app, node, action, label, new Vector2f(5.5f, 2), new Vector2f(0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the button is hovered over. Can be overridden to define hover-specific behavior.
|
||||
* Currently, no additional behavior is implemented.
|
||||
*/
|
||||
@Override
|
||||
public void onHover() {
|
||||
// Placeholder for hover behavior
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the pointer stops hovering over the button. Can be overridden to define unhover-specific behavior.
|
||||
* Currently, no additional behavior is implemented.
|
||||
*/
|
||||
@Override
|
||||
public void onUnHover() {
|
||||
// Placeholder for unhover behavior
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.simsilica.lemur.component.IconComponent;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents a button in the settings menu, designed to display an icon and handle hover effects.
|
||||
* Inherits from {@link ClickButton} and customizes its behavior for settings functionality.
|
||||
*/
|
||||
public class SettingsButton extends ClickButton {
|
||||
|
||||
/**
|
||||
* The icon displayed on the button, which changes based on hover state.
|
||||
*/
|
||||
private IconComponent icon;
|
||||
|
||||
/**
|
||||
* Constructs a SettingsButton with a predefined size and position.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the button belongs
|
||||
* @param action the action to execute when the button is clicked
|
||||
*/
|
||||
public SettingsButton(MdgaApp app, Node node, Runnable action) {
|
||||
super(app, node, action, "", new Vector2f(2, 2), new Vector2f(HORIZONTAL - 0.5f, VERTICAL - 0.5f));
|
||||
|
||||
// Enable adjustment for positioning
|
||||
adjust = true;
|
||||
|
||||
pictureNormal.setImage(app.getAssetManager(), "Images/Settings_Button_normal.png", true);
|
||||
pictureHover.setImage(app.getAssetManager(), "Images/Settings_Button_hover.png", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles hover behavior by changing the icon to the hover state.
|
||||
*/
|
||||
@Override
|
||||
public void onHover() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unhover behavior by restoring the icon to the normal state.
|
||||
*/
|
||||
@Override
|
||||
public void onUnHover() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
package pp.mdga.client.button;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.simsilica.lemur.*;
|
||||
import com.simsilica.lemur.component.QuadBackgroundComponent;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* Represents a slider button component with a label, providing functionality for
|
||||
* adjusting values in a graphical user interface. It includes increment, decrement,
|
||||
* and thumb buttons, allowing for interactive adjustments.
|
||||
*/
|
||||
public class SliderButton extends AbstractButton {
|
||||
|
||||
/**
|
||||
* The label displayed next to the slider.
|
||||
*/
|
||||
private Label label;
|
||||
|
||||
/**
|
||||
* The slider component for adjusting values.
|
||||
*/
|
||||
private Slider slider;
|
||||
|
||||
/**
|
||||
* A container to hold the label and slider for layout management.
|
||||
*/
|
||||
private Container container = new Container();
|
||||
|
||||
/**
|
||||
* The size of the slider button in relative units.
|
||||
*/
|
||||
protected Vector2f size;
|
||||
|
||||
/**
|
||||
* Constructs a SliderButton with the specified label.
|
||||
*
|
||||
* @param app the application instance for accessing resources
|
||||
* @param node the node in the scene graph to which the slider button belongs
|
||||
* @param label the text label displayed alongside the slider
|
||||
*/
|
||||
public SliderButton(MdgaApp app, Node node, String label) {
|
||||
super(app, node);
|
||||
|
||||
this.label = new Label(label);
|
||||
this.label.setColor(TEXT_NORMAL);
|
||||
|
||||
// Configure the slider
|
||||
slider = new Slider("slider");
|
||||
|
||||
// Configure decrement button
|
||||
slider.getDecrementButton().setText(" - ");
|
||||
slider.getDecrementButton().setFont(font);
|
||||
slider.getDecrementButton().setFocusColor(TEXT_NORMAL);
|
||||
slider.getDecrementButton().setTextVAlignment(VAlignment.Bottom);
|
||||
slider.getDecrementButton().setColor(TEXT_NORMAL);
|
||||
slider.getDecrementButton().setHighlightColor(TEXT_NORMAL);
|
||||
|
||||
// Configure increment button
|
||||
slider.getIncrementButton().setText(" + ");
|
||||
slider.getIncrementButton().setFont(font);
|
||||
slider.getIncrementButton().setFocusColor(TEXT_NORMAL);
|
||||
slider.getIncrementButton().setTextVAlignment(VAlignment.Bottom);
|
||||
slider.getIncrementButton().setColor(TEXT_NORMAL);
|
||||
slider.getIncrementButton().setHighlightColor(TEXT_NORMAL);
|
||||
|
||||
// Configure thumb button
|
||||
slider.getThumbButton().setText("X");
|
||||
slider.getThumbButton().setFont(font);
|
||||
slider.getThumbButton().setFocusColor(TEXT_NORMAL);
|
||||
slider.getThumbButton().setColor(TEXT_NORMAL);
|
||||
slider.getThumbButton().setHighlightColor(TEXT_NORMAL);
|
||||
|
||||
// Set slider background
|
||||
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
|
||||
slider.setBackground(background);
|
||||
|
||||
// Set label background
|
||||
QuadBackgroundComponent labelBackground = new QuadBackgroundComponent(BUTTON_NORMAL);
|
||||
this.label.setBackground(labelBackground);
|
||||
|
||||
// Configure the label font
|
||||
this.label.setFont(font);
|
||||
this.label.setTextHAlignment(HAlignment.Center);
|
||||
|
||||
// Default position and size
|
||||
pos = new Vector2f(0, 0);
|
||||
size = new Vector2f(6f, 1);
|
||||
|
||||
// Add label and slider to container
|
||||
container.addChild(this.label);
|
||||
container.addChild(slider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the slider button by attaching its container to the scene graph.
|
||||
*/
|
||||
@Override
|
||||
public void show() {
|
||||
calculateRelative();
|
||||
setRelative();
|
||||
|
||||
node.attachChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the slider button by detaching its container from the scene graph.
|
||||
*/
|
||||
@Override
|
||||
public void hide() {
|
||||
node.detachChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the relative size and position of the slider button based on screen resolution.
|
||||
*/
|
||||
protected void setRelative() {
|
||||
this.label.setFontSize(fontSize);
|
||||
|
||||
// Set font sizes for slider components
|
||||
slider.getDecrementButton().setFontSize(fontSize);
|
||||
slider.getIncrementButton().setFontSize(fontSize);
|
||||
slider.getThumbButton().setFontSize(fontSize);
|
||||
|
||||
// Set slider size
|
||||
slider.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
|
||||
|
||||
float xAdjust = 0.0f;
|
||||
if (adjust) {
|
||||
xAdjust = container.getPreferredSize().x;
|
||||
}
|
||||
|
||||
// Set container position
|
||||
container.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
|
||||
|
||||
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (container.getPreferredSize().x / 2);
|
||||
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - container.getPreferredSize().y / 2;
|
||||
|
||||
if (0 == pos.x) {
|
||||
container.setLocalTranslation(horizontalMid, container.getLocalTranslation().y, -1);
|
||||
}
|
||||
|
||||
if (0 == pos.y) {
|
||||
container.setLocalTranslation(container.getLocalTranslation().x, verticalMid, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current percentage value of the slider.
|
||||
*
|
||||
* @return the current percentage value as a float
|
||||
*/
|
||||
public float getPercent() {
|
||||
return (float) slider.getModel().getPercent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the slider to the specified percentage value.
|
||||
*
|
||||
* @param percent the percentage value to set
|
||||
*/
|
||||
public void setPercent(float percent) {
|
||||
slider.getModel().setPercent(percent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.button.MenuButton;
|
||||
import pp.mdga.client.button.SliderButton;
|
||||
import pp.mdga.client.view.MdgaView;
|
||||
|
||||
/**
|
||||
* The {@code AudioSettingsDialog} class represents a dialog for adjusting audio settings in the application.
|
||||
* It provides controls for managing main volume, music volume, and sound effect volume, and includes
|
||||
* a button to return to the previous menu.
|
||||
*/
|
||||
public class AudioSettingsDialog extends Dialog {
|
||||
private final MdgaView view;
|
||||
|
||||
private SliderButton mainVolume;
|
||||
private SliderButton musicVolume;
|
||||
private SliderButton soundVolume;
|
||||
|
||||
private MenuButton backButton;
|
||||
|
||||
private boolean active = false;
|
||||
|
||||
/**
|
||||
* Constructs an {@code AudioSettingsDialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node for attaching UI elements.
|
||||
* @param view The current view, used for navigation and interaction with the dialog.
|
||||
*/
|
||||
public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
|
||||
super(app, node);
|
||||
|
||||
this.view = view;
|
||||
|
||||
mainVolume = new SliderButton(app, node, "Gesamt Lautstärke");
|
||||
musicVolume = new SliderButton(app, node, "Musik Lautstärke");
|
||||
soundVolume = new SliderButton(app, node, "Effekt Lautstärke");
|
||||
|
||||
backButton = new MenuButton(app, node, view::leaveAudioSettings, "Zurück");
|
||||
|
||||
float offset = 2.8f;
|
||||
|
||||
mainVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
offset += 1.0f;
|
||||
|
||||
musicVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
offset += 1.0f;
|
||||
|
||||
soundVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
|
||||
backButton.setPos(new Vector2f(0, 1.8f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is shown. Initializes and displays the volume controls and back button.
|
||||
*/
|
||||
@Override
|
||||
protected void onShow() {
|
||||
active = true;
|
||||
|
||||
mainVolume.setPercent(app.getAcousticHandler().getMainVolume());
|
||||
musicVolume.setPercent(app.getAcousticHandler().getMusicVolume());
|
||||
soundVolume.setPercent(app.getAcousticHandler().getSoundVolume());
|
||||
|
||||
backButton.show();
|
||||
|
||||
mainVolume.show();
|
||||
musicVolume.show();
|
||||
soundVolume.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is hidden. Hides all volume controls and the back button.
|
||||
*/
|
||||
@Override
|
||||
protected void onHide() {
|
||||
active = false;
|
||||
|
||||
backButton.hide();
|
||||
|
||||
mainVolume.hide();
|
||||
musicVolume.hide();
|
||||
soundVolume.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the application audio settings based on the current values of the sliders.
|
||||
* This method is called continuously while the dialog is active.
|
||||
*/
|
||||
public void update() {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.getAcousticHandler().setMainVolume(mainVolume.getPercent());
|
||||
app.getAcousticHandler().setMusicVolume(musicVolume.getPercent());
|
||||
app.getAcousticHandler().setSoundVolume(soundVolume.getPercent());
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.button.AbstractButton;
|
||||
import pp.mdga.client.button.LabelButton;
|
||||
import pp.mdga.client.button.MenuButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The {@code CeremonyDialog} class displays a dialog containing statistical data in a tabular format.
|
||||
* It allows adding rows of statistics and manages their visibility when shown or hidden.
|
||||
*/
|
||||
public class CeremonyDialog extends Dialog {
|
||||
private ArrayList<ArrayList<LabelButton>> labels;
|
||||
|
||||
float offsetX;
|
||||
|
||||
/**
|
||||
* Constructs a {@code CeremonyDialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node for attaching UI elements.
|
||||
*/
|
||||
public CeremonyDialog(MdgaApp app, Node node) {
|
||||
super(app, node);
|
||||
|
||||
prepare();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is shown. Makes all label buttons in the table visible.
|
||||
*/
|
||||
@Override
|
||||
protected void onShow() {
|
||||
for (ArrayList<LabelButton> row : labels) {
|
||||
for (LabelButton b : row) {
|
||||
b.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is hidden. Hides all label buttons in the table.
|
||||
*/
|
||||
@Override
|
||||
protected void onHide() {
|
||||
for (ArrayList<LabelButton> row : labels) {
|
||||
for (LabelButton b : row) {
|
||||
b.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a row of statistical data to the dialog.
|
||||
*
|
||||
* @param name The name of the player or category for the row.
|
||||
* @param v1 The value for the first column.
|
||||
* @param v2 The value for the second column.
|
||||
* @param v3 The value for the third column.
|
||||
* @param v4 The value for the fourth column.
|
||||
* @param v5 The value for the fifth column.
|
||||
* @param v6 The value for the sixth column.
|
||||
*/
|
||||
public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
|
||||
float offsetYSmall = 0.5f;
|
||||
|
||||
ArrayList<LabelButton> row = new ArrayList<>();
|
||||
|
||||
Vector2f sizeSmall = new Vector2f(4, 1.2f);
|
||||
row.add(new LabelButton(app, node, name, sizeSmall, new Vector2f(), true));
|
||||
row.add(new LabelButton(app, node, "" + v1, sizeSmall, new Vector2f(), false));
|
||||
row.add(new LabelButton(app, node, "" + v2, sizeSmall, new Vector2f(), false));
|
||||
row.add(new LabelButton(app, node, "" + v3, sizeSmall, new Vector2f(), false));
|
||||
row.add(new LabelButton(app, node, "" + v4, sizeSmall, new Vector2f(), false));
|
||||
row.add(new LabelButton(app, node, "" + v5, sizeSmall, new Vector2f(), false));
|
||||
row.add(new LabelButton(app, node, "" + v6, sizeSmall, new Vector2f(), false));
|
||||
|
||||
ColorRGBA colorText = AbstractButton.TEXT_NORMAL.clone();
|
||||
colorText.a = 0.2f;
|
||||
|
||||
ColorRGBA colorButton = AbstractButton.BUTTON_NORMAL.clone();
|
||||
colorButton.a = 0.2f;
|
||||
|
||||
int j = 0;
|
||||
for (LabelButton b : row) {
|
||||
if (j > 0) {
|
||||
b.setColor(colorText, colorButton);
|
||||
}
|
||||
|
||||
b.setPos(new Vector2f(offsetX, MenuButton.VERTICAL - offsetYSmall));
|
||||
offsetYSmall += 0.8f;
|
||||
|
||||
j++;
|
||||
}
|
||||
|
||||
offsetX += 2.3f;
|
||||
|
||||
labels.add(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the initial layout of the dialog, including header labels.
|
||||
*/
|
||||
public void prepare() {
|
||||
offsetX = 0.5f;
|
||||
|
||||
labels = new ArrayList<>();
|
||||
|
||||
ArrayList<LabelButton> first = new ArrayList<>();
|
||||
Vector2f size = new Vector2f(4, 1.2f);
|
||||
first.add(new LabelButton(app, node, "", size, new Vector2f(), true));
|
||||
first.add(new LabelButton(app, node, "Figuren geworfen", size, new Vector2f(), true));
|
||||
first.add(new LabelButton(app, node, "Figuren verloren", size, new Vector2f(), true));
|
||||
first.add(new LabelButton(app, node, "Gespielte Bonuskarten", size, new Vector2f(), true));
|
||||
first.add(new LabelButton(app, node, "Gewürfelte 6en", size, new Vector2f(), true));
|
||||
first.add(new LabelButton(app, node, "Gelaufene Felder", size, new Vector2f(), true));
|
||||
first.add(new LabelButton(app, node, "Bonusfeldern erreicht", size, new Vector2f(), true));
|
||||
|
||||
float offsetY = 0.5f;
|
||||
|
||||
for (LabelButton b : first) {
|
||||
b.setPos(new Vector2f(offsetX, MenuButton.VERTICAL - offsetY));
|
||||
offsetY += 0.8f;
|
||||
}
|
||||
|
||||
offsetX += 2.3f;
|
||||
|
||||
labels.add(first);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
|
||||
/**
|
||||
* The {@code Dialog} class serves as an abstract base class for dialogs in the application.
|
||||
* It provides functionality for showing and hiding the dialog and defines abstract methods
|
||||
* for custom behavior when the dialog is shown or hidden.
|
||||
*/
|
||||
public abstract class Dialog {
|
||||
protected final MdgaApp app;
|
||||
protected final Node node = new Node();
|
||||
|
||||
private final Node root;
|
||||
|
||||
/**
|
||||
* Constructs a {@code Dialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node to which the dialog's node will be attached.
|
||||
*/
|
||||
Dialog(MdgaApp app, Node node) {
|
||||
this.app = app;
|
||||
this.root = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog by attaching its node to the root node and invoking the {@code onShow} method.
|
||||
*/
|
||||
public void show() {
|
||||
root.attachChild(node);
|
||||
|
||||
onShow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the dialog by detaching its node from the root node and invoking the {@code onHide} method.
|
||||
*/
|
||||
public void hide() {
|
||||
root.detachChild(node);
|
||||
|
||||
onHide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is shown. Subclasses must implement this method to define custom behavior.
|
||||
*/
|
||||
protected abstract void onShow();
|
||||
|
||||
/**
|
||||
* Called when the dialog is hidden. Subclasses must implement this method to define custom behavior.
|
||||
*/
|
||||
protected abstract void onHide();
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.NetworkSupport;
|
||||
import pp.mdga.client.button.ButtonLeft;
|
||||
import pp.mdga.client.button.ButtonRight;
|
||||
import pp.mdga.client.button.InputButton;
|
||||
import pp.mdga.client.button.MenuButton;
|
||||
import pp.mdga.client.view.MainView;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* The {@code HostDialog} class represents a dialog for hosting a network game session.
|
||||
* It allows users to input a port number, start hosting a server, and navigate back to the previous view.
|
||||
*/
|
||||
public class HostDialog extends NetworkDialog {
|
||||
private InputButton portInput;
|
||||
|
||||
private ButtonRight hostButton;
|
||||
private ButtonLeft backButton;
|
||||
|
||||
private final MainView view;
|
||||
|
||||
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
|
||||
|
||||
/**
|
||||
* Constructs a {@code HostDialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node for attaching UI elements.
|
||||
* @param view The main view used for navigation and interaction with the dialog.
|
||||
*/
|
||||
public HostDialog(MdgaApp app, Node node, MainView view) {
|
||||
super(app, node, (NetworkSupport) app.getNetworkSupport());
|
||||
|
||||
this.view = view;
|
||||
|
||||
portInput = new InputButton(app, node, "Port: ", 5);
|
||||
portInput.setString(prefs.get("hostPort", "11111"));
|
||||
|
||||
hostButton = new ButtonRight(app, node, view::forward, "Spiel hosten", 10);
|
||||
backButton = new ButtonLeft(app, node, view::back, "Zurück", 10);
|
||||
|
||||
float offset = 2.8f;
|
||||
|
||||
portInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
offset += 1.5f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is shown. Displays all input fields and buttons.
|
||||
*/
|
||||
@Override
|
||||
protected void onShow() {
|
||||
portInput.show();
|
||||
hostButton.show();
|
||||
backButton.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is hidden. Hides all input fields and buttons.
|
||||
*/
|
||||
@Override
|
||||
protected void onHide() {
|
||||
portInput.hide();
|
||||
hostButton.hide();
|
||||
backButton.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of the port input field.
|
||||
* This method is called periodically to synchronize the dialog state.
|
||||
*/
|
||||
public void update() {
|
||||
portInput.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
|
||||
*
|
||||
* @return The port number as a string.
|
||||
*/
|
||||
public String getPort() {
|
||||
prefs.put("hostPort", portInput.getString());
|
||||
setPortNumber(Integer.parseInt(portInput.getString()));
|
||||
return portInput.getString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the port input field to its default value and updates preferences accordingly.
|
||||
*/
|
||||
public void resetPort() {
|
||||
portInput.reset();
|
||||
prefs.put("hostPort", "11111");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server to host a network game.
|
||||
*/
|
||||
public void hostServer() {
|
||||
startServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the server as a client.
|
||||
*/
|
||||
public void connectServerAsClient() {
|
||||
connectServer();
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.button.ButtonRight;
|
||||
import pp.mdga.client.button.LabelButton;
|
||||
import pp.mdga.client.button.MenuButton;
|
||||
import pp.mdga.game.Color;
|
||||
|
||||
/**
|
||||
* The {@code InterruptDialog} class represents a dialog that interrupts the game flow,
|
||||
* providing a message and the option to force an action if the user is a host.
|
||||
*/
|
||||
public class InterruptDialog extends Dialog {
|
||||
private ButtonRight forceButton;
|
||||
|
||||
private LabelButton label;
|
||||
|
||||
private String text = "";
|
||||
|
||||
/**
|
||||
* Constructs an {@code InterruptDialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node for attaching UI elements.
|
||||
*/
|
||||
public InterruptDialog(MdgaApp app, Node node) {
|
||||
super(app, node);
|
||||
|
||||
forceButton = new ButtonRight(app, node, () -> app.getModelSynchronize().force(), "Erzwingen", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is shown. Displays the label and optionally the force button if the user is the host.
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected void onShow() {
|
||||
if (app.getGameLogic().isHost()) {
|
||||
forceButton.show();
|
||||
}
|
||||
|
||||
label = new LabelButton(app, node, "Warte auf " + text + "...", new Vector2f(5.5f * 1.5f, 2), new Vector2f(0.5f, 0f), false);
|
||||
|
||||
float offset = 2.8f;
|
||||
label.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
|
||||
label.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is hidden. Hides the label and the force button.
|
||||
*/
|
||||
@Override
|
||||
protected void onHide() {
|
||||
forceButton.hide();
|
||||
label.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the displayed text based on the specified color.
|
||||
*
|
||||
* @param color The color used to determine the text (e.g., "Luftwaffe" for AIRFORCE).
|
||||
*/
|
||||
public void setColor(Color color) {
|
||||
switch (color) {
|
||||
case AIRFORCE:
|
||||
text = "Luftwaffe";
|
||||
break;
|
||||
case ARMY:
|
||||
text = "Heer";
|
||||
break;
|
||||
case NAVY:
|
||||
text = "Marine";
|
||||
break;
|
||||
case CYBER:
|
||||
text = "CIR";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.NetworkSupport;
|
||||
import pp.mdga.client.button.ButtonLeft;
|
||||
import pp.mdga.client.button.ButtonRight;
|
||||
import pp.mdga.client.button.InputButton;
|
||||
import pp.mdga.client.button.MenuButton;
|
||||
import pp.mdga.client.view.MainView;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* The {@code JoinDialog} class represents a dialog for joining a network game.
|
||||
* It allows users to input an IP address and port number, connect to a server, or navigate back to the previous view.
|
||||
*/
|
||||
public class JoinDialog extends NetworkDialog {
|
||||
private InputButton ipInput;
|
||||
private InputButton portInput;
|
||||
|
||||
private ButtonRight joinButton;
|
||||
private ButtonLeft backButton;
|
||||
|
||||
private final MainView view;
|
||||
|
||||
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
|
||||
|
||||
/**
|
||||
* Constructs a {@code JoinDialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node for attaching UI elements.
|
||||
* @param view The main view used for navigation and interaction with the dialog.
|
||||
*/
|
||||
public JoinDialog(MdgaApp app, Node node, MainView view) {
|
||||
super(app, node, (NetworkSupport) app.getNetworkSupport());
|
||||
|
||||
this.view = view;
|
||||
|
||||
ipInput = new InputButton(app, node, "Ip: ", 15);
|
||||
portInput = new InputButton(app, node, "Port: ", 5);
|
||||
portInput.setString(prefs.get("joinPort", "11111"));
|
||||
ipInput.setString(prefs.get("joinIp", ""));
|
||||
|
||||
joinButton = new ButtonRight(app, node, view::forward, "Spiel beitreten", 10);
|
||||
backButton = new ButtonLeft(app, node, view::back, "Zurück", 10);
|
||||
|
||||
float offset = 2.8f;
|
||||
|
||||
ipInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
offset += 1.5f;
|
||||
|
||||
portInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
offset += 1.5f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is shown. Displays all input fields and buttons.
|
||||
*/
|
||||
@Override
|
||||
protected void onShow() {
|
||||
ipInput.show();
|
||||
portInput.show();
|
||||
joinButton.show();
|
||||
backButton.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is hidden. Hides all input fields and buttons.
|
||||
*/
|
||||
@Override
|
||||
protected void onHide() {
|
||||
ipInput.hide();
|
||||
portInput.hide();
|
||||
joinButton.hide();
|
||||
backButton.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of the input fields. This method is called periodically to synchronize the dialog state.
|
||||
*/
|
||||
public void update() {
|
||||
ipInput.update();
|
||||
portInput.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the currently entered IP address, saves it to preferences, and sets it as the hostname.
|
||||
*
|
||||
* @return The IP address as a string.
|
||||
*/
|
||||
public String getIpt() {
|
||||
prefs.put("joinIp", ipInput.getString());
|
||||
setHostname(ipInput.getString());
|
||||
return ipInput.getString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the IP input field to its default value and updates preferences accordingly.
|
||||
*/
|
||||
public void resetIp() {
|
||||
ipInput.reset();
|
||||
prefs.put("joinIp", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
|
||||
*
|
||||
* @return The port number as a string.
|
||||
*/
|
||||
public String getPort() {
|
||||
prefs.put("joinPort", portInput.getString());
|
||||
setPortNumber(Integer.parseInt(portInput.getString()));
|
||||
return portInput.getString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the port input field to its default value and updates preferences accordingly.
|
||||
*/
|
||||
public void resetPort() {
|
||||
portInput.reset();
|
||||
prefs.put("joinPort", "11111");
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the server using the current IP address and port number.
|
||||
*/
|
||||
public void connectToServer() {
|
||||
connectServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the server if a network connection exists.
|
||||
*/
|
||||
public void disconnect() {
|
||||
NetworkSupport network = getNetwork();
|
||||
if (network != null) {
|
||||
try {
|
||||
network.disconnect();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error while disconnecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.NetworkSupport;
|
||||
import pp.mdga.client.server.MdgaServer;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* The {@code NetworkDialog} class serves as an abstract base class for dialogs
|
||||
* that involve network-related functionalities, such as connecting to a server or hosting a game.
|
||||
* It provides methods for initializing, connecting to, and managing a network server.
|
||||
*/
|
||||
public abstract class NetworkDialog extends Dialog {
|
||||
|
||||
private NetworkSupport network;
|
||||
private String hostname;
|
||||
private int portNumber;
|
||||
private Future<Object> connectionFuture;
|
||||
private MdgaServer serverInstance;
|
||||
private Thread serverThread;
|
||||
|
||||
/**
|
||||
* Constructs a {@code NetworkDialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node for attaching UI elements.
|
||||
* @param network The network support instance for managing network interactions.
|
||||
*/
|
||||
public NetworkDialog(MdgaApp app, Node node, NetworkSupport network) {
|
||||
super(app, node);
|
||||
this.network = network;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hostname for the network connection.
|
||||
*
|
||||
* @param hostname The hostname or IP address of the server.
|
||||
*/
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the port number for the network connection.
|
||||
*
|
||||
* @param portNumber The port number to use for the connection.
|
||||
*/
|
||||
public void setPortNumber(int portNumber) {
|
||||
this.portNumber = portNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the network connection using the current hostname and port number.
|
||||
*
|
||||
* @return {@code null} if successful, otherwise throws an exception.
|
||||
*/
|
||||
protected Object initNetwork() {
|
||||
try {
|
||||
this.network.initNetwork(this.hostname, this.portNumber);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the process of connecting to a server asynchronously.
|
||||
*/
|
||||
protected void connectServer() {
|
||||
try {
|
||||
connectionFuture = this.network.getApp().getExecutor().submit(this::initNetwork);
|
||||
} catch (NumberFormatException var2) {
|
||||
throw new NumberFormatException("Port must be a number");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts hosting a server in a separate thread.
|
||||
*/
|
||||
protected void startServer() {
|
||||
serverThread = new Thread(() -> {
|
||||
try {
|
||||
serverInstance = new MdgaServer(portNumber);
|
||||
serverInstance.run();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
serverThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the hosted server and cleans up resources.
|
||||
*/
|
||||
public void shutdownServer() {
|
||||
|
||||
serverInstance.shutdown();
|
||||
|
||||
// Wait for the server to shut down
|
||||
try {
|
||||
serverThread.join(); // Wait for the server thread to finish
|
||||
System.out.println("Server shutdown successfully.");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of the connection process.
|
||||
*
|
||||
* @param delta The time elapsed since the last update call.
|
||||
*/
|
||||
public void update(float delta) {
|
||||
if (this.connectionFuture != null && this.connectionFuture.isDone()) {
|
||||
try {
|
||||
this.connectionFuture.get();
|
||||
} catch (ExecutionException ignored) {
|
||||
// TODO: implement
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the {@code NetworkSupport} instance associated with this dialog.
|
||||
*
|
||||
* @return The {@code NetworkSupport} instance.
|
||||
*/
|
||||
public NetworkSupport getNetwork() {
|
||||
return network;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package pp.mdga.client.dialog;
|
||||
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Node;
|
||||
import pp.mdga.client.MdgaApp;
|
||||
import pp.mdga.client.button.MenuButton;
|
||||
import pp.mdga.client.view.MdgaView;
|
||||
|
||||
/**
|
||||
* The {@code SettingsDialog} class represents a dialog for navigating to various settings sections,
|
||||
* such as video and audio settings, or returning to the previous view.
|
||||
*/
|
||||
public class SettingsDialog extends Dialog {
|
||||
private MenuButton videoButton;
|
||||
private MenuButton audioButton;
|
||||
private MenuButton backButton;
|
||||
|
||||
private final MdgaView view;
|
||||
|
||||
/**
|
||||
* Constructs a {@code SettingsDialog}.
|
||||
*
|
||||
* @param app The main application managing the dialog.
|
||||
* @param node The root node for attaching UI elements.
|
||||
* @param view The view managing navigation and interaction with the settings dialog.
|
||||
*/
|
||||
public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
|
||||
super(app, node);
|
||||
|
||||
this.view = view;
|
||||
|
||||
videoButton = new MenuButton(app, node, view::enterVideoSettings, "Video");
|
||||
audioButton = new MenuButton(app, node, view::enterAudioSettings, "Audio");
|
||||
backButton = new MenuButton(app, node, view::leaveSettings, "Zurück");
|
||||
|
||||
float offset = 2.8f;
|
||||
|
||||
videoButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
offset += 1.25f;
|
||||
|
||||
audioButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
|
||||
|
||||
backButton.setPos(new Vector2f(0, 1.8f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is shown. Displays all buttons for video settings, audio settings, and back navigation.
|
||||
*/
|
||||
@Override
|
||||
protected void onShow() {
|
||||
videoButton.show();
|
||||
audioButton.show();
|
||||
backButton.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is hidden. Hides all buttons for video settings, audio settings, and back navigation.
|
||||
*/
|
||||
@Override
|
||||
protected void onHide() {
|
||||
videoButton.hide();
|
||||
audioButton.hide();
|
||||
backButton.hide();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user